【从UnityURP开始探索游戏渲染】专栏-直达
伽马校正的定义与原理
伽马校正是对颜色值进行非线性变换的过程,其核心是通过幂函数(γ函数)调整亮度值,使人眼感知更均匀。数学表达式为:输出 = 输入^γ,其中γ=0.45用于编码(sRGB到线性空间),γ=2.2用于解码(线性空间到sRGB)。
人眼对亮度的感知是非线性的------对暗部变化敏感,对亮部变化不敏感。例如从1根蜡烛增加到2根蜡烛的变化很容易察觉,而从100根增加到101根则难以察觉。
为什么需要伽马校正
伽马校正主要解决三个问题:
- 存储优化:8位色深(0-255)下,通过伽马编码为暗部分配更多值域,亮部分配较少值域,更符合人眼感知特性。
- 显示一致性:补偿早期CRT显示器电压-亮度非线性关系(γ≈2.2),现代显示器通过硬件模拟保持兼容。
- 渲染准确性:在线性空间计算光照和混合(如PBR),避免亮度计算错误。
历史发展
伽马校正起源于CRT时代,当时显示器物理特性导致输入电压与亮度呈γ≈2.2的幂关系。随着LCD等新技术出现,虽然物理特性改变,但为保持兼容性仍沿用该标准。现代图形管线(如URP)已将其整合为标准化流程。
Unity URP中的实现机制
URP默认使用线性空间(Linear Space),其工作流程为:
- 输入转换:对sRGB纹理自动应用γ=2.2转换到线性空间。
- 计算阶段:所有光照和混合在线性空间执行。
- 输出转换:最终输出应用γ=0.45转换回sRGB空间。
实现原理是通过着色器内置的GammaToLinearSpace()和LinearToGammaSpace()函数完成转换。URP强制使用线性空间是因为:
- 物理正确性:光照计算符合能量守恒
- 混合准确性:如半透明叠加效果更真实
- 跨平台一致性:避免不同设备显示差异
实际应用示例
示例1:手动伽马校正
csharp
csharp
// 在Shader中手动校正
float3 linearColor = pow(sRGBColor, 2.2);// sRGB转线性
float3 processedColor = DoLightingCalculation(linearColor);
float3 gammaCorrected = pow(processedColor, 1/2.2);// 线性转sRGB
示例2:Unity颜色空间设置
csharp
csharp
// 检查当前颜色空间
if (QualitySettings.activeColorSpace == ColorSpace.Linear) {
// 在线性空间下自动处理伽马校正
material.color = Color.red;// Unity会自动处理转换
}
示例3:解决PS与Unity混合差异
当PS(Gamma空间)与Unity(线性空间)混合结果不一致时:
- 在PS中工作于线性空间(编辑→颜色设置→RGB工作空间改为"显示器RGB")
- 或Unity中临时切换至Gamma空间(不推荐)
完整伽马校正的示例
代码与示例
-
Shader部分:
- 包含完整的URP Shader结构,使用HLSL语法
- 通过
pow(processedColor, 1.0/_GammaValue)实现伽马校正 - 自动处理sRGB纹理到线性空间的转换
-
脚本部分:
- 提供运行时伽马值调整
- 检查线性空间设置
- 可选的后处理实现方式
-
Unity设置:
- 在Project Settings > Player > Other Settings中:
- 将Color Space设为Linear
- 确保URP Asset的Post Processing开启
- 对非颜色纹理(如法线贴图)取消sRGB选项
- 在Project Settings > Player > Other Settings中:
-
GammaCorrection.shader
cShader "Custom/GammaCorrection" { Properties { _MainTex ("Texture", 2D) = "white" {} _GammaValue ("Gamma Value", Range(0.1, 3.0)) = 2.2 } SubShader { Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalRenderPipeline" } Pass { HLSLPROGRAM #pragma vertex vert #pragma fragment frag #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" struct Attributes { float4 positionOS : POSITION; float2 uv : TEXCOORD0; }; struct Varyings { float2 uv : TEXCOORD0; float4 positionHCS : SV_POSITION; }; TEXTURE2D(_MainTex); SAMPLER(sampler_MainTex); float _GammaValue; Varyings vert(Attributes IN) { Varyings OUT; OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz); OUT.uv = IN.uv; return OUT; } half4 frag(Varyings IN) : SV_Target { // 采样纹理(自动处理sRGB到线性转换) half4 col = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, IN.uv); // 手动伽马校正(线性空间计算) half3 linearColor = col.rgb; half3 processedColor = linearColor * 2.0; // 示例光照计算 // 应用伽马校正输出 half3 gammaCorrected = pow(processedColor, 1.0/_GammaValue); return half4(gammaCorrected, col.a); } ENDHLSL } } } -
GammaCorrectionSettings.cs
csharpusing UnityEngine; using UnityEngine.Rendering; public class GammaCorrectionSettings : MonoBehaviour { [Range(0.1f, 3.0f)] public float gammaValue = 2.2f; void Start() { // 确保项目使用线性颜色空间 if (QualitySettings.activeColorSpace != ColorSpace.Linear) { Debug.LogWarning("建议在Player Settings中将颜色空间改为Linear"); } } void OnRenderImage(RenderTexture src, RenderTexture dest) { // 后处理方式应用伽马校正 Material mat = new Material(Shader.Find("Hidden/Universal Render Pipeline/GammaCorrection")); mat.SetFloat("_GammaValue", gammaValue); Graphics.Blit(src, dest, mat); } }
使用场景
- PBR材质:确保光照计算在线性空间
- UI混合:避免颜色叠加出现亮度异常
- 后处理效果:如Bloom、Tonemapping前需要正确伽马空间
注意事项
- 移动平台需注意GLES 3.0支持,部分设备可能回退到Gamma空间
- 透明通道(alpha)不参与伽马转换
- 法线贴图等非颜色纹理应标记为"Bypass sRGB"避免错误转换
【从UnityURP开始探索游戏渲染】专栏-直达
(欢迎点赞留言探讨,更多人加入进来能更加完善这个探索的过程,🙏)