初识ShaderGraph并制作地块随着物体移动而出现的效果

好久没有更新博客了,这阶段其实我也不知道在干嘛,没法总结出来自己到底在干什么,唉,真是奇葩的公司、奇葩的项目啊>_<

抱怨的话也就不多说了,反正大家要看也是冲着技术来的,那么我们现在就开始!

首先让我们来看一下需求的效果是什么

图裂了,怪吧

很不好意思,本人不是美术,demo做的丑了点,但意思是那个意思= =
总之就是人物走到某一个点,地板从脚底下由小到大飞上来,把路铺好,最近的崩坏3联动原神的那个活动地图就是类似的效果。

然后我们来说说什么是ShaderGraph。ShaderGraph是Unity推出的一款可视化材质编写器,旨在让游戏制作人员能快速做出自己想要的材质效果,而不是去硬啃代码并编写它。当然对于我们程序猿来说,这东西和UE的蓝图一样,食之无味、弃之可惜。是的,之前我从来没有正视过这玩意儿,总觉得它是美术人员的玩具,我们程序写代码不也一样实现么= =,直到遇到了这个需求,YouTuber AE Tuts向我展示了ShaderGraph如何快速优雅的解决需求,才让我意识到这东西一定会是以后材质编写的主流!

PS. ShaderGraph只能用于URP(原LWRP)和HDRP,原生管线没法用。

思路大概是这样,我们需要两个位置,一个地板开始的位置,一个地板结束的位置,在这两个位置间做Lerp,作为Lerp的第三个参数需要经过精心计算,这样出来的结果作为本地空间的顶点位置去参与渲染管线,基本就可以了。

现在我们先来看地板开始与结束的位置,连线如下图

地板开始与结束位置的连线

我们从Position节点开始看起,这个节点代表每一个顶点的位置(处于世界坐标下,为什么要在世界坐标下呢,因为后面讲到的物体中心点的位置在世界坐标下,那个没法调),那么每一个顶点位置减去物体中心点的位置(Object节点所代表的意思),这样计算得到的向量作为被减数,再用每个顶点的位置去减,这么一减每个顶点都汇集到中心点了,那么面片上所有的顶点都汇集到中心点去了,自然就看不见了(或者理解为这时的面片无穷小),这时再加(或者减这里都可以)一个y方向的偏移(就是那个Vector3节点),出来的结果就可以作为地板的初始位置,而地板的结束位置就是顶点原来的位置,这样在Lerp的时候就有一个从小到大的视觉效果了。

其实算了这么一大串令人头疼的向量,都是为了做一件事,scale。视频原作者说在Shader不太好弄Scale功能,所以只能以这种向量计算的方式来实现scale。我想了想,MVP矩阵里面可以存scale系数,大家有兴趣也可以试试矩阵计算,这里就先用向量计算了。

有了起始位置和结束位置,我们来看Lerp的第三个参数该怎么计算,如下图

Lerp参数连线

我们肯定是要根据玩家与地板间的距离做效果的,距离大那么地板不出现,距离小那么地板就出现,所以一开始需要拿PlayerPosObject Position做减法,这个PlayerPos是自定义的Property,需要从C#那里每帧传入玩家当前的位置的。减法所得的结果我们用x分量去除以一个叫Range的参数以及鉴于一个叫WhereToStart的参数来控制这个距离的范围,也就是说玩家走到多近距离地板就可以出现了。正常情况下我们得到的这个结果并把它限制在0-1的区间内(那个Clamp节点的功能)就可以作为Lerp的第三个参数了,然而为了看起来有一点随机性,我们在这个结果上附加一个噪声来实现。而噪声图怎么用呢?我们先通过物体位置的x和z作为采样噪声图的uv,采样出来的结果就是我们需要的噪声,这个噪声因为是根据物体的x和z来的,所以在Lerp的时候物体x和z的lerp效果会有一点随机性,看起来更棒。最后为了过渡的时候不是线性的,有个Sample Gradient节点需要添加,让过渡的结果作为Sample Gradient节点的Time参数,而Gradient参数需要定义一个Property,类型叫Gradient,如下图

因为类型叫Gradient,那么命名就不能叫这个了,我这边随意叫了个名“G”。虽然知道结果,但这个节点的功能我还要再研究一下。

Lerp出来的结果如下图去应用

由于结果在世界坐标下,而最终Vertex那边的节点需要一个本地坐标,所以需要一个Transform节点把世界坐标转换到本地坐标然后再连线过去。至此,这样一个效果就大概完成了。

顺着思路,我大概用代码还原了一下(Sample Gradient节点还在研究,没有用代码还原),如下

Shader "Otaku/Bastion"
{
    Properties
    {
        _MainTex("Texture", 2D) = "white"{}
        [HideInInspector]_PlayerPos("Player Pos", Vector) = (0,0,0,0)
        _Height("Object Height", Vector) = (0,0,0,0)
        _Range ("Range", Float) = 1
        _Distance ("Distance", Float) = 0
 
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "LightMode"="UniversalForward" "RenderPipeline"="UniversalPipeline"}

        Pass
        {

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            
            struct Attributes
            {
                half4 positionOS : POSITION;
                half2 uv : TEXCOORD0;
            };

            struct Varyings
            {
                half4 positionCS : SV_POSITION;
                half2 uv : TEXCOORD0;
            };

            TEXTURE2D(_MainTex);
            SAMPLER(sampler_MainTex);

            CBUFFER_START(UnityPerMaterial)
                half4 _MainTex_ST;
                half4 _PlayerPos;
                half3 _Height;
                half _Range;
                half _Distance;
            CBUFFER_END

            half2 GradientNoise_Dir(half2 p)
            {
                p %= 289;
                half x = (34 * p.x + 1) * p.x % 289 + p.y;
                x = (34 * x + 1) * x % 289;
                x = frac(x / 41) * 2 - 1;
                return normalize(half2(x - floor(x + 0.5), abs(x) - 0.5));
            }

            half GradientNoise(half2 p)
            {
                half2 ip = floor(p);
                half2 fp = frac(p);
                half d00 = dot(GradientNoise_Dir(ip),fp);
                half d01 = dot(GradientNoise_Dir(ip + half2(0,1)), fp - half2(0,1));
                half d10 = dot(GradientNoise_Dir(ip + half2(1,0)), fp - half2(1,0));
                half d11 = dot(GradientNoise_Dir(ip + half2(1,1)), fp - half2(1,1));
                fp = fp * fp * fp * (fp * (fp * 6 - 15) + 10);
                return lerp(lerp(d00,d01,fp.y),lerp(d10,d11,fp.y),fp.x);

            }
            
            Varyings vert (Attributes input)
            {
                Varyings output;
                
                half3 objWorldPos = unity_ObjectToWorld._m03_m13_m23; //物体中心位置
                half3 dis = objWorldPos - _PlayerPos; //玩家与每个物体的距离
                half2 uv = objWorldPos.xz;
                half noise = GradientNoise(uv * 10) + 0.5;
                dis.x /= _Range;
                dis.x -= _Distance;
                dis.x = noise - dis.x;
                half extend = clamp(dis.x,0,1);
                half3 posWorld = TransformObjectToWorld(input.positionOS.xyz);
                half3 posWorldPlusY = _Height.xyz;
                posWorldPlusY = (posWorld - (posWorld - objWorldPos)) - posWorldPlusY;
                half3 pos = lerp(posWorldPlusY, posWorld, extend);
                pos = TransformWorldToObject(pos);

                output.positionCS = TransformObjectToHClip(pos);
                output.uv = TRANSFORM_TEX(input.uv, _MainTex);
                
                return output;
            }

            half4 frag (Varyings i) : SV_Target
            {
                half4 col = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv);
                return col;
            }
            ENDHLSL
        }

        
    }
    FallBack "Hidden/Universal Render Pipeline/FallbackError"

}

项目地址

参考
在Unity使用Shader Graph实现《堡垒》游戏的关卡地形组成特效

你可能感兴趣的:(初识ShaderGraph并制作地块随着物体移动而出现的效果)