【从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
// 在Shader中手动校正
float3 linearColor = pow(sRGBColor, 2.2);// sRGB转线性
float3 processedColor = DoLightingCalculation(linearColor);
float3 gammaCorrected = pow(processedColor, 1/2.2);// 线性转sRGB
示例2:Unity颜色空间设置
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
Shader "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
using 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开始探索游戏渲染】专栏-直达
(欢迎点赞留言探讨,更多人加入进来能更加完善这个探索的过程,🙏)