【游戏开发小技】Unity中实现Dota里的角色技能地面贴花效果(URP ShaderGraph Decal)(1)

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

    Pass
    {
        Stencil
        {
            Ref[_StencilRef]
            Comp[_StencilComp]
        }

        Cull[_Cull]
        ZTest[_ZTest]

        // 为了支持透明度混合,关闭深度写入
        ZWrite off
        Blend[_SrcBlend][_DstBlend]

        HLSLPROGRAM

        #pragma vertex vert
        #pragma fragment frag

        // 雾效
        #pragma multi\_compile\_fog

        // 为了使用 ddx() & ddy()
        #pragma target 3.0

        #pragma shader\_feature\_local\_fragment \_ProjectionAngleDiscardEnable
        #pragma shader\_feature\_local \_UnityFogEnable
        #pragma shader\_feature\_local\_fragment \_FracUVEnable
        #pragma shader\_feature\_local\_fragment \_SupportOrthographicCamera

        // 所有URP渲染管线的shader都必须引入这个Core.hlsl
        // 它包含内置shader的变量,比如光照相关的变量,文档:https://docs.unity3d.com/Manual/SL-UnityShaderVariables.html
        // 同时它也包含很多工具方法
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

        struct appdata
        {
            // 模型空间下的坐标,OS: Object Space
            float3 positionOS : POSITION;
        };

        struct v2f
        {
            // 齐次裁剪空间坐标,CS: Clip Space
            float4 positionCS : SV_POSITION;
            // 屏幕坐标
            float4 screenPos : TEXCOORD0;
            // xyz分量: 表示viewRayOS, 即模型空间 (Object Space)下的摄像机到顶点的射线
            // w分量: 拷贝positionVS.z的值,即观察空间 (View Space) 下的顶点坐标的z分量
            float4 viewRayOS : TEXCOORD1; 
            // rgb分量:表示模型空间下的摄像机坐标,
            // a分量:表示雾的强度
            float4 cameraPosOSAndFogFactor : TEXCOORD2;
        };

        sampler2D _MainTex;
        sampler2D _CameraDepthTexture;

        // 支持SRP Batcher
        CBUFFER\_START(UnityPerMaterial)               
            float4 _MainTex_ST;
            float _ProjectionAngleDiscardThreshold;
            half4 _Color;
            half2 _AlphaRemap;
            half _MulAlphaToRGB;
        CBUFFER_END

        // 顶点着色器
        v2f vert(appdata input)
        {
            v2f o;

            // VertexPositionInputs包含多个空间坐标系中的位置(world, view, homogeneous clip space, ndc)
            // Unity编译器将剥离所有未使用的引用 (比如你没有使用view space)
            // 因此,这种结构具有更大的灵活性,无需额外的成本
            VertexPositionInputs vertexPositionInput = GetVertexPositionInputs(input.positionOS);
            // 得到齐次裁剪空间 (clip space) 下的坐标
            o.positionCS = vertexPositionInput.positionCS;

            // Unity雾效

#if _UnityFogEnable
o.cameraPosOSAndFogFactor.a = ComputeFogFactor(o.positionCS.z);
#else
o.cameraPosOSAndFogFactor.a = 0;
#endif

            // 准备深度纹理的屏幕空间UV
            o.screenPos = ComputeScreenPos(o.positionCS);

            // 观察空间 (view space) 坐标,即在观察空间中摄像机到顶点的射线向量
            float3 viewRay = vertexPositionInput.positionVS;

            // [注意,这一步很关键]
            //=========================================================
            // viewRay除以z分量必须在片元着色器中执行,不能在顶点着色器中执行! (由于光栅化变化插值的透视校正)
            // 我们先把viewRay.z存到o.viewRayOS.w中,等到片元着色器阶段在进行处理
            o.viewRayOS.w = viewRay.z;
            //=========================================================

            // unity的相机空间是右手坐标系(z轴负方向指向屏幕),我们希望片段着色器中z射线是正的,所以取反
            viewRay \*= -1;

            // 观察空间到模型空间的变换矩阵
            float4x4 ViewToObjectMatrix = mul(UNITY_MATRIX_I_M, UNITY_MATRIX_I_V);

            // 观察空间 (view space) 转模型空间 (object space) 
            o.viewRayOS.xyz = mul((float3x3)ViewToObjectMatrix, viewRay);
            // 模型空间下摄像机的坐标
            o.cameraPosOSAndFogFactor.xyz = mul(ViewToObjectMatrix, float4(0,0,0,1)).xyz; 

            return o;
        }

        half4 frag(v2f i) : SV_Target
        {
            // [注意,这一步很关键]
            //========================================================================
            // 去齐次
            i.viewRayOS.xyz /= i.viewRayOS.w;
            //========================================================================

            // 深度纹理的UV
            float2 screenSpaceUV = i.screenPos.xy / i.screenPos.w;
            // 对深度纹理进行采样,得到深度信息
            float sceneRawDepth = tex2D(_CameraDepthTexture, screenSpaceUV).r;

            float3 decalSpaceScenePos;

// 正交相机
#if _SupportOrthographicCamera
// 我们必须支持正交和透视两种投影
// unity_OrthoParams:
// unity_OrthoParams是内置着色器遍历,存储的信息如下:
// x 是正交摄像机的宽度,y 是正交摄像机的高度,z 未使用,w 在摄像机为正交模式时是 1.0,而在摄像机为透视模式时是 0.0。
// 更多的内置着色器遍历可查看官方手册: https://docs.unity.cn/cn/2019.4/Manual/SL-UnityShaderVariables.html
// (这里要放 UNITY_BRANCH 吗?) 我决定不放,原因看这里: https://forum.unity.com/threads/correct-use-of-unity_branch.476804/
if(unity_OrthoParams.w)
{
// 如果是正交摄像机, _CameraDepthTexture在[0,1]内线性存储场景深度
#if defined(UNITY_REVERSED_Z)
// 如果platform使用反向深度,要使用1-depth
// https://docs.unity3d.com/Manual/SL-PlatformDifferences.html
sceneRawDepth = 1-sceneRawDepth;
#endif

                // 使用简单的lerp插值: lerp(near,far, [0,1] linear depth), 得到观察空间 (view space)的深度信息 
                float sceneDepthVS = lerp(_ProjectionParams.y, _ProjectionParams.z, sceneRawDepth);


                // 投影
			    float2 viewRayEndPosVS_xy = float2(unity_OrthoParams.xy \* (i.screenPos.xy - 0.5) \* 2 /\* 裁剪空间 \*/);  
                // 构建观察空间坐标
			    float4 vposOrtho = float4(viewRayEndPosVS_xy, -sceneDepthVS, 1);                                            
                // 观察空间转世界空间
			    float3 wposOrtho = mul(UNITY_MATRIX_I_V, vposOrtho).xyz;                                                 
                //----------------------------------------------------------------------------

                // 世界空间转模型空间 (贴花空间)
                decalSpaceScenePos = mul(GetWorldToObjectMatrix(), float4(wposOrtho, 1)).xyz;
            }
            else
            {

#endif
// 如果是透视相机,LinearEyeDepth将为用户处理一切
// 记住,我们不能使用LinearEyeDepth处理正交相机!
// _ZBufferParams:
// 用于线性化 Z 缓冲区值。x 是 (1-远/近),y 是 (远/近),z 是 (x/远),w 是 (y/远)。
float sceneDepthVS = LinearEyeDepth(sceneRawDepth, _ZBufferParams);

                // 在任何空间中,场景深度 = rayStartPos + rayDir \* rayLength
                // 这里所有的数据在 模型空间 (object space) 或 贴花空间 (decal space)
                // 注意,viewRayOS 不是一个单位向量,所以不要规一化它,它是一个方向向量,视图空间z的长度是1
                decalSpaceScenePos = i.cameraPosOSAndFogFactor.xyz + i.viewRayOS.xyz \* sceneDepthVS;

#if _SupportOrthographicCamera
}
#endif

            // unity 的 cube 的顶点坐标范围是 [-0.5, 0.5,],我们把它转到 [0,1] 的范围,用于映射UV
            // 只有你使用 cube 作为 mesh filter 时才能这么干
            float2 decalSpaceUV = decalSpaceScenePos.xy + 0.5;

            // 剔除逻辑
            //===================================================
            // 剔除在 cube 以外的像素信息
            float shouldClip = 0;

#if _ProjectionAngleDiscardEnable
// 也丢弃 “场景法向不面对贴花投射器方向” 的像素
// 使用 ddx 和 ddy 重建场景法线信息
// ddx 就是右边的像素块的值减去左边像素块的值,而ddy就是下面像素块的值减去上面像素块的值。
// ddx 和 ddy 的结果就是副切线和切线方向,利用右手定理,叉乘 (cross) 后就是法线,最后执行归一化 (normalize) 得到法线单位向量
float3 decalSpaceHardNormal = normalize(cross(ddx(decalSpaceScenePos), ddy(decalSpaceScenePos)));

            // 判断是否进行剔除
            // 注:decalSpaceHardNormal.z = dot(decalForwardDir, sceneHardNormalDir)
            shouldClip = decalSpaceHardNormal.z > _ProjectionAngleDiscardThreshold ? 0 : 1;

#endif
// 执行剔除
// 如果 ZWrite 关闭,在移动设备上 clip() 函数是足够效率的,因为它不会写入深度缓冲,所以GPU渲染管线不会卡住(经过ARM官方人员确认过)
clip(0.5 - abs(decalSpaceScenePos) - shouldClip);
//===================================================

            // 贴花UV计算
            // \_MainTex\_ST.xy: 表示uv的tilling
            // \_MainTex\_ST.zw: 表示uv的offset 
            float2 uv = decalSpaceUV.xy \* _MainTex_ST.xy + _MainTex_ST.zw;//Texture tiling & offset

#if _FracUVEnable
// UV裂缝处理
uv = frac(uv);
#endif
// 贴花纹理采样
half4 col = tex2D(_MainTex, uv);
// 与颜色相乘
col *= _Color;
// 透明通道重新映射
col.a = saturate(col.a * _AlphaRemap.x + _AlphaRemap.y);
// 插值
col.rgb *= lerp(1, col.a, _MulAlphaToRGB);

#if _UnityFogEnable
// 混合像素颜色与雾色。你可以选择使用MixFogColor来覆盖雾色
col.rgb = MixFog(col.rgb, i.cameraPosOSAndFogFactor.a);
#endif
return col;
}
ENDHLSL
}
}
}


首先有一个前提,就是模型必须使用`Cube`。  
 最核心的一步就是通过深度信息还原世界空间坐标,再转模型空间坐标(也就是贴花空间坐标),计算出贴花`UV`,对贴花图案采样输出。


其中关于如何通过深度纹理重建世界坐标,大家可以阅读 冯乐乐 写的 《Unity Shader 入门精要》 这本书第13章的`13.3.1`小结,她讲得很好,建议大家多看书学习


![请添加图片描述](https://img-blog.csdnimg.cn/957fa4116d8944e7b9545af3a8830fef.png)


我们把上面的`Shader`保存为`UrpDecal.shader`,如下  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/41b5be538bc74c789769914fdbbee393.png)


##### 2、材质球


我们创建一个材质球,重命名为`UrpDecal`,并使用刚刚的`shader`,如下  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/da5647fc40364eca9ddcec80852d5285.png)  
 设置一下材质球参数,如下  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/3fbacbc62f684d418f97a0151b725d1e.png)


##### 3、创建Cube


创建一个`Cube`,重命名为`DecalCube`,  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/e4c039d95bbc4cffb22adfb02692aaca.png)  
 把上面的材质球赋给这个`Cube`,  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/5d8326e843844c30b0ed4160d70c9b99.png)


##### 4、地面场景


简单搭建一下地面场景,  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/210da6598bd841e09378e561805e011b.png)


##### 5、添加Renderer Feature: Decal


点击`Universal Render Pipeline Asset_Renderer`,点击`Add Renderer Feature`,然后点击`Decal`,  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/1437f786b4f34c37bc85c03aeead172d.png)  
 如下  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/6822a8cc936749efb80ea953134e9600.png)


##### 6、移动DecalCube,与地面交叉


选中`DecalCube`,调整下角度和缩放,  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/8f72dae64bb542a1b954e196e7dfc417.png)  
 然后移动`DecalCube`,让它与地面交叉,此时我们就可以看到想要的贴花效果了


##### 7、运行效果


运行效果如下  
 ![请添加图片描述](https://img-blog.csdnimg.cn/6f68deca7ff84c1fa88eb17e79e68506.gif)


#### 三、方案二:使用URP Decal Projector


在默认渲染管线中,我们可以使用`Projector`来实现贴花效果,比较常见的是假阴影的实现。  
 在`URP`渲染管线中,我们可以使用`URP Decal Projector`。


##### 1、添加Renderer Feature: Decal


跟上面一样,也得添加`Decal`,


![在这里插入图片描述](https://img-blog.csdnimg.cn/1437f786b4f34c37bc85c03aeead172d.png)  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/6822a8cc936749efb80ea953134e9600.png)


##### 2、创建Decal Shader Graph


点击菜单`Create / Shader Graph / URP / Decal Shader Graph`,如下  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/98cdc4a33777402ba28f56707524e274.png)  
 双击打开它`ShaderGraph`,  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/08b5dbca5b724e5bbb1c694d76c32bd6.png)  
 连线图如下  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/e07b382831ab479085680d6f549c0897.png)


##### 3、材质球


我们创建一个材质球并重命名为`DecalShaderGraph`,把上面的`ShaderGraph`赋给它,如下  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/d68b5079bada47db8306bc815d0d0849.png)  
 设置一下材质球参数  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/ed61e884a21f4d709e21b3b247d728f4.png)



![img](https://img-blog.csdnimg.cn/img_convert/ecd89e1b1247696be8704e4a936587f2.png)
![img](https://img-blog.csdnimg.cn/img_convert/af2a7ef86ef6840a2ae915762f84a8b0.png)

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618658159)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

9e21b3b247d728f4.png)



[外链图片转存中...(img-5JZO0q48-1715538521929)]
[外链图片转存中...(img-x2UA2W73-1715538521929)]

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618658159)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值