练习项目(八):在URP中显示法线图

概述

在Build in渲染管线下,获取深度图、法线图很简单。但是在URP下,获取深度图很简单,但是并没有提供对获取法线图的支持。本文主要参考Build in渲染管线下获取法线图的原理,在URP下获取法线图。

一、Build in渲染管线中获取法线图

在Build in渲染管线中,获取法线图很简单,在脚本中添加如下代码,然后挂在相机上即可。这样,深度、法线信息就会存储在名为_CameraDepthNormalsTexture的图中,在Shader中可以采样该图获取深度、法线信息。

Camera.main.depthTextureMode = DepthTextureMode.DepthNormals;

使用Frame Debugger调试,可以发现,渲染法线图的Pass使用的Shader是Hidden/Internal-DepthNormalsTexture。那么,可以想到的一种方式是,模仿该Shader,在URP中添加生成法线图的Pass。

二、URP中获取法线图

从Unity官网下载一份Build in的Shader文件,解压后,上述的Shader的位置为DefaultResourcesExtra/Internal-DepthNormalsTexture.shader。先关注渲染不透明物体的SubShader,如下:

    SubShader
    {
        Tags { "RenderType" = "Opaque" }
        Pass
        {
            CGPROGRAM
            
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            struct v2f
            {
                float4 pos: SV_POSITION;
                float4 nz: TEXCOORD0;
                UNITY_VERTEX_OUTPUT_STEREO
            };
            v2f vert(appdata_base v)
            {
                v2f o;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
                o.pos = UnityObjectToClipPos(v.vertex);
                o.nz.xyz = COMPUTE_VIEW_NORMAL;
                o.nz.w = COMPUTE_DEPTH_01;
                return o;
            }
            fixed4 frag(v2f i): SV_Target
            {
                return EncodeDepthNormal(i.nz.w, i.nz.xyz);
            }
            ENDCG
            
        }
    }

在顶点着色器中,通过COMPUTE_VIEW_NORMAL计算出观察空间下的法线,通过COMPUTE_DEPTH_01计算出观察空间中 [ 0 , 1 ] [0,1] [0,1]范围内的深度值。上述两个方法的具体实现,都可以在UnityCG.cginc文件中找到:

#define COMPUTE_DEPTH_01 -(UnityObjectToViewPos( v.vertex ).z * _ProjectionParams.w)
#define COMPUTE_VIEW_NORMAL normalize(mul((float3x3)UNITY_MATRIX_IT_MV, v.normal))

在片元着色器中,通过EncodeDepthNormal方法将深度和法线信息渲染到一张图中,而EncodeDepthNormal的具体实现也可以在UnityCG.cginc文件中找到:

inline float4 EncodeDepthNormal( float depth, float3 normal )
{
    float4 enc;
    enc.xy = EncodeViewNormalStereo (normal);
    enc.zw = EncodeFloatRG (depth);
    return enc;
}

而EncodeDepthNormal里面又调用了两个内置的方法,同样可以找到:

// Encoding/decoding view space normals into 2D 0..1 vector
inline float2 EncodeViewNormalStereo( float3 n )
{
    float kScale = 1.7777;
    float2 enc;
    enc = n.xy / (n.z+1);
    enc /= kScale;
    enc = enc*0.5+0.5;
    return enc;
}
// Encoding/decoding [0..1) floats into 8 bit/channel RG. Note that 1.0 will not be encoded properly.
inline float2 EncodeFloatRG( float v )
{
    float2 kEncodeMul = float2(1.0, 255.0);
    float kEncodeBit = 1.0/255.0;
    float2 enc = kEncodeMul * v;
    enc = frac (enc);
    enc.x -= enc.y * kEncodeBit;
    return enc;
}

这样,我们就可以实现把Internal-DepthNormalsTexture.shader改写成符合URP的形式,代码如下

Shader有了,怎么调用呢?这里就需要用到了Renderer Feature了。

模仿URP内置的一些Renderer Feature,我们可以实现自己的获取法线图的Renderer Feature。这里需要新建两个脚本,一个是DepthNormalsFeature.cs,另一个是DepthNormalsPass.cs。至于具体的原理,可以参考我之前的博客

然后,在ForwardRenderer资源中添加上述DepthNormalsFeature,这样,就可以调用DepthNormalsPass了。

三、显示法线图

上面只是生成了法线图,但是如果没有其他操作的话,我们并不能看到法线图。那么怎么才能看到法线图呢?

答案是使用后处理,具体的实现可以参考练习项目(四):深度图基础及应用中的“1、渲染深度图”部分。

这里也要新建两个脚本,一个是DisplayNormalTexturePassFeature.cs,另一个是DisplayNormalTexturePass.cs。

然后,在ForwardRenderer资源中添加上述DisplayNormalTexturePassFeature,这样,就可以调用DisplayNormalTexturePass了。

最后,还要实现一个显示法线图的Shader。

            Varyings vert(Attributes input)
            {
                Varyings output = (Varyings)0;
                
                UNITY_SETUP_INSTANCE_ID(input);
                UNITY_TRANSFER_INSTANCE_ID(input, output);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);
                
                output.vertex = TransformObjectToHClip(input.positionOS.xyz);
                output.uv = input.uv;
                
                //当有多个RenderTarget时,需要自己处理UV翻转问题
                #if UNITY_UV_STARTS_AT_TOP //DirectX之类的
                    if (_MainTex_TexelSize.y < 0) //开启了抗锯齿
                    output.uv.y = 1 - output.uv.y; //满足上面两个条件时uv会翻转,因此需要转回来
                #endif
                
                return output;
            }
            half4 frag(Varyings input): SV_Target
            {
                UNITY_SETUP_INSTANCE_ID(input);
                UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);

                half4 normalDepth = SAMPLE_TEXTURE2D_X(_CameraDepthNormalsTexture, sampler_CameraDepthNormalsTexture, UnityStereoTransformScreenSpaceTex(input.uv));
                half3 normal = DecodeViewNormalStereo(normalDepth);
                return half4(normal * 0.5 + 0.5, 1);
            }

在片元着色器中,DecodeViewNormalStereo方法并不是URP内置的方法,而是从Build in渲染管线中移植过来的,可以在UnityCG.cginc文件中找到具体实现:

inline float3 DecodeViewNormalStereo( float4 enc4 )
{
    float kScale = 1.7777;
    float3 nn = enc4.xyz*float3(2*kScale,2*kScale,0) + float3(-kScale,-kScale,1);
    float g = 2.0 / dot(nn.xyz,nn.xyz);
    float3 n;
    n.xy = g*nn.xy;
    n.z = g-1;
    return n;
}

代码如下

四、总结

原本,在Build in渲染管线中,是很简单的一件事,但是在URP中,却大费周折。而且在URP的文档中,直到最新版的10.2.2都没有提供支持,以后会不会提供支持还很难说。另外,在URP中获取法线图的相关资料,在中文互联网上基本没有查到,希望本文能对后来者有帮助。

参考

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值