16.表面着色器-实例分析-动态液体-具体实现
16.1 知识点
知识回顾:动态液体效果基本原理
- 如何被容器装载 —— 利用两个模型,外部为透明容器,内部为动态液体
- 如何实现透明渲染 —— 通过透明混合相关设置
- 如何剔除像素 —— 以模型空间中心点为参考,将其转换到世界空间后,用当前世界空间下的点与该参考点相减,判断若点在参考点上方则剔除;可加入自定义变量控制液面高度
- 如何模拟波纹效果 —— 使用流动河流相关公式计算,例如:
某轴位置偏移量 = sin( _Time.y * 波动频率 + 顶点某轴坐标 * 波长的倒数) * 波动幅度
动态液体效果具体实现
主要步骤
- 新建表面着色器 DynamicLiquid(动态液体)
- 删除不必要的代码
- 声明属性以及进行属性映射,属性包括:
- 液体颜色 _Color
- 高光颜色和光滑度(RGB做颜色,A做光滑度) _Specular
- 液体高度 _Height
- 波纹变化速度 _Speed
- 波动幅度 _WaveAmplitude
- 波动频率 _WaveFrequency
- 波长的倒数 _InvWaveLength
- 设置透明混合相关参数:
- RenderType 和 Queue 设置为 Transparent
- 混合模式使用 Blend DstColor SrcColor
- 关闭 ZWrite(深度写入)
- 编译指令设置:
- 使用 StandardSpecular 光照模型,并禁用阴影(noshadow)
- 输入结构体中只需包含当前像素的世界坐标位置
- 实现表面函数:
- 模型中心点转换到世界坐标
- 计算中心点与像素点 Y 轴坐标差
- 像素剔除
- 波纹效果偏移计算
- 设置漫反射颜色、高光颜色与光滑度
新建表面着色器,删除不必要的代码
Shader "Custom/Lesson16_DynamicLiquid"
{
Properties
{
// 属性将在后续步骤中定义
}
SubShader
{
Tags
{
// 渲染标签将在后续设置
}
CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
#pragma surface surf StandardSpecular noshadow
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0
struct Input
{
// 暂无其他输入
};
void surf(Input IN, inout SurfaceOutputStandardSpecular o)
{
// 表面函数将在后续实现
}
ENDCG
}
FallBack "Diffuse"
}
定义液体Shader所需的各项属性,包括控制液体的颜色、光照效果、液体高度以及波纹效果的属性,并进行属性映射
Properties
{
// 液体颜色
_Color("Color", Color) = (1,1,1,1)
// 高光颜色和光滑度参数
_Specular("Specular", Color) = (0,0,0,0)
// 液体的高度
_Height("Height", Float) = 0
// 波纹变化速度
_Speed("Speed", Float) = 1
// 波动幅度
_WaveAmplitude("WaveAmplitude", Float) = 1
// 波动频率
_WaveFrequency("WaveFrequency", Float) = 1
// 波长的倒数
_InvWaveLength("InvWaveLength", Float) = 1
}
fixed4 _Color;
fixed4 _Specular;
float _Height;
float _Speed;
float _WaveAmplitude;
float _WaveFrequency;
float _InvWaveLength;
进行透明混合相关设置
// 设置渲染标签,确保该Shader按透明物体处理,保证混合和绘制顺序正确
Tags
{
"RenderType"="Transparent" "Queue"="Transparent"
}
// 使用目标颜色与源颜色混合,产生特殊的透明效果
Blend DstColor SrcColor
// 关闭深度写入以防止透明对象深度冲突
ZWrite Off
光照模型我们使用StandardSpecular 并且不要阴影 noshadow
// 使用物理基础标准光照模型,并禁用阴影,使光照计算更简单高效
#pragma surface surf StandardSpecular noshadow
// 指定Shader模型版本为3.0,确保支持更复杂的光照运算
#pragma target 3.0
输入结构体中只需要当前像素的世界坐标位置
// 定义输入结构体,包含传入片段处理的必要数据
struct Input
{
//世界空间下的像素点的位置
float3 worldPos;
};
实现表面函数
表面函数流程如下:
- 将模型空间中心点转换到世界空间,作为液体高度计算的参考基准
- 计算当前像素点与中心点在 Y 轴方向的高度差,加上液体整体高度微调(乘以 0.01,使变化更加平缓)
- 利用正弦函数计算波纹偏移,模拟液体表面波动
- 将波纹效果叠加到液体高度上
- 通过 step 函数和 clip 函数对液体边界进行精确剔除
- 最后设置像素的漫反射颜色为液体基本颜色,高光颜色为 Specular 的 RGB,高光平滑度由 Specular 的 Alpha 通道控制
// 表面着色器函数:负责计算每个像素的液体高度,确定该像素是否可见,并设置最终的颜色与光照效果
void surf(Input IN, inout SurfaceOutputStandardSpecular o)
{
// 将模型空间下中心点转换到世界空间下,作为液体高度计算的参考基准
float3 centerPoint = mul(unity_ObjectToWorld, float4(0, 0, 0, 1));
// 当前像素点和中心点的高度差,加上液体整体高度的微调(乘以0.01使变化更加平缓)
float liquidHeight = centerPoint.y - IN.worldPos.y + _Height * 0.01;
// 计算波纹偏移:利用正弦函数制造周期性波动效果,模拟液体表面的波动
float waveOffset = sin(_Time.y * _WaveFrequency + IN.worldPos.x * _InvWaveLength) * _WaveAmplitude;
// 将波纹效果叠加到液体高度上,使得液体表面出现动态波纹效果
liquidHeight += waveOffset;
// 如果liquidHeight >= 0 则返回1 如果小于0 则返回0
// 如果是0 就希望被剔除 否则不剔除
liquidHeight = step(0, liquidHeight);
// 如果liquidHeight是0 - 0.001 肯定小于0 就会被剔除,不会被渲染,从而形成清晰的液体边界
clip(liquidHeight - 0.001);
// 将漫反射颜色设置为液体的基本颜色
o.Albedo = _Color.rgb;
// 设置高光颜色,用于产生液体的光泽反射
o.Specular = _Specular.rgb;
// 根据_specular的alpha通道设置光滑度,影响反射的锐利度
o.Smoothness = _Specular.a;
}
动态液体效果的使用
创建两个胶囊体:
- 一个较大的胶囊体作为容器,使用 Unity 自带 Shader 设置为透明,类似玻璃容器;
- 一个较小的胶囊体作为大的子对象,用动态液体效果制作为容器中的液体。
可以看到效果如下:


总结
本例详细介绍了动态液体效果的基本原理和具体实现步骤:
- 从如何容纳液体、透明混合设置、像素剔除到模拟波纹效果,详细说明了原理。
- 通过新建表面着色器、声明必要属性、设置透明混合、定义输入结构体及实现表面函数,展示了如何实现液体外观效果。
- 最后,通过创建两个胶囊体模拟容器和液体的组合,演示了动态液体效果在实际场景中的应用。
这种方法利用 Shader 中的各种属性和数学函数,结合透明混合与精细的像素剔除,成功实现了逼真的动态液体效果。
16.2 知识点代码
Lesson16_DynamicLiquid.shader
Shader "Custom/Lesson16_DynamicLiquid"
{
// 定义了液体Shader所需的各项属性,控制液体的颜色、光照效果、液体高度以及波纹效果
Properties
{
//液体颜色
_Color("Color", Color) = (1,1,1,1)
//高光颜色和光滑度参数
_Specular("Specular", Color) = (0,0,0,0)
//液体的高度
_Height("Height", Float) = 0
//波纹变化速度
_Speed("Speed", Float) = 1
//波动幅度
_WaveAmplitude("WaveAmplitude", Float) = 1
//波动频率
_WaveFrequency("WaveFrequency", Float) = 1
//波长的倒数
_InvWaveLength("InvWaveLength", Float) = 1
}
SubShader
{
// 设置渲染标签,确保该Shader按透明物体处理,保证混合和绘制顺序正确
Tags
{
"RenderType"="Transparent" "Queue"="Transparent"
}
// 使用目标颜色与源颜色混合,产生特殊的透明效果
Blend DstColor SrcColor
// 关闭深度写入以防止透明对象深度冲突
ZWrite Off
CGPROGRAM
// 使用物理基础标准光照模型,并禁用阴影,使光照计算更简单高效
#pragma surface surf StandardSpecular noshadow
// 指定Shader模型版本为3.0,确保支持更复杂的光照运算
#pragma target 3.0
// 全局变量:储存Shader属性传入的颜色、光照及波纹参数
fixed4 _Color;
fixed4 _Specular;
float _Height;
float _Speed;
float _WaveAmplitude;
float _WaveFrequency;
float _InvWaveLength;
// 定义输入结构体,包含传入片段处理的必要数据
struct Input
{
//世界空间下的像素点的位置
float3 worldPos;
};
// 片段着色器函数:负责计算每个像素的液体高度,确定该像素是否可见,并设置最终的颜色与光照效果
void surf(Input IN, inout SurfaceOutputStandardSpecular o)
{
// 将模型空间下中心点转换到世界空间下,作为液体高度计算的参考基准
float3 centerPoint = mul(unity_ObjectToWorld, float4(0, 0, 0, 1));
// 当前像素点和中心点的高度差,加上液体整体高度的微调(乘以0.01使变化更加平缓)
float liquidHeight = centerPoint.y - IN.worldPos.y + _Height * 0.01;
// 计算波纹偏移:利用正弦函数制造周期性波动效果,模拟液体表面的波动
float waveOffset = sin(_Time.y * _WaveFrequency + IN.worldPos.x * _InvWaveLength) * _WaveAmplitude;
// 将波纹效果叠加到液体高度上,使得液体表面出现动态波纹效果
liquidHeight += waveOffset;
// 如果liquidHeight >= 0 则返回1 如果小于0 则返回0
// 如果是0 就希望被剔除 否则不剔除
liquidHeight = step(0, liquidHeight);
// 如果liquidHeight是0 - 0.001 肯定小于0 就会被剔除,不会被渲染,从而形成清晰的液体边界
clip(liquidHeight - 0.001);
// 将漫反射颜色设置为液体的基本颜色
o.Albedo = _Color.rgb;
// 设置高光颜色,用于产生液体的光泽反射
o.Specular = _Specular.rgb;
// 根据_specular的alpha通道设置光滑度,影响反射的锐利度
o.Smoothness = _Specular.a;
}
ENDCG
}
// 当该Shader不可用时,使用Diffuse Shader作为后备方案
FallBack "Diffuse"
}
Lesson16_表面着色器_实例分析_动态液体_具体实现.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Lesson16_表面着色器_实例分析_动态液体_具体实现 : MonoBehaviour
{
void Start()
{
#region 知识回顾 动态液体效果 基本原理
//1.如何被容器装载 —— 两个模型,外部透明,内部动态液体
//2.如何透明渲染 —— 透明混合相关设置
//3.如何剔除像素 —— 将模型空间中心点作为参考点,将其转换到世界空间下
// 再用模型当前世界空间下的点和它进行减法运算
// 如果判断点在参考点上方的我们便对其进行剔除
// 可以加入自定义变量控制液面高度
//4.如何模拟波纹效果 —— 使用流动的河流的相关公式
// 某轴位置偏移量 = sin( _Time.y * 波动频率 + 顶点某轴坐标 * 波长的倒数) * 波动幅度
#endregion
#region 知识点一 动态液体效果 具体实现
//1.新建表面着色器 DynamicLiquid(动态液体)
//2.删除不必要的代码
//3.声明属性以及属性映射
// 液体颜色 _Color
// 高光颜色和光滑度(rgb做颜色 a做光滑度) _Specular
// 液体高度 _Height
// 波纹变化速度 _Speed
// 波动幅度 _WavaAmplitude
// 波动频率 _WavaFrequency
// 波长的倒数 _InvWaveLength
//4.透明混合相关设置
// RenderType和Queue为Transparent
// Blend DstColor SrcColor
// Zwrite Off
//5.编译指令设置
// 光照模型我们使用StandardSpecular 并且不要阴影 noshadow
//6.输入结构体
// 只需要当前像素的世界坐标位置
//7.实现表面函数
// 7-1:模型中心点转世界坐标
// 7-2:计算中心点和像素点y轴坐标差
// 7-3:像素剔除
// 7-4:波纹效果偏移计算
// 7-5:漫反射颜色、高光颜色、光滑度设置
#endregion
#region 知识点二 动态液体效果 的使用
//1.创建两个胶囊体,一大一小,小的做为大的子对象
//2.大的胶囊体,用Unity自带Shader设置为透明的,类似玻璃容器
//3.小的胶囊体,用动态液体效果制作为容器中的液体
#endregion
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com