ShaderGraph制作UV动画水
需要配置Lightweight Render Pipeline,使用ShaderGraph制作
水效果:
属性:
节点图如下:
节点函数用途:
// Shared Graph Node Functions
void Unity_FresnelEffect_float(float3 Normal, float3 ViewDir, float Power, out float Out)
{
Out = pow((1.0 - saturate(dot(normalize(Normal), normalize(ViewDir)))), Power);
}
void Unity_Power_float(float A, float B, out float Out)
{
Out = pow(A, B);
}
void Unity_Lerp_float4(float4 A, float4 B, float4 T, out float4 Out)
{
Out = lerp(A, B, T);
}
void Unity_Multiply_float (float A, float B, out float Out)
{
Out = A * B;
}
void Unity_Multiply_float (float2 A, float2 B, out float2 Out)
{
Out = A * B;
}
void Unity_TilingAndOffset_float(float2 UV, float2 Tiling, float2 Offset, out float2 Out)
{
Out = UV * Tiling + Offset;
}
void Unity_NormalBlend_float(float3 A, float3 B, out float3 Out)
{
Out = normalize(float3(A.rg + B.rg, A.b * B.b));
}
基于Depth水
深度图里存放了[0,1]范围的非线性分布的深度值,这些深度值来自NDC坐标。 在延迟渲染中,深度值默认已经渲染到G-buffer;而在前向渲染中,你需要去申请。
设置Camera.main.depthTextureMode = DepthTextureMode.Depth,表明了主摄像机渲染了深度图。在shader中通过_CameraDepthTexture获得深度值。
深度纹理中的深度值是d,Zndc对应了NDC坐标中的z分量的值(在摄像机推导中一文中推导),由下列公式计算可得:
由此可知LinearEyeDepth计算结果和zeye一致,它的取值范围就是视锥体深度范围,即[Near,Far]。
//LinearEyeDepth 负责把深度纹理的采样结果转换到视角空间下的深度值
// Z buffer to linear depth
inline float LinearEyeDepth( float z )
{
return 1.0 / (_ZBufferParams.z * z + _ZBufferParams.w);
}
其中_ZBufferParams的定义如下:
double zc0, zc1;
// OpenGL would be this:
// zc0 = (1.0 - m_FarClip / m_NearClip) / 2.0;
// zc1 = (1.0 + m_FarClip / m_NearClip) / 2.0;
// D3D is this:
zc0 = 1.0 - m_FarClip / m_NearClip;// m_FarClip远裁剪平面
zc1 = m_FarClip / m_NearClip;
// now set _ZBufferParams with (zc0, zc1, zc0/m_FarClip, zc1/m_FarClip);
如果我们想要得到范围在[0, 1]之间的深度值,只需要把上面得到的结果除以Far即可。这样,0就表示该点与摄像机位于同一位置,1表示该点位于视锥体的远裁剪平面上。结果如下:
Linear01Depth 则会返回一个范围在[0, 1]的线性深度值,也就是我们上面得到的Z01。
// Z buffer to linear 0..1 depth (0 at eye, 1 at far plane)
inline float Linear01Depth( float z )
{
return 1.0 / (_ZBufferParams.x * z + _ZBufferParams.y);
}
COMPUTE_EYEDEPTH(i):计算顶点在眼空间的深度(eye space depth),并输出到o中。当不渲染到深度纹理时,在顶点程序中使用它。
水效果:
属性:
Shader "Map/Sea" {
Properties {
//水
_WaterAlphaTex ("WaterAlphaTex", 2D) = "black" {}
_WaterNormalTex ("WaterNormalTex", 2D) = "bump" {}
_WaterGradient ("WaterGradient", 2D) = "white" {}
_WaterNosieTex ("WaterNoise", 2D) = "white" {}
_WaterSpeed ("WaterSpeed", float) = 0.74
_Refract("Refract", float) = 0.07
_Specular("Specular", float) = 1.86
_Gloss("Gloss", float) = 0.71
_SpecColor("SpecColor", color) = (1, 1, 1, 1)
_CoastRange("CoastRange", vector) = (0.13, 1.53, 0.37, 0.78)
//浪花
_FoamTex("FoamTex", 2D) = "black" {}
_FoamSpeed ("FoamSpeed", float) = -12.64
_FoamRange ("FoamRange", float) = 0.3
_FoamDelta("FoamDelta", float) = 2.43
_FoamNoiseRange ("FoamNoiseRange", float) = 6.43
}
CGINCLUDE
fixed4 LightingSeaLight(SurfaceOutput s, fixed3 lightDir, half3 viewDir, fixed atten) {
half3 halfVector = normalize(lightDir + viewDir);
float diffFactor = max(0, dot(lightDir, s.Normal)) * 0.8 + 0.2;
float nh = max(0, dot(halfVector, s.Normal));
float spec = pow(nh, s.Specular * 128.0) * s.Gloss;
fixed4 c;
c.rgb = (s.Albedo * _LightColor0.rgb * diffFactor + _SpecColor.rgb * spec * _LightColor0.rgb) * (atten);
c.a = s.Alpha + spec * _SpecColor.a;
return c;
}
ENDCG
SubShader {
Tags { "RenderType"="Transparent" "Queue"="Transparent"}
LOD 200
GrabPass{}
zwrite off
CGPROGRAM
#pragma surface surf SeaLight vertex:vert alpha noshadow
#pragma target 3.0
sampler2D _WaterAlphaTex;
float4 _WaterAlphaTex_TexelSize;
sampler2D _WaterGradient;
sampler2D _WaterNormalTex;
sampler2D _WaterNosieTex;
sampler2D _FoamTex;
sampler2D _CameraDepthTexture;
sampler2D _GrabTexture;
half4 _GrabTexture_TexelSize;
float4 _CoastRange;
half _WaterSpeed;
fixed _Refract;
half _Specular;
fixed _Gloss;
half _FoamNoiseRange;
half _FoamSpeed;
fixed _FoamDelta;
half _FoamRange;
struct Input {
float2 uv_WaterAlphaTex;
float2 uv_WaterNosieTex;
float4 proj;
float3 viewDir;
};
void vert (inout appdata_full v, out Input i) {
UNITY_INITIALIZE_OUTPUT(Input, i);
i.proj = ComputeScreenPos(UnityObjectToClipPos(v.vertex));
COMPUTE_EYEDEPTH(i.proj.z);
}
fixed4 uvMove(sampler2D tex,float2 uv,float offset){
fixed4 colorX = tex2D(tex, uv + offset + float2(_WaterSpeed*_Time.x,0));
fixed4 colorXY = tex2D(tex, float2(1-uv.y,uv.x) + offset + float2(_WaterSpeed*_Time.x,0));
fixed4 color = (colorX+colorXY)/2;
return color;
}
float coastDepth(float rang,float depth){
return min(rang, depth)/rang;
}
fixed4 coastWaveColor(fixed delta,half deltaDepth,float offset,fixed4 noiseColor){
fixed4 waveColor = tex2D(_FoamTex, float2(1-coastDepth(_CoastRange.z, deltaDepth)+_FoamRange*sin(_Time.x*_FoamSpeed+delta+noiseColor.r*_FoamNoiseRange),1)+offset);
waveColor.rgb *= (1-(sin(_Time.x*_FoamSpeed+delta+noiseColor.r*_FoamNoiseRange)+1)/2)*noiseColor.r;
return waveColor;
}
void surf (Input IN, inout SurfaceOutput o) {
float2 uv = IN.proj.xy/IN.proj.w;
#if UNITY_UV_STARTS_AT_TOP
if(_WaterAlphaTex_TexelSize.y<0)
uv.y = 1 - uv.y;
#endif
fixed4 waterAlpha = uvMove(_WaterAlphaTex,IN.uv_WaterAlphaTex,half2(0,0));
float4 offsetColor = uvMove(_WaterNormalTex,IN.uv_WaterAlphaTex,half2(0,0));
half2 offset = UnpackNormal(offsetColor).xy * _Refract;
half deltaDepth = LinearEyeDepth(tex2Dproj (_CameraDepthTexture, IN.proj).r) - IN.proj.z;
fixed4 wateNoiseColor = tex2D(_WaterNosieTex, IN.uv_WaterNosieTex);
half4 seafloor = tex2D(_GrabTexture, uv+offset);
fixed4 waterColor = tex2D(_WaterGradient, float2(coastDepth(_CoastRange.y, deltaDepth),1));
fixed4 waveColorA = coastWaveColor(0,deltaDepth,offset,wateNoiseColor);
fixed4 waveColorB = coastWaveColor(_FoamDelta,deltaDepth,offset,wateNoiseColor);
half water_A = 1-coastDepth(_CoastRange.z, deltaDepth);
half water_B = coastDepth(_CoastRange.w, deltaDepth);
float4 normalColor = uvMove(_WaterNormalTex,IN.uv_WaterAlphaTex,offset);
o.Normal = UnpackNormal(normalColor).xyz;
o.Specular = _Specular;
o.Gloss = _Gloss;
o.Albedo = seafloor.rgb * (1 - water_B) + waterColor.rgb * water_B;
o.Albedo = o.Albedo * (1 - waterAlpha.a*water_A) + waterAlpha.rgb * waterAlpha.a*water_A;
o.Albedo += (waveColorA.rgb+waveColorB.rgb) * water_A;
//o.Albedo = water;
o.Alpha = coastDepth(_CoastRange.x, deltaDepth);
}
ENDCG
}
//FallBack "Diffuse"
}
基于Vertex Color水
波浪运动原理参见王者荣耀地图资源(随风飘摇的小草) 文中的正弦曲线公式。
切线空间
世界空间中worldPos(x,y,z),切线空间中pos’(tangent,binormal,normal),其中tangent、binormal、normal 相互垂直。tangent = (tx,ty,tz),binormal = (bx,by,bz),normal = (nx,ny,nz)。
世界空间变化到切线空间为变换矩阵M,切线空间变化到世界空间为变换矩阵MT
水效果:
属性:
Shader "Map/Sea"
{
Properties
{
_FoamTex("FoamTex(R:waveFoam,G:coastFoam,B:waveDisturbance)", 2D) = "white" {}
[Normal]_NormalTex("NormalTex", 2D) = "bump" {}
_WaveMask ("WaveMask", 2D) = "white" {}
_WaveTex("WaveTex", 2D) = "white" {}
_Gradient("WaterGradient", 2D) = "white" {}
_Sky("Sky", cube) = "" {}
[Space]
_WaveParams ("WaveParams(x:waveRange,y:waveOffset,z:waveDisturbance,w:foamDisturbance)", vector) = (0,0,0,0)
_FoamParams("CoastFoamParams(x:fadeIn,y:fadeOut,z:width,w:alpha)", vector) = (0,0,0,0)
_Speed("Speed(x:windSpeed,y:waveSpeed)", vector) = (0,0,0,0)
[Space]
_NormalScale ("NormalScale", range(0, 1)) = 1
_Fresnel("Fresnel", float) = 0
_Specular("Specular", float) = 0
_Gloss("Gloss", float) = 0
_FoamColor ("FoamColor", color) = (1,1,1,1)
_SpecColor ("SpecColor", color) = (0.4,0.4,0.4,1)
_LightDir("LightDir", vector) = (0, 0, 0, 0)
}
SubShader
{
Tags { "RenderType" = "Transparent" "Queue" = "Transparent" "IgnoreProjector" = "true" }
LOD 100
Pass
{
blend srcalpha oneminussrcalpha
zwrite off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fog
#pragma target 3.0
#include "UnityCG.cginc"
struct v2f
{
float2 uv_FoamTex : TEXCOORD0;
float2 uv_NormalTex : TEXCOORD1;
UNITY_FOG_COORDS(2)
float4 TW0:TEXCOORD3;
float4 TW1:TEXCOORD4;
float4 TW2:TEXCOORD5;
float4 vertex : SV_POSITION;
float4 color : COLOR;
};
sampler2D _FoamTex;
float4 _FoamTex_ST;
sampler2D _WaveTex;
sampler2D _WaveMask;
half4 _Speed;
fixed4 _WaveParams;
half _NormalScale;
half4 _FoamParams;
sampler2D _Gradient;
sampler2D _NormalTex;
float4 _NormalTex_ST;
half _Fresnel;
samplerCUBE _Sky;
half _Specular;
fixed _Gloss;
half4 _LightDir;
half4 _SpecColor;
fixed4 _FoamColor;
//下面函数修改自Unity内置Shader "Unlit/SkyReflection Per Pixel"
v2f vert (appdata_full v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv_FoamTex = TRANSFORM_TEX(v.texcoord, _FoamTex);
o.uv_NormalTex = TRANSFORM_TEX(v.texcoord, _NormalTex);
//o.uv_WaveTex = TRANSFORM_TEX(v.texcoord, _WaveTex);
UNITY_TRANSFER_FOG(o,o.vertex);
/*
法线贴图中的法线向量在切线空间中:
http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-13-normal-mapping/
在点p处存在着一个切线空间,在此空间中,p的法线作为z轴,p的tangent作为x轴,p的binormal由normal和tangent叉乘而得,
作为y轴。tangent由点所在的三角形中的uv方向确定(三角形中的uv值)
*/
//这里是计算顶点的世界坐标
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
//得到顶点法线转换到世界空间的法线,得到切线空间的N
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
//得到顶点的切线转换到世界空间的切线,得到切线空间的T
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
//计算方向(副切线的正负值)
fixed tangentSign = v.tangent.w * unity_WorldTransformParams.w;
//通过T和N的向量积,得到垂直这两个向量的向量,但是它的方向有两个,所以乘以上面得到的方向参数,得到最终的向量,得到切线空间的B
fixed3 worldBinormal = cross(worldNormal, worldTangent) * tangentSign;
//tangent space matrix:切线空间转世界空间
o.TW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
o.TW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
o.TW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
o.color = v.color;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//采样法线贴图
fixed4 normalCol = (tex2D(_NormalTex, i.uv_NormalTex + fixed2(_Time.x*_Speed.x, 0)) + tex2D(_NormalTex, fixed2(_Time.x*_Speed.x + i.uv_NormalTex.y, i.uv_NormalTex.x))) / 2;
half3 worldNormal = UnpackNormal(normalCol);
//泡沫使用法线贴图的rg进行扰动
half3 foam = tex2D(_FoamTex, i.uv_FoamTex + worldNormal.xy*_WaveParams.w).rgb;
worldNormal = lerp(half3(0, 0, 1), worldNormal, _NormalScale);
/*
transform normal from tangent to world space:
通过向量计算,得到世界法线的方向,这里Surf方法里 worldNormal = UnpackNormal(tex2D(_NormalTex, i.uv_NormalTex));
得到的是该顶点的切空间的法线方向,实际上,下面这个方法也等于
worldNormal = normalize(mul( float3x3(i.TW0.xyz, i.TW1.xyz.xyz, i.TW2.xyz.xyz),worldNormal));
*/
worldNormal = normalize(fixed3(dot(i.TW0.xyz, worldNormal), dot(i.TW1.xyz, worldNormal), dot(i.TW2.xyz, worldNormal)));
//根据顶点颜色r通道采样海水渐变
//color.r:深度 color.g 浪花透明 color.b 海浪波纹透明 color.a 整体透明
fixed4 col = tex2D(_Gradient, float2(i.color.r, 0.5));
//采样反射天空盒
half3 worldPos = half3(i.TW0.w, i.TW1.w, i.TW2.w);
half3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
half3 refl = reflect(-viewDir, worldNormal);
half vdn = saturate(pow(dot(viewDir, worldNormal), _Fresnel));
col.rgb = lerp(texCUBE(_Sky, refl), col.rgb, vdn);
//计算海浪和岸边泡沫
//顶点色海岸深度+海浪偏移+海浪范围*sin(时间*海浪速度 + 海浪扰动调整系数*海浪扰动)
//正弦曲线公式参见:https://chenanbao.github.io/2018/10/25/%E9%9A%8F%E9%A3%8E%E9%A3%98%E6%91%87%E7%9A%84%E5%B0%8F%E8%8D%89/
fixed wave1 = tex2D(_WaveTex, float2(i.color.r + _WaveParams.y + _WaveParams.x*sin(_Time.x*_Speed.y + _WaveParams.z*foam.b), 0)).r;
fixed wave2 = tex2D(_WaveTex, float2(i.color.r + _WaveParams.y + _WaveParams.x*cos(_Time.x*_Speed.y + _WaveParams.z*foam.b), 0)).r;
//基于顶点色海岸深度计算水深度透明度
fixed waveAlpha = tex2D(_WaveMask, float2(i.color.r, 0)).r;
//1 - (淡入-顶点色海岸深度)/淡入
fixed sfadein = 1 - saturate((_FoamParams.x - i.color.r) / _FoamParams.x);
//1 - (顶点色海岸深度-淡出)/宽度
fixed sfadeout = 1 - saturate((i.color.r - _FoamParams.y) / _FoamParams.z);
//(岸边泡沫颜色-海水渐变颜色)*(浪一+浪二)*水深度透明度*海浪泡沫法线*海浪波纹透明度
col+= (_FoamColor - col)* (wave1 + wave2)*waveAlpha*foam.r*i.color.b;
//(岸边泡沫颜色-海水渐变颜色)*淡入结果*淡出结果*海岸浪花透明度*浪花透明度
col+= (_FoamColor - col)* sfadein*sfadeout *_FoamParams.w*foam.g*i.color.g;
//计算高光
half3 h = normalize(viewDir - normalize(_LightDir.xyz));
fixed ndh = max(0, dot(worldNormal, h));
col += _Gloss*pow(ndh, _Specular*128.0)*_SpecColor;
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
//应用顶点透明度
col.a *= i.color.a;
//col = i.color;
return col;
}
ENDCG
}
}
}
参考资料: