白纸一张

三十而立,四十而不惑

0%

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

概述

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

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

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

1
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,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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]\)范围内的深度值。上述两个方法的具体实现,都可以在UnityCG.cginc文件中找到:

1
2
#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文件中找到:

1
2
3
4
5
6
7
inline float4 EncodeDepthNormal( float depth, float3 normal )
{
float4 enc;
enc.xy = EncodeViewNormalStereo (normal);
enc.zw = EncodeFloatRG (depth);
return enc;
}

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

1
2
3
4
5
6
7
8
9
10
// 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;
}
1
2
3
4
5
6
7
8
9
10
// 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。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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;
}
1
2
3
4
5
6
7
8
9
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文件中找到具体实现:

1
2
3
4
5
6
7
8
9
10
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中获取法线图的相关资料,在中文互联网上基本没有查到,希望本文能对后来者有帮助。

参考

-------------本文结束感谢您的阅读-------------