Unity Shader 实现一个简单的屏幕空间反射(1)

屏幕空间反射(Screen Space Reflection):用途蛮广的,诸如水面的倒影、光滑地面的反射、大厦玻璃的反射,写好了的话对于画面表现上来说属于锦上添花的效果。
先来看看实现的效果:
在这里插入图片描述
如其名字——屏幕空间,使用了类似于后处理的手法,用渲染好的RenderTexture作为反射源输入,所以没被渲染进RenderTexture的场景信息没有办法得到正确的反射效果。如上图所示,天花板没办法反射出来、背后的方块颜色没法反射。
在这里插入图片描述
并且这是一个全屏的效果,没有哪个场景是所有的物体都需要这么强烈的反射效果的,所以我给地板加上一个Stencil Test,这样就只有地板进行了反射。虽然依旧存在瑕疵,但是比上面全屏效果要好的多了,只有地板会进行反射(至于Capsule为啥也有反射,我也属实不懂,反正不是我的原因,估计是Unity Stencil Test自己的原因)。
在这里插入图片描述
Shader的算法思想主要参考在Unity中实现屏幕空间反射Screen Space Reflection系列。对于算法的步骤已经有了详细的阐述,我就不再赘述了,以下是Shader代码。

Shader "Hidden/SSR_Blog"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
        //LOD 100
        CGINCLUDE
        #include "UnityCG.cginc"

        sampler2D _MainTex;
        float4 _MainTex_TexelSize;
        fixed _RayPercent;
        //Gbuffer1通常为镜面反射 + 粗糙度
        sampler2D _CameraGBufferTexture1;
        //Gbuffer1通常为法线
        sampler2D _CameraGBufferTexture2;
        float4x4 _WorldToView;

        UNITY_DECLARE_DEPTH_TEXTURE(_CameraDepthTexture);
        float4x4 _Projection;
        sampler2D _BackfaceDepthTexture;
        float RAY_LENGTH;
        int STEP_COUNT;

        bool traceRay(float3 start,float3 direction, out float2 hitPixel,out fixed3 debugCol)
        {
            UNITY_LOOP
            for(int i=1;i<=STEP_COUNT;i++)
            {
            	//p是当前的光线的空间位置
                float3 p=start+(float)i/STEP_COUNT*RAY_LENGTH*direction;
				//_ProjectionParams.z是far clip plane的值。
				//又因为viewspace下正前方z值是负的,所以加个负号。
                float pDepth=p.z/-_ProjectionParams.z;
				
				//将光线投影到screen space中。
                float4 screenCoord=mul(_Projection,float4(p,1));
                screenCoord/=screenCoord.w;
                if(screenCoord.x<-1||screenCoord.y<-1||screenCoord.x>1||screenCoord.y>1)
                {
                    return false;
                }
                //获取当前像素的深度。为了使用循环结构,这里必须用tex2Dlod而不是tex2D。
                float compareDepth=Linear01Depth(tex2Dlod(_CameraDepthTexture,
                                float4(screenCoord.xy/2+0.5,0,0)));
                float backZ=tex2Dlod(_BackfaceDepthTexture,float4(screenCoord.xy/2+0.5,0,0));
                if(pDepth>compareDepth&&pDepth<backZ)
                {
                    hitPixel=screenCoord.xy/2+0.5;
                    debugCol=fixed3(hitPixel,0);
                    return true;
                }
            }
            return false;
        }

        struct v2f
        {
            float2 uv : TEXCOORD0;
            float4 vertex : SV_POSITION;
            float4 csRay:TEXCOORD1;
        };

        v2f vert ( appdata_base v)
        {
            v2f o;
            o.vertex = UnityObjectToClipPos(v.vertex);
            o.uv = v.texcoord.xy;
            
			//作为一个后期特效,我们可以通过uv坐标,来获得相机光线方向。
			//注意坐标z为1.0,这里的cameraRay是从原点到far clip plane的光线
            float4 cameraRay=float4(v.texcoord.xy*2-1,1,1);
            //将相机光线从clip space转移到view space       
            cameraRay=mul(unity_CameraInvProjection,cameraRay);
            o.csRay=cameraRay/cameraRay.w;
            return o;
        }

        fixed4 fragA (v2f i) : SV_Target
        {
            float decodedDepth=Linear01Depth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv));
            //因为i.csRay是指着far clip plane的光线,此时csRayOrigin是view space的光线起点
            float3 csRayOrigin=decodedDepth*i.csRay;

            float3 worldNormal=tex2D(_CameraGBufferTexture2,i.uv).rgb*2-1;
            float3 cameraNormal=normalize(mul((float3x3)_WorldToView,worldNormal));

            float2 hitPixel;
            float3 debugCol;
            fixed4 reflection=0;

            if (traceRay(csRayOrigin,normalize(reflect(csRayOrigin,cameraNormal)),
                    hitPixel,debugCol))
            {
                reflection=(1- _RayPercent)*tex2D(_MainTex,hitPixel);
            }

            return tex2D(_MainTex,i.uv)+tex2D(_CameraGBufferTexture1,i.uv)*reflection;
			
        }
        ENDCG

        Pass
        {
        	//注意我在这里使用了模板测试
            Stencil
            {
                Ref 1
                Comp Equal
            }

            Cull Off

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment fragA
            #pragma target 3.0
            ENDCG
        }

    }
}

这里还需要一个渲染背面的深度的Shader

Shader "Hidden/BackfaceShader"
{
    Properties
    {
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "Queue"="Geometry+1"}
        LOD 100
        Cull Front

        Pass
        {
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct v2f {
                float4 position : POSITION;
                float4 linearDepth : TEXCOORD0;
            };

            v2f vert(appdata_base v) {
                v2f output;
                output.position = UnityObjectToClipPos(v.vertex);
                output.linearDepth = float4(0.0, 0.0, COMPUTE_DEPTH_01, 0.0);
                return output;
            }

            float4 frag(v2f input) : COLOR
            {
                return float4(input.linearDepth.z, 0.0, 0.0, 0.0);
            }
            ENDCG
        }
    }
}

最后是C#脚本

using UnityEngine;

[ExecuteInEditMode,RequireComponent(typeof(Camera))]
public class SSREffect : MonoBehaviour
{
    private Shader _backfaceShader;
    private Material _ssrMaterial;
    [Range(0.0f, 1.0f)]
    public float RayPercent=0.5f;
    [Range(1.0f, 10.0f)]
    public float RayLength=4.0f;
    [Range(32,124)]
    public int StepCount=64;


    private void Awake()
    {
        Camera.main.depthTextureMode = DepthTextureMode.Depth;
    }
	
	//SSR材质
    Material SSRMaterial
    {
        get
        {
            if(_ssrMaterial==null)
            {
                _ssrMaterial = new Material(Shader.Find("Hidden/SSR_Blog"));
            }
            return _ssrMaterial;
        }
    }
	
	//背面深度材质
    Shader BFShader
    {
        get
        {
            if(_backfaceShader==null)
            {
                _backfaceShader = Shader.Find("Hidden/BackfaceShader");
            }
            return _backfaceShader;
        }
    }

    private RenderTexture backfaceTexture;
    private Camera backfaceCamera;

    RenderTexture _deferredTexture;
    private Camera defferredCamere;

    public void OnPostRender()
    {
        RenderBackFace();
        DeferredRender();
        SSRMaterial.hideFlags = HideFlags.HideAndDontSave;
        SSRMaterial.SetMatrix("_WorldToView", GetComponent<Camera>().worldToCameraMatrix);
        SSRMaterial.SetTexture("_BackfaceDepthTexture", GetBackFaceTexture());
        SSRMaterial.SetMatrix("_Projection", GetComponent<Camera>().projectionMatrix);
        SSRMaterial.SetTexture("_MainTex", GetDeferredTexture());

        SSRMaterial.SetFloat("_RayPercent", RayPercent);
        SSRMaterial.SetFloat("RAY_LENGTH", RayLength);
        SSRMaterial.SetInt("STEP_COUNT", StepCount);

        GL.PushMatrix();
        GL.LoadOrtho();

        SSRMaterial.SetPass(0);
        // draw a quad over whole screen
        GL.Begin(GL.QUADS);
        GL.TexCoord2(0, 0);
        GL.Vertex3(0, 0, 0);
        GL.TexCoord2(1, 0);
        GL.Vertex3(1, 0, 0);
        GL.TexCoord2(1, 1);
        GL.Vertex3(1, 1, 0);
        GL.TexCoord2(0, 1);
        GL.Vertex3(0, 1, 0);
        GL.End();

        GL.PopMatrix();
    }

	//背面渲染深度渲染贴图
    private RenderTexture GetBackFaceTexture()
    {
        if(backfaceTexture==null)
        {
            backfaceTexture = new RenderTexture(Screen.width, Screen.height, 24, RenderTextureFormat.RFloat);
            backfaceTexture.filterMode = FilterMode.Point;
        }
        return backfaceTexture;
    }
    
    //延迟渲染贴图
    private RenderTexture GetDeferredTexture()
    {
        if(_deferredTexture==null)
        {
            _deferredTexture = new RenderTexture(Screen.width, Screen.height, 24, RenderTextureFormat.ARGB32);
        }
        return _deferredTexture;
    }
	
	//渲染背面深度
    private void RenderBackFace()
    {
        if(backfaceCamera==null)
        {
            GameObject _object = new GameObject();
            Camera _camera = Camera.main;
            _object.transform.SetParent(_camera.transform);
            _object.hideFlags = HideFlags.HideAndDontSave;
            backfaceCamera = _object.AddComponent<Camera>();
            backfaceCamera.CopyFrom(_camera);
            backfaceCamera.enabled = false;
            backfaceCamera.clearFlags = CameraClearFlags.SolidColor;
            backfaceCamera.backgroundColor = Color.white;
            backfaceCamera.renderingPath = RenderingPath.Forward;
            backfaceCamera.SetReplacementShader(BFShader,"RenderType");
            backfaceCamera.targetTexture = GetBackFaceTexture();
        }
        backfaceCamera.Render();
    }
	
	//延迟渲染
    private void DeferredRender()
    {
        if (defferredCamere == null)
        {
            GameObject _object = new GameObject();
            _object.name = "DeferredCamera";
            Camera _camera = Camera.main;
            _object.transform.SetParent(_camera.transform);
            _object.hideFlags = HideFlags.HideAndDontSave;
            defferredCamere = _object.AddComponent<Camera>();
            defferredCamere.CopyFrom(_camera);
            defferredCamere.enabled = false;
            defferredCamere.clearFlags = CameraClearFlags.Skybox;
            defferredCamere.backgroundColor = Color.white;
            defferredCamere.allowMSAA = false;
            defferredCamere.renderingPath = RenderingPath.DeferredShading;
            defferredCamere.targetTexture = GetDeferredTexture();
        }
        defferredCamere.Render();
    }
}

这种方法其实不难,就是有几个坑,而且由于用了延迟渲染和模板测试手机上就别想用了。
下面我来详细讲讲我遇到的几个坑。
为什么用OnPostRender() 而不用 OnRenderImage()?因为用OnRenderImage()模板测试就不好用了,并且貌似Unity的延迟渲染对模板测试也不是很好?还是因为Shader的原因?说到模板测试,这里还用一个写入模板值的Shader,该Shader用作反射物的Shader,我的是用在地板上,以下是代码。

Shader "Custom/FloorStencil"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "Queue"="Geometry"}
        //LOD 200

        Stencil
        {
            Ref 1
            Comp Always
            Pass Replace
        }

        CGPROGRAM
        #pragma surface surf Standard fullforwardshadows

        #pragma target 3.0

        sampler2D _MainTex;

        struct Input
        {
            float2 uv_MainTex;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            // Metallic and smoothness come from slider variables
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

请注意我在这里用了Surface Shader,因为Surface Shader有着对延迟渲染的良好支持,我也有写一个Unlit Shader版本但是关于延迟渲染部分写得不对(想想我就没写过支持延迟渲染的Shader,有时间再研究研究吧),死活没办法实现模板测试…就挺难受的,只能用Surface Shader了。
还有一个坑,看图:
在这里插入图片描述
可以看到胶囊体的倒影出现了长长的拖尾,我用自己写的Surface Shader。
在这里插入图片描述
然后我改成了Standard…
在这里插入图片描述
我属实不知道什么原因,难道还是因为延迟渲染?
这大概就是我遇到的所有的问题了,下一篇准备介绍两个相机的平面反射实现版本。本文到此结束,感谢你的阅读,如有错误欢迎指正。

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
Unity2019内置管线(Universal Render Pipeline)提供了可以实现实时折射和反射Shader。 在URP中,可以使用内置的Lit Shader实现实时折射和反射。下面是一个简单的示例: ``` Shader "Custom/LitRefractionReflection" { Properties { _MainTex ("Base (RGB)", 2D) = "white" {} _NormalMap ("Normal Map", 2D) = "bump" {} _Metallic ("Metallic", Range(0,1)) = 0.0 _Smoothness ("Smoothness", Range(0,1)) = 0.5 _Color ("Color", Color) = (1,1,1,1) _RefractiveIndex ("Refractive Index", Range(1.0, 2.0)) = 1.5 } SubShader { Tags { "RenderType"="Opaque" } CGPROGRAM #pragma surface surf Standard struct Input { float3 worldPos; float3 worldNormal; float3 worldRefl; float3 worldViewDir; float2 uv_MainTex; }; sampler2D _MainTex; sampler2D _NormalMap; float _Metallic; float _Smoothness; fixed4 _Color; float _RefractiveIndex; void surf (Input IN, inout SurfaceOutputStandard o) { // Diffuse o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb * _Color.rgb; // Metallic o.Metallic = _Metallic; // Smoothness o.Smoothness = _Smoothness; // Normal Map o.Normal = UnpackNormal (tex2D (_NormalMap, IN.uv_MainTex)); // Refraction float3 refractionVector = refract(IN.worldViewDir, IN.worldNormal, _RefractiveIndex); o.Refraction = tex2D(_MainTex, IN.uv_MainTex + refractionVector.xy * 0.1).rgb; // Reflection o.Metallic = 1.0; o.Smoothness = 1.0; o.Reflectivity = 1.0; o.Normal = IN.worldNormal; o.Alpha = 0.0; } ENDCG } FallBack "Diffuse" } ``` 这个Shader实现了基本的漫反射、法线贴图、金属度、光滑度,以及折射和反射效果。可以通过调整_RefractiveIndex来修改材质的折射率,从而改变折射效果。 需要注意的是,在URP中实现实时折射和反射需要使用屏幕空间技术,因此需要在摄像机上添加一个反射探针(Reflection Probe)组件来捕获场景中的反射信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值