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

img
img
img

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

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

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

本文最终效果
请添加图片描述

文章目录
一、前言

嗨,大家好,我是新发。
我平时偶尔会打打Dota2,在Dota里面,技能释放前会有一个地面贴花效果来作为范围的显示,比如深渊领主的这个技能,如下
请添加图片描述
我们可以看到技能范围特效是可以投射到场景地面和物体上(比如树木),这个在Unity中如何去实现呢?本文我以URP渲染管线为例,讲一下这个技能地面贴花效果的制作过程吧~

注:本文我使用的Unity版本为2021.3.1f1c1Universal RP版本为12.1.6

二、环境准备
1、URP环境准备

首先通过PackageManager安装Universal RP,在Project窗口中右键鼠标,点击菜单Create / Rendering / URP Asset (with Universal Renderer)
在这里插入图片描述
此时会创建出两个文件,如下
在这里插入图片描述
打开Project Settings窗口,点击Graphics,把Uinversal Render Pipeline Asset文件拖给Scriptable Render Pipeline Settings,如下
在这里插入图片描述

2、技能范围图案

接着我们去找一张技能范围的图案,我找了一张这样的,
在这里插入图片描述

接下来就是具体的实现过程了。有两个方案,下面我分别给大家讲解一下。

二、方案一:写Shader实现
1、Shader脚本:UrpDecal.shader

我在GitHub上看到有个外国小哥写了个URP渲染管线的贴花Shader
GitHub地址:https://github.com/ColinLeung-NiloCat/UnityURPUnlitScreenSpaceDecalShader

在这里插入图片描述
我给他的Shader添加了中文注释,方便大家阅读,如下

// see README here: 
// github.com/ColinLeung-NiloCat/UnityURPUnlitScreenSpaceDecalShader

Shader "Universal Render Pipeline/NiloCat Extension/Screen Space Decal/Unlit"
{
    Properties
    {
        [Header(Basic)]
        \_MainTex("Texture", 2D) = "white" {}
        [HDR]\_Color("\_Color (default = 1,1,1,1)", color) = (1,1,1,1)

        [Header(Blending)]
        // BlendMode官方手册:https://docs.unity3d.com/ScriptReference/Rendering.BlendMode.html
        // 混合模式
        [Enum(UnityEngine.Rendering.BlendMode)]\_SrcBlend("\_SrcBlend (default = SrcAlpha)", Float) = 5 // 5 = SrcAlpha
        [Enum(UnityEngine.Rendering.BlendMode)]\_DstBlend("\_DstBlend (default = OneMinusSrcAlpha)", Float) = 10 // 10 = OneMinusSrcAlpha

        [Header(Alpha remap(extra alpha control))]
        \_AlphaRemap("\_AlphaRemap (default = 1,0,0,0) \_\_\_\_\_alpha will first mul x, then add y (zw unused)", vector) = (1,0,0,0)

        [Header(Prevent Side Stretching(Compare projection direction with scene normal and Discard if needed))]
        [Toggle(_ProjectionAngleDiscardEnable)] \_ProjectionAngleDiscardEnable("\_ProjectionAngleDiscardEnable (default = off)", float) = 0
        \_ProjectionAngleDiscardThreshold("\_ProjectionAngleDiscardThreshold (default = 0)", range(-1,1)) = 0

        [Header(Mul alpha to rgb)]
        [Toggle]\_MulAlphaToRGB("\_MulAlphaToRGB (default = off)", Float) = 0

        [Header(Ignore texture wrap mode setting)]
        [Toggle(_FracUVEnable)] \_FracUVEnable("\_FracUVEnable (default = off)", Float) = 0

        //====================================== 在常规的用例中,通常可以忽略下面这些设置 =====================================================================
        [Header(Stencil Masking)]
        // https://docs.unity3d.com/ScriptReference/Rendering.CompareFunction.html
        \_StencilRef("\_StencilRef", Float) = 0
        [Enum(UnityEngine.Rendering.CompareFunction)]\_StencilComp("\_StencilComp (default = Disable) \_\_\_\_\_Set to NotEqual if you want to mask by specific \_StencilRef value, else set to Disable", Float) = 0 //0 = disable

        [Header(ZTest)]
        // https://docs.unity3d.com/ScriptReference/Rendering.CompareFunction.html
        // 默认Disable, 因为我们需要确保即使相机进入贴花立方体体积,贴花渲染也正确,尽管默认禁用ZTest将阻止EarlyZ(不利于GPU性能)
        [Enum(UnityEngine.Rendering.CompareFunction)]\_ZTest("\_ZTest (default = Disable) \_\_\_\_\_to improve GPU performance, Set to LessEqual if camera never goes into cube volume, else set to Disable", Float) = 0 //0 = disable

        [Header(Cull)]
        // CullMode官方手册: https://docs.unity3d.com/ScriptReference/Rendering.CullMode.html
        // 默认为Front, 因为我们需要确保即使相机进入贴花立方体体积,贴花渲染也正确
        [Enum(UnityEngine.Rendering.CullMode)]\_Cull("\_Cull (default = Front) \_\_\_\_\_to improve GPU performance, Set to Back if camera never goes into cube volume, else set to Front", Float) = 1 //1 = Front

        [Header(Unity Fog)]
        [Toggle(_UnityFogEnable)] \_UnityFogEnable("\_UnityFogEnable (default = on)", Float) = 1

        [Header(Support Orthographic camera)]
        [Toggle(_SupportOrthographicCamera)] \_SupportOrthographicCamera("\_SupportOrthographicCamera (default = off)", Float) = 0
    }

    SubShader
    {
        // 关于tags的内容可以查阅官网手册:https://docs.unity3d.com/Manual/SL-SubShaderTags.html
        // 为了避免渲染顺序问题, Queue必须 >= 2501, 它位于透明队列中
        // 在透明队列中,Unity总是从后到前渲染
        // 2500以下是不透明物体队列,会进行渲染优化,比如被遮住的就剔除掉不进行渲染
        // 2500以上是透明物体队列,它会根据距离摄像机的距离进行排序
        // 从最远的开始渲染,到最近的结束
        // 天空盒被渲染在所有不透明和透明物体之间
        // "Queue" = "Transparent-499" 即 "Queue" = "2501", 使得它早于所有透明物体进行渲染
        Tags { "RenderType" = "Overlay" "Queue" = "Transparent-499" "DisableBatching" = "True" }

        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
                }


![img](https://img-blog.csdnimg.cn/img_convert/30eca78467f10588fcdea23ef7488daf.png)
![img](https://img-blog.csdnimg.cn/img_convert/9cdb0abe7e2e48e45083ad286e3afecd.png)
![img](https://img-blog.csdnimg.cn/img_convert/d8aa1fad822f8c096f56e06bcf796a69.png)

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

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

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618658159)**

           }


[外链图片转存中...(img-vQNzTKpX-1715538559082)]
[外链图片转存中...(img-4Zh6vTLW-1715538559083)]
[外链图片转存中...(img-I8SWgUk2-1715538559083)]

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

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

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618658159)**

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值