用Unity实现displacement
除了使用normal mapping和parallax mapping以外,我们可以利用tessellation,首先为物体表面生成若干新的顶点,然后对顶点的信息进行调整,来真正地增加物体表面细节的丰富程度。为此,需要对vertex shader修改,在一开始就调整传入的顶点信息:
float displacement = tex2Dlod(_DisplacementMap, float4(i.uv.xy, 0, 0)).g;
displacement = (displacement - 0.5) * _DisplacementStrength;
v.normal = normalize(v.normal);
v.vertex.xyz += v.normal * displacement;
可以看到效果还不错。但是如果要加上阴影,就会出现如下的情况:
第一张图使用的是forward rendering path,而第二张图使用的是deferred rendering path。如果选择前向渲染路径,则物体接收和投射的阴影都是变换顶点之前的;如果选择延迟渲染路径,物体接收阴影的效果是正确的,但投射还是变换顶点之前的。投射阴影有问题的原因不难猜到,shadow caster pass也需要实现displacement。不过,接收阴影,为什么会出现前向渲染错误而延迟渲染正确的情况呢?回忆一下,所谓接收阴影,就是要从shadow map中取出深度信息进行深度比较,那问题应该就出在这里。对于前向渲染,会调用shadow caster pass来渲染depth buffer;而延迟渲染,会调用deferred pass来渲染GBuffer。
补上shadow caster pass的displacement之后,效果如下:
在tessellation的过程中,如果生成的顶点压根不在摄像机的可见范围内,那后续的处理过程都是极大的浪费。我们可以计算生成顶点到摄像机六个剪裁面的距离,来判断该点是否在摄像机的视锥体内,如果不是,就可以跳过tessellation步骤:
TessellationFactors MyPatchConstantFunction (
InputPatch<TessellationControlPoint, 3> patch
) {
float3 p0 = mul(unity_ObjectToWorld, patch[0].vertex).xyz;
float3 p1 = mul(unity_ObjectToWorld, patch[1].vertex).xyz;
float3 p2 = mul(unity_ObjectToWorld, patch[2].vertex).xyz;
TessellationFactors f;
float bias = -0.5 * _DisplacementStrength;
if (TriangleIsCulled(p0, p1, p2, bias)) {
f.edge[0] = f.edge[1] = f.edge[2] = f.inside = 0;
}
else {
...
}
return f;
}
我们选择在tessellation的patch const function中做这件事。只要TessellationFactors的各分量都为0,tessellation就不会在这个三角形上执行了。这里我们还加入了一个bias的机制,防止经过displacement的顶点,因为偏移到了不可见的范围,被裁剪掉了。避免表现上看上去会比较奇怪。
最后来看一下TriangleIsCulled
这个函数的实现:
bool TriangleIsBelowClipPlane (
float3 p0, float3 p1, float3 p2, int planeIndex, float bias
) {
float4 plane = unity_CameraWorldClipPlanes[planeIndex];
return
dot(float4(p0, 1), plane) < bias &&
dot(float4(p1, 1), plane) < bias &&
dot(float4(p2, 1), plane) < bias;
}
bool TriangleIsCulled (float3 p0, float3 p1, float3 p2, float bias) {
return
TriangleIsBelowClipPlane(p0, p1, p2, 0, bias) ||
TriangleIsBelowClipPlane(p0, p1, p2, 1, bias) ||
TriangleIsBelowClipPlane(p0, p1, p2, 2, bias) ||
TriangleIsBelowClipPlane(p0, p1, p2, 3, bias);
}
这里,只有三角形的3个点全部在某个剪裁面之外,才会被认为需要裁剪掉。
如果你觉得我的文章有帮助,欢迎关注我的微信公众号:我是真的想做游戏啊
Reference