好久没有更新博客了,这阶段其实我也不知道在干嘛,没法总结出来自己到底在干什么,唉,真是奇葩的公司、奇葩的项目啊>_<
抱怨的话也就不多说了,反正大家要看也是冲着技术来的,那么我们现在就开始!
首先让我们来看一下需求的效果是什么
很不好意思,本人不是美术,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的第三个参数该怎么计算,如下图
我们肯定是要根据玩家与地板间的距离做效果的,距离大那么地板不出现,距离小那么地板就出现,所以一开始需要拿PlayerPos
和Object 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实现《堡垒》游戏的关卡地形组成特效