冰冻效果Shader案例

老规矩先上图
请添加图片描述

最近我在设计一个星球题材的独立游戏,其中一个特色是让怪物逐渐被冰冻,并在其下方生成冰柱的特效。在这里,我想和大家分享一下我的设计过程。



一、贴合冰面

首先这个特效需要和与原模型紧密贴合,需要将源模型基础上沿着法线方向向外扩大一些,以避免与原模型有交错。具体代码如下:

//因为是叠加在原本的材质上使用的,所以需要比原本的稍微大一些,我们使用向法线方向延展的方式
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"
}
  • 8
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小盖子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值