第二部分:贴花(decal)
贴花(decal)是覆盖在场景中正常物体上的几何体,用于动态改变物体外观,弹孔和脚印等都是贴花的例子。
附加纹理贴花:
直接Shader中增加一个贴花纹理,可以使用第二套uv。缺点是不便于制作跨物体的贴花。
二次渲染贴花:
例如Unity标准包内置的Projector。(Projector组件会把投影的对象使用投影材质重新渲染一遍)
- 得到与projector相交的Mesh。
- 正常渲染之后对每个Mesh进行额外的贴花渲染。
基本原理就是把Mesh的uv用Projector的VP矩阵进行变换然后再渲染。
网格贴花:
- 简单面片贴花:
直接渲染一个半透明面片。
- 复杂的网格贴花:
- 把要贴花的方形区域投影在场景中,从第一次相交的表面提取三角形面片。
- 用投影长方体的四个包围面裁剪面片,并生成纹理坐标等。
- 贴上纹理。(通常要用少许深度偏移(z-bias)来避免和原来的表面产生深度冲突)
屏幕空间贴花(前向渲染):
CSDN「puppet_master」: 需要用到全屏的深度纹理,有可能用到全屏法线纹理。对于前向渲染和延迟渲染,DepthTexture都是可以得到的,但是全屏的法线纹理,对于延迟和前向渲染是不一致的。延迟渲染通过GBuffer获得,前向渲染可以通过DepthNormalTexture获得。但是不管怎样,二者的实现原理是一致的,而且Depth Normal Texture本身就是一个Mini GBuffer。
使用前向渲染时需要相机开启Depth/DepthNormals模式,着色器中使用
_CameraDepthNormalsTexture
/_CameraDepthTexture
变量获取全屏深度图。
- 基本原理:
- 创建一个立方体作为贴花的代理。(渲染该立方体即可)
- 在片元着色器中计算深度对应的世界空间位置,然后变换到立方体模型空间求得UV。
- 剔除掉[0, 1]范围外的片元。(不剔除就是平铺效果)
- 渲染贴花。
- 代码示例:
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.screenPos = ComputeScreenPos(o.vertex);
o.ray =UnityObjectToViewPos(v.vertex) * float3(-1,-1,1);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//深度重建视空间坐标
float2 screenuv = i.screenPos.xy / i.screenPos.w;
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, screenuv);
float viewDepth = Linear01Depth(depth) * _ProjectionParams.z;
float3 viewPos = i.ray * viewDepth / i.ray.z;
//转化到世界空间坐标
float4 worldPos = mul(unity_CameraToWorld, float4(viewPos, 1.0));
//转化为物体空间坐标
float3 objectPos = mul(unity_WorldToObject, worldPos);
//剔除掉在立方体外面的内容
clip(float3(0.5, 0.5, 0.5) - abs(objectPos));
//使用物体空间坐标的xz坐标作为采样uv
float2 uv = objectPos.xz + 0.5;
fixed4 col = tex2D(_MainTex, uv);
return col;
}
屏幕空间贴花(延迟渲染):
CSDN「puppet_master」:在延迟渲染下,Depth和Normal都是有的,而且都是高精度的,可以实现更加精确的效果;而另一方面由于Deferred Decal是发生在G-Buffer之后,全屏着色之前,我们可以直接写入一个颜色信息到GBuffer0也就是Albedo中,最终着色使用的仍然是默认的Deferred Shading着色器;也就是说我们可以用一个很简单的Shader实现Decal与原始场景光照效果完美融合,而不必专门再写一个特殊的带光照的Decal Shader。
注意有一点略微不同,GBuffer中的Normal是世界空间的。
- 通过Command Buffer将渲染Decal的DC插入在Lighting之前:
private void OnEnable()
{
commandBuffer = new CommandBuffer();
commandBuffer.SetRenderTarget(BuiltinRenderTextureType.GBuffer0, BuiltinRenderTextureType.CameraTarget);
commandBuffer.DrawMesh(meshFilter.sharedMesh, transform.localToWorldMatrix, meshRenderer.sharedMaterial);
Camera.main.AddCommandBuffer(CameraEvent.BeforeLighting, commandBuffer);
}
private void OnDisable()
{
Camera.main.RemoveCommandBuffer(CameraEvent.BeforeLighting, commandBuffer);
}
参考文章:
https://blog.csdn.net/puppet_master/article/details/84310361
(END)