找回密码
 立即注册
查看: 360|回复: 3

Unity Shader学习:贴花(Decal)

[复制链接]
发表于 2022-3-4 21:06 | 显示全部楼层 |阅读模式
贴花(Decal)指在物体表面贴上图案,是游戏中较常见的效果,比如墙上的弹孔、地面的破裂、角色的纹身等。本文尝试了两种实现方法:Projector贴花、屏幕空间贴花(类似延迟贴花)。使用前向渲染、Built-in管线。
Projector贴花

原理
Projector贴花可以理解为从一个管子(自建的projector,也可以是Camera)里看世界,管子的一头贴着图案,通过管子看到的物体就是要贴上图案的目标。按上面的描述,流程分两步:

  • 得到与projector相交(projector中可见)mesh;
  • 正常渲染后,逐mesh进行额外的贴花渲染。
步骤1是判断物体是否在视锥体中的过程。
步骤2中,使用自建的投影矩阵和ObjectToWorld矩阵得到MVP。在顶点阶段,将mesh顶点由模型空间变换到上述裁剪空间,映射到[0,1]区间内得到uv,然后在片元阶段采样贴花纹理获得颜色。
Mono
这里使用了正交矩阵,计算得到projector的VP,传入shader。
float half = size / 2f;
orthoProjector = Matrix4x4.Ortho(-half, half, -half, half, nearClip, farClip);
var projectionMatrix = GL.GetGPUProjectionMatrix(orthoProjector, false);
var vpMatrix = projectionMatrix * this.transform.worldToLocalMatrix;
decalMat.SetMatrix("_DecalVPMatrix", vpMatrix);
关闭需贴花mesh的batching。遍历收集到的相交mesh,进行额外的贴花渲染。
for (int i = 0; i < gos.Count; ++i)
{
    var go = gos;
    var mesh = go.GetComponent<MeshFilter>().sharedMesh;
    Graphics.DrawMesh(mesh, go.transform.localToWorldMatrix, decalMat, 6, null);
}
Shader
使用正常的透明度混合。
vertex中将顶点变换到自建的projector的裁剪空间,然后通过ComputeScreenPos获得齐次坐标系下的屏幕坐标值,范围[0, w]。
frag中通过tex2Dproj采样贴花纹理获得最终颜色。
Blend SrcAlpha OneMinusSrcAlpha

vertex:
float4x4 decalMVP = mul(_DecalVPMatrix, unity_ObjectToWorld);
float4 decalProjectionPos = mul(decalMVP, v.vertex);
o.uvDecal = ComputeScreenPos(decalProjectionPos);

fragment:
fixed4 decalColor = tex2Dproj(_DecalTex, UNITY_PROJ_COORD(i.uvDecal));实践中出现了如下拖尾现象,发生在mesh与projector的forward方向几乎平行时。这里,我通过裁减掉顶点法线与projector forward几乎垂直的片元来避免。



拖尾

mono:
decalMat.SetVector("_DecalProjectorDir", this.transform.forward * -1f);

vertex:
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
o.clipV = dot(worldNormal, _DecalProjectorDir);

fragment:
clip(i.clipV - 0.05);效果



Projector贴花



Frame Debug

Projector贴花是通过多pass来实现的(上图也印证了),每多相交一个mesh,就多一个pass。另一方面,哪怕只相交了一点点,也需要遍历mesh的所有顶点和片元,性能开销需留意。
屏幕空间贴花

原理
屏幕空间贴花很巧妙,同时理解起来会略困难一些。
贴花渲染的对象是一个长方体,需要贴花的部分是该长方体与场景模型相交的、处于该长方体内的那些表面。所以,在frag中,将该长方体的片元根据场景深度信息,计算出其在世界空间的真实位置,然后变换到长方体的模型空间,从而得到该片元对应的贴花纹理uv。
其中的关键问题是“如何通过深度值计算世界空间位置”。
正常渲染的过程:
vertex in object space --(Matrix_M)--> world space --(Matrix_V)--> view space
--(Matrix_P)--> clip space --(齐次/透视除法) --> NDC --(屏幕映射) --> frag in screen space如果将上述过程反过来,就能从屏幕空间变换回模型空间。
Mono
在前向渲染中,获得深度信息需设置相机模式
Camera.main.depthTextureMode |= DepthTextureMode.Depth;
使用Unity Cube创建一个长方体,位置、旋转、缩放按需调整,材质使用贴花shader



贴花用Cube

Shader
参考CJT:Unity从深度缓冲重建世界空间位置,通过深度值获得世界空间坐标
vert:
o.pos = UnityObjectToClipPos(v.vertex);
o.screenPos = ComputeScreenPos(o.pos);

frag:
//齐次除法,计算ndc
float4 divW = i.screenPos / i.screenPos.w;
float4 ndcPos = divW * 2 - 1;
//将屏幕像素对应在摄像机远平面的点转换到剪裁空间,也是相机(0,0,0)指向该点的向量
float far = _ProjectionParams.z;
float3 farClipVec = float3(ndcPos.xy, 1) * far;
//通过逆投影矩阵将向量转换到观察空间
float3 viewVec = mul(unity_CameraInvProjection, farClipVec.xyzz).xyz;
//将向量乘以线性深度值,得到在深度缓冲中储存的值在观察空间的位置
float2 screenUV = divW.xy;
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, screenUV);
float3 viewPos = viewVec * Linear01Depth(depth);
//观察空间变换到世界空间
float4 worldPos = mul(UNITY_MATRIX_I_V, float4(viewPos, 1.0));然后从世界空间变换到Cube的模型空间,clip掉不在模型内的像素,再压扁Y轴获得uv
float4 objectPos = mul(unity_WorldToObject, worldPos);
clip(float3(0.5, 0.5, 0.5) - abs(objectPos));
float2 uv = objectPos.xz + 0.5;
fixed4 finalColor = tex2D(_DecalTex, uv);效果



屏幕空间贴花

屏幕空间贴花pass只跟贴花的Cube数有关、与相交的mesh数无关,但需要额外pass生成深度图。
参考

Unity Shader-Decal贴花(SelfDecal,Alpha Blend,Mesh Decal,Projector,Deferred Decal)
CJT:Unity从深度缓冲重建世界空间位置
https://github.com/ColinLeung-NiloCat/UnityURPUnlitScreenSpaceDecalShader
Projector - Unity 手册
【Unity Shader】基于屏幕空间贴花(Screen Space Decal)的地形裂痕效果
https://juejin.cn/post/6844904067622404103

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2022-3-4 21:13 | 显示全部楼层
请教一下,有没有办法可以让deferred decal一直显示在镜头最前呢?就是我想做一个显示发生在物体背面状态的效果(比如被枪击,触摸等等),尝试了把decal shader的ztest,zwrite关了,但是没有用,改了渲染队列也没有效果。请问有可以把贴在物体背面的deferred decal也显示出来的办法吗?谢谢
发表于 2022-3-4 21:16 | 显示全部楼层
是要画在物体的背面?感觉用projector好做些,cull front拿到物体背面。如果物体不是半透,在通过stencil把他画到前面。延迟渲染怎么实现没有太多思路,maybe,gbuffer中存背面,deffer贴花用的长方体做stencil标记然后再着色
发表于 2022-3-4 21:17 | 显示全部楼层
好的!我去研究一下,多谢多谢!
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-9-22 17:30 , Processed in 0.087042 second(s), 23 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表