在渲染管線中正確使用Gamma校正是決定渲染效果的一個非常重要的因素,現在商業引擎包括很多國內引擎都已經是在線性空間計算光照。當然也包括我們之前公司設計的引擎。關於gamma校正的定義可以查看wikipedia或者看知乎的這篇文章。
原文地址:http://filmicgames.com/archives/299
對於圖形程序員和技術美術來說,線性空間光照 是最重要的東西。它並不是看上去那么難,但是不知道是什么原因,沒有人真正教過它。就我自己而言,我從Georgia Tech獲得了本科和碩士學位,學習了基本上所有的圖形學知識,但是並沒有聽到關於Gamma的知道,直到我從George Borshukov那里學到。這扁文章是我GDC演講的一個簡化版。你可以查看幻燈片來獲取更多的細節。
我經常以下面這張圖開頭:
希望你的瀏覽器沒有改變它的大小,左邊和右邊是黑白交替顯示的水平線。如果你有一個正確校正的顯示器,中上的方塊應該會比較暗,中下部的方塊應該和交替的線有一樣的強度。在左右兩邊,你會得到差不多0~255中間的值。中間的值 是127/128,對嗎?
不。實際上187差不多是0~255之間的中間值。128會比一半暗很多。怎么回事?
這里有一個0~255的灰階,128並不是0~255的中間值,而是187。為什么?這就是所謂的gamma在搞鬼。
當把一個值顯示到屏幕時,我自然地假設光子的數量跟發送的值是線性增長的關系。例如50發送光子的數量應該比25發送光子的數量多一倍。但是,現實中大多數顯示器遵循一個gamma 2.2的曲線。如果 我們假設輸出的值在0~1之間而不是0~255,那么光子的數量應該跟pow(x,2.2)成正比。
不同條件下可能會不同。如果你聽說Mac有一個不同的gamma,那是因為gamma默認值為1.8(雖然最近我聽說他們已經修改了)。sRGB曲線也非常相似,但是實際上跟gamma 2.2的有一些小差別。
上面是一張我穿着一個很小且很貴的貂皮外套的照片。左邊的圖是一個gamma 1/2.2 (接近0.45)的照片。中間是線性的,或者說是gamma 1.0。右邊圖片的gamma值為2.2。
我們實際上可以通過一個pow操作在gamma空間中轉換。從左邊的亮照片說起,我們可以通過 pow(x,2.2)來得到 蹭的圖片,再通過一個pow(x,2.2)得到右邊的圖片。反過來,我們也可以通過 pow(x,1/2.2)來得到。
假設我們在不知道gamma的條件下制造了一個相機。我們會建造一個感光元件用來接收進入的光線(左邊)並且把每個像素分到255個灰階中。這個圖片會存放到硬盤上(中間圖片)。但是,用戶會在它們的屏幕上來查看圖片,顯示器會應用一個gamma 2.2來校正 圖片(右邊圖片)。如果我們這樣做,用戶會說我們的相機很爛,因為屏幕上的照片跟現實世界的不一樣。
你可以嘗試向他們解釋相機是正常的,只是所有的顯示器有問題(包括你相機背面的預覽LCD)。祝你好運。相反,每一個消費級相機老師把照片存儲在gamma 空間。當光線到達感光器時,相機 會應用一個pow(x,1/2.2)操作,然后再把它映射到255個灰階上,並且把最后結果存儲到硬盤上。傻瓜相機如此,網絡攝像頭也是如此,iPhone也是如此,單反也是如此 。唯一的例外可能是計算機視覺相機 。因此圖片都存儲在gamma空間,它看上去會比較亮且柔和。但是當用戶在屏幕上看到圖片時,它看上去是正確的。
這是關於gamma你需要了解的最重要的東西。每當你看到屏幕上的一張照片時(像右邊的這張),存儲在硬盤上的其實是更亮、更柔和,就像上圖中左邊的一樣。那么 它怎么影響計算機圖形學呢?
左邊的圖片所有的光照計算都是在gamma空間做的,而右邊的是在線性空間計算的。如果是在PC/PS3/360平台上,那么光照計算應該是線性空間內。如果 是在Wii/PSP/iPhone平台上,你必須做你必須做的事。
左邊的圖片錯在了幾個地方。首先,它有一個非常柔軟的衰減,這樣看上去不正確。你也會看到很多的色調偏移,尤其是在高光處,白色到黃色和黑色到黃色的偏移。當你看右邊的圖片時,它看起來像一個漫反射表面加高光,它更符合光照模型描述的樣子。最后,我不是在討論什么看起來是好了,我是在討論什么是正確的。這是一個非常不同的討論。
如果你不關心gamma,你基本上是遵循上面的光照流程。假設你有一個shader如下所示:
1 float specular = ...; 2 float3 color=tex2D(samp,uv.xy); 3 float diffuse = saturate(dot(N,L)); 4 float3 finalColor = color * diffuse + specular; 5 return finalColor;
首先讀取紋理,但是在你硬盤上的紋理是存儲在gamma空間的。紋理看起來比較亮且飽和度比較低。所以你使用了這張太亮的紋理,快些 基礎上應用你的光照模型得到蹭的圖像。最后顯示器應用了pow(2.2)減小了它的亮度信息得到最終的結果。如何糾正它得到正確的結果?
下面是修改后的shader:
1 float specular = ...; 2 float3 color=pow(tex2D(samp,uv.xy),2.2); 3 float diffuse = saturate(dot(N,L)); 4 float3 finalColor = pow(color * diffuse + specular,1/2.2); 5 return finalColor;
唯一的不同之處是上面的兩個pow指令。當紋理由硬件讀取時,它是在gamma空間的(第一張圖)。但是pow(x,2.2)把這張紋理轉換線性空間中(第二張圖)。然后我們計算光照(第三張圖)。現在我們得到了想要的結果,再應用pow(x,1/2.2)把它轉換到gamma空間中。Gamma空間的圖片傳輸到顯示器,這時會對像素應用pow(x,2.2)來顯示最終的圖片。
當然,這些pow函數並不是沒有開銷的。但是它們實際上是免費的如果你使用硬件采樣狀態。對於紋理讀取來說 ,你可以使用D3DSAMP_SRGBTEXTURE,對於要寫入的幀緩沖區,可以使用D3DRS_SRGBWRITEENABLE。所以你的shader代碼跟原來一樣了,並且硬件狀態來幫助你免費轉換。
這里有一張排球的真實照片。上面的圖片是在線性空間而下面是在gamma空間。注意線性的照片是如何在分明的衰減上與真實照片吻合的,但是gamma空間計算的就不是這個樣子的。
那么這就是我們如何使用我們的圖片看起來正確的。怎么讓它變好看留給讀者自己當練習解決。