zoukankan      html  css  js  c++  java
  • 在Unity中实现屏幕空间反射Screen Space Reflection(2)

    traceRay函数

    在上一篇中,我们有如下签名的traceRay函数

    bool traceRay(float3 start, float3 direction, out float2 hitPixel, out float3 debugCol ) {
    }
    

    其中的参数意义都很明了。start和direction是相机空间下的光线起点,以及光线方向。

    traceRay的核心代码并不复杂,如下:

    #define RAY_LENGTH 2.0
    #define STEP_COUNT 64	//maximum sample count.
    				UNITY_LOOP    //强制使用循环结构,不然就会代码5秒钟,编译1小时
    				for (int i = 1; i <= STEP_COUNT; i++) {
    					float3 p = start + (float)i/STEP_COUNT * RAY_LENGTH * direction ;  //p是当前的光线的空间位置
    					float pDepth = p.z / -_ProjectionParams.z;        //_ProjectionParams.z是far clip plane的值。又因为viewspace下正前方z值是负的,所以加个负号。
    					float4 screenCoord = mul(_Projection, float4(p,1));    //将光线投影到screen space中。
    					screenCoord /= screenCoord.w;
    					if (screenCoord.x < -1 || screenCoord.y < -1 || screenCoord.x > 1 || screenCoord.y > 1)
    						return false;
    					float camDepth = Linear01Depth(tex2Dlod(_CameraDepthTexture, float4(screenCoord.xy / 2 + 0.5,0,0)));    //获取当前像素的深度。为了使用循环结构,这里必须用tex2Dlod而不是tex2D。
    					if (Intersect(pDepth,camDepth) ) {    //相交检测
    						hitPixel = screenCoord.xy / 2 + 0.5;
    						debugCol = float3(hitPixel, 0);
    						return true;
    					}
    				}
    

    相交检测

    最简单的方式

    最简单的,如果该像素的深度大于当前光线的深度(离相机更远),此时我们认为这是一个命中。

    if (pDepth > camDepth) {
        ...
    }
    


    该种方法如上图所示,可以看到物体的下方会有明显的“拖影”。

    加入厚度

    为了改进效果,我们加入一个像素厚度的考量。当光线位于像素后面,并且不超出该像素的厚度时,才算命中。我们往往给像素一个固定的厚度。

    if (pDepth > camDepth && pDepth < camDepth + 0.001 ) {        //0.001是厚度
    ...
    }
    


    如图,拖影不见了。

    获取像素实际的厚度

    这种方法一般情况下就已经足够好了。如果要进一步改进的话,我们可以通过backface渲染,得到第二张深度贴图。通过将两张深度贴图的采样相减,得到一个像素的“厚度”。再按照这个厚度去做相交测试。

    后处理脚本:

        private void OnRenderImage(RenderTexture source, RenderTexture destination) {
            RenderBackface();
            mat.SetTexture("_BackfaceTex", GetBackfaceTexture());
            mat.SetMatrix("_WorldToView", GetComponent<Camera>().worldToCameraMatrix);
            Graphics.Blit(source, destination, mat,0);
        }
    private void RenderBackface() {
            if (backfaceCamera == null) {
                var t = new GameObject();
                var mainCamera = Camera.main;
                t.transform.SetParent(mainCamera.transform);
                t.hideFlags = HideFlags.HideAndDontSave;
                backfaceCamera = t.AddComponent<Camera>();
                backfaceCamera.CopyFrom(mainCamera);
                backfaceCamera.enabled = false;
                backfaceCamera.clearFlags = CameraClearFlags.SolidColor;
                backfaceCamera.backgroundColor = Color.white;
                backfaceCamera.renderingPath = RenderingPath.Forward;
                backfaceCamera.SetReplacementShader(backfaceShader, "RenderType");
                backfaceCamera.targetTexture = GetBackfaceTexture();
            }
            backfaceCamera.Render();
            
        }
    
        private RenderTexture backfaceText;
        private RenderTexture GetBackfaceTexture() {
            if (backfaceText == null) { 
                backfaceText = new RenderTexture(Screen.width, Screen.height, 24, RenderTextureFormat.RFloat);
                backfaceText.filterMode = FilterMode.Point;     //VERY IMPORTANT!
            }
            return backfaceText;
        }
    

    渲染背面深度的shader(来自kode80):

    // Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
    
    Shader "Unlit/BackfaceShader"
    {
    	Properties
    	{
    	}
    	SubShader
    	{
    		Tags { "RenderType"="Opaque" }
    		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
    
    		}
    	}
    }
    
    
    					float camDepth = Linear01Depth(tex2Dlod(_CameraDepthTexture, float4(screenCoord.xy / 2 + 0.5, 0, 0)));
    					float backZ = tex2Dlod(_BackfaceTex, float4(screenCoord.xy / 2 + 0.5, 0, 0)).r;
    					if (pDepth > camDepth && pDepth < backZ) {
    						hitPixel = screenCoord.xy / 2 + 0.5;
    						debugCol = float3(hitPixel, 0);
    						return true;
    					}
    


    如图

    注意我在C#脚本中标注的IMPORTANT一行。少了这一行导致了一个非常难debug的bug。具体原因是相机的深度贴图是Point filter的,而自己创建的rendertexture是默认Bilinear filter的;如果不修改的话,我们用同一个坐标去采样会导致实际上是不同位置的采样进行相减。

    要注意的是,这种获取物体厚度的办法并不万能。比如一个物体是只有单面的,此时厚度计算就会出问题(可以想想为什么),类似的,如果相机在一个物体内部(其实也相当于单面)也会出问题。

    对于这些单面物体,如果是透明物体,可以设置为Transparent,不写入z缓冲,并且RenderType设置为非Opqaue,此时背面渲染shader就会忽视这个物体。

    同时,此时光线有可能和物体的“背面“相交,但是毫无疑问我们只能获得物体“正面”的颜色信息。此时反射出现的内容依然是物体的正面,对于纯色物体这没什么问题,但是对于其他物体就会显得很weird了。

  • 相关阅读:
    7zip 自解压安装程序
    修改当前启动菜单项的HyperVisorLaunchType
    VMware 虚拟镜像转 Hyper-V(Win10/2016)
    旋转基础知识
    变换及移动基础知识
    文字及排版章末小结
    文字排版相关
    文字变形及封套扭曲
    LinQ学习笔记.
    PHP笔记-PHP中Web Service.
  • 原文地址:https://www.cnblogs.com/yangrouchuan/p/7574500.html
Copyright © 2011-2022 走看看