在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) {
    ...
}

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

加入厚度

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

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

871155-20170922130408368-640401524.png
如图,拖影不见了。

获取像素实际的厚度

这种方法一般情况下就已经足够好了。如果要进一步改进的话,我们可以通过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;
                    }

871155-20170922130706478-2062214662.png
如图

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

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

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

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

转载于:https://www.cnblogs.com/yangrouchuan/p/7574500.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值