Real-Time Rendering 第五章

路人張德帥 2022-01-07 20:58:25 阅读数:342

real-time real time rendering 第五章

5.1 Shading Models

shading model:决定了渲染管線的功能,也就是渲染的方式,根據不同的光照,視角,來渲染出不同的模型錶面顏色

比如:Gooch shading model :根據頂點法線方向和光源的相對比特置,來决定模型的錶面顏色,法線朝向光源,暖色調,背向光源,冷色調

模型的錶面顏色一般受視角方向和光照方向的影響

這就是一個簡單的光照模型,其中錶示限制值為0-1,這其中出現了兩次差值,一次是Ccool和Cwarm的差值,然後是 hightlight 和 diffuse 的差值,可以用 lerp 錶示

r 錶示反射光,也可以用reflect (-l,n)錶示 ,t就是一個半蘭伯特模型

5.2 Light Sources

現實生活中的光照計算非常複雜,可能有不同的光源類型,不同的光源形狀(shape)、顏色(color)、强度(intensity),還要考慮間接光的影響(PBS)

光照計算複雜的原因還有讓shading model 以binary way 的方式,在有光照和沒光照兩種情况下做出不同的交互,比如陰影的錶現

光照從出現到消失,可以用intensity 的差值錶示,比如

 錶示不受光照影響時的錶面顏色,根據你想實現的效果,可以賦予它不同的值,比如(0,0,0)錶示不受光照時為純黑色,一般來講,這部分代錶了間接光照的效果,比如天空盒的顏色,或者反射的其它物體的光

如果光源方向l 和錶面法線n 的角度超過90度,則該光源就不再影響該錶面的顏色

光源到達錶面的光線的密度叫做輻射度,它决定了光照的强度(intensity) ,從上圖可以看出,輻射度和cos(l 與 n 之間的角度成反比),和 l 與 n  之間的角度成正比

更准確的說 當 n點乘l 為正值,也就是角度小於90度時,輻射度成正比,當點乘為負值時,光照對錶面沒有任何影響,錶現為黑色,所以上面模型使用了半蘭伯特模型,提高暗處的亮度

+ 好錶示,最小值為0,上式錶示多個光照使用了同樣的光照模型,所以用了積分

5.2.1 Directional Lights 

平行光:光照方向l 和 光源顏色 Clight 都是一個常量,除了Clight 會在陰影處衰减,相對來說  平行光是沒有比特置屬性的,因為它到錶面的距離要遠遠大於場景的大小,它也可以擴展為 Clight 是可變的,根據不同的場景,比如模擬太陽落山

5.2.2 Punctual Lights(point light)

有比特置,有方向,向各個方向發射的光强都是一樣的,點光源和聚光燈(spot light)是同種類型的光源

 光照方向L ,根據錶面點的比特置不同而不同,計算一個向量的長度,可以用

r錶示 錶面到光源的距離,除了應用在光照方向上面,它也决定了光照的衰减

Clight 根據r變化而變化,光的輻射度和r的平方成反比,距離越遠,光的强度越弱,不像平行光,光强只在一個方向上有變化,點光源是在一個二維的平面上有變化,光的輻射度和R的平方成反比

 上式也叫inverse-square light attenuation(平方衰减),r 錶示當前的距離光源的距離,R0錶示 點光源的半徑,雖然從技術上來說,點光源的距離衰减是正確的,但是對於實際的陰影使用來說,有一些問題使得這個方程不那麼理想

比如:當r 接近於0時,Clight 就是無限大,當r為0時,除數就為0了,這是不正確的,為了解决這個問題

我們加上了一個常數E,UE引擎中,E =1 cm ,根據不同的引擎,實際的值也不同

 也有的引擎使用上述公式,解决r=0的問題,Rmin 錶示光源本身的半徑,比如一個球是一個光源,光源的輻射半徑是R0,球的半徑是Rmin

第二個問題就是當r無限大的時候,Clight接近於沒有,但是永遠不為0,為了渲染的效率,我們希望在超過一個距離是,顏色直接為0 ,而不是繼續計算它,但是要避免在該邊界處,光照的突變,要有個過渡

還有一些引擎使用

 fdist(r) 叫做 distance falloff functions,而不是平方衰减函數,根據引擎的性能做出選擇

比如遊戲《Just Cause 2》,采用下圖的方式,讓過渡平滑的同時,節省些性能

 Spotlights

聚光燈,光强不僅和光源到錶面的距離有關,還和關照方向有關

Fdir(l):錶示光照隨著光照方向的改變的函數

聚光燈的光照區域是關於中間的光照方向左右對稱的,是一個錐體,中間的光照方向我們用向量s 錶示,所以 Fdir(l) 可以錶示成 光照方向-l 與 s 之間的夾角 和 s 之間的關系,-l 錶示指向物體錶面的方向,l 錶示 點指向光源比特置

當然這個夾角也是有範圍的,用錶示,超過這個角度,值為0 ,也就是不受該聚光燈影響,它可以在計算距離衰减之前,就過濾掉哪些不受光源影響的錶面,因為角度超過了限制,就不用再計算距離衰减了

同時也有一個penumbra angle θp 角度,在這個角度內,光照强度為1

不同的引擎,采取的方法也不一樣

5.2.3 Other Light Types

Directional and punctual lights 對錶面顏色的影響主要受光照方向的影響,不同類型的光源有不同的計算光照方向的方法,除了上述提到的光源類型,遊戲《Tomb Raider》還有一個capsule lights 它的光照方向是距離光源最近點的方向

我們現在討論的光源都是抽象的,在現實當中 light sources 有大小,形狀,可以從多個角度照射到錶滿,在渲染中我們叫 area lights,它們的使用在漸漸增多,  Area-light 的渲染技術大致分為兩類,: 由於區域光被遮擋,模擬邊緣的陰影是一種,模擬區域光對模型錶面的影響是第二種,th. 對於光滑的鏡面錶面來說,第二種類型的照明是最明顯的,在這種錶面上,光的形狀和大小可以從它的反射中清楚地辨別出來。

5.3 Implementing Shading Models

shading model的實現:就是把我們上述的公式,用代碼錶示出來

5.3.1 Frequency of Evaluation

在設計 shading 實現的時候, 它的計算能力是根據 frequency of evaluation來劃分的,首先,確定該計算的結果,是否在整個draw call當中都是保持不變的,如果是 則這個計算可以由CPU執行(比如頂點數據),然後通過圖形API 作為uniform shader inputs傳遞下去,而GPU來計算哪些更耗費性能的計算.

在這個條件下,也有很大的範圍空間,比如 一個常量錶達式,在整個draw call 中都是一樣的,但是可以通過配置改變,這樣的計算在shader 編譯的時候,就計算了,根本不需要額外的輸入,也就是不是運行時計算的 ,在應用安裝時就計算了

另一種就是在應用運行時,計算結果實時改變的,但是計算的頻率很低,比如計算一天中的光照,就分為幾個階段

其它的類型包括計算結果實時變化的,且更新頻率很高,比如連接視圖和透視矩陣,每一幀都在變化等等,根據計算的頻率,把uniform shader inputs分組,比如合批,有助於提高性能,也能够降低GPU 更新的頻率

如果計算的結果在一個draw call 中會變化,就要通過varying shader inputs 傳遞給可編程shader 階段,理論上來講,shading computations 可以在任何可編程階段執行,每一個可編程階段有不同的 evaluation frequency:

現在大部分計算都在片元著色器中執行,頂點著色器只執行一些坐標變換或者頂點擾動,

 

 片元和頂點中計算光照的區別

造成這種錯誤的原因是在頂點著色器中就進行插值計算了,應該是在片元著色器中差值,雖然這能節省一部分性能,但是插值計算本身的消耗就是非常低的

注意 盡管vertex shader 輸出的是單比特法線,差值也會更改它們的長度,所以normals需要在 片元著色器中再單比特化一次,如果法線長度在頂點之間變化非常大,通常在差值前和差值後進行一次 歸一化(normalize),比如在 vertex 和 pixel shaders各進行一次

除了錶面法線,視角方向,以及光照方向,也需要在片元著色器中計算,通過向量相减,這個計算非常快,並且在差值之前不要歸一化

 5.3.2 Implementation Example

比如:

上述式子可以轉換成:

Csurface 通常存儲在頂點中,或者是貼圖中

上面公式實現,需要shader 動態遍曆 光源,這是用了shader的動態分支 功能,但是不適用於光源比較多的場景

shader model 不是一個單獨的過程,是需要大量的工作要做的

在shader 之前 ,我們需要先定義一些變量

shader inputs 分為兩種,一種叫 uniform inputs, 在CPU中計算,在整個draw call 中是常量,比如你定義的一些屬性,或者頂點的一些屬性,模型坐標,貼圖坐標等,另一種叫 varying inputs, 在頂點著色器和片元著色器中是可以改變的,比如世界坐標,世界空間下的法線,這些是通過計算得到的.

注意:在頂點著色器中,法線並沒有歸一化,因為它在模型中就是已經歸一化過得,如果不進行一些操作,比如頂點混合vertex blending ,nonuniform scaling,就不用進行歸一化,上面的操作會改變法線的長度

5.3.3 Material Systems

Rendering frameworks 一般很少由一個shader 渲染組成,比如一個模型,可能有多個material,material  system 就是專門來處理多個材質球,多個shading models 的情况。

shader 僅僅是流水線中的可編程階段,它是最底層的圖像API ,所以交互起來可能不太友好,material system 讓交互更加的形象,可以在面板上顯示出來

material  和shader  不是一對一的關系,有的material 由多個shader 組成,一個shader 也可以在多個material中共享

最通用的就是 parameterized materials,parameterized materials需要兩種類型的material entity,material templates 和 material instances,簡單來講就是基類和子類的關系,material templates有一組共同的參數,比如貼圖,顏色,instance 在此基礎上加上一組特有的參數

這些參數可以在運行時,通過uniform inputs 傳入進去,也可以在編譯階段,把值代入

一個比較通用的編譯時參數,就是bool 值,用來控制shader分支的,會在編譯時就代入進去,從而只編譯滿足的shader 代碼

material 的參數 和 shader的參數並不總是一對一的,比如我們聲明了屬性,還要在shader中聲明對應的變量,從而使用material的參數,這些參數可以保持不變,但也可以在shader中變化,這種變化不會體現到material 中

material system 一個很重要的功能就是:把不同的shader  function 獨立出來,然後控制它們的組合,來達到不同的目的

GPU不支持代碼在編譯後鏈接(post-compilation linking of code fragments),每一個shader 階段 都是單獨編譯的,所以通常通過 #include, #if,  #define 指令來編譯不同的代碼塊

在設計shader variants system的時候,首要問題就是怎麼在編譯時通過條件判斷執行不同的變體,或者在運行時通過動態分支選擇不同的變體,在之前硬件不發達的情况下,動態分支判斷非常慢,所以都是在編譯時判斷,在現在,動態分支的判斷變得非常快,尤其當所有的pixel 都是走同一個分支時,但是如果判斷多了,也就意味著 寄存器多了,則 占有率就下降了,性能也就下降了,所以 編譯時判斷 也是非常重要的,它避免了一些不必要的變體計算

現在的 material system 同時包含了運行時變體和編譯時變體,雖說負擔從編譯時轉向了運行時,但隨著變體的增加,編譯時的變體仍然很多

material system 通過采取不同的策略,來减少編譯的數量了大小,比如:

• Code reuse—把公用的功能寫在一個文件中,然後通過 #include 訪問

• Subtractive—使用編譯時預處理,或者動態分支,來删除掉哪些用不到的變體.

• Additive—通過圖形軟件

除了上述考慮,還要考慮硬件的適用性,需要支持不同的平臺,並且是複制到代碼最小化

5.4 Aliasing and Antialiasing

想象一個大的黑色三角形在白色背景上緩慢移動。當一個像素被三角形覆蓋時,錶示該像素的顏色值,應該平滑的過渡到黑色,但是通常發生的情况是,當像素的中心被覆蓋時,像素顏色立即從白色變成黑色。像素的顏色在這種變化中非黑即白的錶現就叫鋸齒。鋸齒是顏色突變造成的

5.4.1 Sampling and Filtering Theory

渲染圖片其實就是采樣的過程

上圖顯示了一個連續的信號,每個一段時間采樣一次,形成了一個離散的數字信號,然後再通過filtering 重新形成一個(reconstructed)連續的信號

當采樣結束時,就會出現鋸齒,一個經典的案例就是用電影攝影機拍攝的車輪

因為輪子的轉速要遠遠高於攝像機記錄的速度,所以你看起來輪子會出現 倒轉,不轉  ,或者轉的很慢的情况,這是因為車輪的圖像是在一系列的時間步驟中拍攝的,這種效果被稱為temporal aliasing

 在計算機圖形中,temporal aliasing常見例子有栅格化線或三角形邊緣的“鋸齒”、被稱為“螢火蟲”的閃爍的高光,以及帶有格子圖案的紋理被縮小

當以很低的頻率采樣時,鋸齒就會出現,源新號被采樣之後,形成了一個比原來頻率低很多的新的新號,為了讓一個新號被正確采樣,采樣的頻率需要是原來信號頻率的至少兩倍,這個理論叫做  sampling theorem, 這個采樣頻率叫 Nyquist rate 或者 Nyquist limit,在原理論中,Nyquist limit 被稱之為maximum frequency,也就是采樣頻率也是有上限的,band-limited

 也就是說提高采樣率,是能够完美重構出原來的信號曲線的

采用點采樣,是無論被采樣的物體多麼小,我們都可以采樣成功

Reconstruction

 給定一個band-limit采樣信號,我們現在將討論如何從采樣信號重建原始信號

為了reconstruction必須使用filter, 請注意,濾波器的值應該始終為1,也就是和原信號的值,保持一致,否則重建信號可能會出現增大或縮小。

 

上面這個filter 濾波器非常差,因為重構出來的圖像是非連續的,使用box filter 的原因是因為它簡單,方法就是把濾波器 和 采樣後的信號的y值重合

上面的這個叫做 low-pass filter (低通濾波器),它的頻率是sin(2πf),f是濾波器的頻率,鑒於此,低通濾波器去除所有頻率高於濾波器定義的特定頻率的頻率成分。

 為什麼叫低通濾波器?它其實是box filter,當它與信號相乘時,它會去除濾波器寬度以上的所有頻率

在使用濾波器後,得到了一個連續的信號。然而,在計算機圖形學中,我們不能直接顯示連續信號,但我們可以對連續信號重新采樣到另一個size的信號

Resampling

Resampling 用來放大或者縮小信號,這裏的放大或者縮小不是指y值上的放大縮小,而是指采樣的間隔,比如原始信號的坐標x 為 (0, 1, 2, . . . ),resample 之後,我們的得到的采樣點間隔a, a > 1, 就是downsampling, a < 1, upsampling.

downsample  發生在原始信號的頻率非常高,這時我們只能降低采樣的頻率,結果就是是原來的圖像稍微模糊一點

5.4.2 Screen-Based Antialiasing

如果圖像的邊緣不經過采樣和過濾器處理,就會出現鋸齒,有一個基於屏幕的通用thread,也就是 對渲染管線輸出的結果進行操作.同時需要明確的是 沒有一種抗鋸齒的方法是十全十美的,都有各自的有點和缺點,根據不同的情形,選擇不同的算法

 上面講的黑色三角形在白色背景上緩慢移動的例子,一個原因是采樣頻率太慢,另一個是采樣點只有中心點一處,通用的策略是,多個個采樣點,然後共同决定該像素的顏色

 n 是采樣點的數量,w 是該采樣點决定最終顏色的權重,所有點的權重加起來和為1, c(i,x,y)在該比特置下的顏色,也就是不同的比特置的采樣點的顏色是不同的

如果只有一個采樣點,那麼返回的是該像素中心點的顏色值,w=1

這種計算一個像素中多個采樣點的抗鋸齒方法,稱為 過采樣或者超級采樣(supersampling (or oversampling)),也叫full-scene antialiasing (FSAA)或者 “supersampling antialiasing” (SSAA)

比如:渲染一個高分辨率的場景圖像,然後經過重采樣,生成一張屏幕大小的圖像,比如 需要一張1280*1024的圖像,你先生成了一張2560 × 2048的圖像,也就是 原一個像素包含了四個像素的大小,所以每個像素要采樣四次,然後通過box filter 過濾,這種方法非常耗性能,因為每次采樣都需要重走一遍pass,並且各自包含了z-depth,所以FSAA的優點就是簡單

過采樣的原理就是利用 accumulation buffer(累計緩存),它的大小和屏幕大小相同,而不是屏幕外的大圖像,只不過每一個顏色通道包含了更多的bit,比如一個4個采樣點的圖像,生成了四張image,這四張圖像是在不同的采樣點下生成的,需要把這四張圖像的數據拷貝到屏幕上,這也是耗費性能的地方。所以這種方式視情况而定,如果性能不是很吃緊,就可以使用

Multisampling antialiasing (MSAA) 通過執行一次shader pass ,然後結果在采樣點之間共享,從而减少了性能耗費。

 

 同樣也是四個采樣點,擁有各自的color 和z-depth,但是 shader pass 只執行一次,如果所以的采樣點都被顏色覆蓋了,則以中間點的數據為准,計算color,如果只是覆蓋了一部分,則GPU  會自動調整采樣點的比特置,這種行為叫做 centroid sampling 或者 centroid interpolation

MSAA 比FSAA 快的地方就是,它只進行一次shader pass,這種思想進一步提昇為不需要為每一個采樣點都保留color 和 z-depth,只需保存不同的color 和 z-depth,從而脫離的采樣點的比特置,這種方式叫EQAA , 如果超過存儲的顏色數量,則該采樣點作廢,比如你有一萬個采樣點,這是不現實的。

當所有的采樣點 都計算完畢,就需要計算最終的顏色值了,根據上面的算法,但是如果使用了HDR的顏色時,需要先映射到0-255範圍內,這非常耗性能,所以一般是貼圖映射

采樣之後就需要filter了,之前使用的是box filter,在現代GPU中,你可以使用任意類型的filter,

更好的效果是記錄一下上一幀的結果,然後和當前幀進行混合,只不過權重不一樣

Sampling Patterns

采樣模式,就是我們上圖的那樣,就是采樣點的比特置不同,以及數量不同,好的選擇是避免兩個采樣點離得太近,這樣意義不大,一般采樣模式,內置於硬件當中

 

5.5 Transparency, Alpha, and Compositing 

一種照射半透明物體的方法叫screen-door transparency,這種方法簡單,缺點是只能渲染一個半透明物體

另一種更常見的方法是,采用透明度混合的方法

一個像素的 alpha 值不僅僅錶示透明度,也可以錶示顏色的覆蓋度,比如肥皂泡覆蓋了四分之三的像素, 也就是0.75的部分接近於透明,能够讓十分之九的光線進入眼睛,所以它是十分之一的不透明, 則它的 alpha是 0.75 × 0.1 = 0.075. 但是,當我們使用 MSAA 采樣算法時,我們需要考慮采樣點本身的透明度, 四分之三的采樣點都受泡泡的影響,因此,每一個采樣點的透明度都是0.1

5.5.1 Blending Order

 Cs 是當前shader計算得到的顏色值,Cd 是color buffer中原來的顏色

當模擬透過玻璃看其他物體的效果是,上述公式就不怎麼管用了,在現實世界中,透過紅色玻璃觀看藍色物體,藍色物體通常看起來很暗,因為這個物體反射的光很少、

另一種常用地混合方法,是相加

它適用於發光的物體,能够讓顏色更亮更鮮豔

為了時渲染效果正確,我們必須在所有的不透明物體渲染完畢後,再渲染,但是這種問題就是,z-buffer中只能存儲一個物體的深度值,當有多個不透明物體時,就不能渲染出正確的結果了

一種方法就是對渲染物體進行排序,但是如果物體穿插,就無法進行排序了,首先要關閉深度寫入,因為這種大致的排序比較簡單,所有也經常用,還有就是拆分模型,另一個方法就是渲染背面,再渲染前面

上圖錶示 source alpha 為0.7  destination alpha 為0.6  ,然後這個片元整體的透明度為0.88

5.5.2 Order-Independent Transparency

處理透明度混合的方法有很多種,都有一定的優點和缺點,一種是根據z-depth 剝離層,一個pass可以剝離很多層,然後進行混合,這種方法缺點是性能消耗高,pass數不確定

一種是額外開辟一個緩存,k-buffer,用來存儲該像素覆蓋的各個片元,相當於一個鏈錶,然後計算平均顏色值,這個不好的地方是需要額外的內存,並且內存上限不可預知

一種是上述方法的改進,對各個fragment 的值進行加權,根據距離加權和根據透明度加權,計算最後的值,但是如果在一個大場景中,距離差不多,透明度也差不多,最後的權值差不多,會和真實場景有點區別

5.6 Display Encoding

當我們計算光照,紋理或者其它顏色操作時,我們需要在 linear  空間下. 為了避免產生不同的效果,display buffers 和 textures 使用 nonlinear encodings 的情况,我們必須考慮進去. 也就是說: shader 輸出的color 在 [0, 1] 範圍內,然後乘以一個  1/2.2 次幂,也就是大約0.45, 這個叫做 gamma correction(伽馬校正).

在最初的CRT階段,也就是二極管階段,輸出的顏色和輸入的電壓乘一個幂率的關系,大概是2.2

也就是你的顏色本來是0.5,輸出的顏色為0.25左右,

簡而言之: 我們sRGB 的圖,它是經過display transfer function 的,也就是已經處理過了,要比線性空間要暗一些,我們采樣的時候,會把值轉換到線性空間,然後當我們最終寫入到framebuffer的時候,進行一次伽馬校正,讓顏色提亮一些,最後,經過屏幕顯示出來的,就正常了

版权声明:本文为[路人張德帥]所创,转载请带上原文链接,感谢。 https://gsmany.com/2022/01/202201072058247764.html