zhouqijie

第二部分:贴花(decal)

贴花(decal)是覆盖在场景中正常物体上的几何体,用于动态改变物体外观,弹孔和脚印等都是贴花的例子。

附加纹理贴花:

直接Shader中增加一个贴花纹理,可以使用第二套uv。缺点是不便于制作跨物体的贴花。

二次渲染贴花:

例如Unity标准包内置的Projector。(Projector组件会把投影的对象使用投影材质重新渲染一遍)

  1. 得到与projector相交的Mesh。
  2. 正常渲染之后对每个Mesh进行额外的贴花渲染。

基本原理就是把Mesh的uv用Projector的VP矩阵进行变换然后再渲染。

网格贴花:

直接渲染一个半透明面片。

  1. 把要贴花的方形区域投影在场景中,从第一次相交的表面提取三角形面片。
  2. 用投影长方体的四个包围面裁剪面片,并生成纹理坐标等。
  3. 贴上纹理。(通常要用少许深度偏移(z-bias)来避免和原来的表面产生深度冲突)

屏幕空间贴花(前向渲染):

CSDN「puppet_master」: 需要用到全屏的深度纹理,有可能用到全屏法线纹理。对于前向渲染和延迟渲染,DepthTexture都是可以得到的,但是全屏的法线纹理,对于延迟和前向渲染是不一致的。延迟渲染通过GBuffer获得,前向渲染可以通过DepthNormalTexture获得。但是不管怎样,二者的实现原理是一致的,而且Depth Normal Texture本身就是一个Mini GBuffer。

使用前向渲染时需要相机开启Depth/DepthNormals模式,着色器中使用_CameraDepthNormalsTexture/_CameraDepthTexture变量获取全屏深度图。

  1. 创建一个立方体作为贴花的代理。(渲染该立方体即可)
  2. 在片元着色器中计算深度对应的世界空间位置,然后变换到立方体模型空间求得UV。
  3. 剔除掉[0, 1]范围外的片元。(不剔除就是平铺效果)
  4. 渲染贴花。
            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是世界空间的。

    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)