13 Four Modes Related to Transparent Effects#
1. Alpha Blend (AB)#
Usage#
- Objects with complex outlines and no clear edges
- Semi-transparent objects
- General base for effects
Advantages#
Good performance on mobile, natural edge effect
Disadvantages#
Has sorting issues
Key Code#
Shader "Zhuangdong/AP1/L07/L07_AB" {
Properties{
_MainTex ("RGB: Color A: Alpha", 2D) = "white" {}
_Opacity ("Opacity", range(0,1)) = 0.5
}
SubShader{
Tags {
"Queue" = "Transparent"
// Render transparent objects last to prevent objects behind from disappearing
"RenderType" = "Transparent"
"ForceNoShadowCasting" = "True" // Disable shadow casting
"IgnoreProject" = "True" // Does not affect projector
}
Pass {
Name "FORWARD"
Tags {
"LightMode" = "ForwardBase"
}
Blend One OneMinusSrcAlpha // Declare blend mode
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#pragma multi_compile_fwdbase_fullshadows
#pragma target 3.0
uniform sampler2D _MainTex; uniform float4 _MainTex_ST;
uniform half _Opacity;
struct VertexInput {
float4 vertex : POSITION;
float2 uv0 : TEXCOORD0;
};
struct VertexOutput {
float4 pos : SV_POSITION;
float2 uv0 : TEXCOORD0;
};
VertexOutput vert(VertexInput v) {
VertexOutput o = (VertexOutput)0;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv0 = TRANSFORM_TEX(v.uv0, _MainTex);
return o;
}
half4 frag(VertexOutput i) : COLOR{ // Standard writing
half4 var_MainTex = tex2D(_MainTex, i.uv0);
half3 finalRGB = var_MainTex.rgb;
half opacity = var_MainTex.a * _Opacity;
return half4(finalRGB * opacity, opacity);
}
ENDCG
}
}
FallBack "Diffuse"
}
2. Additive (AD)#
Usage#
- Luminous objects, glow effects
- Brightening effects
Disadvantages#
- Has sorting issues
- Multiple layers easily cause overdraw
- For glow effects, usually can be replaced by post-processing
Key Code#
// Use AB example as template
...
SubShader{
...
pass{
...
Blend One One
// Just change the BlendMode from the AB example
...
}
...
}
3. Alpha CutOut (AC)#
Usage#
- Complex outlines, clear edges (e.g. leaves, hair, metal hollow-outs)
- Cartoon effects (with anti-aliasing)
Advantages#
No sorting issues
Disadvantages#
Edges are harsh, poor performance on mobile
Key Code#
// Use AB example as template
...
SubShader{
Tags{
"RenderType" = "TransparentCutout" // Corresponds to AC
"ForceNoShadowCasting" = "True"
"IgnoreProject" = "True"
}
pass{
...
half4 frag(VertexOutput i) : COLOR{
half4 var_MainTex = tex2D(_MainTex, i.uv0);
half opacity = var_MainTex.a;
clip(opacity - _Cutoff); // Alpha cutout
return half4(var_MainTex.rgb, 1.0);
}
}
...
}
4. Custom Blending#
Usage#
Custom blend formulas for flexible special effects
Combination#
Src * SrcFactor op Dst * DstFactor
Elements#
- Src: Source, the current shader drawing result
- Dst: Destination, the background before the current shader draws
- SrcFactor: Source multiplier
- DstFactor: Destination multiplier
- op: Blend operator
The multipliers determine how the elements participate in blending, and the operator determines the form of blending. For details, please refer to the Unity official documentation.
Key Code#
...
Pass {
Name "FORWARD"
Tags {
"LightMode" = "ForwardBase"
}
BlendOp [_BlendOp]
Blend [_BlendSrc] [_BlendDst]
// Add the formula after Tags
...
15 Common Issues#
1. Sorting Issue#
The front and back relationship is unclear when rendering transparent objects
Solutions#
- Detach/Attach (modify model vertex order with DDC software)
- ZWrite Off (disable depth writing)
2. Premultiplied Texture Issue#
Some texture assets are premultiplied (BaseColor * Alpha), some are not, so the blend mode needs to be distinguished
- AB mode: For premultiplied, use
One OneMinusSrcAlpha
; for non-premultiplied, useSrcAlpha OneMinusSrcAlpha
, or multiply in frag - AD mode: Premultiplied can have no A channel, non-premultiplied needs to multiply in frag
3. Example: GhostFlow#
// Use AB as template
Shader "Zhuangdong/AP1/L08/L08_GhostFlow" {
Properties{
_MainTex ("RGB: Color A: Alpha", 2D) = "gray" {}
_Opacity ("Opacity", range(0,1)) = 0.5
_WarpTex ("Distortion Map", 2D) = "gray" {}
_WarpInt ("Distortion Intensity", range(0, 1)) = 0.5
_NoiseInt("Noise Intensity", range(0, 5)) = 0.5
_FlowSpeed ("Flow Speed", range(0, 10)) = 5
}
SubShader{
...
Pass {
...
Blend One OneMinusSrcAlpha
...
struct VertexInput {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct VertexOutput {
float4 pos : SV_POSITION;
float2 uv0 : TEXCOORD0;
float2 uv1 : TEXCOORD1;
};
VertexOutput vert(VertexInput v) {
VertexOutput o = (VertexOutput)0;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv0 = v.uv;
o.uv1 = TRANSFORM_TEX(v.uv, _WarpTex); // Enable Tiling
o.uv1.y = o.uv1.y + frac(_Time.x * _FlowSpeed); // Start V axis flow
return o;
}
half4 frag(VertexOutput i) : COLOR{
half3 var_WarpTex = tex2D(_WarpTex, i.uv1);
half2 uvBias = (var_WarpTex.rg - 0.5) * _WarpInt;
half2 uv0 = i.uv0 + uvBias;
half4 var_MainTex = tex2D(_MainTex, uv0);
half3 finalRGB = var_MainTex.rgb;
half noise = lerp(1.0, var_WarpTex.b * 2.0, _NoiseInt);
noise = max(0.0, noise);
half opacity = var_MainTex.a * _Opacity * noise;
return half4(finalRGB * opacity, opacity);
}
ENDCG
}
}
FallBack "Diffuse"
}
Core Idea#
By using a distortion map and flow speed parameter, achieve dynamic changes in transparent effects
o.uv1.y = o.uv1.y + frac(_Time.x * _FlowSpeed); // V axis flow
half opacity = var_MainTex.a * _Opacity * noise;
_Time
: A parameter that increases with time, with x, y, z, w components representing different speed levels_FlowSpeed
controls the flow speedfrac
takes the fractional part, used here to prevent _Time from growing indefinitely and causing moiré patternsnoise = lerp(1.0, var_WarpTex.b * 2.0, _NoiseInt)
ensures brightness does not darken_NoiseInt
The higher this value, the clearer the noise; when above 1, noise contrast increasesmax(0.0, noise)
prevents negative values that cause color anomalies
4. Example: GhostWarp#
// Use GhostFlow as template
...
half3 var_WarpTex = tex2D(_WarpTex, i.uv1);
half2 uvBias = (var_WarpTex.rg - 0.5) * _WarpInt;
half2 uv0 = i.uv0 + uvBias;
half4 var_MainTex = tex2D(_MainTex, uv0);
...
Principle#
The rg channels of WarpTex control the direction of UV distortion, and -0.5 ensures even distribution of distortion
Note#
When WarpInt is too large, the distortion will be abrupt. This is caused by large grayscale differences between the bright and dark sides
14, 16 Flame and Water Ripple Effect Cases#
Flame Effect#
Key Code#
// Use GhostFlow as template
Shader "Zhuangdong/AP1/L09/L09_Fire" {
Properties{
_Mask ("R:Outer Flame G:Inner Flame B:Alpha", 2D) = "blue" {}
_Noise ("R:Noise1 G:Noise2", 2D) = "gray" {}
_Noise1Params ("Noise1 x:Scale y:Speed z:Intensity", vector) = (1.0, 0.2, 0.2, 1.0)
_Noise2Params ("Noise2 x:Scale y:Speed z:Intensity", vector) = (1.0, 0.2, 0.2, 1.0)
_color1 ("Outer Flame Color", Color) = (1,1,1,1)
_color2 ("Inner Flame Color", Color) = (1,1,1,1)
}
SubShader{
...
Pass {
...
Blend One OneMinusSrcAlpha
...
struct VertexInput {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct VertexOutput {
float4 pos : SV_POSITION;
float2 uv0 : TEXCOORD0;
float2 uv1 : TEXCOORD1;
float2 uv2 : TEXCOORD2;
};
VertexOutput vert(VertexInput v) {
VertexOutput o = (VertexOutput)0;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv0 = v.uv;
o.uv1 = v.uv * _Noise1Params.x - float2(0.0, frac(_Time.x * _Noise1Params.y));
o.uv2 = v.uv * _Noise2Params.x - float2(0.0, frac(_Time.x * _Noise2Params.y));
return o;
}
half4 frag(VertexOutput i) : COLOR{
half warpMask = tex2D(_Mask, i.uv0).b;
half var_Noise1 = tex2D(_Noise, i.uv1).r;
half var_Noise2 = tex2D(_Noise, i.uv2).g;
half noise = var_Noise1 * _Noise1Params.z +
var_Noise2 * _Noise2Params.z;
float2 warpUV = i.uv0 - float2(0.0, noise) * warpMask;
half3 var_Mask = tex2D(_Mask, warpUV);
half3 finalRGB = _color1 * var_Mask.r + _color2 * var_Mask.g;
half opacity = var_Mask.r + var_Mask.g;
return half4(finalRGB, opacity);
}
ENDCG
}
}
FallBack "Diffuse"
}
Principle#
Two layers of noise overlay to control flame dynamics, and use mask to customize the color of inner and outer flames
Key Points#
_NoiseParams.x
controls tiling_NoiseParams.y
controls speed_NoiseParams.z
controls distortion intensityfloat2(0,0, frac())
If v.uv - frac(), the texture will flow diagonally, because frac is automatically recognized as float2(frac(), frac())
Water Ripple Effect#
Key Code#
// Use Fire as template
Shader "Zhuangdong/AP1/L09/L09_Water" {
Properties{
_MainTex ("Color Texture", 2D) = "white" {}
_WarpTex ("Distortion Map", 2D) = "gray" {}
_Speed ("x: Speed x y: Speed y", vector) = (1.0, 1.0, 0.5, 1.0)
_Warp1Params ("Noise1 x: Scale y: Speed x z: Speed y w: Intensity", vector) = (1.0, 0.2, 0.2, 1.0)
_Warp2Params ("Noise2 x: Scale y: Speed x z: Speed y w: Intensity", vector) = (1.0, 0.2, 0.2, 1.0)
}
SubShader{
Tags {
"RenderType" = "Opaque"
}
Pass {
Name "FORWARD"
Tags {
"LightMode" = "ForwardBase"
}
...
VertexOutput vert(VertexInput v) {
VertexOutput o = (VertexOutput)0;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv0 = v.uv - frac(_Time.x * _Speed); // Main texture flow
o.uv1 = v.uv * _Warp1Params.x - frac(_Time.x * _Warp1Params.yz); // Distortion map 1 flow
o.uv2 = v.uv * _Warp2Params.x - frac(_Time.x * _Warp2Params.yz); // Distortion map 2 flow
return o;
}
float4 frag(VertexOutput i) : COLOR{
half3 var_Warp1 = tex2D(_WarpTex, i.uv1).rgb; // Distortion value 1
half3 var_Warp2 = tex2D(_WarpTex, i.uv2).rgb; // Distortion value 2
half2 warp = (var_Warp1.xy - 0.5) * _Warp1Params.w +
(var_Warp2.xy - 0.5) * _Warp2Params.w;
float2 warpUV = i.uv0 + warp; // Add distortion value
half4 var_MainTex = tex2D(_MainTex, warpUV);
return float4(var_MainTex.xyz, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
Principle#
Main texture UV plus two layers of noise to achieve water surface ripple effect
17 ScreenUV & ScreenWarp#
ScreenUV#
By sampling in screen space UV, achieve effects where the texture changes and flows with camera distance.
The key is to use view space coordinates to correct distortion and overlay flow effects.
Key Code#
// Use AB as template
...
VertexOutput vert(VertexInput v) {
VertexOutput o = (VertexOutput)0;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
float3 posVS = UnityObjectToViewPos(v.vertex).xyz; // Vertex position OS to VS
o.screenUV = posVS.xy / posVS.z; // VS space distortion correction
float originDist = UnityObjectToViewPos(float3(0.0, 0.0, 0.0)).z; // Origin position
o.screenUV *= originDist; // o.screenUV * originDist
o.screenUV = o.screenUV * _ScreenTex_ST.xy - frac(_Time.x * _ScreenTex_ST.zw); // Add tiling, offset, and flow
return o;
}
half4 frag(VertexOutput i) : COLOR{
half4 var_MainTex = tex2D(_MainTex, i.uv);
half var_ScreenTex = tex2D(_ScreenTex, i.screenUV).r;
half3 finalRGB = var_MainTex.rgb;
half opacity = var_MainTex.a * _Opacity * var_ScreenTex;
return half4(finalRGB * opacity, opacity);
}
...
Key Points#
o.screenUV
view space corrected UV, prevents depth distortionoriginDist
controls texture scaling by origin distancetime
overlays flow effect
ScreenWarp#
Use GrabPass to get the background, and use a channel of the main texture to distort the screen UV, achieving background distortion in semi-transparent areas (similar to Photoshop overlay).
Key Code#
Shader "Zhuangdong/AP1/L10/L10_ScreenWarp" {
Properties{
_MainTex ("RGB: Color A: Alpha", 2D) = "white" {}
_Opacity ("Opacity", range(0,1)) = 0.5
_WarpMidVal ("Distortion Mid Value", range(0, 1)) = 0.5
_WarpInt ("Distortion Intensity", range(0, 3)) = 0.2
}
SubShader{
...
GrabPass{
"_BGTex"
}
...
uniform sampler2D _BGTex;
...
struct VertexOutput {
float4 pos : SV_POSITION;
float2 uv0 : TEXCOORD0;
float4 grabPos : TEXCOORD1; // Background sampling coordinates
};
VertexOutput vert(VertexInput v) {
VertexOutput o = (VertexOutput)0;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv0 = TRANSFORM_TEX(v.uv0, _MainTex);
o.grabPos = ComputeGrabScreenPos(o.pos); // Background texture sampling coordinates for grabpass
return o;
}
half4 frag(VertexOutput i) : COLOR{
half4 var_MainTex = tex2D(_MainTex, i.uv0);
i.grabPos.xy += (var_MainTex.b - _WarpMidVal) * _WarpInt * _Opacity;
half3 var_BGTex = tex2Dproj(_BGTex, i.grabPos);
// tex2Dproj is a command specifically for sampling this type of texture
half3 finalRGB = var_MainTex.rgb * var_BGTex;
half opacity = var_MainTex.a * _Opacity;
return half4(finalRGB * opacity, opacity);
...
Key Points#
GrabPass
gets the background and saves it in BGTexWarpMidVal
adjusts the UV sampling position_Opacity
is bound to i.grabPos, the lower the opacity, the more obvious the distortion
Similar Methods#
GrabPass
: Suitable for high quality but high performance costCommandBuffer
: Custom pipeline method before SRP eraLwrp/Urp
: Custom pipeline method after SRP era (recommended)
18. Sequence Animation (Sequence)#
Principle of Sequence Animation#
In effect production, a sequence texture containing multiple frames is often used, with each frame representing a state of the animation. By switching the UV sampling area, the animation playback effect is achieved.
- The sample texture is 3 rows and 4 columns, with each frame arranged in order.
- The effect layer floats above the object surface (extruded along the normal direction by the vertex).
- The UV origin is at the top left, and the initial sampling area needs to be adjusted.
Key Code#
// Add a pass based on the AB example
...
pass{
Name "FORWARD"
Tags {
"LightMode" = "ForwardBase"
}
Blend One One
...
VertexOutput vert(VertexInput v) {
VertexOutput o = (VertexOutput)0;
// Extrude vertex along normal direction
v.vertex.xyz += v.normal * 0.01;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _Sequence);
// Calculate current frame index
float id = floor(_Time.z * _Speed);
float idV = floor(id / _ColCount);
float idU = id - idV * _ColCount;
float stepU = 1.0 / _ColCount;
float stepV = 1.0 / _RowCount;
// UV scaling and adjust origin to top left
float2 initUV = o.uv * float2(stepU, stepV) + float2(0.0, stepV * (_RowCount -1));
o.uv = initUV + float2(idU * stepU, -idV * stepV);
return o;
}
half4 frag(VertexOutput i) : COLOR{
half4 var_Sequence = tex2D(_Sequence, i.uv);
half3 finalRGB = var_Sequence.rgb;
half opacity = var_Sequence.a;
return half4(finalRGB * opacity, opacity);
}
ENDCG
...
Key Points Summary#
floor
rounds down, e.g. 1.9 outputs 1id
moves horizontally by1/_ColCount
each timeidV = floor(id / _ColCount)
when id is an integer multiple of the column count, idV increases by 1idU = id - idV * _ColCount
moves vertically by1/_RowCount
for each full row, and idU resets to zerofloat2(idU * stepU, -idV * stepV)
moves by1/_ColCount
each time, when the number of moves equals_ColCount
, idU resets to the leftmost, and moves vertically by1/_RowCount
Note: Effect algorithms should be as simple as possible to avoid overdraw.
Polar Coordinate Animation (PolarCoord)#
Polar coordinate transformation can achieve radial flow, scanning and other effects. By converting UV from Cartesian coordinates to polar coordinates and overlaying time flow, special animation effects are achieved.
Polar Coordinates#
Assume an x-axis, connect the origin to point M, the angle between OM and the x-axis is θ, OM length is P, polar coordinates are (θ, P)
Key Code#
half4 frag(VertexOutput i) : COLOR {
i.uv = i.uv - float2(0.5, 0.5);
float theta = atan2(i.uv.y, i.uv.x);
theta = theta / 3.1415926 * 0.5 + 0.5;
float p = length(i.uv) + frac(_Time.x * 3);
i.uv = float2(theta, p);
half4 var_MainTex = tex2D(_MainTex, i.uv);
half3 finalRGB = (1 - var_MainTex.rgb);
half opacity = (1 - var_MainTex.r) * _Opacity * i.color.r;
return half4(finalRGB * opacity, opacity);
}
Key Points Summary#
atan2
calculates angle θ, result is (-π, π), normalized to [0,1].
length
calculates the distance from point (x, y) to the origin. Since the origin is shifted from (0,0) to (0.5, 0.5), the output will look like this
float2(theta, p)
maps UV to polar coordinates to achieve radial animation.i.color
vertex color is used to soften the edge.
19. Vertex Animation#
19.1 Translation#
Use sine function to make the vertex move periodically in the Y axis direction, achieving an overall up and down floating effect.
Key Code#
// Use AB as template
...
#define TWO_PI 6.283185
void Translation (inout float3 vertex) {
vertex.y += _MoveRange * sin(frac(_Time.z * _MoveSpeed) * TWO_PI);
}
VertexOutput vert(VertexInput v) {
VertexOutput o = (VertexOutput)0;
Translation(v.vertex.xyz);
o.pos = UnityObjectToClipPos(v.vertex);
o.uv0 = v.uv0;
return o;
}
Key Points#
frac
Fractional part, takes the decimal part, ensures the time parameter cycles in [0,1], prevents overflow.sin
sin(0, 2π) is a periodic motion from 0 to 1 and back to 0.
19.2 Scale#
All vertices are scaled by the same ratio.
Key Code#
// Use AB as template
void Scaling (inout float3 vertex) {
vertex.xyz *= 1.0 + _ScaleRange * sin(frac(_Time.z * _ScaleSpeed) * TWO_PI);
}
VertexOutput vert(VertexInput v) {
VertexOutput o = (VertexOutput)0;
Scaling(v.vertex.xyz);
o.pos = UnityObjectToClipPos(v.vertex);
o.uv0 = TRANSFORM_TEX(v.uv0, _MainTex);
return o;
}
Key Points#
Ensure the scale value is greater than 0 to avoid reverse scaling.
19.3 Rotation#
Vertices rotate periodically around the Y axis, achieving effects like head shaking and swinging.
Key Code#
// Use AB as template
void Rotation (inout float3 vertex) {
float angleY = _RotateRange * sin(frac(_Time.z * _Rotatepeed) * TWO_PI);
float radY = radians(angleY);
float sinY, cosY = 0;
sincos(radY, sinY, cosY);
vertex.xz = float2(
vertex.x * cosY - vertex.z * sinY,
vertex.x * sinY + vertex.z * cosY
);
}
Key Points#
radians
converts degrees to radians, which improves performancesincos(raY, sinY, cosY)
is equivalent to sinY = sin(radY), cosY = cos(radY), this writing improves performance.vertex.xz
rotation matrix M (x * cosθ - y * sinθ, x * sinθ + y * cosθ)
19.4 Composite Animation (AnimationGhost)#
Combine scaling, translation, rotation and other animations, and use vertex color to achieve complex movements (such as ghosts, angel rings, etc).
Key Code#
...
// Use AB as template
void AnimGhost (inout float3 vertex, inout float3 color){
// 天使圈缩放
float scale = _ScaleParams.x * color.g * sin(frac(_Time.z * _ScaleParams.y) * TWO_PI);
vertex.xyz *= 1.0 + scale;
vertex.y -= _ScaleParams.z * -scale;
// 幽灵摆动
float swingX = _SwingXparams.x * sin(frac(_Time.z * _SwingXparams.y + vertex.y * _SwingXparams.z) * TWO_PI);
float swingZ = _SwingZparams.x * sin(frac(_Time.z * _SwingZparams.y + vertex.y * _SwingZparams.z) * TWO_PI);
vertex.xz += float2(swingX, swingZ) * color.r;
// 幽灵摇头
float radY = radians(_ShakeYparams.x) * (1.0 - color.r) * sin(frac(_Time.z * _ShakeYparams.y - color.g * _ShakeYparams.z) * TWO_PI);
float sinY, cosY = 0;
sincos(radY, sinY, cosY);
vertex.xz = float2(
vertex.x * cosY - vertex.z * sinY,
vertex.x * sinY + vertex.z * cosY
);
// 幽灵起伏
float swingY = _SwingYparams.x * sin(frac(_Time.z * _SwingYparams.y - color.g * _SwingYparams.z) * TWO_PI);
vertex.y += swingY;
// 处理顶点色
float lightness = 1.0 + color.g * 1.0 + scale * 2.0;
color = float3(lightness, lightness, lightness);
}
...
Key Points#
- Use vertex color R, G channels to control different animation areas.
vertex.y -= _ScaleParams.z * -scale
is to scale around the model origin, control y axis scale amplitude, avoid angel ring leaving originvertex.y * _SwingXparams.z
sin result is affected by vertex.y, achieve S-shaped swing_ShakeYoarams.z
_SwingYparams.z
make angel ring and other parts of animation, produce time laglightness
brightness changes with time
20. Clock Animation (ClockAnim)#
20.1 Clock Pointer Animation#
By C# script obtaining system time, driving Clock pointer rotation in Shader, achieving real clock animation.
Key Code#
...
void RotateZwithOffset(float angle, float offset, float mask, inout float3 vertex){
vertex.y -= offset * mask;
float radZ = radians(angle * mask);
float sinZ, cosZ = 0;
sincos(radZ, sinZ, cosZ);
vertex.xy = float2(
vertex.x * cosZ - vertex.y * sinZ,
vertex.x * sinZ + vertex.y * cosZ
);
vertex.y += offset * mask;
}
void ClockAnim(float3 color, inout float3 vertex) {
RotateZwithOffset(_HourHandAngle, _RotateOffset, color.r, vertex);
RotateZwithOffset(_MinuteHandAngle, _RotateOffset, color.g, vertex);
RotateZwithOffset(_SecondHandAngle, _RotateOffset, color.b, vertex);
}
VertexOutput vert(VertexInput v) {
VertexOutput o = (VertexOutput)0;
ClockAnim(v.color.rgb, v.vertex.xyz);
...
Key Points#
angle
represents the rotation angle of each time unitoffset
adjusts the rotation center. Initial center is at model origin, needs adjustment
C# script binding system time#
using System;
using UnityEngine;
public class HelloWorld : MonoBehaviour
{
// --------- Public -------
public Material clockMat;
// --------- private --------
private bool valid;
private int hourAnglePropID;
private int minuteAnglePropID;
private int secondAnglePropID;
// Start is called before the first frame update
void Start()
{
if(clockMat == null) return;
hourAnglePropID = Shader.PropertyToID("_HourHandAngle");
minuteAnglePropID = Shader.PropertyToID("_MinuteHandAngle");
secondAnglePropID = Shader.PropertyToID("_SecondHandAngle");
if(clockMat.HasProperty(hourAnglePropID) && clockMat.HasProperty(minuteAnglePropID) && clockMat.HasProperty(secondAnglePropID))
valid = true;
Debug.Log("hourAnglePropID" + hourAnglePropID);
Debug.Log("minuteAnglePropID" + minuteAnglePropID);
Debug.Log("secondAnglePropID" + secondAnglePropID);
Debug.Log(valid);
}
// Update is called once per frame
void Update()
{
if(!valid) return;
int second = DateTime.Now.Second;
float secondAngle = second /60.0f * 360.0f;
clockMat.SetFloat(secondAnglePropID, secondAngle);
int minute = DateTime.Now.Minute;
float minuteAngle = minute /60.0f * 360.0f;
clockMat.SetFloat(minuteAnglePropID, minuteAngle);
int hour = DateTime.Now.Hour;
float hourAngle = (hour % 12) / 12.0f * 360.0f + minuteAngle / 360.0f * 30.0f;
clockMat.SetFloat(hourAnglePropID, hourAngle);
}
}
Key Points#
%
remainder, ensure hour over 12 to 0
20.2 Shadow Projection Pass#
Key Code#
Pass {
Name "ShadowCaster"
Tags { "LightMode" = "ShadowCaster" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_shadowcaster
#include "UnityCG.cginc"
uniform float _HourHandAngle, _MinuteHandAngle, _SecondHandAngle, _RotateOffset;
struct appdata {
float4 vertex : POSITION;
float3 normal : NORMAL;
float3 color : COLOR;
};
struct v2f {
V2F_SHADOW_CASTER;
};
void RotateZwithOffset(float angle, float offset, float mask, inout float3 vertex){
vertex.y -= offset * mask;
float radZ = radians(angle * mask);
float sinZ, cosZ = 0;
sincos(radZ, sinZ, cosZ);
vertex.xy = float2(
vertex.x * cosZ - vertex.y * sinZ,
vertex.x * sinZ + vertex.y * cosZ
);
vertex.y += offset * mask;
}
void ClockAnim(float3 color, inout float3 vertex) {
RotateZwithOffset(_HourHandAngle, _RotateOffset, color.r, vertex);
RotateZwithOffset(_MinuteHandAngle, _RotateOffset, color.g, vertex);
RotateZwithOffset(_SecondHandAngle, _RotateOffset, color.b, vertex);
}
v2f vert(appdata v) {
v2f o;
ClockAnim(v.color.rgb, v.vertex.xyz);
// 关键修复:计算带法线偏移的阴影坐标
float3 posWS = mul(unity_ObjectToWorld, v.vertex).xyz;//动画后的顶点转世界空间
float3 normalWS = UnityObjectToWorldNormal(v.normal);//法线转世界空间
o.pos = UnityClipSpaceShadowCasterPos(posWS, normalWS);
TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
return o;
}
float4 frag(v2f i) : SV_Target {
SHADOW_CASTER_FRAGMENT(i)
}
ENDCG
}
Key Points#
ShadowCaster
is fixed for projection Pass naming and LightModeUnityClipSpaceShadowCasterPos(posWS, normalWS)
normal offset correction, get animation front normal and animation back vertex, calculate animation back normal direction, finally output animation back vertex position and corrected normal in light cone clipping space coordinatesV2F_SHADOW_CASTER
Unity built-in macro, equivalent to:- float4 pos : SV_POSITION
- float3 vec : TEXCOORD0 (only used when there is point light)
TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
input posWS, normalWS, outputV2F_SHADOW_CASTER
SHADOW_CASTER_FRAGMENT(i)
input pos, process projection according to light type
Note: Only need ShadowCaster
, V2F_SHADOW_CASTER
, TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
, SHADOW_CASTER_FRAGMENT(i)
4 elements to get a complete shadow projection Pass
21. Effect Dissolve Animation Case#
21.1 Gray Scale and Noise Control Dissolve#
By multi-channel gray scale map, noise map, and vertex color, achieve complex dissolve effects like mesh disappearance, randomness, and light emission.
Key Structure and Function#
struct VertexInput {
float4 vertex : POSITION;
float2 uv0 : TEXCOORD0;
float2 uv1 : TEXCOORD1;
float4 normal : NORMAL;
float4 tangent : TANGENT;
float4 color : COLOR;
};
struct VertexOutput {
float4 pos : SV_POSITION;
float2 uv0 : TEXCOORD0;
float2 uv1 : TEXCOORD1;
float4 posWS : TEXCOORD2;
float3 nDirWS : TEXCOORD3;
float3 tDirWS : TEXCOORD4;
float3 bDirWS : TEXCOORD5;
float4 effectMask : TEXCOORD6;
LIGHTING_COORDS(7,8)
};
float4 CyberpunkAnim(float noise, float mask, float3 normal, inout float3 vertex){
float baseMask = abs(frac(vertex.y * _EffParams.x - _Time.x * _EffParams.y) -0.5) * 2.0;
baseMask = min(1.0, baseMask * 2.0);
baseMask += (noise - 0.5) * _EffParams.z;
float4 effectMask = float4(0.0, 0.0, 0.0, 0.0);
effectMask.x = smoothstep(0.0, 0.9, baseMask);
effectMask.y = smoothstep(0.2, 0.7, baseMask);
effectMask.z = smoothstep(0.4, 0.5, baseMask);
effectMask.w = mask;
vertex.xz += normal.xz * (1.0 - effectMask.y) * _EffParams.w * mask;
return effectMask;
}
Vertex Shader#
VertexOutput vert(VertexInput v) {
float noise = tex2Dlod(_EffectMap02, float4(v.uv1, 0.0, 0.0)).r;
VertexOutput o = (VertexOutput)0;
o.effectMask = CyberpunkAnim(noise, v.color, v.normal.xyz, v.vertex.xyz);
o.pos = UnityObjectToClipPos(v.vertex);
o.uv0 = v.uv0;
o.uv1 = v.uv1;
o.posWS = mul(unity_ObjectToWorld, v.vertex);
o.nDirWS = UnityObjectToWorldNormal(v.normal);
o.tDirWS = normalize( mul( unity_ObjectToWorld, float4( v.tangent.xyz, 0.0 ) ).xyz );
o.bDirWS = normalize(cross(o.nDirWS, o.tDirWS) * v.tangent.w);
TRANSFER_VERTEX_TO_FRAGMENT(o)
return o;
}
Fragment Shader#
float4 frag(VertexOutput i) : COLOR{
// ...(向量、光照、采样等略)...
//光照模型
float3 baseCol = var_MainTex.rgb * _BaseColor;
float Lambert = max(0.0, nDotl);//lambert
float specCol = var_SpecTex.rgb;
float specpow = lerp(1, _SpecularPow_Value, var_SpecTex.a);
float Phong = pow(max(0.0, vDotr), _SpecularPow_Value);
float shadow = LIGHT_ATTENUATION(i);
float3 dirlighting = (baseCol * Lambert + specCol * Phong) * _LightColor0 * shadow;
float3 EnvCol = TriColAmbient(nDirWS, _TopCol, _MidCol, _BotCol);
float fresnel = pow(1.0 - ndotv, _FresnelPow);
float occlusion = var_MainTex.a;
float3 envLighting = (baseCol * EnvCol * _EnvDiffInt + var_Cubemap * fresnel* _EnvSpecint * var_SpecTex.a) * occlusion;
//自发光
float3 emission = var_EmitTex * _EmitInt;
float3 _EffectMap01_var = tex2D(_EffectMap01, i.uv1).xyz;
float meshMask = _EffectMap01_var.x;
float faceRandomMask = max(0.001, _EffectMap01_var.y);
float faceSlopMask = max(0.001, _EffectMap01_var.z);
float smallMask = i.effectMask.x;
float midMask = i.effectMask.y;
float bigMask = i.effectMask.z;
float baseMask = i.effectMask.w;
float midOpacity = saturate(floor(min(faceRandomMask, 0.999999) + midMask));
float bigOpacity = saturate(floor(min(faceSlopMask, 0.999999) + bigMask));
float opacity = lerp(1.0, min(bigOpacity, midOpacity), baseMask);
float meshEmitInt = (bigMask - smallMask) * meshMask;//只在半透明的区域有发光效果
meshEmitInt = meshEmitInt * meshEmitInt * 2.0;//进行固定值的 power,让发光区域缩小
emission += _EffCol * meshEmitInt * baseMask;
float3 FinalColor = dirlighting + envLighting + emission;
return float4(FinalColor * opacity, opacity);
}
Key Points#
EffMap01
- R - WireframeMap records model wire frame
- G - RandomGrayScale records random gray scale based on surface
- B - DisappearanceGrayscale records uniform depth based on surface
EffMap02
- 3DPerlinNoise
DisappearanceGrayscale
RandomGrayScale
贴图制作与合并
mask
vertex color, animate only on characterbaseMask = abs(frac(vertex.y * _EffParams.x - _Time.x * _EffParams.y) -0.5) * 2.0;
frac(vertex.y)
only takes the decimal, visual gradient Linear 1EffectParams.x
controls gradient Linear 1 TilingEffectParams.y
controls animation speed and directionabs(frac() -0.5) * 2.0
abs takes absolute value, finally result gradient Linear 1 to gradient Linear 3
baseMask = min()
increases value range to 1baseMask += (noise - 0.5) * _EffParams.z;
makes baseMask value non 1 area change,_EffParams.z
controls change intensitysmoothstep
adjusts specified area waveformvertex.xz +=
animate transparent area,EffParams.w
controls animation intensityfloor
less than 1 to 0, greater than 1 to 1saturate
limits value range to (0, 1)meshEmitInt * meshEmitInt
equivalent to Power = value^2faceRandomMask = max(0.001, _EffectMap01_var.y)
prevent negative, negative causes display error