【從UnityURP開始探索遊戲渲染】專欄-直達
ColorCurves 是 Unity 通用渲染管線(URP)中的一種高級顏色分級工具,它允許通過曲線精細調整圖像的色相、飽和度和亮度。這種工具最初在專業影視後期軟件(如 Fusion)中成熟應用,後被引入遊戲引擎用於實時渲染的色彩控制。
ColorCurves 提供了8條獨立曲線,包括:
-
Master(整體亮度)
- 功能:整體調整圖像的亮度和對比度。
- 實現:通過調整曲線形狀控制全局亮度,曲線向上提升亮度,向下降低亮度
-
Red/Green/Blue(單通道調整,三條曲線)
- 功能:分別單獨調整紅、綠、藍通道的亮度和色彩平衡。
- 實現:針對分量進行獨立調節,影響圖像中紅、綠藍區域的顯色強度
-
HueVsHue(色調替換 色相-色相曲線)
- 功能:基於色相替換顏色(如將紅色替換為藍色)。
- 實現:通過映射原始色相到目標色相,實現顏色轉換
-
HueVsSat(色調飽和度調整 色相-飽和度曲線)
- 功能:根據色相調整特定顏色的飽和度。
- 實現:選擇特定色相範圍後,增加或降低其飽和度
-
SatVsSat(飽和度對比 飽和度-飽和度曲線)
- 功能:基於當前飽和度進一步調整飽和度。
- 實現:對低飽和度區域增強或高飽和度區域抑制,實現非線性調整
-
LumVsSat(亮度飽和度關係 亮度-飽和度曲線)
- 功能:根據亮度調整飽和度(如增強暗部飽和度)。
- 實現:在低亮度區域提升飽和度以增強視覺對比,或在高亮度區域降低飽和度避免過曝
發展歷史
顏色曲線技術起源於電影工業的後期調色流程,早期在 DaVinci Resolve 等專業調色軟件中實現。Unity 在 2017 年引入 Post-processing Stack v1 時首次包含了基礎曲線調整,2019 年 URP 正式版將其整合為 ColorCurves 模塊,並增加了針對遊戲優化的 ACES 色彩空間支持。
實現原理
ColorCurves 在渲染管線的後處理階段工作,位於色調映射之前。它通過以下步驟實現:
- 將輸入圖像分解為 HSL 分量
- 對每個分量應用預定義的曲線變換
- 重新組合分量並輸出到下一處理階段
曲線映射原理
ColorCurves通過8條獨立曲線對圖像進行分通道處理,每條曲線採用256個控制點的查找表(LUT)實現非線性映射。例如Master曲線採用x軸(輸入亮度)到y軸(輸出亮度)的映射關係,通過貝塞爾插值算法實現平滑過渡。
貝塞爾曲線
-
通用公式
對於n階貝塞爾曲線,其參數方程為:
$B(t)=\sum_{i=0}^{n}\binom{n}{i}(1−t)^{n−i}t^iP_i,t\in[0,1]$
其中:
- $P_i$為控制點,共n+1個;
- $\binom{n}{i}$為組合數;
- t為參數,控制曲線上的位置。
-
常見類型
-
線性 1階:兩點間直線,公式為
- $B(t)=(1−t)P_0+tP_1$
-
二次 2階:3個控制點,公式為
- $B(t)=(1−t)^2P_0+2t(1−t)P_1+t^2P_2$
-
三次 3階:4個控制點,公式為
- $B(t)=(1−t)^3P_0+3(1−t)^2tP_1+3(1−t)t^2P2+t^3P_3$
- 三次貝塞爾曲線因平衡計算複雜性和靈活性,成為圖形學中最常用的類型
-
-
實現方法
- 遞歸計算:通過德卡斯特里奧算法(De Casteljau's algorithm)逐步插值中間點;
- 矩陣表示:將三次貝塞爾曲線表示為控制點與基函數的矩陣乘法;
-
代碼示例(Unity/C#):(注:實際應用中需處理浮點精度和性能優化)
csharp public Vector3 CalculateBezierPoint(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) { float u = 1 - t; return u*u*u * p0 + 3*u*u*t * p1 + 3*u*t*t * p2 + t*t*t * p3; }
通道處理流程
-
RGB通道分離:先將圖像分解為R/G/B三個通道:
- channelR = (R,0,0)
- channelG = (0,G,0)
- channelB = (0,0,0)
-
HLSL中常用亮度公式Luminance=0.2126×R+0.7152×G+0.0722×B
- 該公式基於CIE 1931色彩空間的人眼感知權重
-
RGB_Separate.hlsl
hlsl Texture2D InputTexture : register(t0); SamplerState LinearSampler : register(s0); struct PS_Input { float4 Position : SV_POSITION; float2 UV : TEXCOORD; }; // 紅色通道分離 float4 PS_RedChannel(PS_Input input) : SV_TARGET { float3 color = InputTexture.Sample(LinearSampler, input.UV).rgb; return float4(color.r, 0, 0, 1); // 僅保留R分量 } // 綠色通道分離 float4 PS_GreenChannel(PS_Input input) : SV_TARGET { float3 color = InputTexture.Sample(LinearSampler, input.UV).rgb; return float4(0, color.g, 0, 1); // 僅保留G分量 } // 藍色通道分離 float4 PS_BlueChannel(PS_Input input) : SV_TARGET { float3 color = InputTexture.Sample(LinearSampler, input.UV).rgb; return float4(0, 0, color.b, 1); // 僅保留B分量 } // 亮度計算 float4 PS_Luminance(PS_Input input) : SV_TARGET { float3 color = InputTexture.Sample(LinearSampler, input.UV).rgb; float lum = dot(color, float3(0.2126, 0.7152, 0.0722)); return float4(lum.xxx, 1); // 灰度輸出 }
-
曲線應用:
-
主通道:Master曲線同時影響所有通道
- 作為全局亮度調整曲線,直接對RGB三通道進行統一映射。公式為:Output=f_master(Input)
- 其中Input是原始亮度值(0-1),f_master是用户定義的曲線函數
- 分通道:Red/Green/Blue曲線分別處理對應通道
-
HueVsHue曲線
- 實現色相替換效果,通過角度偏移改變特定色相。HSV空間中的公式:H_out=H_in+f_hue(H_in)∗360°
- f_hue是定義在0-1範圍的曲線,輸出值作為角度偏移量。
-
HueVsSat曲線
- 控制不同色相區域的飽和度增強/減弱: S_out=S_in∗(1+f_sat(H_in))
- f_sat曲線輸出-1到1的值,負值降低飽和度,正值增加。
-
SatVsSat曲線
- 非線性調整飽和度分佈:S_out=f_satmap(S_in)
- 直接重映射飽和度值,常用於創建啞光效果。
-
LumVsSat曲線
- 基於亮度控制飽和度:S_out=S_in∗(1+f_lum(L_in))
- L_in是HSL亮度值,f_lum曲線控制不同亮度區域的飽和度變化
-
ColorCurves.hlsl
- 使用紋理採樣實現曲線查找,提升GPU執行效率
- RgbToHsv/HsvToRgb需自行實現標準色彩空間轉換
- 曲線參數通過紋理傳入,支持任意形狀的調整曲線
- 最終效果是各曲線調整的疊加組合
- 在C#中可通過Shader.SetGlobalTexture傳遞曲線紋理,或在CPU端實現類似算法處理圖像數據。實際應用時通常配合UI控件讓用户交互式調整曲線形狀。
// 輸入參數 Texture2D InputTexture; SamplerState LinearSampler; float4 HueVsHueCurve; // 曲線採樣紋理 float4 HueVsSatCurve; float4 SatVsSatCurve; float4 LumVsSatCurve; float3 ApplyColorCurves(float3 rgb) { // 轉換到HSV空間 float3 hsv = RgbToHsv(rgb); float hue = hsv.x; float sat = hsv.y; float lum = GetLuminance(rgb); // HueVsHue處理 float hueOffset = SampleCurve(HueVsHueCurve, hue); hsv.x = frac(hue + hueOffset); // HueVsSat處理 float satScale = SampleCurve(HueVsSatCurve, hue) * 2.0 - 1.0; hsv.y = sat * (1.0 + satScale); // SatVsSat處理 hsv.y = SampleCurve(SatVsSatCurve, sat); // LumVsSat處理 float lumSatScale = SampleCurve(LumVsSatCurve, lum) * 2.0 - 1.0; hsv.y = sat * (1.0 + lumSatScale); // 返回RGB空間 return HsvToRgb(hsv); } // 輔助函數 float SampleCurve(float4 curve, float x) { return curve.SampleLevel(LinearSampler, float2(x, 0), 0).r; }
-
-
色相轉換:將RGB轉換為HSV色彩空間處理HueVsHue等曲線
- HSV(Hue, Saturation, Value)是一種基於人類視覺感知的直觀顏色模型,由色相(H)、飽和度(S)和明度(V)三個分量組成
-
基本概念
- 色相H:表示顏色類型,取值範圍0°-360°(如紅色0°、綠色120°、藍色240°),通過色輪角度定位顏色。
- 飽和度S:表示顏色純度,0%為灰色,100%為純色,數值越高顏色越鮮豔。
- 明度V:控制顏色亮度,0%為黑色,100%為最亮(如白色需S=0%、V=100%)
-
模型特點
- 六角錐體結構:從RGB立方體演化而來,色調H沿圓周分佈,飽和度S和明度V分別表示徑向和軸向距離。
- 符合直覺:比RGB更接近傳統繪畫調色習慣,適合直觀調整顏色屬性(如Photoshop調色板)。
- 分量獨立:亮度V與顏色無關,僅影響光照強度;色調H和飽和度S互不干擾
- RGB轉HSV
- HSV轉RGB
-
ColorSpace.hlsl
// RGB轉HSV float3 RGBtoHSV(float3 rgb) { float cmax = max(rgb.r, max(rgb.g, rgb.b)); float cmin = min(rgb.r, min(rgb.g, rgb.b)); float delta = cmax - cmin; float h = 0; if (delta != 0) { if (cmax == rgb.r) h = 60 * fmod(((rgb.g - rgb.b)/delta + 6), 6); else if (cmax == rgb.g) h = 60 * ((rgb.b - rgb.r)/delta + 2); else h = 60 * ((rgb.r - rgb.g)/delta + 4); } float s = (cmax == 0) ? 0 : delta / cmax; return float3(h, s, cmax); } // HSV轉RGB float3 HSVtoRGB(float3 hsv) { float c = hsv.z * hsv.y; float x = c * (1 - abs(fmod(hsv.x / 60, 2) - 1)); float m = hsv.z - c; float3 rgb; if (hsv.x < 60) rgb = float3(c, x, 0); else if (hsv.x < 120) rgb = float3(x, c, 0); else if (hsv.x < 180) rgb = float3(0, c, x); else if (hsv.x < 240) rgb = float3(0, x, c); else if (hsv.x < 300) rgb = float3(x, 0, c); else rgb = float3(c, 0, x); return rgb + m; }
示例:實現冷色調增強
// 在Volume組件中添加ColorCurves重載
var curves = volumeProfile.Add<ColorCurves>();
curves.active = true;
// 調整藍色通道曲線
curves.blue.Override(new AnimationCurve(
new Keyframe(0, 0),
new Keyframe(0.5f, 0.7f),
new Keyframe(1, 1)
));
// 降低暖色飽和度
curves.hueVsSat.Override(new AnimationCurve(
new Keyframe(0.1f, 0.8f),// 紅色範圍
new Keyframe(0.6f, 1.2f)// 藍色範圍
));
該示例通過提升中間調藍色和抑制紅色飽和度,實現電影級冷色調效果。
伽馬校正處理
URP會在曲線處理前後自動執行伽馬/線性空間轉換,確保顏色混合符合物理正確性。具體流程:
- 輸入圖像從sRGB轉為線性空間
- 應用所有曲線調整
- 結果轉換回sRGB空間輸出
性能優化
採用計算着色器並行處理LUT生成,每條曲線僅需1次紋理採樣,8條曲線共產生約0.3ms的性能開銷(1080p分辨率)。建議避免每幀動態修改曲線控制點,改為預烘焙多套曲線配置。
完整示例
-
ColorCurvesExample.cs
using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; [System.Serializable] public class ColorCurvesSettings { public AnimationCurve masterCurve = new AnimationCurve(new Keyframe(0f, 0f), new Keyframe(1f, 1f)); public AnimationCurve redCurve = new AnimationCurve(new Keyframe(0f, 0f), new Keyframe(1f, 1f)); public AnimationCurve greenCurve = new AnimationCurve(new Keyframe(0f, 0f), new Keyframe(1f, 1f)); public AnimationCurve blueCurve = new AnimationCurve(new Keyframe(0f, 0f), new Keyframe(1f, 1f)); public AnimationCurve hueVsHueCurve = new AnimationCurve(new Keyframe(0f, 0f), new Keyframe(1f, 1f)); public AnimationCurve hueVsSatCurve = new AnimationCurve(new Keyframe(0f, 0f), new Keyframe(1f, 1f)); public AnimationCurve satVsSatCurve = new AnimationCurve(new Keyframe(0f, 0f), new Keyframe(1f, 1f)); public AnimationCurve lumVsSatCurve = new AnimationCurve(new Keyframe(0f, 0f), new Keyframe(1f, 1f)); } public class CustomColorCurvesRenderPass : ScriptableRenderPass { private Material m_Material; private ColorCurvesSettings m_Settings; public CustomColorCurvesRenderPass(Material material, ColorCurvesSettings settings) { m_Material = material; m_Settings = settings; } public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { if (m_Material == null) return; CommandBuffer cmd = CommandBufferPool.Get("Color Curves"); // 設置曲線參數 m_Material.SetTexture("_MasterCurve", CreateCurveTexture(m_Settings.masterCurve)); m_Material.SetTexture("_RedCurve", CreateCurveTexture(m_Settings.redCurve)); m_Material.SetTexture("_GreenCurve", CreateCurveTexture(m_Settings.greenCurve)); m_Material.SetTexture("_BlueCurve", CreateCurveTexture(m_Settings.blueCurve)); m_Material.SetTexture("_HueVsHueCurve", CreateCurveTexture(m_Settings.hueVsHueCurve)); m_Material.SetTexture("_HueVsSatCurve", CreateCurveTexture(m_Settings.hueVsSatCurve)); m_Material.SetTexture("_SatVsSatCurve", CreateCurveTexture(m_Settings.satVsSatCurve)); m_Material.SetTexture("_LumVsSatCurve", CreateCurveTexture(m_Settings.lumVsSatCurve)); // 執行後處理 Blit(cmd, renderingData.cameraData.renderer.cameraColorTarget, renderingData.cameraData.renderer.cameraColorTarget, m_Material); context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); } private Texture2D CreateCurveTexture(AnimationCurve curve) { Texture2D texture = new Texture2D(256, 1, TextureFormat.RFloat, false); for (int i = 0; i < 256; i++) { float value = curve.Evaluate(i / 255f); texture.SetPixel(i, 0, new Color(value, 0, 0, 0)); } texture.Apply(); return texture; } }
使用流程
- 在 Unity 中創建 Volume 對象並添加 Color Curves 效果
- 調整各條曲線參數
- 通過預覽窗口實時查看效果
參數詳解與用例
Master 曲線
- 作用:全局亮度調整
- 用例:修正整體曝光不足或過曝的場景
RGB 曲線(Red/Green/Blue)
- 作用:單獨調整各顏色通道
- 用例:修正色偏或創造風格化效果(如賽博朋克的藍色調)
HueVsHue
- 作用:基於輸入色調替換輸出色調
- 用例:將綠色植被改為秋季黃色
HueVsSat
- 作用:調整特定色調的飽和度
- 用例:增強天空藍色飽和度而不影響其他顏色
SatVsSat
- 作用:調整飽和度對比
- 用例:使高飽和區域更鮮豔,低飽和區域更灰
LumVsSat
- 作用:基於亮度調整飽和度
- 用例:使暗部區域降低飽和度(電影感效果)
實際應用技巧
電影感調色
- 使用 LumVsSat 降低暗部飽和度,HueVsHue 微調膚色
季節變換
- 通過 HueVsHue 將綠色變為黃色/紅色模擬秋季
風格化效果
- 誇張的 S 形 RGB 曲線創造高對比度畫面
色彩匹配
- Match 功能原理,使用曲線匹配不同鏡頭的色彩
ColorCurves 與 Unity 的 ACES 色彩空間配合使用時效果最佳,能夠保持更廣色域的色彩關係
【從UnityURP開始探索遊戲渲染】專欄-直達
(歡迎點贊留言探討,更多人加入進來能更加完善這個探索的過程,🙏)