4.模型描边效果具体实现

4.模型描边效果-具体实现


4.1 知识点

知识回顾:模型描边效果基本原理

模型描边效果采用两个 Pass 来渲染对象:

  • 一个 Pass 用于渲染沿法线方向放大的模型(实现描边效果),
  • 另一个 Pass 用于渲染正常模型。

需要注意:

  • 第一个 Pass 中必须关闭深度写入,以确保描边效果能覆盖重合部分;
  • Shader 的渲染队列应设置为 Transparent(透明队列)。

模型描边效果具体实现

主要步骤

  • 新建 Shader(命名为 OutLine),删除无用代码。
  • 属性声明
    • 边缘线颜色 _OutLineColor
    • 边缘线粗细 _OutLineWidth
  • 复制 Pass
    • 第一个 Pass 用于绘制边缘线,要求:
      • 关闭深度写入
      • 通过属性映射定义描边参数
      • 结构体只保留顶点位置(不需要纹理坐标,因为颜色是固定的,不需要采样)
      • 顶点着色器中,顶点沿法线方向偏移后转换到裁剪空间
      • 片元着色器直接返回固定的边缘线颜色
    • 第二个 Pass 用于正常渲染模型,采用传统的纹理采样 Pass 进行测试(如有光照需求,可自行添加)
  • 设置 Fallback"Diffuse",以便在不支持自定义 Shader 时使用默认效果。

新建 Shader(命名为 OutLine)并删除无用代码

Shader "Unlit/Lesson04_OutLine"
{
    Properties
    {
        // 主纹理,用于显示模型的基础图像
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags
        {
            "RenderType"="Opaque"
        }
      
        Pass
        {
            CGPROGRAM
            #pragma vertex vert 
            #pragma fragment frag 

            #include "UnityCG.cginc"

            struct v2f
            {
                float2 uv : TEXCOORD0; // 纹理坐标
                float4 vertex : SV_POSITION; // 裁剪空间的顶点位置
            };

            sampler2D _MainTex; // 主纹理

            // 顶点着色器:用于膨胀模型,生成边缘
            v2f vert(appdata_base appdata_base)
            {
                v2f v2f;
                // 根据顶点法线方向偏移顶点位置,控制模型的膨胀程度
                float3 newVertex = appdata_base.vertex + normalize(appdata_base.normal) * _OutLineWidth;
                // 将膨胀后的顶点转换到裁剪空间
                v2f.vertex = UnityObjectToClipPos(float4(newVertex, 1));
                return v2f;
            }

            // 片段着色器:为边缘填充颜色
            fixed4 frag(v2f v2f) : SV_Target
            {
                // 此处代码待补充,实际效果由第一个 Pass 实现
            }
            ENDCG
        }
    }
}

声明属性:主纹理、边缘线颜色和边缘线粗细

Properties
{
    // 主纹理,用于显示模型的基础图像
    _MainTex ("Texture", 2D) = "white" {}
    // 边缘线颜色,控制描边的颜色
    _OutLineColor("OutLineColor", Color) = (1,1,1,1)
    // 边缘线宽度,控制描边的粗细
    _OutLineWidth("OutLineWidth", Float) = 0.1
}
// 定义 Shader 属性变量
sampler2D _MainTex;   // 主纹理
float4 _MainTex_ST;   // 主纹理的 Tiling 和 Offset 数据
fixed4 _OutLineColor; // 边缘线颜色
fixed _OutLineWidth;  // 边缘线宽度

复制一个Pass,第一个作为绘制边缘线Pass,第二个作为正常渲染模型Pass

SubShader
{
    // 第一 Pass:绘制边缘线
    Pass
    {
        //...
    }

    // 第二 Pass:正常渲染模型
    Pass
    {
        //...
    }
}

编写第一个绘制边缘线Pass。在第一个 Pass 中,关闭深度写入,通过属性映射定义描边参数,结构体仅保留顶点位置无需纹理坐标,顶点着色器将顶点沿法线方向偏移后转换到裁剪空间,片段着色器直接返回固定的边缘线颜色

// 第一 Pass:绘制边缘线
Pass
{
    // 关闭深度写入,确保描边效果覆盖物体重合部分
    ZWrite Off

    CGPROGRAM
    #pragma vertex vert 
    #pragma fragment frag

    #include "UnityCG.cginc"

    struct v2f
    {
        // 第一个 Pass 渲染纯色,不需要采样,因此不保留纹理坐标
        float4 vertex : SV_POSITION; // 裁剪空间的顶点位置
    };

    sampler2D _MainTex;    // 主纹理
    fixed4 _OutLineColor;  // 边缘线颜色
    fixed _OutLineWidth;   // 边缘线宽度

    // 顶点着色器:用于膨胀模型,生成边缘
    v2f vert(appdata_base appdata_base)
    {
        v2f v2f;
        
        // 根据顶点法线方向偏移顶点位置,控制模型的膨胀程度
        float3 newVertex = appdata_base.vertex + normalize(appdata_base.normal) * _OutLineWidth;
        
        // 将膨胀后的顶点转换到裁剪空间
        v2f.vertex = UnityObjectToClipPos(float4(newVertex, 1));
        
        return v2f;
    }

    // 片段着色器:为边缘填充颜色
    fixed4 frag(v2f v2f) : SV_Target
    {
        return _OutLineColor;
    }
    ENDCG
}

暂时先注释掉第二个Pass。创建材质,赋值Shader后给球体挂上,加上一个白色球对比,可以明显看出来往外扩了


编写第二个正常渲染模型Pass,用编写传统的纹理采样Pass进行测试即可(如果有光照相关的需求 自定添加即可)

// 第二 Pass:正常渲染模型
Pass
{
    CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag
    
    #include "UnityCG.cginc"
    
    struct v2f
    {
        float2 uv : TEXCOORD0; // 纹理坐标
        float4 vertex : SV_POSITION; // 裁剪空间的顶点位置
    };
    
    sampler2D _MainTex;   // 主纹理
    half4 _MainTex_ST;    // 主纹理的 Tiling 和 Offset 数据
    
    // 顶点着色器:用于生成基础模型的裁剪空间位置和纹理坐标
    v2f vert(appdata_base appdata_base)
    {
        v2f v2f;
        // 转换顶点位置到裁剪空间
        v2f.vertex = UnityObjectToClipPos(appdata_base.vertex);
        // 根据 Tiling 和 Offset 变换纹理坐标
        v2f.uv = TRANSFORM_TEX(appdata_base.texcoord, _MainTex);
        return v2f;
    }
    
    // 片段着色器:采样并输出主纹理颜色
    fixed4 frag(v2f v2f) : SV_Target
    {
        return tex2D(_MainTex, v2f.uv);
    }
    ENDCG
}

可以查看到描边效果了,但是由于没有改渲染队列,会看到剑覆盖了描边,这时需要添加透明渲染队列,就可以让描边覆盖到剑了

Tags
{
    // 渲染类型为不透明,但队列设置为透明(使描边效果覆盖物体本身)
    "RenderType"="Opaque" "Queue"="Transparent"
}

Shader 失败处理

// 使用 Diffuse Shader 的后备效果(在不支持自定义 Shader 时使用)
Fallback "Diffuse"

4.2 知识点代码

Lesson04_OutLine.shader

Shader "Unlit/Lesson04_OutLine"
{
    Properties
    {
        // 主纹理,用于显示模型的基础图像
        _MainTex ("Texture", 2D) = "white" {}
        // 边缘线颜色,控制描边的颜色
        _OutLineColor("OutLineColor", Color) = (1,1,1,1)
        // 边缘线宽度,控制描边的粗细
        _OutLineWidth("OutLineWidth", Float) = 0.1
    }
    SubShader
    {
        Tags
        {
            // 渲染类型为不透明,队列设置为透明(描边需要覆盖物体本身)
            "RenderType"="Opaque" "Queue"="Transparent"
        }

        // 第一 Pass:绘制边缘线
        Pass
        {
            // 关闭深度写入,确保描边效果覆盖物体重合部分
            ZWrite Off

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct v2f
            {
                //第一个Pass渲染纯色,不需要采样,可以不保留纹理
                float4 vertex : SV_POSITION; // 裁剪空间的顶点位置
            };

            sampler2D _MainTex; // 主纹理
            fixed4 _OutLineColor; // 边缘线颜色
            fixed _OutLineWidth; // 边缘线宽度

            // 顶点着色器:用于膨胀模型,生成边缘
            v2f vert(appdata_base appdata_base)
            {
                v2f v2f;

                // 根据顶点法线方向偏移顶点位置,控制模型的膨胀程度
                float3 newVertex = appdata_base.vertex + normalize(appdata_base.normal) * _OutLineWidth;

                // 将膨胀后的顶点转换到裁剪空间
                v2f.vertex = UnityObjectToClipPos(float4(newVertex, 1));

                return v2f;
            }

            // 片段着色器:为边缘填充颜色
            fixed4 frag(v2f v2f) : SV_Target
            {
                return _OutLineColor;
            }
            ENDCG
        }

        // 第二 Pass:正常渲染模型
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct v2f
            {
                float2 uv : TEXCOORD0; // 纹理坐标
                float4 vertex : SV_POSITION; // 裁剪空间的顶点位置
            };

            sampler2D _MainTex; // 主纹理
            half4 _MainTex_ST; // 主纹理的 Tiling 和 Offset 数据

            // 顶点着色器:用于生成基础模型的裁剪空间位置和纹理坐标
            v2f vert(appdata_base appdata_base)
            {
                v2f v2f;
                // 转换顶点位置到裁剪空间
                v2f.vertex = UnityObjectToClipPos(appdata_base.vertex);
                // 根据 Tiling 和 Offset 变换纹理坐标
                v2f.uv = TRANSFORM_TEX(appdata_base.texcoord, _MainTex);
                return v2f;
            }

            // 片段着色器:采样并输出主纹理颜色
            fixed4 frag(v2f v2f) : SV_Target
            {
                return tex2D(_MainTex, v2f.uv);
            }
            ENDCG
        }
    }

    // 使用 Diffuse Shader 的后备效果(在不支持自定义 Shader 时使用)
    Fallback "Diffuse"
}

Lesson04_模型描边效果_具体实现.cs

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

public class Lesson04_模型描边效果_具体实现 : MonoBehaviour
{
    void Start()
    {
        #region 知识回顾 模型描边效果 基本原理

        //两个Pass渲染对象,一个Pass用于渲染沿法线方向放大的模型,一个Pass用于渲染正常模型。
        //第一个Pass需要关闭深度写入
        //Shader的渲染队列应该设置为Transparent透明队列

        #endregion

        #region 知识点 模型描边效果 具体实现

        //1.新建Shader 取名 OutLine
        //  删除无用代码
        //2.属性声明
        //  边缘线颜色 _OutLineColor
        //  边缘线粗细 _OutLineWidth
        //3.复制一个Pass
        //4.编写第一个Pass
        //  4-1.关闭深度写入
        //  4-2.属性映射
        //  4-3.结构体相关
        //      不需要纹理坐标,因为颜色定死 不需要采样
        //  4-4.顶点着色器
        //      顶点朝法线方向进行偏移后再转换到裁剪空间
        //  4-5.片元着色器
        //      直接返回边缘线颜色
        //5.编写第二个Pass
        //  编写传统的纹理采样Pass进行测试即可(如果有光照相关的需求 自定添加即可)
        //6.FallBack "Diffuse"

        #endregion
    }
}


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

×

喜欢就点赞,疼爱就打赏