顶点涟漪与遮罩
飘动旗帜
顶点动画
- 需求:旗子波浪式飘动(动画);越接近旗杆处飘动越受限(遮罩)
- 波浪式飘动(三角函数配合_Time使用),将其附加到需要的方向上;
- 确定需要上下起伏的轴(y);确定作为变量的轴(z)
//addValue = sin((timer + 顶点的z值) * 周期的倒数)* 幅度;
float timer = _Time[3] * _Frequency;
float addValue = sin((timer + v.vertex.z)*3)* _Amplitude;
遮罩
将一般顶点动画的变化量乘以一个由 Mask 图提供的“牵连程度”信息
float maskTex = tex2Dlod(_MaskTex, float4(v.texcoord.xy, 0, 0)).r;
- 因为这个例子中只需要一位信息,所以只取了r值
- 顶点着色器中引入纹理需要注意使用 tex2Dlod, 而非 tex2D
遮罩位置如上,r值越接近0的地方摆动的幅度越弱。最后将显示的部分换成原来的贴图即可。
效果
顶点着色器代码:
void vert(inout appdata_full v)
{
float maskTex = tex2Dlod(_MaskTex, float4(v.texcoord.xy, 0, 0)).r; //遮罩引入
float4 timer = _Time[3] * _Freq; //加入动量
float addValue = sin((timer + v.vertex.z)*3)* _Ampl;
addValue *= maskTex; //遮罩因子
v.vertex.y += addValue;
}
人物呼吸 猴子瞪眼
基本原理
1.需求:使用遮罩控制角色顶点膨胀程度,大致原理与飘扬旗帜相同
2.然而我没有合适的整块人物模型,于是改成了猴子瞪眼
操作过程
这是那只经典的CG猴子的遮罩,在 blender 里只描了眼睛的白色。我希望的效果是各个面片之间是含有缝隙的,因此使用朝法线方向挤出的方式(两种挤出方式见卡通着色那一章)。使用了两个CGPROGRAM块:
一个在内部,负责提供裂缝中的颜色以及瞪出的眼;
float WaveValue = pow(sin(timer), 8); //浮动函数塑造
WaveValue = WaveValue* _Amplitude * mask;//最终浮动量
v.vertex.xyz += normalize(v.normal) * WaveValue;//沿着法线方向挤出(有裂开的效果)
另一个负责形成一个稍微向外裂出的壳,以露出内部的内容。
v.vertex.xyz += normalize(v.normal) * 0.0002;
效果
内部的颜色使用了[HDR]颜色,发光,好看。
完整代码:
Shader "Lesson/sd_MonkeyBlink"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
_MaskTex("Mask Tex", 2D) = "white"{}
_Freq("_Frequency", Range(0, 1)) = 1
_Ampl("Amplitude", Range(0, 1)) = 1
[HDR]_InnerColor("Inner Color", Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
Cull Off
LOD 200
CGPROGRAM
#pragma surface surf Standard fullforwardshadows
#pragma vertex vert
#pragma target 3.0
sampler2D _MainTex;
sampler2D _MaskTex;
struct Input
{
float2 uv_MainTex;
};
half _Glossiness;
half _Metallic;
fixed4 _Color;
UNITY_INSTANCING_BUFFER_START(Props)
UNITY_INSTANCING_BUFFER_END(Props)
void vert(inout appdata_full v)
{
v.vertex.xyz += normalize(v.normal) * 0.0002; //沿着法线方向裂开一点
}
void surf (Input IN, inout SurfaceOutputStandard o)
{
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
ENDCG
CGPROGRAM
#pragma surface surf Lambert
#pragma vertex vert
#pragma target 3.0
sampler2D _MaskTex;
struct Input
{
float2 uv_MaskTex;
};
float _Freq;
float _Ampl;
fixed4 _InnerColor;
float _Emission;
void vert(inout appdata_full v)
{
float mask = tex2Dlod(_MaskTex, float4(v.texcoord.xy, 0, 0)).r; //黑白,只取一个值即可
float timer = _Time[2] * _Freq; //时间参数
float WaveValue = pow(sin(timer), 8); //浮动函数塑造
_Ampl *= 0.01; //调整幅度值
WaveValue = WaveValue* _Ampl * mask; //最终浮动量
v.vertex.xyz += normalize(v.normal) * WaveValue; //沿着法线方向挤出(有裂开的效果)
}
void surf(Input IN, inout SurfaceOutput o)
{
o.Albedo = _InnerColor;
}
ENDCG
}
FallBack "Diffuse"
}
鼠标交互涟漪
一块果冻!!
基本原理
- 需求:可指定中心点的涟漪(球形浮动);浮动的数值随时间变化而降低(脚本控制);因首次扰动而掉落的小旗(脚本控制,可有可无);
- 模型空间与世界空间的转换:unity_ObjectToWorld 矩阵的使用;
- 朝顶点方向挤出,否则可能会裂开(见下图)
- 用脚本将射线击中点送入着色器中改变波动中点;
- 用脚本使每次波动的幅度都线性降低,直到下一次击中;
另外如果要实现史莱姆的效果,需要调低涟漪层数以及摇晃幅度 ,且模型顶点数足够才能Q弹。
实现过程
shader顶点部分:
void vert(inout appdata_full v)
{
//波纹高度图制作----------------------------
float3 worldPos = mul(unity_ObjectToWorld, v.vertex.xyz).xyz;//转为世界坐标系
float dis = distance(worldPos, _RippleCenter);//世界坐标下与波动中心的距离
dis *= _RippleAmount;
dis = 1 - dis;//默认情况是边缘->中心,改成中心->边缘
float timer = _Time[2] * _Speed;//时间因子
//波纹
float waveValue = cos(timer + dis);//基本偏移量
waveValue *= _Amplitude * 0.001;
//v.vertex.xyz += normalize(v.normal) * waveValue;//如果使用法线依旧会裂开
v.vertex.xyz += normalize(v.vertex.xyz) * waveValue;
}
脚本部分:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ClickVertex : MonoBehaviour
{
[Header("射线变量")]
private Ray ray;
private RaycastHit hit; //point, not obj
[SerializeField]
private GameObject obj;
private Material mat;
[Header("摇晃")]
public FlagDrop flag;
public float amplitude = 1.6f;
[SerializeField]
private bool isWaving;
[SerializeField]
private float ampl = 0f;
void Update()
{
if(Input.GetMouseButtonDown(0))
{
ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if(Physics.Raycast(ray, out hit))
{
obj = hit.collider.gameObject;
if(obj.name == "Milk Cube")
{
flag.FirstClick(); //检测是否第一次击中
isWaving = true;
ampl = amplitude;
mat = obj.GetComponent<Renderer>().material;
mat.SetVector("_RippleCenter", hit.point);
mat.SetFloat("_Amplitude", amplitude);
}
}
}
if(isWaving)
{
ampl -= Time.deltaTime;
mat.SetFloat("_Amplitude", ampl);
if(ampl <= 0)
{
isWaving = false;
mat.SetFloat("_Amplitude", 0); //避免直接减到负数部分
}
}
}
}
然后你就可以获得一块可爱的史莱姆啦!
完整shader:
Shader "Lesson/sd_ClickVert"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic("Metatllic", Range(0, 1)) = 0.5
_RippleCenter("涟漪中心", Vector) = (0,0,0,0)
_RippleAmount("涟漪数量", float) = 2
_Speed("涟漪速度", float) = 1.5
_Amplitude("幅度", Range(0, 2)) = 1
}
SubShader
{
Tags { "RenderType"="Transparent"
"Queue" = "Transparent"
"IgnoreProjector" = "True" }
LOD 200
CGPROGRAM
#pragma surface surf Standard fullforwardshadows //alpha
#pragma vertex vert
#pragma target 3.0
sampler2D _MainTex;
struct Input
{
float2 uv_MainTex;
};
half _Glossiness;
half _Metallic;
fixed4 _Color;
float4 _RippleCenter;
float _RippleAmount;
float _Speed;
float _Amplitude;
UNITY_INSTANCING_BUFFER_START(Props)
UNITY_INSTANCING_BUFFER_END(Props)
void vert(inout appdata_full v)
{
//波纹高度图制作----------------------------
float3 worldPos = mul(unity_ObjectToWorld, v.vertex.xyz).xyz; //转为世界坐标
float dis = distance(worldPos, _RippleCenter); //世界坐标下与波动中心的距离
dis *= _RippleAmount;
dis = 1 - dis; //默认情况是边缘->中心,改成中心->边缘
float timer = _Time[2] * _Speed; //时间因子
//波纹
float waveValue = cos(timer + dis); //基本偏移量
waveValue *= _Amplitude * 0.001;
//v.vertex.xyz += normalize(v.normal) * waveValue; //如果使用法线依旧会裂开
v.vertex.xyz += normalize(v.vertex.xyz) * waveValue;
}
void surf (Input IN, inout SurfaceOutputStandard o)
{
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Smoothness = _Glossiness;
o.Metallic = _Metallic;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}