ShaderDemo
翻书效果
- 绕Z轴旋转
修改 _Angle 大小,来旋转平面
Properties
{
_MainTex ("Texture", 2D) = "white" {}
//旋转角度
_Angle("Angle",Range(0,180))=0
}
....
sampler2D _MainTex;
//角度
float _Angle;
//顶点着色器
v2f vert (appdata v)
{
v2f o;
float s;
float c;
//通过该方法可以计算出该角度的正余弦值
sincos(radians(_Angle),s,c);
//旋转矩阵
float4x4 rotateMatrix={
c ,s,0,0,
-s,c,0,0,
0 ,0,1,0,
0 ,0,0,1
};
//顶点左乘以旋转矩阵
v.vertex = mul(rotateMatrix,v.vertex);
//模型空间转换到裁剪空间
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
....
通过测试发现,这样的旋转并不是我们想要的效果,此时旋转的轴在中心,我们想让它的旋转轴在最左边,此时就需要把所有顶点在旋转之前都往左偏移5个单位,旋转完成之后再向右偏移5个单位就可以达到我们想要的效果了
v2f vert (appdata v)
{
v2f o;
//旋转之前向左偏移5个单位
v.vertex -= float4(5,0,0,0);
float s;
float c;
//通过该方法可以计算出该角度的正余弦值
sincos(radians(_Angle),s,c);
//旋转矩阵
float4x4 rotateMatrix={
c ,s,0,0,
-s,c,0,0,
0 ,0,1,0,
0 ,0,0,1
};
//顶点左乘以旋转矩阵
v.vertex = mul(rotateMatrix,v.vertex);
//旋转之后偏移回来
v.vertex += float4(5,0,0,0);
//模型空间转换到裁剪空间
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
现在的翻书效果太生硬了,为了接近真实的翻书效果,我们就需要通过正余弦函数修改顶点的y坐标,来达到一个弧度的效果。
v2f vert (appdata v)
{
v2f o;
//旋转之前向右偏移5个单位
v.vertex -= float4(5,0,0,0);
float s;
float c;
//通过该方法可以计算出该角度的正余弦值
sincos(radians(_Angle),s,c);
//旋转矩阵
float4x4 rotateMatrix={
c ,s,0,0,
-s,c,0,0,
0 ,0,1,0,
0 ,0,0,1
};
//根据x坐标,通过正弦函数计算出 y坐标的正弦值, _WaveLength 控制波长, 振幅就跟随角度正弦值动态变化
v.vertex.y = sin(v.vertex.x*_WaveLength) * s ;
//顶点左乘以旋转矩阵
v.vertex = mul(rotateMatrix,v.vertex);
//旋转之后偏移回来
v.vertex += float4(5,0,0,0);
//模型空间转换到裁剪空间
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
仔细观察会发现“翻书”的过程,背面有点不真实,不应该是该纹理的反面,而是另一张新的纹理,此时我们该怎么办呢?其实很简单,只需要把正面和反面分开渲染就可以了,一个Pass渲染正面,一个Pass渲染背面。
完整代码
Shader "Learn Unity Shader/openBook"
{
Properties
{
//正面纹理
_MainTex ("Texture", 2D) = "white" {}
//背面纹理
_SecTex("SecTex",2D)="White"{}
//旋转角度
_Angle("Angle",Range(0,180))=0
//波长
_WaveLength("WaveLength",Range(-1,1))=0
}
SubShader
{
Pass
{
//剔除背面
Cull Back
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
//角度
float _Angle;
//波长
float _WaveLength;
v2f vert (appdata v)
{
v2f o;
//旋转之前向右偏移5个单位
v.vertex -= float4(5,0,0,0);
float s;
float c;
//通过该方法可以计算出该角度的正余弦值
sincos(radians(_Angle),s,c);
//旋转矩阵
float4x4 rotateMatrix={
c ,s,0,0,
-s,c,0,0,
0 ,0,1,0,
0 ,0,0,1
};
//根据x坐标,通过正弦函数计算出 y坐标的正弦值, _WaveLength 控制波长, 振幅就跟随角度正弦值动态变化
v.vertex.y = sin(v.vertex.x*_WaveLength) * s ;
//顶点左乘以旋转矩阵
v.vertex = mul(rotateMatrix,v.vertex);
//旋转之后偏移回来
v.vertex += float4(5,0,0,0);
//模型空间转换到裁剪空间
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
return col;
}
ENDCG
}
Pass
{
//剔除正面
Cull Front
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
//角度
float _Angle;
//波长
float _WaveLength;
sampler2D _SecTex;
float4 _SecTex_ST;
v2f vert (appdata v)
{
v2f o;
//旋转之前向右偏移5个单位
v.vertex -= float4(5,0,0,0);
float s;
float c;
//通过该方法可以计算出该角度的正余弦值
sincos(radians(_Angle),s,c);
//旋转矩阵
float4x4 rotateMatrix={
c ,s,0,0,
-s,c,0,0,
0 ,0,1,0,
0 ,0,0,1
};
//根据x坐标,通过正弦函数计算出 y坐标的正弦值, _WaveLength 控制波长, 振幅就跟随角度正弦值动态变化
v.vertex.y = sin(v.vertex.x*_WaveLength) * s ;
//顶点左乘以旋转矩阵
v.vertex = mul(rotateMatrix,v.vertex);
//旋转之后偏移回来
v.vertex += float4(5,0,0,0);
//模型空间转换到裁剪空间
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_SecTex, i.uv);
return col;
}
ENDCG
}
}
}
压扁效果
实现原理:
1、通过脚本求得物体的的最高顶点和最低顶点;
2、传给shader,得到最高顶点和最低顶点的范围,并计算各顶点到最高顶点的距离归一化比;
3、在通过一个参数,来控制压缩程度,即是 模型 顶点 y的偏移程度,实现压缩效果;
注意事项:
1、 使用 max(0,_Control-normalizedDist) 来调整压缩
2、因为使用了归一化顶点比例,所以最终偏移顶点y 的时候需要乘以一个 最高顶点与最低顶点的距离;
3、因为这是世界位置上的压缩,所以记得把计算的顶点位置添加上 _ObjectWorldPosY;
4、必要可以取消归一化那一步骤,就把不需要最后再乘以 Range_Top_Bottom;
// https://www.freesion.com/article/7411549136/
Shader "Kaima/Other/Squash"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_TopY("Top Y", Float) = 0 //The top Y of the GameObject in world coord
_BottomY("Bottom Y", Float) = 0
_Control("Control Squash", Range(0, 1)) = 0 //control the level of squash
}
SubShader
{
Tags { "RenderType"="Opaque" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float _TopY;
float _BottomY;
float _Control;
float GetNormalizedDist(float worldPosY)
{
float range = _TopY - _BottomY;
float border = _TopY;
float dist = abs(worldPosY - border);
float normalizedDist = saturate(dist / range);
return normalizedDist;
}
v2f vert (appdata v)
{
v2f o;
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
float worldPosY = mul(unity_ObjectToWorld, v.vertex).y;
//float3 localNegativeY = mul(unity_WorldToObject, float4(0, -1, 0, 1)).xyz;
float normalizedDist = GetNormalizedDist(worldPosY);
压缩比例(至于为什么max 是因为压缩normalizedDist 为0-1, _Control - 为负,
//说明未压缩到,故取0,不然就反置压扁,你可以试试不要 max 看看结果)
float val = max(0, _Control - normalizedDist);
//v.vertex.xyz += localNegativeY * val;
v.vertex.y -= val; // v.vertex.y += val;
因为Y 值上压扁变化,故减掉 y 上的压扁的距离(注意乘上 顶点的距差(不然,不乘以距离差,你可以试试capsule会压不扁))
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
return col;
}
ENDCG
}
}
}
使用
public class ToySquashY : MonoBehaviour
{
void Start()
{
Material mat = GetComponent<Renderer>().material;
float minY, maxY;
CalculateMinMaxY(out minY, out maxY);
mat.SetFloat("_BottomY", minY);
mat.SetFloat("_TopY", maxY);
mat.SetFloat("_ObjectWorldPosY", transform.position.y);
}
/// <summary>
/// 计算模型 Y 最高值顶点和最小值顶点
/// </summary>
/// <param name="minY"></param>
/// <param name="maxY"></param>
void CalculateMinMaxY(out float minY, out float maxY)
{
Vector3[] vertices = GetComponent<MeshFilter>().mesh.vertices;
minY = maxY = vertices[0].y;
for (int i = 1; i < vertices.Length; i++)
{
Vector3 tmp = vertices[i];
float y = tmp.y;
if (y < minY)
minY = y;
if (y > maxY)
maxY = y;
}
// 转到世界坐标上去
minY += transform.position.y;
maxY += transform.position.y;
}
}
物体被地面吸收效果
实现原理:
- 通过脚本求得物体的最高点和最低点
- 计算各顶点到最高点的距离归一化
- 使用参数控制物体吸收的程度, 范围 0-2
- 对地里面的部分 clip
注意:
_Control = sin(_Time.y * _Speed ) + 1; 用来代码模拟的,正式用的时候可以用其他方法代替;
Shader "Kaima/Other/BornFromY"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_TopY("Top Y", Float) = 0
_BottomY("Bottom Y", Float) = 0
_Control("Born Control", Range(0, 2)) = 0
}
SubShader
{
Tags { "RenderType"="Opaque" }
Pass
{
Cull Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float3 worldPos : TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float _TopY;
float _BottomY;
float _Control;
//计算到上边界的值
float GetNormalizedDist(float worldPosY)
{
float range = _TopY - _BottomY;
float border = _TopY;
float dist = abs(worldPosY - border);
float normalizedDist = saturate(dist / range);
return normalizedDist;
}
v2f vert (appdata v)
{
v2f o;
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
float3 localPositiveY = mul(unity_WorldToObject, float4(0, 1, 0, 1)).xyz;
float normalizedDist = GetNormalizedDist(worldPos.y);
float val = max(0, _Control - normalizedDist);
v.vertex.xyz += localPositiveY * val;
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
clip(_TopY - i.worldPos.y);
fixed4 col = tex2D(_MainTex, i.uv);
return col;
}
ENDCG
}
}
}
黑洞吸收效果
Shader "Unlit/ShaderBlackHole"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_NearPos("Near Postion",Vector) =(0,0,0,0)
_FarPos("Far Position",Vector)=(0,0,0,0)
_Control("Control Squash",Range(0,2))=0
_Speed("Speed",Range(0,10))=1
_ObjectWorldPos("Object World Position",Vector)=(0,0,0,0)
_BlackHolePos("Black Hole Position",Vector)=(0,0,0,0)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata{
float4 vertex:POSITION;
float2 uv:TEXCOORD0;
};
struct v2f{
float2 uv:TEXCOORD0;
float4 vertex:SV_POSITION;
float3 worldPos:TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float3 _NearPos;
float3 _FarPos;
float3 _BlackHolePos;
float _Control;
float _Speed;
float rangeFar_Near;
// 求顶点到最高顶点的距离比例
float GetNormalizedDist(float3 worldPos){
// 加上在世界坐标的位置
worldPos = worldPos ;
// 最大最小范围
rangeFar_Near = length(_FarPos-_NearPos);
// 以最高顶点为黑洞吸收点开始点
float3 border = _NearPos;
// 求顶点据最高顶点的距离绝对值
float dist = length( worldPos-border);
//归一化距离值
float normalizedDist = saturate(dist/rangeFar_Near);
return normalizedDist;
}
v2f vert(appdata v){
v2f o;
o.uv = TRANSFORM_TEX(v.uv,_MainTex);
// 把顶点撞到世界坐标上
float3 worldPos = mul(unity_ObjectToWorld, v.vertex);
// 得到顶点距离顶部的比例
float normalizedDist = GetNormalizedDist(worldPos);
// sin 值范围为 (-1,1),+1 使得范围再(0,2)周期变化
_Control = sin(_Time.y * _Speed ) + 1;
// 变化比例(至于为什么max 是因为变化normalizedDist 为0-1, _Control - 为负,说明未变化到,故取0,不然就,你可以试试不要 max 看看结果)
float val = max(0,_Control-normalizedDist);
float3 toBlackHole = mul(unity_WorldToObject, (_BlackHolePos - worldPos)).xyz;
float3 srcVertex = v.vertex.xyz;
// 向黑洞方向偏移顶点数值,渐渐被黑洞吸收
v.vertex.xyz += toBlackHole* val;
// 因为 val 的值会大于1,所以当顶点偏移到黑洞就一直保持
// length(v.vertex.xyz - srcVertex) 当前顶点偏移的值
// _BlackHolePos - worldPos 顶点到黑洞的最大值
// 故当 当前顶点偏移的值 大于 顶点到黑洞的最大值,让顶点值设置为黑洞处
if(length(v.vertex.xyz - srcVertex) > length(_BlackHolePos - worldPos)){
v.vertex.xyz = srcVertex+ toBlackHole;
}
// 转到裁剪空间
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag(v2f i):SV_Target{
fixed4 col = tex2D(_MainTex,i.uv);
return col;
}
ENDCG
}
}
}
残影
一般残影的实现原理: 在主题发生一定位移后,每间隔一段时间,克隆一段Mesh,用FadeShader进行渲染, 1秒后销毁
本文的残影实现不同于上面的原理!
Shader "Kaima/Other/GhostShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_GhostColor("Ghost Color", Color) = (1,1,1,1) //残影颜色
_Offset("Offset", Range(0, 2)) = 0 //残影偏离本位的距离
_GhostAlpha("Ghost Alpha", Range(0, 1)) = 1 //残影的透明度
_ShakeLevel("Shake Level", Range(0, 2)) = 0 //残影抖动的程度
_ShakeSpeed("Shake Speed", Range(0, 50)) = 1 //残影的移动速度
_ShakeDir("Shake Direction", Vector) = (0, 0, 1, 0) //残影移动的方向
_Control("Control", Range(0, 0.54)) = 0 //整体控制残影
}
SubShader
{
Tags { "Queue"="Transparent" "RenderType"="Transparent" }
//第一个通道渲染影子
Pass //残影
{
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
};
sampler2D _MainTex;
fixed4 _GhostColor;
float _Offset;
float _GhostAlpha;
float _ShakeLevel;
float _ShakeSpeed;
float _Control;
float4 _ShakeDir;
v2f vert(appdata_base v)
{
float yOffset = 0.5 * (floor(v.vertex.x * 10) % 2);
v2f o;
v.vertex += _Offset * cos(_Time.y * _ShakeSpeed) * _ShakeDir * _Control; //偏移
v.vertex += _ShakeLevel * yOffset * sin(_Time.y * _ShakeSpeed) * _ShakeDir * _Control; //抖动
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return fixed4(tex2D(_MainTex, i.uv).rgb * _GhostColor, _GhostAlpha);
}
ENDCG
}
//第二个通道渲染角色
Pass //普通着色
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return tex2D(_MainTex, i.uv);
}
ENDCG
}
}
}
折纸
思路是对顶点以折叠处为中心进行旋转。如果只是折叠一边,则只旋转一边;如果折叠两边,则两边一起旋转
Shader "Kaima/Other/PaperFold"
{
Properties
{
_FrontText ("Front Tex", 2D) = "white" {}
_BackTex ("Back Tex", 2D) = "white" {}
_FoldPos ("Fold Pos", float) = 5
_FoldAngle ("Fold Angle", Range(1, 180)) = 90
[Toggle(ENABLE_DOUBLE)] _DoubleFold ("Double Fold", Float) = 0
}
SubShader
{
Tags { "Queue"="Transparent" "RenderType"="Transparent" }
Pass
{
ZWrite On
Cull Back
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma shader_feature ENABLE_DOUBLE
#include "UnityCG.cginc"
struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
};
sampler2D _FrontText;
float4 _FrontText_ST;
float _FoldPos, _FoldAngle;
v2f vert (appdata_base v)
{
float angle = _FoldAngle;
float r = _FoldPos - v.vertex.x;
#if ENABLE_DOUBLE
if(r <= 0)
angle = 360 - _FoldAngle;
#else
if(r <= 0)
angle = 180;
#endif
v.vertex.x = _FoldPos + r * cos(angle * UNITY_PI / 180);
v.vertex.y = r * sin(angle * UNITY_PI / 180);
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _FrontText);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_FrontText, i.uv);
return col;
}
ENDCG
}
Pass
{
ZWrite On
Cull Front
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma shader_feature ENABLE_DOUBLE
#include "UnityCG.cginc"
struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
};
sampler2D _BackTex;
float4 _BackTex_ST;
float _FoldPos, _FoldAngle;
v2f vert (appdata_base v)
{
float angle = _FoldAngle;
float r = _FoldPos - v.vertex.x;
#if ENABLE_DOUBLE
if(r <= 0)
angle = 360 - _FoldAngle;
#else
if(r <= 0)
angle = 180;
#endif
v.vertex.x = _FoldPos + r * cos(angle * UNITY_PI / 180);
v.vertex.y = r * sin(angle * UNITY_PI / 180);
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _BackTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_BackTex, i.uv);
return col;
}
ENDCG
}
}
}
消融效果
噪音纹理 + 透明度测试
Shader "Kaima/Dissolve/Basic"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_NoiseTex("Noise", 2D) = "white" {}
_Threshold("Threshold", Range(0.0, 1.0)) = 0.5
}
SubShader
{
Tags { "Queue"="Geometry" "RenderType"="Opaque" }
Pass
{
Cull Off //要渲染背面保证效果正确
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 uvMainTex : TEXCOORD0;
float2 uvNoiseTex : TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _NoiseTex;
float4 _NoiseTex_ST;
float _Threshold;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uvMainTex = TRANSFORM_TEX(v.uv, _MainTex);
o.uvNoiseTex = TRANSFORM_TEX(v.uv, _NoiseTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed cutout = tex2D(_NoiseTex, i.uvNoiseTex).r;
clip(cutout - _Threshold);
fixed4 col = tex2D(_MainTex, i.uvMainTex);
return col;
}
ENDCG
}
}
}
改造 边缘颜色
Shader "Kaima/Dissolve/EdgeColor"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_NoiseTex("Noise", 2D) = "white" {}
_Threshold("Threshold", Range(0.0, 1.0)) = 0.5
_EdgeLength("Edge Length", Range(0.0, 0.2)) = 0.1
_EdgeColor("Edge Color", Color) = (1,1,1,1)
}
SubShader
{
Tags { "Queue"="Geometry" "RenderType"="Opaque" }
Pass
{
Cull Off //要渲染背面保证效果正确
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 uvMainTex : TEXCOORD0;
float2 uvNoiseTex : TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _NoiseTex;
float4 _NoiseTex_ST;
float _Threshold;
float _EdgeLength;
fixed4 _EdgeColor;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uvMainTex = TRANSFORM_TEX(v.uv, _MainTex);
o.uvNoiseTex = TRANSFORM_TEX(v.uv, _NoiseTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//镂空
fixed cutout = tex2D(_NoiseTex, i.uvNoiseTex).r;
// 顶点裁剪
clip(cutout - _Threshold);
//边缘颜色
if(cutout - _Threshold < _EdgeLength)
return _EdgeColor;
fixed4 col = tex2D(_MainTex, i.uvMainTex);
return col;
}
ENDCG
}
}
}
混合2种颜色
Shader "Kaima/Dissolve/TwoEdgeColor"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_NoiseTex("Noise", 2D) = "white" {}
_Threshold("Threshold", Range(0.0, 1.0)) = 0.5
_EdgeLength("Edge Length", Range(0.0, 0.2)) = 0.1
_EdgeFirstColor("First Edge Color", Color) = (1,1,1,1)
_EdgeSecondColor("Second Edge Color", Color) = (1,1,1,1)
}
SubShader
{
Tags { "Queue"="Geometry" "RenderType"="Opaque" }
Pass
{
Cull Off //要渲染背面保证效果正确
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 uvMainTex : TEXCOORD0;
float2 uvNoiseTex : TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _NoiseTex;
float4 _NoiseTex_ST;
float _Threshold;
float _EdgeLength;
fixed4 _EdgeFirstColor;
fixed4 _EdgeSecondColor;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uvMainTex = TRANSFORM_TEX(v.uv, _MainTex);
o.uvNoiseTex = TRANSFORM_TEX(v.uv, _NoiseTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//镂空
fixed cutout = tex2D(_NoiseTex, i.uvNoiseTex).r;
//裁剪
clip(cutout - _Threshold);
//边缘颜色
if(cutout - _Threshold < _EdgeLength)
{
//插值系数
float degree = (cutout - _Threshold) / _EdgeLength;
return lerp(_EdgeFirstColor, _EdgeSecondColor, degree);
}
fixed4 col = tex2D(_MainTex, i.uvMainTex);
return col;
}
ENDCG
}
}
}
混合本来颜色颜色
Shader "Kaima/Dissolve/BlendOriginColor"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_NoiseTex("Noise", 2D) = "white" {}
_Threshold("Threshold", Range(0.0, 1.0)) = 0.5
_EdgeLength("Edge Length", Range(0.0, 0.2)) = 0.1
_EdgeFirstColor("First Edge Color", Color) = (1,1,1,1)
_EdgeSecondColor("Second Edge Color", Color) = (1,1,1,1)
}
SubShader
{
Tags { "Queue"="Geometry" "RenderType"="Opaque" }
Pass
{
Cull Off //要渲染背面保证效果正确
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 uvMainTex : TEXCOORD0;
float2 uvNoiseTex : TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _NoiseTex;
float4 _NoiseTex_ST;
float _Threshold;
float _EdgeLength;
fixed4 _EdgeFirstColor;
fixed4 _EdgeSecondColor;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uvMainTex = TRANSFORM_TEX(v.uv, _MainTex);
o.uvNoiseTex = TRANSFORM_TEX(v.uv, _NoiseTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//镂空
fixed cutout = tex2D(_NoiseTex, i.uvNoiseTex).r;
//裁剪顶点
clip(cutout - _Threshold);
// 插值系数
float degree = saturate((cutout - _Threshold) / _EdgeLength);
//对颜色进行插值
fixed4 edgeColor = lerp(_EdgeFirstColor, _EdgeSecondColor, degree);
//纹理采样
fixed4 col = tex2D(_MainTex, i.uvMainTex);
//对最后的颜色进行插值
fixed4 finalColor = lerp(edgeColor, col, degree);
return fixed4(finalColor.rgb, 1);
}
ENDCG
}
}
}
使用渐变纹理
Shader "Kaima/Dissolve/Ramp"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_NoiseTex("Noise", 2D) = "white" {}
_Threshold("Threshold", Range(0.0, 1.0)) = 0.5
_EdgeLength("Edge Length", Range(0.0, 0.2)) = 0.1
_RampTex("Ramp", 2D) = "white" {}
}
SubShader
{
Tags { "Queue"="Geometry" "RenderType"="Opaque" }
Pass
{
Cull Off //要渲染背面保证效果正确
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 uvMainTex : TEXCOORD0;
float2 uvNoiseTex : TEXCOORD1;
float2 uvRampTex : TEXCOORD2;
};
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _NoiseTex;
float4 _NoiseTex_ST;
float _Threshold;
float _EdgeLength;
sampler2D _RampTex;
float4 _RampTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uvMainTex = TRANSFORM_TEX(v.uv, _MainTex);
o.uvNoiseTex = TRANSFORM_TEX(v.uv, _NoiseTex);
o.uvRampTex = TRANSFORM_TEX(v.uv, _RampTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//镂空
fixed cutout = tex2D(_NoiseTex, i.uvNoiseTex).r;
clip(cutout - _Threshold);
float degree = saturate((cutout - _Threshold) / _EdgeLength);
//使用系数对渐变纹理采样
fixed4 edgeColor = tex2D(_RampTex, float2(degree, degree));
fixed4 col = tex2D(_MainTex, i.uvMainTex);
fixed4 finalColor = lerp(edgeColor, col, degree);
return fixed4(finalColor.rgb, 1);
}
ENDCG
}
}
}
从某一个点开始消融
为了从特定点开始消融,我们需要把片元到特定点的距离clip掉
1. 定义消融开始点,然后求出各片元到该点的距离
2. 求出网格内2个点的最大距离,用来对第一步求出的距离进行归一化。这一步需要在C#脚本中进行,思路就是遍历任意2点,然后找出最大距离
3. 第三步就是归一化距离值
4. 加入一个 _DistanceEffect从之消融的影响程度。
Shader "Kaima/Dissolve/FromPoint"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {} //主纹理
_NoiseTex("Noise", 2D) = "white" {} //噪音纹理
_Threshold("Threshold", Range(0.0, 1.0)) = 0.5 //权值
_EdgeLength("Edge Length", Range(0.0, 0.2)) = 0.1 //边界宽度
_RampTex("Ramp", 2D) = "white" {} //渐变纹理
_StartPoint("Start Point", Vector) = (0, 0, 0, 0) //开始顶点
_MaxDistance("Max Distance", Float) = 0 //通过脚本计算最大距离
_DistanceEffect("Distance Effect", Range(0.0, 1.0)) = 0.5 //控制消融程度
}
SubShader
{
Tags { "Queue"="Geometry" "RenderType"="Opaque" }
Pass
{
Cull Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 uvMainTex : TEXCOORD0;
float2 uvNoiseTex : TEXCOORD1;
float2 uvRampTex : TEXCOORD2;
float3 objPos : TEXCOORD3;
float3 objStartPos : TEXCOORD4;
};
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _NoiseTex;
float4 _NoiseTex_ST;
float _Threshold;
float _EdgeLength;
sampler2D _RampTex;
float4 _RampTex_ST;
float _MaxDistance;
float4 _StartPoint;
float _DistanceEffect;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uvMainTex = TRANSFORM_TEX(v.uv, _MainTex);
o.uvNoiseTex = TRANSFORM_TEX(v.uv, _NoiseTex);
o.uvRampTex = TRANSFORM_TEX(v.uv, _RampTex);
o.objPos = v.vertex; //对象空间
o.objStartPos = mul(unity_WorldToObject, _StartPoint); //开始点从世界空间转到对象空间
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//计算片元到开始点的距离
float dist = length(i.objPos.xyz - i.objStartPos.xyz);
//对距离进行归一化
float normalizedDist = saturate(dist / _MaxDistance);
//下面这句有点难理解
fixed cutout = tex2D(_NoiseTex, i.uvNoiseTex).r * (1 - _DistanceEffect) + normalizedDist * _DistanceEffect;
clip(cutout - _Threshold);
float degree = saturate((cutout - _Threshold) / _EdgeLength);
//渐变采样
fixed4 edgeColor = tex2D(_RampTex, float2(degree, degree));
fixed4 col = tex2D(_MainTex, i.uvMainTex);
fixed4 finalColor = lerp(edgeColor, col, degree);
return fixed4(finalColor.rgb, 1);
}
ENDCG
}
}
}
场景切换
Shader "Kaima/Dissolve/ToPoint"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_NoiseTex("Noise", 2D) = "white" {}
_Threshold("Threshold", Range(0.0, 1.0)) = 0.5
_EdgeLength("Edge Length", Range(0.0, 0.2)) = 0.1
_RampTex("Ramp", 2D) = "white" {}
_StartPoint("Start Point", Vector) = (0, 0, 0, 0)
_MaxDistance("Max Distance", Float) = 0
_DistanceEffect("Distance Effect", Range(0.0, 1.0)) = 0.5
}
SubShader
{
Tags { "Queue"="Geometry" "RenderType"="Opaque" }
Pass
{
Tags {"LightMode" = "ForwardBase"}
Cull Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 uvMainTex : TEXCOORD0;
float2 uvNoiseTex : TEXCOORD1;
float2 uvRampTex : TEXCOORD2;
float3 worldPos : TEXCOORD3;
float3 worldNormal : TEXCOORD4;
};
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _NoiseTex;
float4 _NoiseTex_ST;
float _Threshold;
float _EdgeLength;
sampler2D _RampTex;
float4 _RampTex_ST;
float _MaxDistance;
float4 _StartPoint;
float _DistanceEffect;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uvMainTex = TRANSFORM_TEX(v.uv, _MainTex);
o.uvNoiseTex = TRANSFORM_TEX(v.uv, _NoiseTex);
o.uvRampTex = TRANSFORM_TEX(v.uv, _RampTex);
//转到世界空间
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//计算各个片元到世界空间的距离
float dist = length(i.worldPos.xyz - _StartPoint.xyz);
//归一化距离
float normalizedDist = 1 - saturate(dist / _MaxDistance);
fixed cutout = tex2D(_NoiseTex, i.uvNoiseTex).r * (1 - _DistanceEffect) + normalizedDist * _DistanceEffect;
clip(cutout - _Threshold);
float degree = saturate((cutout - _Threshold) / _EdgeLength);
fixed4 edgeColor = tex2D(_RampTex, float2(degree, degree));
//加点漫反射
fixed4 albedo = tex2D(_MainTex, i.uvMainTex);
float3 worldNormal = normalize(i.worldNormal);
float3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 diffuse = _LightColor0.rgb * albedo.rgb * saturate(dot(worldNormal, worldLightDir));
//对边缘色和漫反射进行插值
fixed4 finalColor = lerp(edgeColor, fixed4(diffuse, 1), degree);
return fixed4(finalColor.rgb, 1);
}
ENDCG
}
}
}
public class DissolveEnvironment : MonoBehaviour {
public Vector3 dissolveStartPoint;
[Range(0, 1)]
public float dissolveThreshold = 0;
[Range(0, 1)]
public float distanceEffect = 0.6f;
void Start () {
//计算所有子物体到消融开始点的最大距离
MeshFilter[] meshFilters = GetComponentsInChildren<MeshFilter>();
float maxDistance = 0;
for(int i = 0; i < meshFilters.Length; i++)
{
float distance = CalculateMaxDistance(meshFilters[i].mesh.vertices);
if (distance > maxDistance)
maxDistance = distance;
}
//传值到Shader
MeshRenderer[] meshRenderers = GetComponentsInChildren<MeshRenderer>();
for(int i = 0; i < meshRenderers.Length; i++)
{
meshRenderers[i].material.SetVector("_StartPoint", dissolveStartPoint);
meshRenderers[i].material.SetFloat("_MaxDistance", maxDistance);
}
}
void Update () {
//传值到Shader,为了方便控制所有子物体Material的值
MeshRenderer[] meshRenderers = GetComponentsInChildren<MeshRenderer>();
for (int i = 0; i < meshRenderers.Length; i++)
{
meshRenderers[i].material.SetFloat("_Threshold", dissolveThreshold);
meshRenderers[i].material.SetFloat("_DistanceEffect", distanceEffect);
}
}
//计算给定顶点集到消融开始点的最大距离
float CalculateMaxDistance(Vector3[] vertices)
{
float maxDistance = 0;
for(int i = 0; i < vertices.Length; i++)
{
Vector3 vert = vertices[i];
float distance = (vert - dissolveStartPoint).magnitude;
if (distance > maxDistance)
maxDistance = distance;
}
return maxDistance;
}
}
从某个方向消融
Shader "Kaima/Dissolve/FromDirectionX"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_NoiseTex("Noise", 2D) = "white" {}
_Threshold("Threshold", Range(0.0, 1.0)) = 0.5
_EdgeLength("Edge Length", Range(0.0, 0.2)) = 0.1
_RampTex("Ramp", 2D) = "white" {}
_Direction("Direction", Int) = 1 //1表示从X正方向开始,其他值则从负方向
_MinBorderX("Min Border X", Float) = -0.5 //从程序传入
_MaxBorderX("Max Border X", Float) = 0.5 //从程序传入
_DistanceEffect("Distance Effect", Range(0.0, 1.0)) = 0.5
}
SubShader
{
Tags { "Queue"="Geometry" "RenderType"="Opaque" }
Pass
{
Cull Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 uvMainTex : TEXCOORD0;
float2 uvNoiseTex : TEXCOORD1;
float2 uvRampTex : TEXCOORD2;
float objPosX : TEXCOORD3;
};
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _NoiseTex;
float4 _NoiseTex_ST;
float _Threshold;
float _EdgeLength;
sampler2D _RampTex;
float4 _RampTex_ST;
int _Direction;
float _MinBorderX;
float _MaxBorderX;
float _DistanceEffect;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uvMainTex = TRANSFORM_TEX(v.uv, _MainTex);
o.uvNoiseTex = TRANSFORM_TEX(v.uv, _NoiseTex);
o.uvRampTex = TRANSFORM_TEX(v.uv, _RampTex);
o.objPosX = v.vertex.x;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float range = _MaxBorderX - _MinBorderX;
float border = _MinBorderX;
if(_Direction == 1) //1表示从X正方向开始,其他值则从负方向
border = _MaxBorderX;
float dist = abs(i.objPosX - border);
float normalizedDist = saturate(dist / range);
fixed cutout = tex2D(_NoiseTex, i.uvNoiseTex).r * (1 - _DistanceEffect) + normalizedDist * _DistanceEffect;
clip(cutout - _Threshold);
float degree = saturate((cutout - _Threshold) / _EdgeLength);
fixed4 edgeColor = tex2D(_RampTex, float2(degree, degree));
fixed4 col = tex2D(_MainTex, i.uvMainTex);
fixed4 finalColor = lerp(edgeColor, col, degree);
return fixed4(finalColor.rgb, 1);
}
ENDCG
}
}
}
原文链接: ShaderDemo.
参考链接:灰信网
原文链接:
Contents
Other
Blog: Unity Shader - 一些玩具Shader
Squash:
BornFromY:
BlackHole:
GhostShader:
PaperFold:
Dissolve
Blog: Unity Shader - 消融效果原理与变体
DissolveFromPoint:
DissolveEnvironment:
DissolveFromDirection:
DirectionAsh:
Trifox:
Bump
Blog: Unity Shader - 表面凹凸技术汇总
Parallax Occlusion Mapping, POM:
Parallax Mapping With Self Shadowing:
Displacement Mapping:
Depth
Blog: Unity Shader - 深度图基础及应用
Scan Line:
Intersection Highlight:
Force Field:
Fog:
EdgeDetection:
Vertical Fog:
Motion Blur:
Depth of Field:
Shape
Blog: Unity Shader - 几何图形绘制