📜 ⬆️ ⬇️

Realistic gravity lensing on Unity

image
The effect of a gravitational lens caused by a cluster of galaxies RCS2 032727-132623

There was a recent need to implement on Unity a fairly believable image of a black hole and, accordingly, the effect of gravitational lensing of the one it caused. The first thought was to find a ready-made implementation and customize it, however, since I did not find a single sufficiently good solution (which is very strange, knowing how popular the games on space subjects are), decided to implement the effect on their own and share the result with the community.

To begin with, we will write a script that we will hang on the camera, and which will apply the shader to the image displayed on the screen.
')
Script
using UnityEngine; [ExecuteInEditMode] public class Lens: MonoBehaviour { public Shader shader; public float ratio = 1; //    ,     public float radius = 0; //       ,       public GameObject BH; //,        private Material _material; //      protected Material material { get { if (_material == null) { _material = new Material (shader); _material.hideFlags = HideFlags.HideAndDontSave; } return _material; } } protected virtual void OnDisable() { if( _material ) { DestroyImmediate( _material ); } } void OnRenderImage (RenderTexture source, RenderTexture destination) { if (shader && material) { //       Vector2 pos = new Vector2( this.camera.WorldToScreenPoint (BH.transform.position).x / this.camera.pixelWidth, 1-this.camera.WorldToScreenPoint (BH.transform.position).y / this.camera.pixelHeight); //      material.SetVector("_Position", new Vector2(pos.x, pos.y)); material.SetFloat("_Ratio", ratio); material.SetFloat("_Rad", radius); material.SetFloat("_Distance", Vector3.Distance(BH.transform.position, this.transform.position)); //    . Graphics.Blit(source, destination, material); } } } 

Now let's get to the more important part: writing the shader itself.

First of all, we need to get a radius, depending on which we will distort the image:
 float2 offset = i.uv - _Position; //      float2 ratio = {_Ratio,1}; //    float rad = length(offset / ratio); //  

In physics, the formula for the refraction of a beam of light passing at a distance r from an object with a mass M has the form:
image
For us, M is the mass of a black hole. Knowing that the radius of a black hole is defined as
image
We get the following construction
 float deformation = 2*_Rad*1/pow(rad*z,2); 

where deformation is the distortion force at each particular point, while z is a certain dependence of the size of the distortion on the distance at which the camera is located. To understand how this dependence is expressed, we turn to the Einstein ring formula.
image
Where
image
In this formula, we are interested in its dependence on distance, because most of it can be discarded by observing only
image
Since the shader processes a 2-dimensional image, we cannot tell how far objects are. And although this can be done with the help of a depth map, it is impossible to distort them correctly, since images of everything that is behind each of the objects will be required. Therefore, suppose that D L << D S and D L << D LS . Then we see that the size of the distortion is inversely proportional to the root of the distance, we get
 deformation = 2*_Rad*1/pow(rad*pow(_Distance,0.5),2); 

Now apply our deformation:
 offset =offset*(1-deformation); 

Let's return the image to its place and display it.
 offset += _Position; half4 res = tex2D(_MainTex, offset); return res; 

Full Shader Code
 Shader "Gravitation Lensing Shader" { Properties { _MainTex ("Base (RGB)", 2D) = "white" {} } SubShader { Pass { ZTest Always Cull Off ZWrite Off Fog { Mode off } CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma fragmentoption ARB_precision_hint_fastest #include "UnityCG.cginc" uniform sampler2D _MainTex; uniform float2 _Position; uniform float _Rad; uniform float _Ratio; uniform float _Distance; struct v2f { float4 pos : POSITION; float2 uv : TEXCOORD0; }; v2f vert( appdata_img v ) { v2f o; o.pos = mul (UNITY_MATRIX_MVP, v.vertex); o.uv = v.texcoord; return o; } float4 frag (v2f i) : COLOR { float2 offset = i.uv - _Position; //      float2 ratio = {_Ratio,1}; //    float rad = length(offset / ratio); //    "" . float deformation = 1/pow(rad*pow(_Distance,0.5),2)*_Rad*2; offset =offset*(1-deformation); offset += _Position; half4 res = tex2D(_MainTex, offset); //if (rad*_Distance<pow(2*_Rad/_Distance,0.5)*_Distance) {res.g+=0.2;} //     //if (rad*_Distance<_Rad){res.r=0;res.g=0;res.b=0;} //   return res; } ENDCG } } Fallback off } 

That's all! You can enjoy the result:


This shader implements distortion for only one massive object. To display what is in front of a black hole, I used another camera that draws over the main one. And although such a decision cannot be called elegant, it works well in my case.

PS Please note that post effects only work in Unity Pro version.

Source: https://habr.com/ru/post/235729/


All Articles