前言
大家好,本期我们来使用shader代码实现一下冰冻效果,带有冰锥下垂和冰锥向上生长的效果
效果展示
冰冻贴图
效果实现分析
- 渲染模式设置透明,开启常用的aphla混合,渲染队列可以是Geometry也可以是Transparent
Tags {
"RenderType"="Transparent" "Queue"="Geometry" }
Blend SrcAlpha OneMinusSrcAlpha
- 使用2个Pass,第1个Pass渲染基础贴图和法线贴图,第2Pass使用1张冰冻贴图为主贴图
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
float4 tangent : TANGENT;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float3 lightDir : TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
TANGENT_SPACE_ROTATION;//使用这个宏必须在appdata定义normal,tangent
o.lightDir=mul(rotation,ObjSpaceLightDir(v.vertex)).xyz;//将光照方向变换到切线空间
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
float3 normal=UnpackNormal(tex2D(_BumpMap,i.uv));
float diffuse=saturate(dot(normal,i.lightDir)*0.5+0.5);//漫反射
return fixed4(col.rgb*diffuse,1);
}
- 第2个Pass在顶点着色器,在模型空间,将所有顶点沿着法线进行控制,模拟物体表面的一层冰
v.vertex.xyz+=v.normal*0.03*_Progress;//0.03是冰的厚度,_Progress是整体的冰冻进程
- 下垂冰锥的实现,在顶点着色器中,使用法线方向点乘向下(0,-1,0)的方向,
也可以使用mask遮罩控制冰锥的范围
这里2个方法结合起来使用
float n = dot(normalize(v.normal), (0,-1,0));
n = step(0.5, n);
half h = tex2Dlod(_IceMask, float4(v.uv, 0, 0)).r;
//在顶点着色器采样贴图使用tex2Dlod,注意第2个参数传入float4类型
v.vertex.xyz += n*(0,-1,0) * h * _IceCone*_Progress;
//_IceCone控制冰锥的长度
- 实现向上凸起的冰锥,使用mask遮罩控制大致的范围,会有大部分的向上冰块,使用一个噪声贴图,噪声贴图最好有很多黑白噪点,这样可以控制生成尖锐的冰锥.
也可以不要噪声贴图,mask里面控制好要凸起的顶点
half _Ice = tex2Dlod(_NoiseIceExtendMask, float4(v.uv, 0, 0)).r;
half _IceNoise = tex2Dlod(_NoiseIceExtendNoise, float4(v.uv, 0, 0)).r;
_IceNoise = step(0.7, _IceNoise);
v.vertex.xyz += _Ice * v.normal * _IceInstensity2 * _IceNoise*_Progress;
//顶点沿着法线偏移,_IceInstensity2控制整体凸起冰锥的高度
- 实现从鼠标点击的地方开始产生冰冻效果,在uv空间中实现,可以实现怪物被一个冰锥击中,以击中点为中心扩散
如果由于uv贴图的原因,效果不好
可以在模型空间,以y轴为方向,向上下扩散
或者,在模型空间,C#传入模型顶点位置xyz,以球形扩散
fixed2 uv = v.uv - float2(_start.x, _start.y);
o.freeze = 1-step(_Progress*1.42, length(uv));
//控制冰冻从哪个uv位置开始产生,_start
TANGENT_SPACE_ROTATION;
o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;
//将光照和视方向变换到切线空间
要物体与射线发生碰撞,给物体添加MeshCollider组件,可以简化网格,不要勾选凸面,否则生成的凸面没有顶点的uv信息
- C#脚本传入uv位置
if (Input.GetMouseButton(0))
{
Ray ray = camera.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
forstMaterial.SetVector("_start", new Vector4(hit.textureCoord.x, hit.textureCoord.y, 0, 0));
if (!_animation.isPlaying)
{
_animation.Play(animation_name);
}
}
}
- 以下是第2个Pass的顶点着色器部分
fixed4 frostCol = tex2D(_FrostTex, i.uv) * _IceColor;//采样冰冻贴图作为第2个Pass的主贴图
fixed noiseMask = tex2D(_NoiseMask, i.uv).r + 0.3;//主冻贴图的噪声mask,控制a
noiseMask = saturate(noiseMask);
fixed a = _Progress * noiseMask * i.freeze;
- 漫反射部分
float3 normal = UnpackNormal(tex2D(_BumpMap, i.uv));
normal.xy *= _BumpScale;
fixed3 viewDir=normalize(i.viewDir);
float diffuse = saturate(dot(normal, i.lightDir) * 0.5 + 0.5);
fixed _Ice = tex2D(_IceMask, i.uv).r;//下巴处的冰锥亮度高
fixed3 halfDir=normalize(i.lightDir+viewDir);
diffuse+=_Ice*1.3*_Progress;//1.3控制亮度
- 镜面反射
normal=normalize(normal);
//_HasNormalTex没有法线贴图,镜面反射为0
//可以得到世界空间的法线和halfDir进行计算
fixed3 specular=pow(saturate(dot(halfDir,normal)),50)*_SpecularColor*_HasNormalTex;
- 菲涅尔效果,实现边缘区域颜色亮
fixed4 fresnelColor=saturate(pow(1-dot(normal,viewDir),_FresnelWidth))*_FresnelColor*_HasNormalTex;
- 最后颜色,基础颜色*漫反射系数+镜面反射+高光反射
frostCol = fixed4(frostCol.rgb * diffuse+specular+fresnelColor, a*0.5*_Progress);
- 兼容不同模型的方向,由于模型经过旋转,向下方向不一定是(0,-1,0)
在材质面板自定义枚举兼容不同的模型
Properties
{
[KeywordEnum(NY,Y,NX,X,NZ,Z)] _DIR("向下的方向",Float)=0
}
...
CGPROGRAM
#pragma multi_compile _DIR_NY _DIR_Y _DIR_NX _DIR_X _DIR_NZ _DIR_Z
16.怎么制作遮罩贴图控制冰锥范围
在blender中导入模型
选择材质
在shader窗口新建mask贴图
在TexturePaint窗口,绘制区域,白色为冰锥区域
最后将遮罩贴图另存为即可
最终的代码
shader
Shader "Unlit/Frost"
{
Properties
{
_MainTex ("基础贴图", 2D) = "white" {
}
_FrostTex ("冰冻贴图", 2D) = "white" {
}
_IceMask ("冰锥遮罩", 2D) = "white" {
}//实现怪物下巴的冰锥
_BumpMap("法线贴图",2D)="white"{
}
_NoiseMask("冰冻贴图整体噪声遮罩",2D)="white"{
}
[HDR]_IceColor("IceColor",Color)=(0,0,1,1)//冰冻的颜色
_Progress("Progress",Range(0,1))=0.5//冰冻整体进程
_IceCone("冰锥强度",Float)=0.5//下巴的冰锥强度
_BumpScale("BumpScale",Float)=1//法线强度,没有法线设置为0
_NoiseIceExtendMask("冰锥遮罩2",2D)="white"{
}//实现身体的冰
_NoiseIceExtendNoise("冰锥遮罩2噪声贴图",2D)="white"{
}//实现身体的冰的遮罩
_IceInstensity2("冰锥2强度",Float)=0
[KeywordEnum(NY,Y,NX,X,NZ,Z)] _DIR("向下的方向",Float)=0
[Enum(Yes,1,NO,0)] _HasNormalTex("有没有法线贴图",Float)=1
_SpecularColor("镜面反射颜色",Color)=(1,0,0,1)
[HDR]_FresnelColor("菲涅尔颜色",Color)=(1,0,0,1)
_FresnelWidth("菲涅尔宽度",Float)=1
}
SubShader
{
Tags {
"RenderType"="Transparent" "Queue"="Geometry" }
LOD 100
Blend SrcAlpha OneMinusSrcAlpha
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
float4 tangent : TANGENT;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float3 lightDir : TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
TANGENT_SPACE_ROTATION;
o.lightDir=mul(rotation,ObjSpaceLightDir(v.vertex)).xyz;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
float3 normal=UnpackNormal(tex2D(_BumpMap,i.uv));
float diffuse=saturate(dot(normal,i.lightDir)*0.5+0.5);
return fixed4(col.rgb*diffuse,1);
}
ENDCG
}
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile _DIR_NY _DIR_Y _DIR_NX _DIR_X _DIR_NZ _DIR_Z
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float freeze:TEXCOORD1;
float3 lightDir :TEXCOORD2;
float3 viewDir :TEXCOORD3;
};
half3 _Dir;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _FrostTex;
sampler2D _NoiseMask;
sampler2D _IceMask;
sampler2D _BumpMap;
fixed4 _IceColor;
float _Progress;
float _IceCone;
float _BumpScale;
sampler2D _NoiseIceExtendMask;
float _IceInstensity2;
sampler2D _NoiseIceExtendNoise;
fixed4 _SpecularColor;
fixed4 _FresnelColor;
float _FresnelWidth;
float _HasNormalTex;
float4 _start;
v2f vert(appdata v)
{
v2f o;
#if _DIR_NY
_Dir = half3(0, -1, 0);
#elif _DIR_Y
_Dir=half3(0,1,0);
#elif _DIR_NX
_Dir=half3(-1,0,0);
#elif _DIR_X
_Dir=half3(1,0,0);
#elif _DIR_NZ
_Dir=half3(0,0,-1);
#else
_Dir=half3(0,0,1);
#endif
v.vertex.xyz+=v.normal*0.03*_Progress;
float n = dot(normalize(v.normal), _Dir);
n = step(0.5, n);
half h = tex2Dlod(_IceMask, float4(v.uv, 0, 0)).r;
v.vertex.xyz += n*_Dir * h * _IceCone*_Progress;
half _Ice = tex2Dlod(_NoiseIceExtendMask, float4(v.uv, 0, 0)).r;
half _IceNoise = tex2Dlod(_NoiseIceExtendNoise, float4(v.uv, 0, 0)).r;
_IceNoise = step(0.7, _IceNoise);
v.vertex.xyz += _Ice * v.normal * _IceInstensity2 * _IceNoise*_Progress;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
fixed2 uv = v.uv - float2(_start.x, _start.y);
o.freeze = 1-step(_Progress*1.42, length(uv));
TANGENT_SPACE_ROTATION;
o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
fixed4 frostCol = tex2D(_FrostTex, i.uv) * _IceColor;
fixed noiseMask = tex2D(_NoiseMask, i.uv).r + 0.3;
noiseMask = saturate(noiseMask);
fixed a = _Progress * noiseMask * i.freeze;
float3 normal = UnpackNormal(tex2D(_BumpMap, i.uv));
normal.xy *= _BumpScale;
fixed3 viewDir=normalize(i.viewDir);
float diffuse = saturate(dot(normal, i.lightDir) * 0.5 + 0.5);
fixed _Ice = tex2D(_IceMask, i.uv).r;//下巴处的冰锥亮度高
fixed3 halfDir=normalize(i.lightDir+viewDir);
diffuse+=_Ice*1.3*_Progress;
normal=normalize(normal);
fixed3 specular=pow(saturate(dot(halfDir,normal)),50)*_SpecularColor*_HasNormalTex;
fixed4 fresnelColor=saturate(pow(1-dot(normal,viewDir),_FresnelWidth))*_FresnelColor*_HasNormalTex;
frostCol = fixed4(frostCol.rgb * diffuse+specular+fresnelColor, a*0.5*_Progress);
return frostCol;
}
ENDCG
}
}
}
C#代码
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FrostRaycast : MonoBehaviour
{
private Camera camera;
public Material forstMaterial;
private Animation _animation;
public string animation_name;
private void Awake()
{
camera = Camera.main;
_animation = GetComponent<Animation>();
}
void Update()
{
if (Input.GetMouseButton(0))
{
Ray ray = camera.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
forstMaterial.SetVector("_start", new Vector4(hit.textureCoord.x, hit.textureCoord.y, 0, 0));
Debug.Log($"hit.gameObject:{
hit.collider.name}");
Debug.Log($"coord.:{
hit.textureCoord.x},{
hit.textureCoord.y}");
if (!_animation.isPlaying)
{
_animation.Play(animation_name);
}
}
}
}
}