16.表面着色器动态液体具体实现

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)
  • 输入结构体中只需包含当前像素的世界坐标位置
  • 实现表面函数:
    1. 模型中心点转换到世界坐标
    2. 计算中心点与像素点 Y 轴坐标差
    3. 像素剔除
    4. 波纹效果偏移计算
    5. 设置漫反射颜色、高光颜色与光滑度

新建表面着色器,删除不必要的代码

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"
}
shaderlab angelscript复制代码

定义液体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
}
shaderlab reasonml复制代码
fixed4 _Color;
fixed4 _Specular;
float _Height;
float _Speed;
float _WaveAmplitude;
float _WaveFrequency;
float _InvWaveLength;
shaderlab abnf复制代码

进行透明混合相关设置

// 设置渲染标签,确保该Shader按透明物体处理,保证混合和绘制顺序正确
Tags
{
    "RenderType"="Transparent" "Queue"="Transparent"
}
// 使用目标颜色与源颜色混合,产生特殊的透明效果
Blend DstColor SrcColor
// 关闭深度写入以防止透明对象深度冲突
ZWrite Off
shaderlab cos复制代码

光照模型我们使用StandardSpecular 并且不要阴影 noshadow

// 使用物理基础标准光照模型,并禁用阴影,使光照计算更简单高效
#pragma surface surf StandardSpecular noshadow

// 指定Shader模型版本为3.0,确保支持更复杂的光照运算
#pragma target 3.0
shaderlab cpp复制代码

输入结构体中只需要当前像素的世界坐标位置

// 定义输入结构体,包含传入片段处理的必要数据
struct Input
{
    //世界空间下的像素点的位置
    float3 worldPos;
};
shaderlab thrift复制代码

实现表面函数

表面函数流程如下:

  • 将模型空间中心点转换到世界空间,作为液体高度计算的参考基准
  • 计算当前像素点与中心点在 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;
}
shaderlab angelscript复制代码

动态液体效果的使用

创建两个胶囊体:

  • 一个较大的胶囊体作为容器,使用 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"
}
shaderlab angelscript复制代码

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
    }
}
csharp复制代码


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏