老规矩先上图
最近我在设计一个星球题材的独立游戏,其中一个特色是让怪物逐渐被冰冻,并在其下方生成冰柱的特效。在这里,我想和大家分享一下我的设计过程。
一、贴合冰面
首先这个特效需要和与原模型紧密贴合,需要将源模型基础上沿着法线方向向外扩大一些,以避免与原模型有交错。具体代码如下:
//因为是叠加在原本的材质上使用的,所以需要比原本的稍微大一些,我们使用向法线方向延展的方式
v.vertex += downNormal * stepNormal + _BaseVectorNormalScale*v.normal;
二、冰冻起始点
假设你要做的是一个类似冰冻枪的功能,射击后从击中点开始结冰,使用RaycastHit.textureCoord可以获得击中点的UV值,赋值给_CenterUv属性。
_CenterUv("裁剪中心",Vector)=(0.5,0.5,0,0)
三、连贯性逐渐冰冻
一般来说 UV 具有一定的连续性,这里可以将 UV 图中某一点作为圆心点,向外逐渐展开,以达到逐渐冰冻的效果。具体的实现方法是计算指定 UV 点与起点的距离,逐渐增大其值以达到效果。
//主图
half4 var_MainTex = tex2D(_MainTex, i.uv0);
//高光遮蔽图(用于区分哪部分有贴图内容)
half4 var_OcclusionTex = tex2D(_OcclusionTex, i.uv0);
//_CenterUv是指定中心,计算uv与中心点的距离,能修修改显示距离达到向外扩展效果
half dic =1- distance(i.uv0.xy, _CenterUv.xy)* var_OcclusionTex;
clip(dic- _ClipNum);
四、下垂的冰柱
我们需要制作一种向下的冰柱,可以先使用云彩图制作随机扰动效果,这些随机效果的色值通常为0-1。在这里,只有超过0.45的图案部分才能形成向下的冰柱。
为了让冰柱看起来更流畅,我们可以在顶点中使用o.nDirWS.g属性,这将根据冰柱方向下降的程度来调整每个顶点的Y坐标。
因此,在确定了UV映射后,我们需要将超过0.45的顶点向上移动指定的距离,这将产生类似于冰柱的形状。
//在顶点阶段读图需要用tex2DLod(这里是云彩干扰图)
half h = tex2Dlod(_CloudTex, float4(v.uv0.xy, 0, 0)).r;
//读云彩图色值,红色值大于0.45的做法线扩展,用于做冰刺效果
//o.nDirWS.g 得到向下的法线,因为冰刺只要向下的 g 也相当于Y轴
half stepNormal = step(h,0.45)*_VectorNormalScale*max(0,(_VectorNormalScale - o.nDirWS.g));
五、半萤光效果
通常,在冰面上通常会存在一些凹凸不平的效果。我们可以使用贴图来模拟这些效果。此外,有时冰面还会有一些闪闪发光的效果。在这种情况下,我们可以使用菲涅尔光来进行模拟。
//光源漫反射
half3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;
half3 baseCol = var_MainTex.rgb*ambient*_LightColor0;//
half lambert = max(0.0, ndotl);
//光源镜面反射
half specPow = _SpecPow;
half phong = pow(max(0.0,vdotr), _SpecPow);
//光源反射混合
half3 baseLighting = (baseCol + phong * _EnvSpecInt);//(baseCol * lambert + phong * _EnvSpecInt)
//环境漫反身
half3 envDiff = baseCol * _EnvDiffInt;
half upMask = max(0.0, nDirTSOrigin.g);
//环境镜面反射
half3 fresnel = pow(max(0.0, 1.0 - vdotn), _FresnelPow) * _FresnelInt*ambient.rgb;//菲涅尔
half3 envSpec = fresnel * _EnvSpecInt*ambient;
//环境反射混合
half3 envLighting = (envDiff + envSpec)*ambient*_LightColor0;
//最终混合
half3 finalRGB = (baseLighting+envLighting + fresnel )* var_OcclusionTex.r;// ;*(1 - scale)
六、下面是完整代码:
这里付上完整代码,大家可以结合代码和上面的讲解会更好理解。也可以扩展成更多的效果,希望对大家有帮助,谢谢。
Shader "Custom/IceAnimStandard" {
Properties{
[Header(Ground_Base)]
_MainTex("基础色 ",2D) = "white"{
}
_CloudTex("干扰图 ",2D) = "white"{
}
_BaseColor("基础色",Color) = (1.0,1.0,1.0,1.0)
_NormMap("法线贴图",2D) = "bump"{
}
[Header(Normal)]
_NormalScale("法线缩放",Range(0.0,1)) = 1
_VectorNormalScale("顶点法线缩放",Range(0,0.1)) = 0
_BaseVectorNormalScale("基础顶点法线缩放",Range(0,1)) = 0.06
[Header(Ground_Diffuse)]
_EnvDiffInt("环境漫反射强度",Range(0,1)) = 0.2
[Header(Ground_Specular)]
_FresnelPow("菲涅尔次幂",Range(0,10)) = 1
_FresnelInt("菲涅尔强度",Range(0,2)) = 1
_SpecPow("高光次幂",Range(0,5)) = 30
_EnvSpecInt("环境镜面反射强度",Range(0,5)) = 0.2
_OcclusionTex("高光遮蔽",2D) = "white"{
}
_CenterUv("裁剪中心",Vector)=(0.5,0.5,0,0)
_ClipNum("裁剪数值",Range(0,1)) = 1
}
SubShader{
Tags{
"RenderType" = "Opaque" "IgnoreProjector" = "True"}//
Pass{
Tags{
"LightMode" = "ForwardBase"}
//ZWrite off
//Cull off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
#include "UnityCG.cginc"
#pragma target 3.0
#pragma multi_compile_fwdbase
//地面参数
uniform sampler2D _MainTex;
uniform sampler2D _CloudTex;
uniform sampler2D _NormMap; uniform half4 _NormMap_ST;
uniform half4 _BaseColor;
uniform half _NormalScale;
uniform half _VectorNormalScale;
uniform half _BaseVectorNormalScale;
uniform half _EnvDiffInt;
uniform half _SpecPow;
uniform half _EnvSpecInt;
uniform half _FresnelPow;
uniform half _FresnelInt;
uniform sampler2D _OcclusionTex;
uniform half2 _CenterUv;
uniform half _ClipNum;
//输入结构
struct a2v {
float4 vertex: POSITION; //顶点信息
float2 uv0: TEXCOORD0; //UV信息
float4 normal: NORMAL; //法线信息
float4 tangent: TANGENT; //切线信息
};
//输出结构
struct v2f {
float4 pos:SV_POSITION; //屏幕定点位置
float2 uv0:TEXCOORD0; //UV(不可动)保持默认
float2 uv2:TEXCOORD2; //法线UV(用于重复平铺)
float3 WorldPos:TEXCOORD7; //世界坐标位置
float3 nDirWS:TEXCOORD4; //世界坐标法线
float3 tDirWS:TEXCOORD5; //世界坐标切线
float3 bDirWS:TEXCOORD6; //世界坐标副切线
SHADOW_COORDS(3)
};
v2f vert(a2v v) {
v2f o; //新输出结构
o.nDirWS = UnityObjectToWorldNormal(v.normal); //法线位置 OS>WS
half h = tex2Dlod(_CloudTex, float4(v.uv0.xy, 0, 0)).r;
half stepNormal = step(h,0.45)*_VectorNormalScale*max(0,(_VectorNormalScale - o.nDirWS.g));
half4 downNormal = -half4(o.nDirWS,0);
downNormal.y = max(0.1, downNormal.y);
downNormal.x = 0;
downNormal.z = 0;
v.vertex += downNormal * stepNormal + _BaseVectorNormalScale*v.normal;
o.pos = UnityObjectToClipPos(v.vertex); //顶点位置 OS>CS
o.uv0 = v.uv0; //传弟UV
o.uv2 = v.uv0 *_NormMap_ST.xy + _NormMap_ST.zw; //传弟UV
o.WorldPos = mul(unity_ObjectToWorld, v.vertex); //点位置 CS>WS
o.tDirWS = normalize(mul(unity_ObjectToWorld, float4(v.tangent.xyz,0)).xyz); //切线方向
o.bDirWS = normalize(cross(o.nDirWS,o.tDirWS)*v.tangent.w); //副切方向
TRANSFER_SHADOW(o);
return o;
}
float4 frag(v2f i) :SV_TARGET{
//纹理采样
half4 var_MainTex = tex2D(_MainTex, i.uv0);
half4 var_OcclusionTex = tex2D(_OcclusionTex, i.uv0);
half dic =1- distance(i.uv0.xy, _CenterUv.xy)* var_OcclusionTex;
clip(dic- _ClipNum);
var_MainTex.rgb *= _BaseColor;
//向量准备
float3x3 TBN = float3x3(i.tDirWS, i.bDirWS, i.nDirWS);
float4 var_normapTex = tex2D(_NormMap,i.uv2);
float3 nDirTS = UnpackNormal(var_normapTex).rgb;
float3 nDirTSOrigin = normalize(mul(nDirTS, TBN));
nDirTS.xy *= (_NormalScale - (abs(i.uv0.y - 0.5)*(_NormalScale)));
nDirTS.z = max(0.5, sqrt(1.0 - saturate(dot(var_normapTex.xy, var_normapTex.xy))));
float3 nDirWS = normalize(mul(nDirTS, TBN));
float3 vDirWS = normalize(_WorldSpaceCameraPos.xyz - i.WorldPos.xyz);
float3 lDirWS = _WorldSpaceLightPos0.xyz;
float3 lrDirWS = reflect(-lDirWS,nDirWS);
//中间量准备
float ndotl = max(0.5, dot(nDirWS, lDirWS));
float vdotr = dot(vDirWS, lrDirWS);
float vdotn = dot(vDirWS, nDirWS);
//光照模型
//光源漫反射
half3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;
half3 baseCol = var_MainTex.rgb*ambient*_LightColor0;//
half lambert = max(0.0, ndotl);
//光源镜面反射
half specPow = _SpecPow;
half phong = pow(max(0.0,vdotr), _SpecPow);
//光源反射混合
half3 baseLighting = (baseCol + phong * _EnvSpecInt);//(baseCol * lambert + phong * _EnvSpecInt)
//环境漫反身
half3 envDiff = baseCol * _EnvDiffInt;
half upMask = max(0.0, nDirTSOrigin.g);
//环境镜面反射
half3 fresnel = pow(max(0.0, 1.0 - vdotn), _FresnelPow) * _FresnelInt*ambient.rgb;//菲涅尔
half3 envSpec = fresnel * _EnvSpecInt*ambient;
//环境反射混合
half3 envLighting = (envDiff + envSpec)*ambient*_LightColor0;
//最终混合
half3 finalRGB = (baseLighting+envLighting + fresnel )* var_OcclusionTex.r;// ;*(1 - scale)
//返回值
//阴影衰减
UNITY_LIGHT_ATTENUATION(atten, i, i.WorldPos);
return half4((finalRGB)*atten,1);
}
ENDCG
}
}
FallBack "Diffuse"
}