Shader编写指南(五十七): 表面着色器(Surface Shader)自定义光照模型示例

在 Unity 内置渲染管线中,表面着色器 (Surface Shaders) 允许通过自定义光照模型实现各种独特的渲染效果。以下示例展示了如何创建不同类型的自定义光照模型,包括基础漫反射、包裹式漫反射、卡通着色、简单高光以及自定义全局光照等。

1. 基础漫反射光照模型 (替代内置 Lambert)

下面是一个使用自定义光照模型实现基础漫反射的示例,效果等同于内置的 Lambert 模型:

hlsl

Shader "Example/CustomDiffuse" {
    Properties {
        _MainTex ("纹理", 2D) = "white" {}
    }
    SubShader {
        Tags { "RenderType" = "Opaque" }
        CGPROGRAM
        #pragma surface surf CustomLambert exclude_path:deferred

        // 自定义Lambert光照模型
        half4 LightingCustomLambert(SurfaceOutput s, half3 lightDir, half atten) {
            // 计算法线与光照方向的点积
            half NdotL = saturate(dot(s.Normal, lightDir));
            // 计算最终颜色: 材质颜色 × 光照颜色 × 漫反射系数 × 光照衰减
            half4 c;
            c.rgb = s.Albedo * _LightColor0.rgb * (NdotL * atten);
            c.a = s.Alpha;
            return c;
        }

        struct Input {
            float2 uv_MainTex;
        };

        sampler2D _MainTex;

        void surf (Input IN, inout SurfaceOutput o) {
            o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb;
        }
        ENDCG
    }
    Fallback "Diffuse"
}

这个自定义光照模型通过计算表面法线与光照方向的点积来确定漫反射强度,与内置的 Lambert 模型原理相同。

2. 包裹式漫反射光照模型 (Wrapped Diffuse)

包裹式漫反射可以使物体边缘获得更柔和的光照效果,常用于模拟次表面散射等效果:

hlsl

Shader "Example/WrappedDiffuse" {
    Properties {
        _MainTex ("纹理", 2D) = "white" {}
        _WrapAmount ("包裹强度", Range(0,1)) = 0.5
    }
    SubShader {
        Tags { "RenderType" = "Opaque" }
        CGPROGRAM
        #pragma surface surf WrappedLambert exclude_path:deferred

        half _WrapAmount;

        // 包裹式漫反射光照模型
        half4 LightingWrappedLambert(SurfaceOutput s, half3 lightDir, half atten) {
            // 计算法线与光照方向的点积,并使用包裹因子调整
            half NdotL = dot(s.Normal, lightDir);
            half diff = (NdotL * (1-_WrapAmount) + _WrapAmount) * 0.5;
            
            half4 c;
            c.rgb = s.Albedo * _LightColor0.rgb * (diff * atten);
            c.a = s.Alpha;
            return c;
        }

        struct Input {
            float2 uv_MainTex;
        };

        sampler2D _MainTex;

        void surf (Input IN, inout SurfaceOutput o) {
            o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb;
        }
        ENDCG
    }
    Fallback "Diffuse"
}

通过调整_WrapAmount 参数,可以控制光照在物体边缘的衰减程度,值越小边缘越暗,值越大边缘越亮。

3. 卡通着色光照模型 (Toon Ramp)

卡通着色通常使用渐变纹理 (Ramp Texture) 来控制光照的明暗过渡,实现阶梯式的光照效果:

hlsl

Shader "Example/ToonRamp" {
    Properties {
        _MainTex ("纹理", 2D) = "white" {}
        _RampTex ("渐变纹理", 2D) = "white" {}
    }
    SubShader {
        Tags { "RenderType" = "Opaque" }
        CGPROGRAM
        #pragma surface surf ToonRamp exclude_path:deferred

        sampler2D _RampTex;

        // 卡通着色光照模型
        half4 LightingToonRamp(SurfaceOutput s, half3 lightDir, half atten) {
            // 计算法线与光照方向的点积
            half NdotL = saturate(dot(s.Normal, lightDir));
            
            // 使用点积值采样渐变纹理,获取阶梯式光照值
            half3 ramp = tex2D(_RampTex, float2(NdotL, 0)).rgb;
            
            half4 c;
            c.rgb = s.Albedo * _LightColor0.rgb * ramp * atten;
            c.a = s.Alpha;
            return c;
        }

        struct Input {
            float2 uv_MainTex;
        };

        sampler2D _MainTex;

        void surf (Input IN, inout SurfaceOutput o) {
            o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb;
        }
        ENDCG
    }
    Fallback "Diffuse"
}

_RampTex 通常是一个水平渐变纹理,从左到右代表从暗到亮的过渡。通过调整这个纹理,可以精确控制卡通着色的明暗阶调。

4. 简单高光光照模型 (类似 Blinn-Phong)

以下是一个简化版的 Blinn-Phong 高光模型实现:

hlsl

Shader "Example/SimpleSpecular" {
    Properties {
        _MainTex ("纹理", 2D) = "white" {}
        _SpecColor ("高光颜色", Color) = (1,1,1,1)
        _Shininess ("高光光泽度", Range(0.1, 100)) = 20
    }
    SubShader {
        Tags { "RenderType" = "Opaque" }
        CGPROGRAM
        #pragma surface surf SimpleSpecular exclude_path:deferred

        fixed4 _SpecColor;
        half _Shininess;

        // 简单高光光照模型
        half4 LightingSimpleSpecular(SurfaceOutput s, half3 lightDir, half3 viewDir, half atten) {
            // 计算漫反射
            half NdotL = saturate(dot(s.Normal, lightDir));
            half4 diff = s.Albedo * _LightColor0.rgb * NdotL * atten;
            
            // 计算半角向量
            half3 h = normalize(lightDir + viewDir);
            // 计算高光强度
            half spec = pow(saturate(dot(s.Normal, h)), _Shininess);
            
            // 最终颜色 = 漫反射 + 高光
            half4 c;
            c.rgb = diff.rgb + _SpecColor.rgb * spec * _LightColor0.rgb * atten;
            c.a = s.Alpha;
            return c;
        }

        struct Input {
            float2 uv_MainTex;
        };

        sampler2D _MainTex;

        void surf (Input IN, inout SurfaceOutput o) {
            o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb;
        }
        ENDCG
    }
    Fallback "Specular"
}

这个光照模型在基础漫反射的基础上添加了高光效果,通过_Shininess 参数控制高光的大小,值越大高光越集中。

5. 自定义全局光照 (带色调映射)

下面的示例展示了如何自定义全局光照处理,并添加色调映射效果:

hlsl

Shader "Example/CustomGI_ToneMapped" {
    Properties {
        _MainTex ("基础色 (RGB)", 2D) = "white" {}
        _Exposure ("曝光", Range(0.1, 5)) = 1.0
        _Contrast ("对比度", Range(0.1, 5)) = 1.0
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        
        CGPROGRAM
        #pragma surface surf CustomGI_ToneMapped exclude_path:deferred

        #include "UnityPBSLighting.cginc"

        half _Exposure;
        half _Contrast;
        sampler2D _MainTex;

        // 自定义色调映射函数
        half3 ToneMap(half3 color) {
            // 简单的曝光和对比度调整
            color *= _Exposure;
            color = (color - 0.5) * _Contrast + 0.5;
            return saturate(color);
        }

        // 自定义光照函数 - 使用标准PBS光照
        inline half4 LightingCustomGI_ToneMapped(SurfaceOutputStandard s, half3 viewDir, UnityGI gi)
        {
            return LightingStandard(s, viewDir, gi);
        }

        // 自定义全局光照处理函数
        inline void LightingCustomGI_ToneMapped_GI(
            SurfaceOutputStandard s,
            UnityGIInput data,
            inout UnityGI gi)
        {
            // 调用标准全局光照处理
            LightingStandard_GI(s, data, gi);
            
            // 应用色调映射到所有光照组件
            gi.light.color = ToneMap(gi.light.color);
            gi.indirect.diffuse = ToneMap(gi.indirect.diffuse);
            gi.indirect.specular = ToneMap(gi.indirect.specular);
        }

        struct Input {
            float2 uv_MainTex;
        };

        void surf (Input IN, inout SurfaceOutputStandard o) {
            o.Albedo = tex2D(_MainTex, IN.uv_MainTex);
        }
        ENDCG
    }
    FallBack "Diffuse"
}

这个示例通过自定义_GI 函数,在标准全局光照计算后应用了色调映射效果,可以调整场景的整体亮度和对比度。

关键技术要点

  1. 光照模型函数签名

    • 对于不依赖视角的光照模型:half4 Lighting<Name>(SurfaceOutput s, half3 lightDir, half atten)
    • 对于依赖视角的光照模型:half4 Lighting<Name>(SurfaceOutput s, half3 lightDir, half3 viewDir, half atten)
  2. 全局光照支持

    • 通过实现Lighting<Name>_GI函数来自定义全局光照处理
    • 使用内置的LightingStandard_GI等函数处理基础全局光照计算
  3. 渲染路径控制

    • 使用exclude_path:deferred指令确保着色器只编译用于前向渲染路径
    • 复杂的自定义光照模型通常不支持延迟渲染路径

这些示例展示了自定义光照模型的基本方法,通过组合不同的光照计算和效果,可以创建出各种独特的渲染风格。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小李也疯狂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值