19.消融效果具体实现

  1. 19.噪声-消融效果-具体实现
    1. 19.1 知识点
      1. 知识回顾:消融效果 基本原理
      2. 准备工作
        1. 导入纹理
        2. 新建 Shader
        3. 创建材质,将新建的 Shader 材质赋值给新建的机器人模型。
      3. 实现消融效果
        1. 主要步骤
        2. 添加属性,包括噪声纹理,渐变纹理,消融进度,边缘范围四个参数。并且添加属性映射
        3. 结构体中加入噪声纹理 UV
        4. 顶点着色器计算噪声纹理的缩放偏移
        5. 片元着色器从噪声纹理中采样,利用clip函数进行剔除
        6. 质赋值噪声图和渐变图,可以看到溶解效果了,但是溶解程度为1时没有完全消失,等一下再解决
        7. 片元着色器利用smoothstep函数计算出采样系数,从渐变纹理中获取溶解边缘的颜色,利用lerp函数决定是用哪个颜色,把颜色返回出去
        8. 修改参数,可以看到溶解效果以及渐变纹理,但是还没消融时就存在红点了,同时消融为1时没有消融干净
        9. 没开始消融存在红点的原因是,当_Dissolve为0但是noiseColor.r不为0(比如是很小的数时)时,计算出来的value仍然不为0,finalColor仍然会进行插值运算。解决方法是使用step函数,作用是当_Dissolve小于一个很小的数时,让 value * step(0.000001, _Dissolve)这一块变成0。这样完全使用模型颜色了。
        10. 消融程度为1还存在的远呀是因为clip函数只会剔除小于0,等于0是不会剔除的。所以可以设置_Dissolve为1时就设置一个负数强行剔除。
      4. 阴影相关添加
        1. 添加预编译指令和自动光照库
        2. 结构体添加世界空间位置与阴影坐标
        3. 顶点着色器添加世界空间位置和阴影坐标计算
        4. 片元着色器添加强度计算,并且让兰伯特乘上强度
        5. 添加后备着色器
        6. 阴影问题说明
      5. 阴影消融效果处理
        1. 主要步骤
        2. 复制投射阴影代码
        3. 加入噪声纹理和消融进度属性映射
        4. 结构体中添加噪声纹理 UV
        5. 顶点着色器中计算uv缩放偏移
        6. 片元着色器中剔除处理(与主 Pass 同步)
        7. 最终效果
    2. 19.2 知识点代码
      1. Lesson19_Dissolve.shader
      2. Lesson19_噪声_消融效果_具体实现.cs

19.噪声-消融效果-具体实现


19.1 知识点

知识回顾:消融效果 基本原理

消融效果的基本原理是通过对比噪声纹理的采样值与消融进度参数,剔除低于阈值的像素,同时在边缘添加渐变颜色,实现动态溶解效果。

  • 剔除像素:利用 clip(噪声纹理值 – 消融进度参数) 来剔除不需要的像素。
  • 处理边缘
    • 使用 smoothstep 函数结合消融阈值计算平滑过渡系数。
    • 利用 lerp 在原始颜色和边缘渐变颜色之间进行插值,从而确定最终使用的颜色。
    • 可通过自定义参数来控制边缘效果。

准备工作

导入纹理

导入噪声纹理和渐变纹理。

新建 Shader

新建 Shader 名称为 Lesson19_Dissolve,复制 Shader 开发入门中用于计算切线空间下法线纹理的ShaderLesson29_Bump_Texture_Tangent

Shader "Unlit/Lesson19_Dissolve"
{
    Properties
    {
        _MainColor("MainColor", Color) = (1,1,1,1)//  漫反射颜色 
        _MainTex("MainTex", 2D) = ""{}//  单张纹理
        _BumpMap("BumpMap", 2D) = ""{}//  法线纹理
        _BumpScale("BumpScale", Range(0,1)) = 1//  凹凸程度
        _SpecularColor("SpecularColor", Color) = (1,1,1,1)//  高光反射颜色
        _SpecularNum("SpecularNum", Range(0,256)) = 18//  光泽度
    }
    SubShader
    {
        Pass
        {
            Tags
            {
                "LightMode"="ForwardBase"
            }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "Lighting.cginc"


            float4 _MainColor; //漫反射颜色
            sampler2D _MainTex; //颜色纹理
            float4 _MainTex_ST; //颜色纹理的缩放和平移
            sampler2D _BumpMap; //法线纹理
            float4 _BumpMap_ST; //法线纹理的缩放和平移
            float _BumpScale; //凹凸程度
            float4 _SpecularColor; //高光颜色
            fixed _SpecularNum; //光泽度

            struct v2f
            {
                //裁剪空间下坐标
                float4 pos:SV_POSITION;


                //纹理uv

                //我们可以单独的声明两个float2的成员用于记录 颜色和法线纹理的uv坐标
                //float2 uvTex:TEXCOORD0;//颜色纹理
                //float2 uvBump:TEXCOORD1;//法线纹理

                //也可以直接声明一个float4的成员 xy用于记录颜色纹理的uv,zw用于记录法线纹理的uv
                float4 uv:TEXCOORD0; //纹理变量


                //光的方向 相对于切线空间下的
                float3 lightDir:TEXCOORD1;

                //视角的方向 相对于切线空间下的
                float3 viewDir:TEXCOORD2;
            };


            //  顶点着色器中传入:
            //  可以使用 UnityCG.cginc 中的 appdata_full
            //  其中包含了我们需要的顶点、法线、切线、纹理坐标相关数据
            v2f vert(appdata_full appdata_full)
            {
                v2f v2f;

                //把模型空间下的顶点转到裁剪空间下
                v2f.pos = UnityObjectToClipPos(appdata_full.vertex);

                //单张纹理和法线纹理 UV坐标缩放偏移计算
                v2f.uv.xy = appdata_full.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
                v2f.uv.zw = appdata_full.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;

                //在顶点着色器当中 得到 模型空间到切线空间的 转换矩阵
                //切线、副切线、法线
                //计算副切线 计算叉乘结果后 垂直与切线和法线的向量有两条 通过乘以 切线当中的w,就可以确定是哪一条(确定副切线方向)
                //cross是叉乘 appdata_full.tangent是切线 appdata_full.normal是法线 appdata_full.tangent.w代表叉乘结果方向
                float3 binormal = cross(normalize(appdata_full.tangent), normalize(appdata_full.normal)) * appdata_full.
                    tangent.w;

                //构建模型空间到切线空间的变换矩阵
                float3x3 rotation = float3x3(appdata_full.tangent.xyz,
                                                binormal,
                                                appdata_full.normal);

                //模型空间下的光的方向
                //v2f.lightDir = ObjSpaceLightDir(appdata_full.vertex);

                //乘以模型空间到切线空间的转换矩阵 就可以得到切线空间下的 光的方向了
                v2f.lightDir = mul(rotation, ObjSpaceLightDir(appdata_full.vertex));

                //模型空间下的视角的方向
                //v2f.viewDir = ObjSpaceViewDir(appdata_full.vertex);

                //乘以模型空间到切线空间的转换矩阵 就可以得到切线空间下的 视角的方向了
                v2f.viewDir = mul(rotation, ObjSpaceViewDir(appdata_full.vertex));

                return v2f;
            }


            //  片元着色器中传入:
            //  自定义一个结构体
            //  其中包含 裁剪空间下坐标、uv坐标、光的方向、视角的方向
            fixed4 frag(v2f v2f) : SV_Target
            {
                //纹理采样函数tex2D
                //通过纹理采样函数 取出法线纹理贴图当中的数据
                float4 packedNormal = tex2D(_BumpMap, v2f.uv.zw);

                //利用内置的UnpackNormal函数对法线信息进行逆运算以及可能的解压
                //将我们取出来的法线数据 进行逆运算并且可能会进行解压缩的运算,最终得到切线空间下的法线数据
                float3 tangentNormal = UnpackNormal(packedNormal);

                //用得到的切线空间的法线数据 乘以 BumpScale 来控制凹凸程度
                //注意 tangentNormal不要进行单位化 直接用即可 因为乘了BumpScale就不是单位向量了
                tangentNormal *= _BumpScale;
                tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

                //接下来就来处理 带颜色纹理的 布林方光照模型计算

                //颜色纹理和漫反射颜色的 叠加
                fixed3 albedo = tex2D(_MainTex, v2f.uv.xy) * _MainColor.rgb;

                //兰伯特漫反射颜色 = 光的颜色 * 漫反射材质的颜色 * max(0, dot(切线坐标系下的法线, 光的方向))
                fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(
                    0, dot(tangentNormal, normalize(v2f.lightDir)));

                //半角向量 = 视角方向 + 光的方向
                float3 halfA = normalize(normalize(v2f.viewDir) + normalize(v2f.lightDir));

                //高光反射的颜色 = 光的颜色 * 高光反射材质的颜色 * pow(max(0, dot(切线坐标系下的法线, 半角向量)), 光泽度)
                fixed3 specularColor = _LightColor0.rgb * _SpecularColor.rgb * pow(
                    max(0, dot(tangentNormal, halfA)), _SpecularNum);

                //布林方光照颜色 = 环境光颜色 + 兰伯特漫反射颜色 + 高光反射的颜色
                fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor + specularColor;

                return fixed4(color.rgb, 1);
            }
            ENDCG
        }
    }
}

创建材质,将新建的 Shader 材质赋值给新建的机器人模型。

实现消融效果

主要步骤

  • 添加属性

    • _Noise 噪声纹理
    • _Gradient 渐变纹理
    • _Dissolve 消融进度
    • _EdgeRange 边缘范围
  • 属性映射:在 Shader 的 Properties 中添加上述参数,并在材质属性声明中定义变量。

  • 结构体扩展:在顶点到片元传递的结构体中加入噪声纹理的 UV 坐标。

  • 顶点着色器:计算噪声纹理的缩放偏移。

  • 片元着色器

    • 采样噪声纹理,通过 clip 函数实现溶解区域的剔除。
    • 使用 smoothstep 计算边缘过渡值,并通过 lerp 实现模型原始颜色与边缘渐变颜色之间的插值。

添加属性,包括噪声纹理,渐变纹理,消融进度,边缘范围四个参数。并且添加属性映射

Properties
{
    // 基础材质属性
    _MainColor("MainColor", Color) = (1,1,1,1)      // 主颜色,用于混合纹理颜色
    _MainTex("MainTex", 2D) = "white" {}              // 主纹理贴图
    _BumpMap("BumpMap", 2D) = "bump" {}               // 法线贴图
    _BumpScale("BumpScale", Range(0,1)) = 1           // 法线强度调节系数
    _SpecularColor("SpecularColor", Color) = (1,1,1,1) // 高光反射颜色
    _SpecularNum("SpecularNum", Range(8,256)) = 18      // 高光反射指数(光泽度)

    // 溶解效果相关属性
    _Noise("Noise", 2D) = "white" {}      // 噪声纹理(决定溶解区域)
    _Gradient("Gradient", 2D) = "white" {}  // 渐变纹理(控制溶解边缘颜色)
    _Dissolve("Dissolve", Range(0,1)) = 0   // 溶解进度(0:完整,1:完全溶解)
    _EdgeRange("EdgeRange", Range(0,1)) = 0 // 溶解边缘过渡宽度
}
// 材质属性声明
float4 _MainColor;         // 漫反射颜色
sampler2D _MainTex;        // 颜色纹理
float4 _MainTex_ST;        // 颜色纹理的缩放和平移
sampler2D _BumpMap;        // 法线纹理
float4 _BumpMap_ST;        // 法线纹理的缩放和平移
float _BumpScale;          // 凹凸程度
float4 _SpecularColor;     // 高光颜色
fixed _SpecularNum;        // 光泽度

// 溶解效果属性
sampler2D _Noise;          // 噪声纹理
float4 _Noise_ST;          // 噪声纹理的缩放和平移
sampler2D _Gradient;       // 渐变纹理
fixed _Dissolve;           // 消融进度
fixed _EdgeRange;          // 消融边界宽窄范围

结构体中加入噪声纹理 UV

struct v2f
{
    float4 pos : SV_POSITION;   // 裁剪空间顶点位置
    float4 uv : TEXCOORD0;        // xy:主纹理 UV,zw:法线纹理 UV
    float2 uvNoise : TEXCOORD1;   // 噪声纹理 UV
    float3 lightDir : TEXCOORD2;  // 切线空间光照方向
    float3 viewDir : TEXCOORD3;   // 切线空间观察方向
};

顶点着色器计算噪声纹理的缩放偏移

v2f vert(appdata_full appdata_full)
{
    v2f v2f;

    //把模型空间下的顶点转到裁剪空间下
    v2f.pos = UnityObjectToClipPos(appdata_full.vertex);

    //单张纹理和法线纹理 UV坐标缩放偏移计算
    v2f.uv.xy = appdata_full.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
    v2f.uv.zw = appdata_full.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;


    //计算噪声纹理的缩放偏移
    v2f.uvNoise = appdata_full.texcoord.xy * _Noise_ST.xy + _Noise_ST.zw;


    //...
}

片元着色器从噪声纹理中采样,利用clip函数进行剔除

fixed4 frag(v2f v2f) : SV_Target
{
    //剔除——消融 溶解区域剔除(当_Dissolve为1时强制剔除)
    //从噪声纹理中采样
    fixed3 noiseColor = tex2D(_Noise, v2f.uvNoise.xy).rgb;
    //剔除判断
    clip(noiseColor.r - _Dissolve);

    //...
}

质赋值噪声图和渐变图,可以看到溶解效果了,但是溶解程度为1时没有完全消失,等一下再解决


片元着色器利用smoothstep函数计算出采样系数,从渐变纹理中获取溶解边缘的颜色,利用lerp函数决定是用哪个颜色,把颜色返回出去

fixed4 frag(v2f v2f) : SV_Target
{
    //剔除——消融 溶解区域剔除(当_Dissolve为1时强制剔除)
    //从噪声纹理中采样
    fixed3 noiseColor = tex2D(_Noise, v2f.uvNoise.xy).rgb;
    //剔除判断
    clip(noiseColor.r - _Dissolve);

    //...
    // return fixed4(color.rgb, 1);


    //需要在模型本来的颜色 和 消融边缘的颜色之间去进行选择

    //渐变颜色的采样

    // 计算溶解边缘的过渡值
    // smoothstep函数用于在指定范围内生成平滑的过渡值,避免硬边缘的出现。
    // 其定义为:smoothstep(edge0, edge1, x),当x在[edge0, edge1]范围内时,返回0到1之间的平滑插值值;
    // 当x小于edge0时,返回0;当x大于edge1时,返回1。
    // 这里,noiseColor.r - _Dissolve表示当前噪声值与溶解进度的差值,
    // _EdgeRange控制过渡的宽度,value表示当前片段的过渡值。
    fixed value = 1 - smoothstep(0, _EdgeRange, noiseColor.r - _Dissolve);
    // 当 _Dissolve 变化时:
    // - noiseColor.r - _Dissolve 增大:
    //     - smoothstep 函数的输入值增大,导致其输出值增大。
    //     - 由于 value 是 1 - smoothstep 的结果,value 会减小。
    // - noiseColor.r - _Dissolve 减小:
    //     - smoothstep 函数的输入值减小,导致其输出值减小。
    //     - 因此,value 会增大。
    // 当 _EdgeRange 变化时:
    // - _EdgeRange 增大:
    //     - smoothstep 函数的范围增大,使得输入值在更宽的范围内变化。
    //     - 这会导致 smoothstep 的输出值变化更平缓,从而使 value 的变化也更平缓。
    // - _EdgeRange 减小:
    //     - smoothstep 函数的范围减小,使得输入值在更窄的范围内变化。
    //     - 这会导致 smoothstep 的输出值变化更剧烈,从而使 value 的变化也更剧烈。


    // 从渐变纹理中获取溶解边缘的颜色
    // tex2D函数用于从纹理中获取指定UV坐标的颜色值。
    // 这里,value作为U坐标,0.5作为V坐标,从渐变纹理中获取对应的颜色。
    // 由于渐变纹理通常是水平渐变的,V坐标固定为0.5可以获取中间的颜色。
    fixed3 gradientColor = tex2D(_Gradient, fixed2(value, 0.5)).rgb;

    //最终颜色
    fixed3 finalColor = lerp(color, gradientColor, value);

    return fixed4(finalColor.rgb, 1);
}

修改参数,可以看到溶解效果以及渐变纹理,但是还没消融时就存在红点了,同时消融为1时没有消融干净



没开始消融存在红点的原因是,当_Dissolve为0但是noiseColor.r不为0(比如是很小的数时)时,计算出来的value仍然不为0,finalColor仍然会进行插值运算。解决方法是使用step函数,作用是当_Dissolve小于一个很小的数时,让 value * step(0.000001, _Dissolve)这一块变成0。这样完全使用模型颜色了。

//最终颜色
//当消融进度为0时 一定要让颜色使用我们的模型本来的颜色
//step函数保证_Dissolve为0是完全从模型颜色中采样
fixed3 finalColor = lerp(color, gradientColor, value * step(0.000001, _Dissolve));
// fixed3 finalColor = lerp(color, gradientColor, value);

消融程度为1还存在的远呀是因为clip函数只会剔除小于0,等于0是不会剔除的。所以可以设置_Dissolve为1时就设置一个负数强行剔除。

//剔除判断
//clip函数只会剔除小于0 等于0是不会剔除的 所以可以设置_Dissolve为1时就设置一个负数 强行剔除
clip(_Dissolve == 1 ? -1 : noiseColor.r - _Dissolve);
// clip(noiseColor.r - _Dissolve);

阴影相关添加

添加预编译指令和自动光照库

#pragma multi_compile_fwdbase// 启用前向渲染多编译变体
#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"//自动光照库

结构体添加世界空间位置与阴影坐标

struct v2f
{
    float4 pos : SV_POSITION;      // 裁剪空间顶点位置
    float4 uv : TEXCOORD0;           // xy:主纹理 UV,zw:法线纹理 UV
    float2 uvNoise : TEXCOORD1;      // 噪声纹理 UV
    float3 lightDir : TEXCOORD2;     // 切线空间光照方向
    float3 viewDir : TEXCOORD3;      // 切线空间观察方向
    float3 worldPos : TEXCOORD4;     // 世界空间位置(用于阴影计算)
    SHADOW_COORDS(5)               // 阴影坐标(自动生成)
};

顶点着色器添加世界空间位置和阴影坐标计算

v2f vert(appdata_full appdata_full)
{
    v2f v2f;

    //...

    // 世界空间位置和阴影坐标
    v2f.worldPos = mul(unity_ObjectToWorld, appdata_full.vertex).xyz;
    TRANSFER_SHADOW(v2f);

    return v2f;
}

片元着色器添加强度计算,并且让兰伯特乘上强度

fixed4 frag(v2f v2f) : SV_Target
{
    //...

    //强度计算
    UNITY_LIGHT_ATTENUATION(atten, v2f, v2f.worldPos);

    //布林方光照颜色 = 环境光颜色 + 兰伯特漫反射颜色 + 高光反射的颜色
    // fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor + specularColor;
    fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor * atten + specularColor;

    //...

    return fixed4(finalColor.rgb, 1);
}

添加后备着色器

Fallback "Diffuse" // 后备着色器(当不支持时使用漫反射)

阴影问题说明

目前实现中支持投射和接收阴影,但阴影部分尚未实现消融效果。

阴影消融效果处理

主要步骤

  • 复制 Shader 开发基础中 Lesson15_ForwardLighting 的自定义投射阴影代码。
  • 加入噪声纹理和消融进度的属性映射。
  • 在结构体中加入 UV 坐标。
  • 在顶点着色器中计算 UV 的缩放偏移。
  • 在片元着色器中加入剔除操作。

复制投射阴影代码

//阴影投影 Pass 主要是用来计算阴影映射纹理
Pass
{
    Tags
    {
        "LightMode" = "ShadowCaster"
    }
    CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag
    
    //  该编译指令时告诉Unity编译器生成多个着色器变体
    //  用于支持不同类型的阴影(SM,SSSM等等)
    //  可以确保着色器能够在所有可能的阴影投射模式下正确渲染
    //  比如有些低端移动设备不支持
    #pragma multi_compile_shadowcaster
    //  其中包含了关键的阴影计算相关的宏
    #include "UnityCG.cginc"

    struct v2f
    {
        //顶点到片元着色器阴影投射结构体数据宏
        //这个宏定义了一些标准的成员变量
        //这些变量用于在阴影投射路径中传递顶点数据到片元着色器
        //我们主要在结构体中使用
        V2F_SHADOW_CASTER;
    };

    v2f vert(appdata_base v)
    {
        v2f v2f;
        
        //转移阴影投射器法线偏移宏
        //用于在顶点着色器中计算和传递阴影投射所需的变量
        //主要做了
        //2-2-1.将对象空间的顶点位置转换为裁剪空间的位置
        //2-2-2.考虑法线偏移,以减轻阴影失真问题,尤其是在处理自阴影时
        //2-2-3.传递顶点的投影空间位置(光源视角下的坐标),用于后续的阴影计算
        //我们主要在顶点着色器中使用
        
        //这个宏会自动使用我们传进来的v 注意v这个参数不可以改名 否则识别不了
        TRANSFER_SHADOW_CASTER_NORMALOFFSET(v2f);
        
        return v2f;
    }

    float4 frag(v2f v2f):SV_Target
    {
        //阴影投射片元宏
        //将深度值写入到阴影映射纹理中
        //我们主要在片元着色器中使用
        SHADOW_CASTER_FRAGMENT(v2f);
    }
    ENDCG
}

加入噪声纹理和消融进度属性映射

// 溶解效果属性
sampler2D _Noise;   // 噪声纹理
float4 _Noise_ST;   // 噪声纹理的缩放和平移
fixed _Dissolve;    // 消融进度

结构体中添加噪声纹理 UV

struct v2f
{
    //顶点到片元着色器阴影投射结构体数据宏
    //这个宏定义了一些标准的成员变量
    //这些变量用于在阴影投射路径中传递顶点数据到片元着色器
    //我们主要在结构体中使用
    V2F_SHADOW_CASTER;// 阴影投射内置结构
    float2 uvNoise : TEXCOORD0; // 噪声纹理UV
};

顶点着色器中计算uv缩放偏移

v2f vert(appdata_base v)
{
    v2f v2f;

    //计算噪声纹理的缩放偏移
    v2f.uvNoise = v.texcoord.xy * _Noise_ST.xy + _Noise_ST.zw;
    
    //转移阴影投射器法线偏移宏
    //用于在顶点着色器中计算和传递阴影投射所需的变量
    //主要做了
    //2-2-1.将对象空间的顶点位置转换为裁剪空间的位置
    //2-2-2.考虑法线偏移,以减轻阴影失真问题,尤其是在处理自阴影时
    //2-2-3.传递顶点的投影空间位置(光源视角下的坐标),用于后续的阴影计算
    //我们主要在顶点着色器中使用

    //这个宏会自动使用我们传进来的v 注意v这个参数不可以改名 否则识别不了
    TRANSFER_SHADOW_CASTER_NORMALOFFSET(v2f);

    return v2f;
}

片元着色器中剔除处理(与主 Pass 同步)

float4 frag(v2f v2f):SV_Target
{
    // 阴影投射时的溶解剔除(与主Pass同步)
    fixed3 noise = tex2D(_Noise, v2f.uvNoise).rgb;
    clip(_Dissolve == 1 ? -1 : noise.r - _Dissolve);

    //阴影投射片元宏
    //将深度值写入到阴影映射纹理中
    //我们主要在片元着色器中使用
    SHADOW_CASTER_FRAGMENT(v2f);
}

最终效果

通过上述步骤,阴影部分也将呈现消融效果。


19.2 知识点代码

Lesson19_Dissolve.shader

Shader "Unlit/Lesson19_Dissolve"
{
    Properties
    {
        // 基础材质属性
        _MainColor("MainColor", Color) = (1,1,1,1) // 主颜色,用于混合纹理颜色
        _MainTex("MainTex", 2D) = "white" {} // 主纹理贴图
        _BumpMap("BumpMap", 2D) = "bump" {} // 法线贴图
        _BumpScale("BumpScale", Range(0,1)) = 1 // 法线强度调节系数
        _SpecularColor("SpecularColor", Color) = (1,1,1,1) // 高光反射颜色
        _SpecularNum("SpecularNum", Range(8,256)) = 18 // 高光反射指数(光泽度)

        // 溶解效果相关属性
        _Noise("Noise", 2D) = "white" {} // 噪声纹理(决定溶解区域)
        _Gradient("Gradient", 2D) = "white" {} // 渐变纹理(控制溶解边缘颜色)
        _Dissolve("Dissolve", Range(0,1)) = 0 // 溶解进度(0:完整 1:完全溶解)
        _EdgeRange("EdgeRange", Range(0,1)) = 0 // 溶解边缘过渡宽度
    }
    SubShader
    {
        Pass
        {
            Tags
            {
                "LightMode"="ForwardBase"
            }
            CGPROGRAM
            #pragma multi_compile_fwdbase       // 启用前向渲染多编译变体
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "AutoLight.cginc"

            // 材质属性声明
            float4 _MainColor; //漫反射颜色
            sampler2D _MainTex; //颜色纹理
            float4 _MainTex_ST; //颜色纹理的缩放和平移
            sampler2D _BumpMap; //法线纹理
            float4 _BumpMap_ST; //法线纹理的缩放和平移
            float _BumpScale; //凹凸程度
            float4 _SpecularColor; //高光颜色
            fixed _SpecularNum; //光泽度

            // 溶解效果属性
            sampler2D _Noise; //噪声纹理
            float4 _Noise_ST; //法线纹理的缩放和平移
            sampler2D _Gradient; //噪声纹理
            fixed _Dissolve; //消融进度
            fixed _EdgeRange; //消融边界宽窄范围

            struct v2f
            {
                float4 pos : SV_POSITION; // 裁剪空间顶点位置
                float4 uv : TEXCOORD0; // xy:主纹理UV zw:法线纹理UV
                float2 uvNoise : TEXCOORD1; // 噪声纹理UV
                float3 lightDir : TEXCOORD2; // 切线空间光照方向
                float3 viewDir : TEXCOORD3; // 切线空间观察方向
                float3 worldPos : TEXCOORD4; // 世界空间位置(用于阴影计算)
                SHADOW_COORDS(5) // 阴影坐标(自动生成)
            };


            //  顶点着色器中传入:
            //  可以使用 UnityCG.cginc 中的 appdata_full
            //  其中包含了我们需要的顶点、法线、切线、纹理坐标相关数据
            v2f vert(appdata_full appdata_full)
            {
                v2f v2f;

                //把模型空间下的顶点转到裁剪空间下
                v2f.pos = UnityObjectToClipPos(appdata_full.vertex);

                //单张纹理和法线纹理 UV坐标缩放偏移计算
                v2f.uv.xy = appdata_full.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
                v2f.uv.zw = appdata_full.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;


                //计算噪声纹理的缩放偏移
                v2f.uvNoise = appdata_full.texcoord.xy * _Noise_ST.xy + _Noise_ST.zw;


                //在顶点着色器当中 得到 模型空间到切线空间的 转换矩阵
                //切线、副切线、法线
                //计算副切线 计算叉乘结果后 垂直与切线和法线的向量有两条 通过乘以 切线当中的w,就可以确定是哪一条(确定副切线方向)
                //cross是叉乘 appdata_full.tangent是切线 appdata_full.normal是法线 appdata_full.tangent.w代表叉乘结果方向
                float3 binormal = cross(normalize(appdata_full.tangent), normalize(appdata_full.normal)) * appdata_full.
                    tangent.w;

                //构建模型空间到切线空间的变换矩阵
                float3x3 rotation = float3x3(appdata_full.tangent.xyz,
                                             binormal,
                                             appdata_full.normal);

                //模型空间下的光的方向
                //v2f.lightDir = ObjSpaceLightDir(appdata_full.vertex);

                //乘以模型空间到切线空间的转换矩阵 就可以得到切线空间下的 光的方向了
                v2f.lightDir = mul(rotation, ObjSpaceLightDir(appdata_full.vertex));

                //模型空间下的视角的方向
                //v2f.viewDir = ObjSpaceViewDir(appdata_full.vertex);

                //乘以模型空间到切线空间的转换矩阵 就可以得到切线空间下的 视角的方向了
                v2f.viewDir = mul(rotation, ObjSpaceViewDir(appdata_full.vertex));

                // 世界空间位置和阴影坐标
                v2f.worldPos = mul(unity_ObjectToWorld, appdata_full.vertex).xyz;
                TRANSFER_SHADOW(v2f);

                return v2f;
            }


            //  片元着色器中传入:
            //  自定义一个结构体
            //  其中包含 裁剪空间下坐标、uv坐标、光的方向、视角的方向
            fixed4 frag(v2f v2f) : SV_Target
            {
                //剔除——消融 溶解区域剔除(当_Dissolve为1时强制剔除)

                //从噪声纹理中采样
                fixed3 noiseColor = tex2D(_Noise, v2f.uvNoise.xy).rgb;

                //剔除判断
                //clip函数只会剔除小于0 等于0是不会剔除的 所以可以设置_Dissolve为1时就设置一个负数 强行剔除
                clip(_Dissolve == 1 ? -1 : noiseColor.r - _Dissolve);
                // clip(noiseColor.r - _Dissolve);


                //纹理采样函数tex2D
                //通过纹理采样函数 取出法线纹理贴图当中的数据
                float4 packedNormal = tex2D(_BumpMap, v2f.uv.zw);

                //利用内置的UnpackNormal函数对法线信息进行逆运算以及可能的解压
                //将我们取出来的法线数据 进行逆运算并且可能会进行解压缩的运算,最终得到切线空间下的法线数据
                float3 tangentNormal = UnpackNormal(packedNormal);

                //用得到的切线空间的法线数据 乘以 BumpScale 来控制凹凸程度
                //注意 tangentNormal不要进行单位化 直接用即可 因为乘了BumpScale就不是单位向量了
                tangentNormal *= _BumpScale;
                tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

                //接下来就来处理 带颜色纹理的 布林方光照模型计算

                //颜色纹理和漫反射颜色的 叠加
                fixed3 albedo = tex2D(_MainTex, v2f.uv.xy) * _MainColor.rgb;

                //兰伯特漫反射颜色 = 光的颜色 * 漫反射材质的颜色 * max(0, dot(切线坐标系下的法线, 光的方向))
                fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(
                    0, dot(tangentNormal, normalize(v2f.lightDir)));

                //半角向量 = 视角方向 + 光的方向
                float3 halfA = normalize(normalize(v2f.viewDir) + normalize(v2f.lightDir));

                //高光反射的颜色 = 光的颜色 * 高光反射材质的颜色 * pow(max(0, dot(切线坐标系下的法线, 半角向量)), 光泽度)
                fixed3 specularColor = _LightColor0.rgb * _SpecularColor.rgb * pow(
                    max(0, dot(tangentNormal, halfA)), _SpecularNum);

                //强度计算
                UNITY_LIGHT_ATTENUATION(atten, v2f, v2f.worldPos);

                //布林方光照颜色 = 环境光颜色 + 兰伯特漫反射颜色 + 高光反射的颜色
                // fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor + specularColor;
                fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor * atten + specularColor;

                // return fixed4(color.rgb, 1);


                //需要在模型本来的颜色 和 消融边缘的颜色之间去进行选择

                //渐变颜色的采样

                // 计算溶解边缘的过渡值
                // smoothstep函数用于在指定范围内生成平滑的过渡值,避免硬边缘的出现。
                // 其定义为:smoothstep(edge0, edge1, x),当x在[edge0, edge1]范围内时,返回0到1之间的平滑插值值;
                // 当x小于edge0时,返回0;当x大于edge1时,返回1。
                // 这里,noiseColor.r - _Dissolve表示当前噪声值与溶解进度的差值,
                // _EdgeRange控制过渡的宽度,value表示当前片段的过渡值。
                fixed value = 1 - smoothstep(0, _EdgeRange, noiseColor.r - _Dissolve);
                // 当 _Dissolve 变化时:
                // - noiseColor.r - _Dissolve 增大:
                //     - smoothstep 函数的输入值增大,导致其输出值增大。
                //     - 由于 value 是 1 - smoothstep 的结果,value 会减小。
                // - noiseColor.r - _Dissolve 减小:
                //     - smoothstep 函数的输入值减小,导致其输出值减小。
                //     - 因此,value 会增大。
                // 当 _EdgeRange 变化时:
                // - _EdgeRange 增大:
                //     - smoothstep 函数的范围增大,使得输入值在更宽的范围内变化。
                //     - 这会导致 smoothstep 的输出值变化更平缓,从而使 value 的变化也更平缓。
                // - _EdgeRange 减小:
                //     - smoothstep 函数的范围减小,使得输入值在更窄的范围内变化。
                //     - 这会导致 smoothstep 的输出值变化更剧烈,从而使 value 的变化也更剧烈。


                // 从渐变纹理中获取溶解边缘的颜色
                // tex2D函数用于从纹理中获取指定UV坐标的颜色值。
                // 这里,value作为U坐标,0.5作为V坐标,从渐变纹理中获取对应的颜色。
                // 由于渐变纹理通常是水平渐变的,V坐标固定为0.5可以获取中间的颜色。
                fixed3 gradientColor = tex2D(_Gradient, fixed2(value, 0.5)).rgb;

                //最终颜色
                //当消融进度为0时 一定要让颜色使用我们的模型本来的颜色
                //step函数保证_Dissolve为0是完全从模型颜色中采样
                fixed3 finalColor = lerp(color, gradientColor, value * step(0.000001, _Dissolve));
                // fixed3 finalColor = lerp(color, gradientColor, value);

                return fixed4(finalColor.rgb, 1);
            }
            ENDCG
        }

        //阴影投影 Pass 主要是用来计算阴影映射纹理
        Pass
        {
            Tags
            {
                "LightMode" = "ShadowCaster"
            }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            //  该编译指令时告诉Unity编译器生成多个着色器变体
            //  用于支持不同类型的阴影(SM,SSSM等等)
            //  可以确保着色器能够在所有可能的阴影投射模式下正确渲染
            //  比如有些低端移动设备不支持
            #pragma multi_compile_shadowcaster
            //  其中包含了关键的阴影计算相关的宏
            #include "UnityCG.cginc"

            struct v2f
            {
                //顶点到片元着色器阴影投射结构体数据宏
                //这个宏定义了一些标准的成员变量
                //这些变量用于在阴影投射路径中传递顶点数据到片元着色器
                //我们主要在结构体中使用
                V2F_SHADOW_CASTER;// 阴影投射内置结构
                float2 uvNoise : TEXCOORD0; // 噪声纹理UV
            };

            // 溶解效果属性
            sampler2D _Noise; //噪声纹理
            float4 _Noise_ST; //法线纹理的缩放和平移
            fixed _Dissolve; //消融进度

            v2f vert(appdata_base v)
            {
                v2f v2f;

                //计算噪声纹理的缩放偏移
                v2f.uvNoise = v.texcoord.xy * _Noise_ST.xy + _Noise_ST.zw;
                
                //转移阴影投射器法线偏移宏
                //用于在顶点着色器中计算和传递阴影投射所需的变量
                //主要做了
                //2-2-1.将对象空间的顶点位置转换为裁剪空间的位置
                //2-2-2.考虑法线偏移,以减轻阴影失真问题,尤其是在处理自阴影时
                //2-2-3.传递顶点的投影空间位置(光源视角下的坐标),用于后续的阴影计算
                //我们主要在顶点着色器中使用

                //这个宏会自动使用我们传进来的v 注意v这个参数不可以改名 否则识别不了
                TRANSFER_SHADOW_CASTER_NORMALOFFSET(v2f);

                return v2f;
            }

            float4 frag(v2f v2f):SV_Target
            {
                // 阴影投射时的溶解剔除(与主Pass同步)
                fixed3 noise = tex2D(_Noise, v2f.uvNoise).rgb;
                clip(_Dissolve == 1 ? -1 : noise.r - _Dissolve);

                //阴影投射片元宏
                //将深度值写入到阴影映射纹理中
                //我们主要在片元着色器中使用
                SHADOW_CASTER_FRAGMENT(v2f);
            }
            ENDCG
        }

    }

    Fallback "Diffuse" // 后备着色器(当不支持时使用漫反射)
}

Lesson19_噪声_消融效果_具体实现.cs

using UnityEngine;

public class Lesson19_噪声_消融效果_具体实现 : MonoBehaviour
{
    void Start()
    {
        #region 知识回顾 消融效果 基本原理

        //消融效果 基本原理
        //通过对比噪声纹理值与消融进度参数,剔除低于阈值的像素,
        //同时在边缘添加渐变颜色实现动态溶解效果。

        //1.如何剔除像素
        //clip(噪声纹理值 – 消融进度参数)
        //2.如何处理边缘
        //利用smoothstep结合消融阈值来决定在渐变纹理中采用的渐变颜色
        //利用lerp来决定在原始颜色和边缘渐变颜色中使用哪个颜色
        //利用自定义参数决定边缘

        #endregion

        #region 知识点一 准备工作

        //1.导入噪声纹理和渐变纹理
        //2.新建Shader Lesson19_Dissolve,复制Shader开发入门中计算切线空间下法线纹理Shader Lesson29_Bump_Texture_Tangent
        
        #endregion

        #region 知识点二 实现消融效果

        //1.添加属性
        //  _Noise 噪声纹理
        //  _Gradient 渐变纹理
        //  _Dissolve 消融进度
        //  _Range 边缘范围
        //  添加属性映射
        //2.结构体 加入一个噪声纹理uv
        //3.片元着色器
        //  3-1:剔除效果制作
        //      从噪声纹理中采样,利用clip函数进行剔除
        //  3-2:边缘渐变采样,范围控制,颜色插值
        //      利用smoothstep函数计算出采样系数
        //      利用lerp函数决定是用哪个颜色

        #endregion

        #region 知识点三 阴影相关添加

        #endregion

        #region 知识点四 阴影消融效果处理

        //1.复制Shader开发基础中Lesson15_ForwardLighting中自定义投射阴影相关代码
        //2.加入噪声纹理和消融进度属性映射
        //3.结构体中加入uv
        //4.顶点着色器中计算uv缩放偏移
        //5.片元着色器中剔除

        #endregion
    }
}


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

×

喜欢就点赞,疼爱就打赏