unity shader效果教程附详细代码_噪声
写在前面:
这个系列来源于书籍冯乐乐老师的《unity shader 入门精要》,
参考的代码库“https://github.com/candycat1992/Unity_Shaders_Book”
我自己跟着写了一遍代码,并在代码块里给shader代码加上了比较详细的注释
更为仔细的解释和unity shader原理和知识书里都有,本blog不做详细解释,推荐买书来看。
本项目的所有代码在https://github.com/takashiwangbh/Unity-shader-effect-reproduction/tree/main
介绍
噪声就是在规则的事物里添加一些“杂乱无章的效果”,所以噪声常常用来做一些神奇的特效,这里使用噪声模拟消融效果,水面波纹和全局雾效的随机波动。值得注意的是,我们的噪声一般是使用噪声纹理,而不是纯代码模拟,所以不管是哪一个模拟中,都会应用对应的噪声纹理。详情可以看代码里是注释。
消融效果
消融效果大概就是噪声纹理+透明度测试,使用对噪声纹理采样的结果和控制消融程度的阈值比较,如果小于阈值,就用cilp函数将对应的函数裁掉。然后使用一个改变阈值的脚本让阈值一直往上加,那么就能够看起来像是消融的效果
shader code
/*
功能说明:
1. **溶解控制**:
- 通过 `_BurnAmount` 参数控制溶解的程度。
- 使用 `_BurnMap` 纹理定义溶解效果的图案。
2. **燃烧线条**:
- 燃烧边界的宽度由 `_LineWidth` 决定。
- 燃烧线条颜色通过 `_BurnFirstColor` 和 `_BurnSecondColor` 进行渐变。
3. **光照支持**:
- 包括环境光和漫反射光的计算。
- 支持 Unity 的前向光照模型和阴影投射。
*/
Shader "Unity Shaders Book/Chapter 15/Dissolve" {
Properties {
_BurnAmount ("Burn Amount", Range(0.0, 1.0)) = 0.0 // 溶解程度控制,从0到1,1表示完全溶解。
_LineWidth ("Burn Line Width", Range(0.0, 0.2)) = 0.1 // 燃烧线条的宽度。
_MainTex ("Base (RGB)", 2D) = "white" {} // 物体的基础纹理。
_BumpMap ("Normal Map", 2D) = "bump" {} // 法线贴图,用于增强表面细节的光照效果。
_BurnFirstColor ("Burn First Color", Color) = (1, 0, 0, 1) // 燃烧线条颜色的起始颜色。
_BurnSecondColor ("Burn Second Color", Color) = (1, 0, 0, 1) // 燃烧线条颜色的结束颜色。
_BurnMap ("Burn Map", 2D) = "white" {} // 定义溶解图案的纹理。
}
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry"} // 定义渲染类型和队列。
Pass {
Tags { "LightMode"="ForwardBase" } // 支持 Unity 的前向光照模型。
Cull Off // 关闭剔除,渲染物体的正面和背面。
CGPROGRAM
#include "Lighting.cginc" // 包含 Unity 的光照函数库。
#include "AutoLight.cginc" // 包含 Unity 自动光照处理的相关函数。
#pragma multi_compile_fwdbase // 支持基础光照模式的多重编译。
#pragma vertex vert // 指定顶点着色器。
#pragma fragment frag // 指定片段着色器。
fixed _BurnAmount; // 溶解进度控制。
fixed _LineWidth; // 燃烧线条宽度控制。
sampler2D _MainTex; // 基础纹理。
sampler2D _BumpMap; // 法线贴图。
fixed4 _BurnFirstColor; // 燃烧线条起始颜色。
fixed4 _BurnSecondColor; // 燃烧线条结束颜色。
sampler2D _BurnMap; // 溶解纹理。
float4 _MainTex_ST; // 基础纹理的 UV 变换。
float4 _BumpMap_ST; // 法线贴图的 UV 变换。
float4 _BurnMap_ST; // 溶解纹理的 UV 变换。
struct a2v {
float4 vertex : POSITION; // 顶点位置。
float3 normal : NORMAL; // 顶点法线。
float4 tangent : TANGENT; // 顶点切线,用于法线贴图。
float4 texcoord : TEXCOORD0; // 顶点的 UV 坐标。
};
struct v2f {
float4 pos : SV_POSITION; // 顶点投影后的屏幕位置。
float2 uvMainTex : TEXCOORD0; // 基础纹理的 UV 坐标。
float2 uvBumpMap : TEXCOORD1; // 法线贴图的 UV 坐标。
float2 uvBurnMap : TEXCOORD2; // 溶解纹理的 UV 坐标。
float3 lightDir : TEXCOORD3; // 光源方向。
float3 worldPos : TEXCOORD4; // 世界空间的顶点位置。
SHADOW_COORDS(5) // 阴影坐标。
};
// 顶点着色器:计算各项数据并传递给片段着色器。
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex); // 转换到投影空间。
o.uvMainTex = TRANSFORM_TEX(v.texcoord, _MainTex); // 计算基础纹理的 UV。
o.uvBumpMap = TRANSFORM_TEX(v.texcoord, _BumpMap); // 计算法线贴图的 UV。
o.uvBurnMap = TRANSFORM_TEX(v.texcoord, _BurnMap); // 计算溶解纹理的 UV。
TANGENT_SPACE_ROTATION; // 计算切线空间的旋转矩阵。
o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz; // 计算切线空间的光照方向。
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; // 转换到世界空间。
TRANSFER_SHADOW(o); // 计算阴影信息。
return o;
}
// 片段着色器:实现溶解效果的颜色计算。
fixed4 frag(v2f i) : SV_Target {
fixed3 burn = tex2D(_BurnMap, i.uvBurnMap).rgb; // 从溶解纹理中取样。
clip(burn.r - _BurnAmount); // 根据 Burn Map 和 Burn Amount 控制像素是否渲染。
float3 tangentLightDir = normalize(i.lightDir); // 归一化切线空间的光照方向。
fixed3 tangentNormal = UnpackNormal(tex2D(_BumpMap, i.uvBumpMap)); // 解码法线。
fixed3 albedo = tex2D(_MainTex, i.uvMainTex).rgb; // 从基础纹理中取样颜色。
// 环境光计算
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
// 漫反射光计算
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir));
// 燃烧线条颜色计算
fixed t = 1 - smoothstep(0.0, _LineWidth, burn.r - _BurnAmount);
fixed3 burnColor = lerp(_BurnFirstColor, _BurnSecondColor, t); // 线条颜色渐变。
burnColor = pow(burnColor, 5); // 加强颜色对比度。
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos); // 光照衰减。
fixed3 finalColor = lerp(ambient + diffuse * atten, burnColor, t * step(0.0001, _BurnAmount));
return fixed4(finalColor, 1); // 返回最终颜色。
}
ENDCG
}
}
FallBack "Diffuse" // 当不支持此 Shader 时,使用 Diffuse 着色器。
}
scipt code
/*
脚本名称: BurnHelper (溶解效果辅助器)
用途: 此脚本用于动态控制材质的溶解效果。通过修改 `_BurnAmount` 属性,实现物体从完整到逐渐溶解的动画。
*/
using UnityEngine;
using System.Collections;
public class BurnHelper : MonoBehaviour {
// 需要应用溶解效果的材质
public Material material;
// 控制溶解速度,范围 [0.01, 1.0]
[Range(0.01f, 1.0f)]
public float burnSpeed = 0.3f;
// 当前溶解程度,范围 [0.0, 1.0]
private float burnAmount = 0.0f;
// 初始化逻辑
void Start () {
// 如果没有手动指定材质,尝试从当前物体或其子物体的 Renderer 中获取材质
if (material == null) {
Renderer renderer = gameObject.GetComponentInChildren<Renderer>();
if (renderer != null) {
material = renderer.material; // 获取 Renderer 的材质
}
}
// 如果未能获取材质,则禁用该脚本
if (material == null) {
this.enabled = false; // 禁用脚本,防止报错
} else {
material.SetFloat("_BurnAmount", 0.0f); // 初始化溶解程度为 0
}
}
// 每帧更新逻辑
void Update () {
// 使用 Time.time 和 burnSpeed 动态计算当前的溶解程度
burnAmount = Mathf.Repeat(Time.time * burnSpeed, 1.0f);
// 更新材质的 _BurnAmount 属性
material.SetFloat("_BurnAmount", burnAmount);
}
}
效果展示

水波效果
同样也可以使用噪声纹理来模拟实时水面,噪声纹理会用作一个高度图,不断修改水面法线方向,使用和时间相关的变量对噪声纹理进行采样,得到法线信息以后进行反射+折射计算得到水面波纹效果
shader code
/*
功能说明:
1. **水波扰动**:
- 使用噪声纹理控制水面的动态法线变化,实现水波效果。
- 控制波动的速度和幅度。
2. **折射效果**:
- 使用屏幕抓取纹理模拟光线穿过水面的折射现象。
- 根据法线扰动计算水面折射后的图像偏移。
3. **反射效果**:
- 使用环境立方体贴图模拟水面的反射。
- 结合法线和视角方向计算反射颜色。
4. **菲涅尔效应**:
- 根据观察角度混合反射和折射,实现真实的水面外观。
*/
Shader "Noise-WaterWave" {
Properties {
_Color ("Main Color", Color) = (0, 0.15, 0.115, 1) // 主颜色,用于整体的颜色调节
_MainTex ("Base (RGB)", 2D) = "white" {} // 基础颜色纹理
_WaveMap ("Wave Map", 2D) = "bump" {} // 水波扰动的法线贴图
_Cubemap ("Environment Cubemap", Cube) = "_Skybox" {} // 环境立方体贴图
_WaveXSpeed ("Wave Horizontal Speed", Range(-0.1, 0.1)) = 0.01 // 水波横向速度
_WaveYSpeed ("Wave Vertical Speed", Range(-0.1, 0.1)) = 0.01 // 水波纵向速度
_Distortion ("Distortion", Range(0, 100)) = 10 // 水波扰动强度
}
SubShader {
// 定义渲染顺序为透明队列,其他物体需先绘制
Tags { "Queue"="Transparent" "RenderType"="Opaque" }
// 抓取屏幕作为折射纹理
GrabPass { "_RefractionTex" }
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#include "UnityCG.cginc"
#include "Lighting.cginc"
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
// 参数声明
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _WaveMap;
float4 _WaveMap_ST;
samplerCUBE _Cubemap;
fixed _WaveXSpeed;
fixed _WaveYSpeed;
float _Distortion;
sampler2D _RefractionTex;
float4 _RefractionTex_TexelSize;
// 顶点着色器
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION; // 顶点位置
float4 scrPos : TEXCOORD0; // 屏幕空间坐标
float4 uv : TEXCOORD1; // UV 坐标
float4 TtoW0 : TEXCOORD2;
float4 TtoW1 : TEXCOORD3;
float4 TtoW2 : TEXCOORD4;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex); // 转换顶点位置到裁剪空间
o.scrPos = ComputeGrabScreenPos(o.pos); // 计算屏幕空间坐标
o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex); // 主纹理 UV
o.uv.zw = TRANSFORM_TEX(v.texcoord, _WaveMap); // 波纹法线贴图 UV
// 计算世界空间的切线、法线和副法线
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
// 存储切线到世界的变换矩阵
o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
return o;
}
// 像素着色器
fixed4 frag(v2f i) : SV_Target {
// 计算视线方向
float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
// 计算水波扰动
float2 speed = _Time.y * float2(_WaveXSpeed, _WaveYSpeed);
fixed3 bump1 = UnpackNormal(tex2D(_WaveMap, i.uv.zw + speed)).rgb;
fixed3 bump2 = UnpackNormal(tex2D(_WaveMap, i.uv.zw - speed)).rgb;
fixed3 bump = normalize(bump1 + bump2); // 组合扰动法线
// 根据扰动计算 UV 偏移
float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;
i.scrPos.xy = offset * i.scrPos.z + i.scrPos.xy;
fixed3 refrCol = tex2D(_RefractionTex, i.scrPos.xy/i.scrPos.w).rgb;
// 计算反射方向
bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
fixed4 texColor = tex2D(_MainTex, i.uv.xy + speed);
fixed3 reflDir = reflect(-viewDir, bump);
fixed3 reflCol = texCUBE(_Cubemap, reflDir).rgb * texColor.rgb * _Color.rgb;
// 菲涅尔效应
fixed fresnel = pow(1 - saturate(dot(viewDir, bump)), 4);
fixed3 finalColor = reflCol * fresnel + refrCol * (1 - fresnel);
return fixed4(finalColor, 1);
}
ENDCG
}
}
// 不渲染阴影
FallBack Off
}
效果展示
图像太大了传不上来,可以在github仓库里查看或者clone我的仓库在unity运行也能查看
全局雾效
之前实现过雾效的效果,现在是实现一个基于深度和噪声的全局雾效。通过结合相机的深度纹理和噪声纹理,动态计算雾气的浓度和变化,最终生成带有随机性的自然雾效。当然也包括一些属性,包括雾的密度、颜色、起始和结束距离,以及噪声的影响。
shader code
Shader "Noise - Global fog effect" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {} // 场景基础纹理
_FogDensity ("Fog Density", Float) = 1.0 // 雾气浓度
_FogColor ("Fog Color", Color) = (1, 1, 1, 1) // 雾的颜色
_FogStart ("Fog Start", Float) = 0.0 // 雾效开始的距离
_FogEnd ("Fog End", Float) = 1.0 // 雾效结束的距离
_NoiseTex ("Noise Texture", 2D) = "white" {} // 噪声纹理,用于动态变化
_FogXSpeed ("Fog Horizontal Speed", Float) = 0.1 // 雾气水平流动速度
_FogYSpeed ("Fog Vertical Speed", Float) = 0.1 // 雾气垂直流动速度
_NoiseAmount ("Noise Amount", Float) = 1 // 噪声对雾气的影响强度
}
SubShader {
CGINCLUDE
#include "UnityCG.cginc"
// 定义全局矩阵和纹理采样器
float4x4 _FrustumCornersRay; // 用于计算屏幕空间到世界空间的光线方向
sampler2D _MainTex; // 场景基础纹理
half4 _MainTex_TexelSize; // 主纹理的像素大小
sampler2D _CameraDepthTexture; // 相机的深度纹理
half _FogDensity; // 雾气浓度
fixed4 _FogColor; // 雾的颜色
float _FogStart; // 雾效开始的距离
float _FogEnd; // 雾效结束的距离
sampler2D _NoiseTex; // 噪声纹理
half _FogXSpeed; // 雾气水平移动速度
half _FogYSpeed; // 雾气垂直移动速度
half _NoiseAmount; // 噪声对雾气影响的强度
// 定义顶点与片元之间的结构体
struct v2f {
float4 pos : SV_POSITION; // 顶点的屏幕坐标
float2 uv : TEXCOORD0; // 主纹理的UV坐标
float2 uv_depth : TEXCOORD1; // 深度纹理的UV坐标
float4 interpolatedRay : TEXCOORD2; // 从屏幕空间到世界空间的光线
};
// 顶点着色器,用于计算光线方向和UV坐标
v2f vert(appdata_img v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex); // 转换为屏幕空间坐标
// 计算纹理的UV坐标
o.uv = v.texcoord;
o.uv_depth = v.texcoord;
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
o.uv_depth.y = 1 - o.uv_depth.y; // 修正UV坐标
#endif
// 根据顶点的位置,选择插值光线方向
int index = 0;
if (v.texcoord.x < 0.5 && v.texcoord.y < 0.5) {
index = 0;
} else if (v.texcoord.x > 0.5 && v.texcoord.y < 0.5) {
index = 1;
} else if (v.texcoord.x > 0.5 && v.texcoord.y > 0.5) {
index = 2;
} else {
index = 3;
}
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
index = 3 - index; // 修正索引
#endif
o.interpolatedRay = _FrustumCornersRay[index]; // 获取光线方向
return o;
}
// 片元着色器,用于计算最终的雾效颜色
fixed4 frag(v2f i) : SV_Target {
// 线性深度,转换屏幕深度值为世界深度值
float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth));
float3 worldPos = _WorldSpaceCameraPos + linearDepth * i.interpolatedRay.xyz; // 世界空间位置
// 计算噪声影响
float2 speed = _Time.y * float2(_FogXSpeed, _FogYSpeed); // 动态噪声移动速度
float noise = (tex2D(_NoiseTex, i.uv + speed).r - 0.5) * _NoiseAmount; // 获取噪声值
// 雾气浓度,根据深度和噪声调整
float fogDensity = (_FogEnd - worldPos.y) / (_FogEnd - _FogStart);
fogDensity = saturate(fogDensity * _FogDensity * (1 + noise)); // 最终浓度
// 混合雾的颜色与场景颜色
fixed4 finalColor = tex2D(_MainTex, i.uv); // 场景颜色
finalColor.rgb = lerp(finalColor.rgb, _FogColor.rgb, fogDensity); // 混合结果
return finalColor; // 返回最终颜色
}
ENDCG
// 渲染通道
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
FallBack Off // 无需降级到默认Shader
}

题外话
至此,对于冯乐乐老师的《unity shader 入门精要》的书籍的效果演示以及代码解释已经全部完了,具体是书的前15章。后面还有几章拓展的就不做效果展示了,此外我将所有的效果也在个人网页和github仓库展示,并且github仓库直接就可以clone下来用unity运行就能够看到所有的效果了,当然,这本书籍也有自己的代码仓库,也是有所有的效果的。做整理的过程本身也就是在学习,对于这种比较入门且经典的书籍,这个过程是值得的,并且也让我确实懂了不少shader的知识,如果在这上面每一块上都去拓展学习,那会是一个很有趣且丰富的过程。
更多推荐
所有评论(0)