12.卡通风格渲染具体实现

  1. 12.卡通风格渲染-具体实现
    1. 12.1 知识点
      1. 知识回顾 卡通风格渲染基恩原理
      2. 实现漫反射变硬和外轮廓效果
        1. 主要步骤
        2. 新建 Shader(将 Shader 开发入门中渐变纹理的综合实践代码直接复制过来)
        3. 之前的渐变纹理的综合实践是不能投射阴影的,需要修改
        4. 将复制过来的代码的 Pass 改为只渲染背面:
        5. 加入阴影接收相关内容。首先,通过编译指令 #pragma multi_compile_fwdbase 开启多重编译支持,并引入内置文件 AutoLight.cginc 来处理光照计算。接着,在 v2f 结构体中加入 float3 worldPos 和 SHADOW_COORDS(4),以便储存世界空间顶点坐标和阴影相关数据。在顶点着色器中,需要计算世界空间顶点坐标,并使用 TRANSFER_SHADOW(o) 完成阴影传递的相关操作。同时,在光照衰减计算中使用 UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos) 来获取光照衰减值,并将其与半兰伯特模型的计算结果(halfLambertNum)相乘,以调整最终光照效果。最后,为了处理未定义的情况下,添加回退选项 FallBack "Diffuse"。
        6. 修改后,和原来的渐变纹理相比,可以接受阴影和投射阴影了
        7. 处理渲染外轮廓(描边),加入边缘线颜色和宽度属性 _OutLineColor _OutLineWidth,再加入背面渲染的Pass,用于处理轮廓描边
        8. 此 Pass 渲染完成后,即可看到描边效果,并可通过调整 _OutLineWidth 修改描边粗细。
      3. 实现高光变硬效果
        1. 主要步骤
        2. 修改 _SpecularNum 范围
        3. 计算高光反射
        4. 查看效果,关闭材质的发现后,可以看到白光非常硬,这正是卡通效果的体现。
    2. 12.2 知识点代码
      1. Lesson12_ToonShader.shader
      2. Lesson12_卡通风格渲染_具体实现.cs

12.卡通风格渲染-具体实现


12.1 知识点

知识回顾 卡通风格渲染基恩原理

卡通风格渲染的基本原理是让光的过渡效果变硬,并且实现轮廓描边。

关键点:

  • 如何让光的过渡效果变硬
    利用渐变纹理处理漫反射,并通过自定义简化公式计算高光反射
  • 如何实现轮廓描边
    通过两个 Pass 实现:一个 Pass 渲染背面以实现轮廓描边,另一个 Pass 正常渲染正面
    不需要关闭深度写入,模型正面的内容可以通过深度测试

实现漫反射变硬和外轮廓效果

主要步骤

  • 新建 Shader,将渐变纹理的综合实践相关代码直接复制过来
  • 将复制过来的代码的 Pass 改为只渲染背面
  • 加入阴影接收相关内容
    • 编译指令:#pragma multi_compile_fwdbase
    • 内置文件:#include "AutoLight.cginc"
    • 在 v2f 结构体中加入:float3 worldPosSHADOW_COORDS(4)
    • 在顶点着色器中加入:
      • 世界空间顶点坐标的计算
      • 调用 TRANSFER_SHADOW(o); 传递阴影数据
    • 在光照衰减计算中使用:
      • UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos); 用于与半兰伯特值相乘
    • 添加回退方案:Fallback "Diffuse"
  • 处理渲染外轮廓(描边)
    • 加入边缘线颜色和宽度属性:_OutLineColor_OutLineWidth
    • 新增一个专门渲染背面描边效果的 Pass

新建 Shader(将 Shader 开发入门中渐变纹理的综合实践代码直接复制过来)

Shader "Unlit/Lesson12_ToonShader"
{
    Properties
    {
        _MainColor("MainColor", Color) = (1,1,1,1)
        _MainTex("MainTex", 2D) = ""{}
        _BumpMap("BumpMap", 2D) = ""{}
        _BumpScale("BumpScale", Range(0,1)) = 1
        _RampTex("RampTex", 2D) = ""{}
        _SpecularColor("SpecularColor", Color) = (1,1,1,1)
        _SpecularNum("SpecularNum", Range(8,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; //凹凸程度
            sampler2D _RampTex; //渐变纹理
            float4 _RampTex_ST; //渐变纹理的缩放和平移(基本不会用)
            float4 _SpecularColor; //高光颜色
            fixed _SpecularNum; //光泽度

            struct v2f
            {
                float4 pos:SV_POSITION;
                //float2 uvTex:TEXCOORD0;
                //float2 uvBump:TEXCOORD1;
                //我们可以单独的声明两个float2的成员用于记录 颜色和法线纹理的uv坐标
                //也可以直接声明一个float4的成员 xy用于记录颜色纹理的uv,zw用于记录法线纹理的uv
                float4 uv:TEXCOORD0;
                //光的方向 相对于切线空间下的
                float3 lightDir:TEXCOORD1;
                //视角的方向 相对于切线空间下的
                float3 viewDir:TEXCOORD2;
            };


            v2f vert(appdata_full appdata_full)
            {
                v2f v2f;
                //把模型空间下的顶点转到裁剪空间下
                v2f.pos = UnityObjectToClipPos(appdata_full.vertex);
                //计算纹理的缩放偏移
                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,就可以确定是哪一条
                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);
                //模型空间下的光的方向
                //data.lightDir = ObjSpaceLightDir(v.vertex);
                //乘以模型空间到切线空间的转换矩阵 就可以得到切线空间下的 光的方向了
                v2f.lightDir = mul(rotation, ObjSpaceLightDir(appdata_full.vertex));

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

                return v2f;
            }

            fixed4 frag(v2f v2f) : SV_Target
            {
                //通过纹理采样函数 取出法线纹理贴图当中的数据
                float4 packedNormal = tex2D(_BumpMap, v2f.uv.zw);
                
                //将我们取出来的法线数据 进行逆运算并且可能会进行解压缩的运算,最终得到切线空间下的法线数据
                float3 tangentNormal = UnpackNormal(packedNormal);
                
                //乘以凹凸程度的系数
                tangentNormal.xy *= _BumpScale;
                tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

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

                //颜色纹理和漫反射颜色的 叠加
                fixed3 albedo = tex2D(_MainTex, v2f.uv.xy) * _MainColor.rgb;
                
                //修改为 渐变纹理相关的计算方式
                fixed halfLambertNum = dot(normalize(tangentNormal), normalize(v2f.lightDir)) * 0.5 + 0.5;
                
                //渐变纹理 漫反射计算方式
                fixed3 diffuseColor = _LightColor0.rgb * albedo.rgb *
                    tex2D( _RampTex, fixed2(halfLambertNum, halfLambertNum)).rgb;
                
                //半角向量
                float3 halfA = normalize(normalize(v2f.viewDir) + normalize(v2f.lightDir));
                
                //高光反射
                fixed3 specularColor = _LightColor0.rgb * _SpecularColor.rgb * pow(
                    max(0, dot(tangentNormal, halfA)), _SpecularNum);
                
                //布林方
                fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + diffuseColor + specularColor;

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

之前的渐变纹理的综合实践是不能投射阴影的,需要修改

将复制过来的代码的 Pass 改为只渲染背面:

Cull Back // 绘制正面以避免背面渲染
// 光照模型与透明混合设置可以在此扩展(目前未启用)

加入阴影接收相关内容。首先,通过编译指令 #pragma multi_compile_fwdbase 开启多重编译支持,并引入内置文件 AutoLight.cginc 来处理光照计算。接着,在 v2f 结构体中加入 float3 worldPosSHADOW_COORDS(4),以便储存世界空间顶点坐标和阴影相关数据。在顶点着色器中,需要计算世界空间顶点坐标,并使用 TRANSFER_SHADOW(o) 完成阴影传递的相关操作。同时,在光照衰减计算中使用 UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos) 来获取光照衰减值,并将其与半兰伯特模型的计算结果(halfLambertNum)相乘,以调整最终光照效果。最后,为了处理未定义的情况下,添加回退选项 FallBack "Diffuse"

SubShader
{
    Pass
    {
        Tags
        {
            "LightMode"="ForwardBase"
        }

        Cull Back // 剔除背面,只渲染正面

        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #pragma multi_compile_fwdbase // 编译多个光照模式

        #include "UnityCG.cginc"
        #include "Lighting.cginc"
        #include "AutoLight.cginc" // 自动光照相关函数

        float4 _MainColor; //漫反射颜色
        sampler2D _MainTex; //颜色纹理
        float4 _MainTex_ST; //颜色纹理的缩放和平移
        sampler2D _BumpMap; //法线纹理
        float4 _BumpMap_ST; //法线纹理的缩放和平移
        float _BumpScale; //凹凸程度
        sampler2D _RampTex; //渐变纹理
        float4 _RampTex_ST; //渐变纹理的缩放和平移(基本不会用)
        float4 _SpecularColor; //高光颜色
        fixed _SpecularNum; //光泽度

        struct v2f
        {
            float4 pos:SV_POSITION;
            //float2 uvTex:TEXCOORD0;
            //float2 uvBump:TEXCOORD1;
            //我们可以单独的声明两个float2的成员用于记录 颜色和法线纹理的uv坐标
            //也可以直接声明一个float4的成员 xy用于记录颜色纹理的uv,zw用于记录法线纹理的uv
            float4 uv:TEXCOORD0;
            //光的方向 相对于切线空间下的
            float3 lightDir:TEXCOORD1;
            //视角的方向 相对于切线空间下的
            float3 viewDir:TEXCOORD2;
            float3 worldPos : TEXCOORD3; // 世界空间的顶点位置
            SHADOW_COORDS(4) // 阴影相关数据
        };


        v2f vert(appdata_full appdata_full)
        {
            v2f v2f;
            //把模型空间下的顶点转到裁剪空间下
            v2f.pos = UnityObjectToClipPos(appdata_full.vertex);
            //计算纹理的缩放偏移
            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,就可以确定是哪一条
            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);
            //模型空间下的光的方向
            //data.lightDir = ObjSpaceLightDir(v.vertex);
            //乘以模型空间到切线空间的转换矩阵 就可以得到切线空间下的 光的方向了
            v2f.lightDir = mul(rotation, ObjSpaceLightDir(appdata_full.vertex));

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

            // 世界空间的顶点位置
            v2f.worldPos = mul(unity_ObjectToWorld, appdata_full.vertex).xyz;
            // 传递阴影数据
            TRANSFER_SHADOW(v2f);

            return v2f;
        }

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

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

            //乘以凹凸程度的系数
            tangentNormal.xy *= _BumpScale;
            tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

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

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

            //修改为 渐变纹理相关的计算方式
            fixed halfLambertNum = dot(normalize(tangentNormal), normalize(v2f.lightDir)) * 0.5 + 0.5;


            // 计算光照衰减,考虑距离影响
            UNITY_LIGHT_ATTENUATION(atten, v2f, v2f.worldPos);
            // 将光照衰减与半 Lambertian 反射值相乘
            halfLambertNum *= atten;

            //渐变纹理 漫反射计算方式
            fixed3 diffuseColor = _LightColor0.rgb * albedo.rgb *
                tex2D(_RampTex, fixed2(halfLambertNum, halfLambertNum)).rgb;

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

            //高光反射
            fixed3 specularColor = _LightColor0.rgb * _SpecularColor.rgb * pow(
                max(0, dot(tangentNormal, halfA)), _SpecularNum);

            //布林方
            fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + diffuseColor + specularColor;

            return fixed4(color.rgb, 1);
        }
        ENDCG
    }
}
Fallback "Diffuse" // 当Shader不支持时,使用Diffuse作为降级方案

修改后,和原来的渐变纹理相比,可以接受阴影和投射阴影了

处理渲染外轮廓(描边),加入边缘线颜色和宽度属性 _OutLineColor _OutLineWidth,再加入背面渲染的Pass,用于处理轮廓描边

Properties
{
    // 主颜色(用于调节整体颜色)
    _MainColor("MainColor", Color) = (1,1,1,1)
    // 主纹理贴图(用于颜色纹理)
    _MainTex("MainTex", 2D) = ""{}
    // 法线贴图(用于表现细节凹凸)
    _BumpMap("BumpMap", 2D) = ""{}
    // 法线强度(调整法线凹凸效果的强弱)
    _BumpScale("BumpScale", Range(0,1)) = 1
    // 渐变纹理(用于实现卡通风格的光影过渡)
    _RampTex("RampTex", 2D) = ""{}
    // 高光颜色(用于控制高光的颜色)
    _SpecularColor("SpecularColor", Color) = (1,1,1,1)
    // 光泽度(用于调节高光的范围大小)
    _SpecularNum("SpecularNum", Range(8,256)) = 18
    // 描边颜色(控制物体边缘描边的颜色)
    _OutLineColor("OutLineColor", Color) = (0,0,0,1)
    // 描边宽度(用于调节边缘线的粗细)
    _OutLineWidth("OutLineWidth", Range(0,1)) = 0.04
}
// 第一个Pass:用于渲染描边效果
Pass
{
    Cull Front // 剔除正面,只渲染背面(为描边效果做准备)
    CGPROGRAM
    #pragma vertex vert // 顶点着色器
    #pragma fragment frag // 片段着色器

    #include "UnityCG.cginc" // 引入Unity的通用着色器函数库

    struct v2f
    {
        float4 vertex : SV_POSITION; // 裁剪空间坐标
    };

    // 描边颜色
    fixed4 _OutLineColor;
    // 描边宽度
    float _OutLineWidth;

    // 顶点着色器:处理模型的顶点位置,用于实现描边
    v2f vert(appdata_base appdata_base)
    {
        v2f v2f;
        // 将顶点沿法线方向偏移,扩大模型尺寸
        appdata_base.vertex.xyz += normalize(appdata_base.normal) * _OutLineWidth;
        // 将偏移后的顶点从模型空间转换到裁剪空间
        v2f.vertex = UnityObjectToClipPos(appdata_base.vertex);
        return v2f;
    }

    // 片段着色器:为描边赋予颜色
    fixed4 frag(v2f v2f) : SV_Target
    {
        // 返回预设的描边颜色
        return _OutLineColor;
    }
    ENDCG
}

此 Pass 渲染完成后,即可看到描边效果,并可通过调整 _OutLineWidth 修改描边粗细。

实现高光变硬效果

主要步骤

主要修改高光反射颜色计算相关内容,步骤包括:

  • 计算出半角向量
  • 用法线和半角向量进行点乘
  • 将点乘的结果与一个阈值进行比较:如果小于阈值则取 0,大于阈值则取 1
  • 利用这个比较结果与高光颜色进行叠加
  • 最后将此结果参与到布林方颜色公式的计算中

修改 _SpecularNum 范围

将 _SpecularNum 的范围修改为 0-1,用作高光反射的阈值。属性声明如下:

Properties
{
    // 主颜色(用于调节整体颜色)
    _MainColor("MainColor", Color) = (1,1,1,1)
    // 主纹理贴图(用于颜色纹理)
    _MainTex("MainTex", 2D) = ""{}
    // 法线贴图(用于表现细节凹凸)
    _BumpMap("BumpMap", 2D) = ""{}
    // 法线强度(调整法线凹凸效果的强弱)
    _BumpScale("BumpScale", Range(0,1)) = 1
    // 渐变纹理(用于实现卡通风格的光影过渡)
    _RampTex("RampTex", 2D) = ""{}
    // 高光颜色(用于控制高光的颜色)
    _SpecularColor("SpecularColor", Color) = (1,1,1,1)
    
    // 光泽度(用于调节高光的范围大小)
    _SpecularNum("SpecularNum", Range(0,1)) = 0.5
    
    // 描边颜色(控制物体边缘描边的颜色)
    _OutLineColor("OutLineColor", Color) = (0,0,0,1)
    // 描边宽度(用于调节边缘线的粗细)
    _OutLineWidth("OutLineWidth", Range(0,1)) = 0.04
}

计算高光反射

注释高光反射相关的计算过程如下,主要内容包括:

  • 计算半角向量
  • 用法线和半角向量进行点乘
  • 将点乘结果与预设的光泽度 (_SpecularNum) 阈值进行比较:如果点乘值小于阈值则取 0,大于或等于阈值则取 1
// 计算光泽反射(高光),根据法线与半角向量的点积判断反射强度
// 点积计算:通过计算法线 (tangentNormal) 与视角和光照方向的半角向量 (halfA) 的点积,判断它们的夹角关系。
// 点积值范围为 -1 到 1,其中 1 表示完全一致的方向(完美镜面反射),-1 表示完全相反,0 表示垂直。
// 使用 normalize 确保法线和半角向量的方向是标准化的。
// step 函数:根据点积结果与预设光泽度 (_SpecularNum) 进行比较,如果点积值大于或等于光泽度则返回 1,表示有高光反射;
// 如果点积值小于光泽度则返回 0,表示没有高光反射。
// 最终,spec 的值为 0 或 1:
// - spec = 1:表示存在高光反射,可以计算高光颜色;
// - spec = 0:表示无高光反射,反射强度为 0。
fixed spec = step(_SpecularNum, dot(normalize(tangentNormal), normalize(halfA)));

// 根据光泽度和高光颜色计算最终的高光反射颜色
fixed3 specularColor = _SpecularColor.rgb * spec;

查看效果,关闭材质的发现后,可以看到白光非常硬,这正是卡通效果的体现。



12.2 知识点代码

Lesson12_ToonShader.shader

Shader "Unlit/Lesson12_ToonShader"
{
    Properties
    {
        // 主颜色(用于调节整体颜色)
        _MainColor("MainColor", Color) = (1,1,1,1)
        // 主纹理贴图(用于颜色纹理)
        _MainTex("MainTex", 2D) = ""{}
        // 法线贴图(用于表现细节凹凸)
        _BumpMap("BumpMap", 2D) = ""{}
        // 法线强度(调整法线凹凸效果的强弱)
        _BumpScale("BumpScale", Range(0,1)) = 1
        // 渐变纹理(用于实现卡通风格的光影过渡)
        _RampTex("RampTex", 2D) = ""{}
        // 高光颜色(用于控制高光的颜色)
        _SpecularColor("SpecularColor", Color) = (1,1,1,1)

        // 光泽度(用于调节高光的范围大小)
        _SpecularNum("SpecularNum", Range(0,1)) = 0.5

        // 描边颜色(控制物体边缘描边的颜色)
        _OutLineColor("OutLineColor", Color) = (0,0,0,1)
        // 描边宽度(用于调节边缘线的粗细)
        _OutLineWidth("OutLineWidth", Range(0,1)) = 0.04
    }
    SubShader
    {
        // 第一个Pass:用于渲染描边效果
        Pass
        {
            Name "OUTLINE"
            Cull Front // 剔除正面,只渲染背面(为描边效果做准备)
            CGPROGRAM
            #pragma vertex vert // 顶点着色器
            #pragma fragment frag // 片段着色器

            #include "UnityCG.cginc" // 引入Unity的通用着色器函数库

            struct v2f
            {
                float4 vertex : SV_POSITION; // 裁剪空间坐标
            };

            // 描边颜色
            fixed4 _OutLineColor;
            // 描边宽度
            float _OutLineWidth;

            // 顶点着色器:处理模型的顶点位置,用于实现描边
            v2f vert(appdata_base appdata_base)
            {
                v2f v2f;
                // 将顶点沿法线方向偏移,扩大模型尺寸
                appdata_base.vertex.xyz += normalize(appdata_base.normal) * _OutLineWidth;
                // 将偏移后的顶点从模型空间转换到裁剪空间
                v2f.vertex = UnityObjectToClipPos(appdata_base.vertex);
                return v2f;
            }

            // 片段着色器:为描边赋予颜色
            fixed4 frag(v2f v2f) : SV_Target
            {
                // 返回预设的描边颜色
                return _OutLineColor;
            }
            ENDCG
        }

        // 第二个Pass:用于渲染主物体表面
        Pass
        {
            Tags
            {
                "LightMode"="ForwardBase"
            }

            Cull Back // 剔除背面,只渲染正面

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdbase // 编译多个光照模式

            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "AutoLight.cginc" // 自动光照相关函数

            float4 _MainColor; //漫反射颜色
            sampler2D _MainTex; //颜色纹理
            float4 _MainTex_ST; //颜色纹理的缩放和平移
            sampler2D _BumpMap; //法线纹理
            float4 _BumpMap_ST; //法线纹理的缩放和平移
            float _BumpScale; //凹凸程度
            sampler2D _RampTex; //渐变纹理
            float4 _RampTex_ST; //渐变纹理的缩放和平移(基本不会用)
            float4 _SpecularColor; //高光颜色
            fixed _SpecularNum; //光泽度

            struct v2f
            {
                float4 pos:SV_POSITION;
                //float2 uvTex:TEXCOORD0;
                //float2 uvBump:TEXCOORD1;
                //我们可以单独的声明两个float2的成员用于记录 颜色和法线纹理的uv坐标
                //也可以直接声明一个float4的成员 xy用于记录颜色纹理的uv,zw用于记录法线纹理的uv
                float4 uv:TEXCOORD0;
                //光的方向 相对于切线空间下的
                float3 lightDir:TEXCOORD1;
                //视角的方向 相对于切线空间下的
                float3 viewDir:TEXCOORD2;
                float3 worldPos : TEXCOORD3; // 世界空间的顶点位置
                SHADOW_COORDS(4) // 阴影相关数据
            };


            v2f vert(appdata_full appdata_full)
            {
                v2f v2f;
                //把模型空间下的顶点转到裁剪空间下
                v2f.pos = UnityObjectToClipPos(appdata_full.vertex);
                //计算纹理的缩放偏移
                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,就可以确定是哪一条
                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);
                //模型空间下的光的方向
                //data.lightDir = ObjSpaceLightDir(v.vertex);
                //乘以模型空间到切线空间的转换矩阵 就可以得到切线空间下的 光的方向了
                v2f.lightDir = mul(rotation, ObjSpaceLightDir(appdata_full.vertex));

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

                // 世界空间的顶点位置
                v2f.worldPos = mul(unity_ObjectToWorld, appdata_full.vertex).xyz;
                // 传递阴影数据
                TRANSFER_SHADOW(v2f);

                return v2f;
            }

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

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

                //乘以凹凸程度的系数
                tangentNormal.xy *= _BumpScale;
                tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

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

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

                //修改为 渐变纹理相关的计算方式
                fixed halfLambertNum = dot(normalize(tangentNormal), normalize(v2f.lightDir)) * 0.5 + 0.5;


                // 计算光照衰减,考虑距离影响
                UNITY_LIGHT_ATTENUATION(atten, v2f, v2f.worldPos);
                // 将光照衰减与半 Lambertian 反射值相乘
                halfLambertNum *= atten;

                //渐变纹理 漫反射计算方式
                fixed3 diffuseColor = _LightColor0.rgb * albedo.rgb *
                    tex2D(_RampTex, fixed2(halfLambertNum, halfLambertNum)).rgb;

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

                // //高光反射
                // fixed3 specularColor = _LightColor0.rgb * _SpecularColor.rgb * pow(
                //     max(0, dot(tangentNormal, halfA)), _SpecularNum);

                // 计算光泽反射(高光),根据法线与半角向量的点积判断反射强度
                // 点积计算:通过计算法线(tangentNormal)与视角和光照方向的半角向量(halfA)的点积,判断它们的夹角关系。
                // 点积值范围:-1 到 1,其中1表示完全一致的方向(完美镜面反射),-1表示完全相反的方向,0表示垂直关系。
                // 使用normalize确保法线和半角向量的方向是标准化的。
                // step函数:根据计算得到的点积结果和预设的光泽度(_SpecularNum)值进行比较,如果点积值大于或等于光泽度值(_SpecularNum),则返回1,表示有高光反射;如果点积值小于光泽度值,则返回0,表示没有高光反射。
                // 最终,spec会是0或1:
                // - spec = 1:表示反射强度为高光反射,可以计算高光颜色。
                // - spec = 0:表示没有高光反射,反射强度为0。
                fixed spec = step(_SpecularNum, dot(normalize(tangentNormal), normalize(halfA)));

                // 根据光泽度和高光颜色计算最终的高光反射颜色
                fixed3 specularColor = _SpecularColor.rgb * spec;

                //布林方
                fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + diffuseColor + specularColor;

                return fixed4(color.rgb, 1);
            }
            ENDCG
        }
    }
    Fallback "Diffuse" // 当Shader不支持时,使用Diffuse作为降级方案
}

Lesson12_卡通风格渲染_具体实现.cs

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

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

        //卡通风格渲染 基本原理
        //让光的过渡效果变硬并且实现轮廓描边!

        //关键点:
        //1.如何让光的过渡效果变硬
        //  利用渐变纹理处理漫反射,利用自定义简化公式计算高光反射
        //2.如何实现轮廓描边
        //  用两个Pass,一个渲染背面实现轮廓描边,一个正常渲染正面
        //  不需要关闭深度写入,模型正面的内容能够通过深度测试

        #endregion

        #region 知识点一 实现漫反射变硬和外轮廓效果

        //1.新建Shader 将渐变纹理的综合实践相关代码直接复制过来
        //2.将复制过来的代码的Pass 改为只渲染背面
        //3.加入阴影接收相关内容
        //  2-1.编译指令 #pragma multi_compile_fwdbase
        //  2-2.内置文件 #include "AutoLight.cginc"
        //  2-3.v2f结构体中加入 float3 worldPos 和 SHADOW_COORDS(4)
        //  2-4.顶点着色器计算中加入
        //      世界空间顶点坐标计算
        //      TRANSFER_SHADOW(o);
        //  2-5.光照衰减相关计算
        //      UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
        //      用来和halfLambertNum进行乘法运算
        //  2-6.FallBack "Diffuse"

        //4.处理渲染外轮廓(描边)
        //  4-1.加入边缘线颜色和宽度属性 _OutLineColor _OutLineWidth
        //  4-2.加入背面渲染的Pass,用于处理轮廓描边

        #endregion

        #region 知识点二 实现高光变硬效果

        //主要修改高光反射颜色计算相关内容
        //1.计算出半角向量
        //2.用法线和半角向量进行点乘
        //3.用点乘的结果和一个阈值进行比较 如果小于阈值 取0 大于阈值 取1
        //4.利用这个结果和高光颜色进行叠加
        //5.最后参与到布林方颜色公式计算中

        #endregion
    }
}


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

×

喜欢就点赞,疼爱就打赏