在 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 函数,在标准全局光照计算后应用了色调映射效果,可以调整场景的整体亮度和对比度。
关键技术要点
-
光照模型函数签名:
- 对于不依赖视角的光照模型:
half4 Lighting<Name>(SurfaceOutput s, half3 lightDir, half atten)
- 对于依赖视角的光照模型:
half4 Lighting<Name>(SurfaceOutput s, half3 lightDir, half3 viewDir, half atten)
- 对于不依赖视角的光照模型:
-
全局光照支持:
- 通过实现
Lighting<Name>_GI
函数来自定义全局光照处理 - 使用内置的
LightingStandard_GI
等函数处理基础全局光照计算
- 通过实现
-
渲染路径控制:
- 使用
exclude_path:deferred
指令确保着色器只编译用于前向渲染路径 - 复杂的自定义光照模型通常不支持延迟渲染路径
- 使用
这些示例展示了自定义光照模型的基本方法,通过组合不同的光照计算和效果,可以创建出各种独特的渲染风格。