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

    技术2023-11-12  110

    屏幕空间反射(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… 我属实不知道什么原因,难道还是因为延迟渲染? 这大概就是我遇到的所有的问题了,下一篇准备介绍两个相机的平面反射实现版本。本文到此结束,感谢你的阅读,如有错误欢迎指正。

    Processed: 0.012, SQL: 9