Gamma矫正在计算机图形学中是一个基础但至关重要的概念。简单来说,Gamma值描述的是显示设备对输入信号的响应曲线。在现实世界中,人眼对光线的感知并不是线性的,而是遵循一种近似对数的响应曲线。这种特性使得人眼对暗部细节的变化更为敏感。
显示器等输出设备也存在类似的非线性特性。大多数显示器的默认Gamma值约为2.2,这意味着当输入信号强度为0.5时,实际显示亮度约为0.5^2.2≈0.22。这种非线性响应恰好与人眼的感知特性相匹配,使得有限的色彩深度(通常每通道8位)能够得到更有效的利用。
注意:Gamma值不是固定的,不同设备可能有不同的Gamma特性。现代显示器通常遵循sRGB标准,其近似Gamma值为2.2。
Unity的通用渲染管线(URP)作为现代实时渲染解决方案,必须正确处理Gamma空间和线性空间的转换,主要原因有以下几点:
光照计算本质上是一个物理过程,应该在线性空间进行。如果在Gamma空间进行光照计算,会导致以下问题:
例如,两个0.5灰度值的颜色在线性空间混合应该是0.5+0.5=1.0,但在Gamma空间实际上是(0.5^2.2 + 0.5^2.2)^(1/2.2)≈0.73,这显然不符合物理规律。
当我们需要混合颜色或进行多重纹理采样时,线性空间能提供更自然的结果。常见的场景包括:
现代PBR(基于物理的渲染)工作流都假设所有计算在线性空间进行。URP作为支持PBR的渲染管线,必须保证:
URP处理Gamma矫正的完整流程可以分为以下几个阶段:
Unity在导入纹理时会根据纹理类型自动处理:
csharp复制// 典型纹理导入设置示例
TextureImporter textureImporter = (TextureImporter)AssetImporter.GetAtPath(assetPath);
textureImporter.sRGBTexture = true; // 对于颜色纹理
textureImporter.sRGBTexture = false; // 对于非颜色纹理(法线、金属度等)
颜色纹理(如Albedo)会被标记为sRGB,在采样时自动从Gamma空间转换到线性空间。而非颜色数据(如法线贴图)则保持线性。
URP着色器中,所有光照计算都在线性空间进行。核心着色器代码通常会包含:
hlsl复制// 将sRGB颜色转换到线性空间
float3 albedo = pow(baseColor.rgb, 2.2);
// 在线性空间进行光照计算
float3 diffuse = albedo * saturate(dot(normal, lightDir));
// 转换回Gamma空间输出
diffuse = pow(diffuse, 1.0/2.2);
return float4(diffuse, 1.0);
URP最终输出到帧缓冲时需要考虑显示设备的Gamma特性。在Unity中,这通过Graphics API设置控制:
csharp复制// 启用sRGB写入
GL.sRGBWrite = true;
虽然Gamma矫正很重要,但在移动平台可能需要权衡:
编写自定义后处理效果时,必须注意:
hlsl复制// 正确采样屏幕纹理
float3 color = pow(SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv).rgb, 2.2);
// 处理效果...
// 正确输出
return float4(pow(result, 1.0/2.2), 1.0);
不同平台对sRGB的支持可能不同:
Unity提供了多种调试Gamma问题的方法:
可以通过以下简单测试验证Gamma是否正确:
Gamma矫正对性能的影响主要体现在:
可以使用Unity Profiler分析具体影响。