14.素描风格渲染具体实现

  1. 14.素描风格渲染-具体实现
    1. 14.1 知识点
      1. 知识回顾 素描风格渲染基本原理
      2. 纹理资源导入、相关属性添加
        1. 主要步骤
        2. 导入素描纹理资源
        3. 新建Shader,Lesson14_Sketch,删除无用代码。添加属性。包括颜色属性Color用于颜色叠加,平铺系数TileFactor用于平铺纹理,让采样细节更多,素描纹理Sketch1~6用于采样模拟素描表现效果,并进行属性映射
      3. 权重计算(顶点着色器函数逻辑)
        1. 主要步骤
        2. v2f结构体声明,包括顶点位置、uv、用两个fixed3记录素描纹理权重
        3. 进行顶点着色器函数的实现。首先进行顶点坐标的转换,然后对UV坐标进行平铺和缩放,以适应纹理的平铺系数。接着,转换世界空间中的光照方向和法线,计算兰伯特漫反射光照系数,并将光照系数从0到1的范围扩展到0到7之间,最后根据计算得到的光照系数,决定素描纹理的权重。
      4. 颜色采样(片元着色器函数逻辑)
        1. 主要步骤
        2. 对 1~6 张纹理进行采样 并乘以权重 得到各纹理采样颜色并计算出白色部分,汇总颜色后返回
        3. 当前计算逻辑的问题
        4. 修改权重算法以减少白色部分
        5. 可以看到没有那么多白色了
      5. 外轮廓、阴影相关添加
        1. 主要步骤
        2. 给Lesson12_ToonShader中的外轮廓Pass添加名字
        3. Lesson14_Sketch添加边缘线参数和UsePass ,使用外轮廓Pass。不需要属性映射,因为是在外部Pass内部处理的
        4. 添加阴影相关处理
        5. 最终效果可以看到有描边和阴影效果了。
    2. 14.2 知识点代码
      1. Lesson14_Sketch.shader
      2. Lesson14_素描风格渲染_具体实现.cs

14.素描风格渲染-具体实现


14.1 知识点

知识回顾 素描风格渲染基本原理

  • 基本原理:

    • 用漫反射系数决定采样权重,在多张具有不同密度和方向的素描纹理中进行采样,并将采样结果叠加得到最终效果
  • 关键点:

    • 多张具有不同密度和方向的素描纹理
    • 漫反射系数决定采样权重
    • 采样结果进行叠加

纹理资源导入、相关属性添加

主要步骤

  • 下载素描纹理资源
  • 新建 Shader —— Lesson14_Sketch,删除无用代码
  • 添加属性:
    • 颜色属性 Color —— 用于颜色叠加
    • 平铺系数 TileFactor —— 用于平铺纹理,让采样细节更多
    • 素描纹理 Sketch1~6 —— 用于采样模拟素描表现效果
  • 进行属性映射

导入素描纹理资源

新建Shader,Lesson14_Sketch,删除无用代码。添加属性。包括颜色属性Color用于颜色叠加,平铺系数TileFactor用于平铺纹理,让采样细节更多,素描纹理Sketch1~6用于采样模拟素描表现效果,并进行属性映射

Shader "Unlit/Lesson14_Sketch"
{
    Properties
    {
        // 整体颜色叠加,用于调整整个效果的颜色
        _Color("Color", Color) = (1,1,1,1)
    
        // 平铺纹理的系数,控制纹理的缩放,数值越大,纹理细节越多
        _TileFactor("TileFactor", Float) = 1
    
        // 6张素描纹理贴图,用于根据不同的光照强度从不同的纹理中采样
        _Sketch0("Sketch0", 2D) = ""{}
        _Sketch1("Sketch1", 2D) = ""{}
        _Sketch2("Sketch2", 2D) = ""{}
        _Sketch3("Sketch3", 2D) = ""{}
        _Sketch4("Sketch4", 2D) = ""{}
        _Sketch5("Sketch5", 2D) = ""{}
    }

    SubShader
    {
        Tags
        {
            "RenderType"="Opaque" // 渲染类型:不透明
        }

        Pass
        {
            CGPROGRAM
            #pragma vertex vert 
            #pragma fragment frag 
            #include "UnityCG.cginc"

            // 声明Shader中使用的参数
            fixed4 _Color; // 整体颜色
            fixed _TileFactor; // 纹理平铺系数
            sampler2D _Sketch0, _Sketch1, _Sketch2, _Sketch3, _Sketch4, _Sketch5; // 6张素描纹理

            // 定义顶点着色器的数据结构
            struct v2f
            {
                float4 vertex : SV_POSITION; // 顶点位置
                float2 uv : TEXCOORD0; // 纹理坐标
            };

            // 顶点着色器函数
            v2f vert(appdata_base appdata_base)
            {
                
            }

            // 片段着色器函数
            fixed4 frag(v2f v2f) : SV_Target
            {
                
            }
            ENDCG
        }
    }
}

权重计算(顶点着色器函数逻辑)

主要步骤

  • 声明 v2f 结构体,包含顶点位置、uv,以及两个 fixed3 用来记录素描纹理权重
  • 实现顶点着色器函数:
    • 顶点坐标转换
    • uv 坐标平铺缩放(乘以平铺系数)
    • 世界空间光照方向与法线转换
    • 计算兰伯特漫反射光照系数
    • 将光照系数从 01 扩展到 07
    • 根据扩展后的系数决定各素描纹理的采样权重

v2f结构体声明,包括顶点位置、uv、用两个fixed3记录素描纹理权重

// 定义顶点着色器的数据结构
struct v2f
{
    float4 vertex : SV_POSITION; // 顶点位置
    float2 uv : TEXCOORD0; // 纹理坐标
    fixed3 sketchWeights0 : TEXCOORD1; // 第1、2、3张素描纹理的权重
    fixed3 sketchWeights1 : TEXCOORD2; // 第4、5、6张素描纹理的权重
    float3 worldPos : TEXCOORD3; // 世界空间坐标
    SHADOW_COORDS(4) // 用于阴影坐标
};

进行顶点着色器函数的实现。首先进行顶点坐标的转换,然后对UV坐标进行平铺和缩放,以适应纹理的平铺系数。接着,转换世界空间中的光照方向和法线,计算兰伯特漫反射光照系数,并将光照系数从0到1的范围扩展到0到7之间,最后根据计算得到的光照系数,决定素描纹理的权重。

// 顶点着色器函数
v2f vert(appdata_base appdata_base)
{
    v2f v2f;

    // 将模型空间的顶点坐标转换为裁剪空间坐标
    v2f.vertex = UnityObjectToClipPos(appdata_base.vertex);

    // 纹理坐标平铺缩放,数值越大纹理细节越多,越小越粗糙
    v2f.uv = appdata_base.texcoord.xy * _TileFactor;

    // 获取世界空间的光照方向
    fixed3 worldLightDir = normalize(WorldSpaceLightDir(appdata_base.vertex));

    // 将法线从模型空间转换到世界空间
    fixed3 worldNormal = UnityObjectToWorldNormal(appdata_base.normal);

    // 计算兰伯特漫反射光照强度(0~1)
    fixed diff = max(0, dot(worldLightDir, worldNormal));

    // 将光照强度扩展到0~7的范围
    diff = diff * 7.0;

    // 初始化素描纹理的权重,默认为0
    v2f.sketchWeights0 = fixed3(0, 0, 0);
    v2f.sketchWeights1 = fixed3(0, 0, 0);

    // 根据光照强度分配不同的素描纹理权重
    if (diff > 6.0)
    {
        // 最亮部分,不改变任何纹理权重
    }
    else if (diff > 5.0)
    {
        // 从第1张图采样,设置权重
        v2f.sketchWeights0.x = diff - 5.0;
    }
    else if (diff > 4.0)
    {
        // 从第2张图采样,设置权重
        v2f.sketchWeights0.y = diff - 4.0;
    }
    else if (diff > 3.0)
    {
        // 从第3张图采样,设置权重
        v2f.sketchWeights0.z = diff - 3.0;
    }
    else if (diff > 2.0)
    {
        // 从第4张图采样,设置权重
        v2f.sketchWeights0.x = diff - 2.0;
    }
    else if (diff > 1.0)
    {
        // 从第5张图采样,设置权重
        v2f.sketchWeights1.y = diff - 1.0;
    }
    else
    {
        // 从第6张图采样,设置权重
        v2f.sketchWeights1.z = diff;
    }

    return v2f;
}

颜色采样(片元着色器函数逻辑)

主要步骤

  • 在片段着色器中,对 1~6 张纹理分别进行采样,并乘以各自的权重,得到各纹理采样颜色
  • 根据各纹理权重计算出白色高光部分的颜色
  • 将所有纹理采样颜色与白色部分相加,得到最终叠加的颜色

对 1~6 张纹理进行采样 并乘以权重 得到各纹理采样颜色并计算出白色部分,汇总颜色后返回

// 片段着色器函数
fixed4 frag(v2f v2f) : SV_Target
{
    // 根据顶点着色器计算的纹理权重,从不同的素描纹理中采样
    fixed4 sketchColor0 = tex2D(_Sketch0, v2f.uv) * v2f.sketchWeights0.x;
    fixed4 sketchColor1 = tex2D(_Sketch1, v2f.uv) * v2f.sketchWeights0.y;
    fixed4 sketchColor2 = tex2D(_Sketch2, v2f.uv) * v2f.sketchWeights0.z;
    fixed4 sketchColor3 = tex2D(_Sketch3, v2f.uv) * v2f.sketchWeights1.x;
    fixed4 sketchColor4 = tex2D(_Sketch4, v2f.uv) * v2f.sketchWeights1.y;
    fixed4 sketchColor5 = tex2D(_Sketch5, v2f.uv) * v2f.sketchWeights1.z;

    // 计算最亮部分的白色叠加
    fixed4 whiteColor = fixed4(1, 1, 1, 1) * (1 - v2f.sketchWeights0.x - v2f.sketchWeights0.y - v2f.sketchWeights0.z - v2f.sketchWeights1.x - v2f.sketchWeights1.y - v2f.sketchWeights1.z);

    // 汇总所有的素描颜色
    fixed4 sketchColor = sketchColor0 + sketchColor1 + sketchColor2 + sketchColor3 + sketchColor4 + sketchColor5 + whiteColor;

    // 最终返回计算后的颜色值
    return fixed4(sketchColor.rgb * _Color.rgb, 1);
}

当前计算逻辑的问题

目前的计算逻辑会导致白色占了大部分——例如当 diff 较高时,大部分采样结果都是白色,显得有些奇怪:



修改权重算法以减少白色部分

采用相邻两张图的采样替代大部分白色的情况,修改顶点着色器中权重分配的算法:

// 根据光照强度分配不同的素描纹理权重
if (diff > 6.0)
{
    // 最亮部分,不改变任何纹理权重
}
else if (diff > 5.0)
{
    // 从第1张图采样,设置权重
    v2f.sketchWeights0.x = diff - 5.0;
}
else if (diff > 4.0)
{
    // 从第1、2张图采样,设置权重
    v2f.sketchWeights0.x = diff - 4.0;
    v2f.sketchWeights0.y = 1 - v2f.sketchWeights0.x;
}
else if (diff > 3.0)
{
    // 从第2、3张图采样,设置权重
    v2f.sketchWeights0.y = diff - 3.0;
    v2f.sketchWeights0.z = 1 - v2f.sketchWeights0.y;
}
else if (diff > 2.0)
{
    // 从第3、4张图采样,设置权重
    v2f.sketchWeights0.z = diff - 2.0;
    v2f.sketchWeights1.x = 1 - v2f.sketchWeights0.z;
}
else if (diff > 1.0)
{
    // 从第4、5张图采样,设置权重
    v2f.sketchWeights1.x = diff - 1.0;
    v2f.sketchWeights1.y = 1 - v2f.sketchWeights1.x;
}
else
{
    // 从第5、6张图采样,设置权重
    v2f.sketchWeights1.y = diff;
    v2f.sketchWeights1.z = 1 - diff;
}

可以看到没有那么多白色了

外轮廓、阴影相关添加

主要步骤

  • 外轮廓
    复用 Lesson12_ToonShader 中的外轮廓 Pass 代码,并为该 Pass 添加名称

  • 阴影相关添加

    • 添加 SHADOW_COORDS、TRANSFER_SHADOW、UNITY_LIGHT_ATTENUATION
    • 添加回退(Fallback)到默认的 Diffuse 着色器

给Lesson12_ToonShader中的外轮廓Pass添加名字

// 第一个Pass:用于渲染描边效果
Pass
{
    Name "OUTLINE"
    //...
}

Lesson14_Sketch添加边缘线参数和UsePass ,使用外轮廓Pass。不需要属性映射,因为是在外部Pass内部处理的

Properties
{
    // 整体颜色叠加,用于调整整个效果的颜色
    _Color("Color", Color) = (1,1,1,1)

    // 平铺纹理的系数,控制纹理的缩放,数值越大,纹理细节越多
    _TileFactor("TileFactor", Float) = 1

    // 6张素描纹理贴图,用于根据不同的光照强度从不同的纹理中采样
    _Sketch0("Sketch0", 2D) = ""{}
    _Sketch1("Sketch1", 2D) = ""{}
    _Sketch2("Sketch2", 2D) = ""{}
    _Sketch3("Sketch3", 2D) = ""{}
    _Sketch4("Sketch4", 2D) = ""{}
    _Sketch5("Sketch5", 2D) = ""{}

    // 边缘线相关参数
    _OutLineColor("OutLineColor", Color) = (0,0,0,1) // 边缘线颜色
    _OutLineWidth("OutLineWidth", Range(0,1)) = 0.04 // 边缘线宽度
}

SubShader
{
    Tags
    {
        "RenderType"="Opaque" // 渲染类型:不透明
    }

    // 使用外部Pass,用于实现轮廓线
    UsePass "Unlit/Lesson12_ToonShader/OUTLINE"

    //...
}

添加阴影相关处理

SubShader
{
    Tags
    {
        "RenderType"="Opaque" // 渲染类型:不透明
    }

    // 使用外部Pass,用于实现轮廓线
    UsePass "Unlit/Lesson12_ToonShader/OUTLINE"

    Pass
    {
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag

        #pragma multi_compile_fwdbase

        // 引入Unity内置的常用CG库
        #include "UnityCG.cginc"
        #include "Lighting.cginc"
        #include "AutoLight.cginc"

        // 声明Shader中使用的参数
        fixed4 _Color; // 整体颜色
        fixed _TileFactor; // 纹理平铺系数
        sampler2D _Sketch0, _Sketch1, _Sketch2, _Sketch3, _Sketch4, _Sketch5; // 6张素描纹理

        // 定义顶点着色器的数据结构
        struct v2f
        {
            float4 vertex : SV_POSITION; // 顶点位置
            float2 uv : TEXCOORD0; // 纹理坐标
            fixed3 sketchWeights0 : TEXCOORD1; // 第1、2、3张素描纹理的权重
            fixed3 sketchWeights1 : TEXCOORD2; // 第4、5、6张素描纹理的权重
            float3 worldPos : TEXCOORD3; // 世界空间坐标
            SHADOW_COORDS(4) // 用于阴影坐标
        };

        // 顶点着色器函数
        v2f vert(appdata_base appdata_base)
        {
            v2f v2f;

            // 将模型空间的顶点坐标转换为裁剪空间坐标
            v2f.vertex = UnityObjectToClipPos(appdata_base.vertex);

            // 纹理坐标平铺缩放,数值越大纹理细节越多,越小越粗糙
            v2f.uv = appdata_base.texcoord.xy * _TileFactor;

            // 获取世界空间的光照方向
            fixed3 worldLightDir = normalize(WorldSpaceLightDir(appdata_base.vertex));

            // 将法线从模型空间转换到世界空间
            fixed3 worldNormal = UnityObjectToWorldNormal(appdata_base.normal);

            // 计算兰伯特漫反射光照强度(0~1)
            fixed diff = max(0, dot(worldLightDir, worldNormal));

            // 将光照强度扩展到0~7的范围
            diff = diff * 7.0;

            // 初始化素描纹理的权重,默认为0
            v2f.sketchWeights0 = fixed3(0, 0, 0);
            v2f.sketchWeights1 = fixed3(0, 0, 0);

            // 根据光照强度分配不同的素描纹理权重
            if (diff > 6.0)
            {
                // 最亮部分,不改变任何纹理权重
            }
            else if (diff > 5.0)
            {
                // 从第1张图采样,设置权重
                v2f.sketchWeights0.x = diff - 5.0;
            }
            else if (diff > 4.0)
            {
                // 从第1、2张图采样,设置权重
                v2f.sketchWeights0.x = diff - 4.0;
                v2f.sketchWeights0.y = 1 - v2f.sketchWeights0.x;
            }
            else if (diff > 3.0)
            {
                // 从第2、3张图采样,设置权重
                v2f.sketchWeights0.y = diff - 3.0;
                v2f.sketchWeights0.z = 1 - v2f.sketchWeights0.y;
            }
            else if (diff > 2.0)
            {
                // 从第3、4张图采样,设置权重
                v2f.sketchWeights0.z = diff - 2.0;
                v2f.sketchWeights1.x = 1 - v2f.sketchWeights0.z;
            }
            else if (diff > 1.0)
            {
                // 从第4、5张图采样,设置权重
                v2f.sketchWeights1.x = diff - 1.0;
                v2f.sketchWeights1.y = 1 - v2f.sketchWeights1.x;
            }
            else
            {
                // 从第5、6张图采样,设置权重
                v2f.sketchWeights1.y = diff;
                v2f.sketchWeights1.z = 1 - diff;
            }

            // // 根据光照强度分配不同的素描纹理权重
            // if (diff > 6.0)
            // {
            //     // 最亮部分,不改变任何纹理权重
            // }
            // else if (diff > 5.0)
            // {
            //     // 从第1张图采样,设置权重
            //     v2f.sketchWeights0.x = diff - 5.0;
            // }
            // else if (diff > 4.0)
            // {
            //     // 从第2张图采样,设置权重
            //     v2f.sketchWeights0.y = diff - 4.0;
            // }
            // else if (diff > 3.0)
            // {
            //     // 从第3张图采样,设置权重
            //     v2f.sketchWeights0.z = diff - 3.0;
            // }
            // else if (diff > 2.0)
            // {
            //     // 从第4张图采样,设置权重
            //     v2f.sketchWeights0.x = diff - 2.0;
            // }
            // else if (diff > 1.0)
            // {
            //     // 从第5张图采样,设置权重
            //     v2f.sketchWeights1.y = diff - 1.0;
            // }
            // else
            // {
            //     // 从第6张图采样,设置权重
            //     v2f.sketchWeights1.z = diff;
            // }

            // 将顶点坐标转换为世界坐标
            v2f.worldPos = mul(unity_ObjectToWorld, appdata_base.vertex).xyz;
            
            TRANSFER_SHADOW(v2f); // 传递阴影坐标

            return v2f;
        }

        // 片段着色器函数
        fixed4 frag(v2f v2f) : SV_Target
        {
            // 根据顶点着色器计算的纹理权重,从不同的素描纹理中采样
            fixed4 sketchColor0 = tex2D(_Sketch0, v2f.uv) * v2f.sketchWeights0.x;
            fixed4 sketchColor1 = tex2D(_Sketch1, v2f.uv) * v2f.sketchWeights0.y;
            fixed4 sketchColor2 = tex2D(_Sketch2, v2f.uv) * v2f.sketchWeights0.z;
            fixed4 sketchColor3 = tex2D(_Sketch3, v2f.uv) * v2f.sketchWeights1.x;
            fixed4 sketchColor4 = tex2D(_Sketch4, v2f.uv) * v2f.sketchWeights1.y;
            fixed4 sketchColor5 = tex2D(_Sketch5, v2f.uv) * v2f.sketchWeights1.z;

            // 计算最亮部分的白色叠加
            fixed4 whiteColor = fixed4(1, 1, 1, 1) * (1 - v2f.sketchWeights0.x - v2f.sketchWeights0.y - v2f.
                sketchWeights0.z - v2f.sketchWeights1.x - v2f.sketchWeights1.y - v2f.sketchWeights1.z);

            // 汇总所有的素描颜色
            fixed4 sketchColor = sketchColor0 + sketchColor1 + sketchColor2 + sketchColor3 + sketchColor4 +
                sketchColor5 + whiteColor;

            // 考虑光照衰减
            UNITY_LIGHT_ATTENUATION(atten, v2f, v2f.worldPos);

            // 最终返回计算后的颜色值
            return fixed4(sketchColor.rgb * atten * _Color.rgb, 1);
            // return fixed4(sketchColor.rgb * _Color.rgb, 1);
        }
        ENDCG
    }
}

// 回退到默认的Diffuse着色器
Fallback "Diffuse"

最终效果可以看到有描边和阴影效果了。


14.2 知识点代码

Lesson14_Sketch.shader

Shader "Unlit/Lesson14_Sketch"
{
    Properties
    {
        // 整体颜色叠加,用于调整整个效果的颜色
        _Color("Color", Color) = (1,1,1,1)

        // 平铺纹理的系数,控制纹理的缩放,数值越大,纹理细节越多
        _TileFactor("TileFactor", Float) = 1

        // 6张素描纹理贴图,用于根据不同的光照强度从不同的纹理中采样
        _Sketch0("Sketch0", 2D) = ""{}
        _Sketch1("Sketch1", 2D) = ""{}
        _Sketch2("Sketch2", 2D) = ""{}
        _Sketch3("Sketch3", 2D) = ""{}
        _Sketch4("Sketch4", 2D) = ""{}
        _Sketch5("Sketch5", 2D) = ""{}

        // 边缘线相关参数
        _OutLineColor("OutLineColor", Color) = (0,0,0,1) // 边缘线颜色
        _OutLineWidth("OutLineWidth", Range(0,1)) = 0.04 // 边缘线宽度
    }

    SubShader
    {
        Tags
        {
            "RenderType"="Opaque" // 渲染类型:不透明
        }

        // 使用外部Pass,用于实现轮廓线
        UsePass "Unlit/Lesson12_ToonShader/OUTLINE"

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #pragma multi_compile_fwdbase

            // 引入Unity内置的常用CG库
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "AutoLight.cginc"

            // 声明Shader中使用的参数
            fixed4 _Color; // 整体颜色
            fixed _TileFactor; // 纹理平铺系数
            sampler2D _Sketch0, _Sketch1, _Sketch2, _Sketch3, _Sketch4, _Sketch5; // 6张素描纹理

            // 定义顶点着色器的数据结构
            struct v2f
            {
                float4 vertex : SV_POSITION; // 顶点位置
                float2 uv : TEXCOORD0; // 纹理坐标
                fixed3 sketchWeights0 : TEXCOORD1; // 第1、2、3张素描纹理的权重
                fixed3 sketchWeights1 : TEXCOORD2; // 第4、5、6张素描纹理的权重
                float3 worldPos : TEXCOORD3; // 世界空间坐标
                SHADOW_COORDS(4) // 用于阴影坐标
            };

            // 顶点着色器函数
            v2f vert(appdata_base appdata_base)
            {
                v2f v2f;

                // 将模型空间的顶点坐标转换为裁剪空间坐标
                v2f.vertex = UnityObjectToClipPos(appdata_base.vertex);

                // 纹理坐标平铺缩放,数值越大纹理细节越多,越小越粗糙
                v2f.uv = appdata_base.texcoord.xy * _TileFactor;

                // 获取世界空间的光照方向
                fixed3 worldLightDir = normalize(WorldSpaceLightDir(appdata_base.vertex));

                // 将法线从模型空间转换到世界空间
                fixed3 worldNormal = UnityObjectToWorldNormal(appdata_base.normal);

                // 计算兰伯特漫反射光照强度(0~1)
                fixed diff = max(0, dot(worldLightDir, worldNormal));

                // 将光照强度扩展到0~7的范围
                diff = diff * 7.0;

                // 初始化素描纹理的权重,默认为0
                v2f.sketchWeights0 = fixed3(0, 0, 0);
                v2f.sketchWeights1 = fixed3(0, 0, 0);

                // 根据光照强度分配不同的素描纹理权重
                if (diff > 6.0)
                {
                    // 最亮部分,不改变任何纹理权重
                }
                else if (diff > 5.0)
                {
                    // 从第1张图采样,设置权重
                    v2f.sketchWeights0.x = diff - 5.0;
                }
                else if (diff > 4.0)
                {
                    // 从第1、2张图采样,设置权重
                    v2f.sketchWeights0.x = diff - 4.0;
                    v2f.sketchWeights0.y = 1 - v2f.sketchWeights0.x;
                }
                else if (diff > 3.0)
                {
                    // 从第2、3张图采样,设置权重
                    v2f.sketchWeights0.y = diff - 3.0;
                    v2f.sketchWeights0.z = 1 - v2f.sketchWeights0.y;
                }
                else if (diff > 2.0)
                {
                    // 从第3、4张图采样,设置权重
                    v2f.sketchWeights0.z = diff - 2.0;
                    v2f.sketchWeights1.x = 1 - v2f.sketchWeights0.z;
                }
                else if (diff > 1.0)
                {
                    // 从第4、5张图采样,设置权重
                    v2f.sketchWeights1.x = diff - 1.0;
                    v2f.sketchWeights1.y = 1 - v2f.sketchWeights1.x;
                }
                else
                {
                    // 从第5、6张图采样,设置权重
                    v2f.sketchWeights1.y = diff;
                    v2f.sketchWeights1.z = 1 - diff;
                }

                // // 根据光照强度分配不同的素描纹理权重
                // if (diff > 6.0)
                // {
                //     // 最亮部分,不改变任何纹理权重
                // }
                // else if (diff > 5.0)
                // {
                //     // 从第1张图采样,设置权重
                //     v2f.sketchWeights0.x = diff - 5.0;
                // }
                // else if (diff > 4.0)
                // {
                //     // 从第2张图采样,设置权重
                //     v2f.sketchWeights0.y = diff - 4.0;
                // }
                // else if (diff > 3.0)
                // {
                //     // 从第3张图采样,设置权重
                //     v2f.sketchWeights0.z = diff - 3.0;
                // }
                // else if (diff > 2.0)
                // {
                //     // 从第4张图采样,设置权重
                //     v2f.sketchWeights0.x = diff - 2.0;
                // }
                // else if (diff > 1.0)
                // {
                //     // 从第5张图采样,设置权重
                //     v2f.sketchWeights1.y = diff - 1.0;
                // }
                // else
                // {
                //     // 从第6张图采样,设置权重
                //     v2f.sketchWeights1.z = diff;
                // }

                // 将顶点坐标转换为世界坐标
                v2f.worldPos = mul(unity_ObjectToWorld, appdata_base.vertex).xyz;
                
                TRANSFER_SHADOW(v2f); // 传递阴影坐标

                return v2f;
            }

            // 片段着色器函数
            fixed4 frag(v2f v2f) : SV_Target
            {
                // 根据顶点着色器计算的纹理权重,从不同的素描纹理中采样
                fixed4 sketchColor0 = tex2D(_Sketch0, v2f.uv) * v2f.sketchWeights0.x;
                fixed4 sketchColor1 = tex2D(_Sketch1, v2f.uv) * v2f.sketchWeights0.y;
                fixed4 sketchColor2 = tex2D(_Sketch2, v2f.uv) * v2f.sketchWeights0.z;
                fixed4 sketchColor3 = tex2D(_Sketch3, v2f.uv) * v2f.sketchWeights1.x;
                fixed4 sketchColor4 = tex2D(_Sketch4, v2f.uv) * v2f.sketchWeights1.y;
                fixed4 sketchColor5 = tex2D(_Sketch5, v2f.uv) * v2f.sketchWeights1.z;

                // 计算最亮部分的白色叠加
                fixed4 whiteColor = fixed4(1, 1, 1, 1) * (1 - v2f.sketchWeights0.x - v2f.sketchWeights0.y - v2f.
                    sketchWeights0.z - v2f.sketchWeights1.x - v2f.sketchWeights1.y - v2f.sketchWeights1.z);

                // 汇总所有的素描颜色
                fixed4 sketchColor = sketchColor0 + sketchColor1 + sketchColor2 + sketchColor3 + sketchColor4 +
                    sketchColor5 + whiteColor;

                // 考虑光照衰减
                UNITY_LIGHT_ATTENUATION(atten, v2f, v2f.worldPos);

                // 最终返回计算后的颜色值
                return fixed4(sketchColor.rgb * atten * _Color.rgb, 1);
                // return fixed4(sketchColor.rgb * _Color.rgb, 1);
            }
            ENDCG
        }
    }

    // 回退到默认的Diffuse着色器
    Fallback "Diffuse"
}

Lesson14_素描风格渲染_具体实现.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Lesson14_素描风格渲染_具体实现 : MonoBehaviour
{
    void Start()
    {
        #region 知识回顾

        //基本原理:
        //用漫反射系数决定采样权重,在多张具有不同密度和方向的素描纹理中
        //进行采样,并将采样结果进行叠加得到最终效果

        //关键点:
        //1 多张具有不同密度和方向的素描纹理
        //2 漫反射系数决定采样权重
        //3 采样结果进行叠加

        #endregion

        #region 知识点一 纹理资源导入、相关属性添加

        //1.下载素描纹理资源
        //2.新建Shader——Lesson14_Sketch,删除无用代码
        //3.添加属性
        //  颜色属性Color —— 用于颜色叠加
        //  平铺系数TileFactor —— 用于平铺纹理,让采样细节更多
        //  素描纹理Sketch1~6 —— 用于采样模拟素描表现效果
        //4.进行属性映射

        #endregion

        #region 知识点二 权重计算(顶点着色器函数逻辑)

        //1.v2f结构体声明
        //  顶点位置、uv、用两个fixed3记录素描纹理权重
        //2.顶点着色器函数实现
        //  2-1 顶点坐标转换
        //  2-2 uv坐标平铺缩放 让纹理*平铺系数
        //  2-3 世界空间光照方向、世界空间法线转换
        //  2-4 兰伯特漫反射光照系数计算
        //  2-5 将光照系数 从 0~1 扩充到 0~7
        //  2-6 根据系数决定素描纹理权重

        #endregion

        #region 知识点三 颜色采样(片元着色器函数逻辑)

        //在片元着色器中实现
        //1.对 1~6 张纹理进行采样 并乘以权重 得到各纹理采样颜色
        //2.根据 1~6张纹理权重计算出白色高光部分颜色
        //3.将1~6张纹理采样颜色 和 白色部分 相加得到最终叠加颜色

        #endregion

        #region 知识点四 外轮廓、阴影相关添加

        //1.外轮廓
        //  直接复用Lesson12_ToonShader中的外轮廓Pass代码 给Lesson12_ToonShader中的外轮廓Pass添加名字
        //2.阴影相关添加
        //  加入 SHADOW_COORDS、TRANSFER_SHADOW、UNITY_LIGHT_ATTENUATION
        //  加入 FallBack "Diffuse"

        #endregion
    }
}


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

×

喜欢就点赞,疼爱就打赏