最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

延遲渲染是如何做到渲染一幀的

2022-01-24 14:36 作者:Lucas_dudu  | 我要投稿

延遲著色法

引用:https://learnopengl.com/Advanced-Lighting/Deferred-Shading

我們現(xiàn)在一直使用的光照方式叫做正向渲染(Forward Rendering)或者正向著色法(Forward Shading),它是我們渲染物體的一種非常直接的方式,在場景中我們根據(jù)所有光源照亮一個物體,之后再渲染下一個物體,以此類推。它非常容易理解,也很容易實現(xiàn),但是同時它對程序性能的影響也很大,因為對于每一個需要渲染的物體,程序都要對每一個光源每一個需要渲染的片段進行迭代,這是非常多的!因為大部分片段著色器的輸出都會被之后的輸出覆蓋,正向渲染還會在場景中因為高深的復雜度(多個物體重合在一個像素上)浪費大量的片段著色器運行時間。

延遲著色法(Deferred Shading),或者說是延遲渲染(Deferred Rendering),為了解決上述問題而誕生了,它大幅度地改變了我們渲染物體的方式。這給我們優(yōu)化擁有大量光源的場景提供了很多的選擇,因為它能夠在渲染上百甚至上千光源的同時還能夠保持能讓人接受的幀率。下面這張圖片包含了一共1874個點光源,它是使用延遲著色法來完成的,而這對于正向渲染幾乎是不可能的(圖片來源:Hannes Nevalainen)。

延遲渲染下1874個點光源效果

延遲著色法基于我們延遲(Defer)推遲(Postpone)大部分計算量非常大的渲染(像是光照)到后期進行處理的想法。它包含兩個處理階段(Pass):在第一個幾何處理階段(Geometry Pass)中,我們先渲染場景一次,之后獲取對象的各種幾何信息,并儲存在一系列叫做G緩沖(G-buffer)的紋理中;想想位置向量(Position Vector)、顏色向量(Color Vector)、法向量(Normal Vector)和/或鏡面值(Specular Value)。場景中這些儲存在G緩沖中的幾何信息將會在之后用來做(更復雜的)光照計算。下面是一幀中G緩沖的內(nèi)容:

幾何處理階段的G緩沖(G-buffer)

我們會在第二個光照處理階段(Lighting Pass)中使用G緩沖內(nèi)的紋理數(shù)據(jù)。在光照處理階段中,我們渲染一個屏幕大小的方形,并使用G緩沖中的幾何數(shù)據(jù)對每一個片段計算場景的光照;在每個像素中我們都會對G緩沖進行迭代。我們對于渲染過程進行解耦,將它高級的片段處理挪到后期進行,而不是直接將每個對象從頂點著色器帶到片段著色器。光照計算過程還是和我們以前一樣,但是現(xiàn)在我們需要從對應的G緩沖而不是頂點著色器(和一些uniform變量)那里獲取輸入變量了。

下面這幅圖片很好地展示了延遲著色法的整個過程:

延遲著色通過讀取G-buffer在后期處理光照

這種渲染方法一個很大的好處就是能保證在G緩沖中的片段和在屏幕上呈現(xiàn)的像素所包含的片段信息是一樣的,因為深度測試已經(jīng)最終將這里的片段信息作為最頂層的片段。這樣保證了對于在光照處理階段中處理的每一個像素都只處理一次,所以我們能夠省下很多無用的渲染調(diào)用。除此之外,延遲渲染還允許我們做更多的優(yōu)化,從而渲染更多的光源。

當然這種方法也帶來幾個缺陷, 由于G緩沖要求我們在紋理顏色緩沖中存儲相對比較大的場景數(shù)據(jù),這會消耗比較多的顯存,尤其是類似位置向量之類的需要高精度的場景數(shù)據(jù)。 另外一個缺點就是他不支持混色(因為我們只有最前面的片段信息), 因此也不能使用MSAA了。針對這幾個問題我們可以做一些變通來克服這些缺點,這些我們留會在教程的最后討論。

在幾何處理階段中填充G緩沖非常高效,因為我們直接儲存像素位置,顏色或者是法線等對象信息到幀緩沖中,而這幾乎不會消耗處理時間。在此基礎上使用多渲染目標(Multiple Render Targets, MRT)技術(shù),我們甚至可以在一個渲染處理之內(nèi)完成這所有的工作。

G緩沖

G緩沖(G-buffer)是對所有用來儲存光照相關(guān)的數(shù)據(jù),并在最后的光照處理階段中使用的所有紋理的總稱。趁此機會,讓我們順便復習一下在正向渲染中照亮一個片段所需要的所有數(shù)據(jù):

  • 一個3D位置向量來計算(插值)片段位置變量供lightDirviewDir使用

  • 一個RGB漫反射顏色向量,也就是反照率(Albedo)

  • 一個3D向量來判斷平面的斜率

  • 一個鏡面強度(Specular Intensity)浮點值

  • 所有光源的位置和顏色向量

  • 玩家或者觀察者的位置向量

有了這些(逐片段)變量的處置權(quán),我們就能夠計算我們很熟悉的(布林-)馮氏光照(Blinn-Phong Lighting)了。光源的位置,顏色,和玩家的觀察位置可以通過uniform變量來設置,但是其它變量對于每個對象的片段都是不同的。如果我們能以某種方式傳輸完全相同的數(shù)據(jù)到最終的延遲光照處理階段中,我們就能計算與之前相同的光照效果了,盡管我們只是在渲染一個2D方形的片段。

OpenGL并沒有限制我們能在紋理中能存儲的東西,所以現(xiàn)在你應該清楚在一個或多個屏幕大小的紋理中儲存所有逐片段數(shù)據(jù)并在之后光照處理階段中使用的可行性了。因為G緩沖紋理將會和光照處理階段中的2D方形一樣大,我們會獲得和正向渲染設置完全一樣的片段數(shù)據(jù),但在光照處理階段這里是一對一映射。

整個過程在偽代碼中會是這樣的:

對于每一個片段我們需要儲存的數(shù)據(jù)有:一個位置向量、一個向量,一個顏色向量,一個鏡面強度值。所以我們在幾何處理階段中需要渲染場景中所有的對象并儲存這些數(shù)據(jù)分量到G緩沖中。我們可以再次使用多渲染目標(Multiple Render Targets)來在一個渲染處理之內(nèi)渲染多個顏色緩沖,在之前的泛光教程中我們也簡單地提及了它。

對于幾何渲染處理階段,我們首先需要初始化一個幀緩沖對象,我們很直觀的稱它為gBuffer,它包含了多個顏色緩沖和一個單獨的深度渲染緩沖對象(Depth Renderbuffer Object)。對于位置和法向量的紋理,我們希望使用高精度的紋理(每分量16或32位的浮點數(shù)),而對于反照率和鏡面值,使用默認的紋理(每分量8位浮點數(shù))就夠了。

由于我們使用了多渲染目標,我們需要顯式告訴OpenGL我們需要使用glDrawBuffers渲染的是和GBuffer關(guān)聯(lián)的哪個顏色緩沖。同樣需要注意的是,我們使用RGB紋理來儲存位置和法線的數(shù)據(jù),因為每個對象只有三個分量;但是我們將顏色和鏡面強度數(shù)據(jù)合并到一起,存儲到一個單獨的RGBA紋理里面,這樣我們就不需要聲明一個額外的顏色緩沖紋理了。隨著你的延遲渲染管線變得越來越復雜,需要更多的數(shù)據(jù)的時候,你就會很快發(fā)現(xiàn)新的方式來組合數(shù)據(jù)到一個單獨的紋理當中。

接下來我們需要渲染它們到G緩沖中。假設每個對象都有漫反射,一個法線和一個鏡面強度紋理,我們會想使用一些像下面這個片段著色器的東西來渲染它們到G緩沖中去。

因為我們使用了多渲染目標,這個布局指示符(Layout Specifier)告訴了OpenGL我們需要渲染到當前的活躍幀緩沖中的哪一個顏色緩沖。注意我們并沒有儲存鏡面強度到一個單獨的顏色緩沖紋理中,因為我們可以儲存它單獨的浮點值到其它顏色緩沖紋理的alpha分量中。

請記住,因為有光照計算,所以保證所有變量在一個坐標空間當中至關(guān)重要。在這里我們在世界空間中存儲(并計算)所有的變量。

如果我們現(xiàn)在想要渲染一大堆納米裝戰(zhàn)士對象到gBuffer幀緩沖中,并通過一個一個分別投影它的顏色緩沖到鋪屏四邊形中嘗試將他們顯示出來,我們會看到向下面這樣的東西:


gBuffer幀緩沖

嘗試想象世界空間位置和法向量都是正確的。比如說,指向右側(cè)的法向量將會被更多地對齊到紅色上,從場景原點指向右側(cè)的位置矢量也同樣是這樣。一旦你對G緩沖中的內(nèi)容滿意了,我們就該進入到下一步:光照處理階段了。

延遲光照處理階段

現(xiàn)在我們已經(jīng)有了一大堆的片段數(shù)據(jù)儲存在G緩沖中供我們處置,我們可以選擇通過一個像素一個像素地遍歷各個G緩沖紋理,并將儲存在它們里面的內(nèi)容作為光照算法的輸入,來完全計算場景最終的光照顏色。由于所有的G緩沖紋理都代表的是最終變換的片段值,我們只需要對每一個像素執(zhí)行一次昂貴的光照運算就行了。這使得延遲光照非常高效,特別是在需要調(diào)用大量重型片段著色器的復雜場景中。

對于這個光照處理階段,我們將會渲染一個2D全屏的方形(有一點像后期處理效果)并且在每個像素上運行一個昂貴的光照片段著色器。

我們在渲染之前綁定了G緩沖中所有相關(guān)的紋理,并且發(fā)送光照相關(guān)的uniform變量到著色器中。

光照處理階段的片段著色器和我們之前一直在用的光照教程著色器是非常相似的,除了我們添加了一個新的方法,從而使我們能夠獲取光照的輸入變量,當然這些變量我們會從G緩沖中直接采樣。

光照處理階段著色器接受三個uniform紋理,代表G緩沖,它們包含了我們在幾何處理階段儲存的所有數(shù)據(jù)。如果我們現(xiàn)在再使用當前片段的紋理坐標采樣這些數(shù)據(jù),我們將會獲得和之前完全一樣的片段值,這就像我們在直接渲染幾何體。在片段著色器的一開始,我們通過一個簡單的紋理查找從G緩沖紋理中獲取了光照相關(guān)的變量。注意我們從gAlbedoSpec紋理中同時獲取了Albedo顏色和Spqcular強度。

因為我們現(xiàn)在已經(jīng)有了必要的逐片段變量(和相關(guān)的uniform變量)來計算布林-馮氏光照(Blinn-Phong Lighting),我們不需要對光照代碼做任何修改了。我們在延遲著色法中唯一需要改的就是獲取光照輸入變量的方法。

運行一個包含32個小光源的簡單Demo會是像這樣子的:

32個小光源測試

你可以在以下位置找到Demo的完整源代碼:

https://learnopengl.com/code_viewer.php?code=advanced-lighting/deferred

幾何渲染階段的

頂點著色器:https://learnopengl.com/code_viewer.php?code=advanced-lighting/deferred_geometry&type=vertex

片段著色器:https://learnopengl.com/code_viewer.php?code=advanced-lighting/deferred_geometry&type=fragment

還有光照渲染階段的

頂點著色器:https://learnopengl.com/code_viewer.php?code=advanced-lighting/deferred&type=vertex

片段著色器:https://learnopengl.com/code_viewer.php?code=advanced-lighting/deferred&type=vertex

延遲著色法的其中一個缺點就是它不能進行混合(Blending),因為G緩沖中所有的數(shù)據(jù)都是從一個單獨的片段中來的,而混合需要對多個片段的組合進行操作。延遲著色法另外一個缺點就是它迫使你對大部分場景的光照使用相同的光照算法,你可以通過包含更多關(guān)于材質(zhì)的數(shù)據(jù)到G緩沖中來減輕這一缺點。

為了克服這些缺點(特別是混合),我們通常分割我們的渲染器為兩個部分:一個是延遲渲染的部分,另一個是專門為了混合或者其他不適合延遲渲染管線的著色器效果而設計的的正向渲染的部分。為了展示這是如何工作的,我們將會使用正向渲染器渲染光源為一個小立方體,因為光照立方體會需要一個特殊的著色器(會輸出一個光照顏色)。

結(jié)合延遲渲染與正向渲染

現(xiàn)在我們想要渲染每一個光源為一個3D立方體,并放置在光源的位置上隨著延遲渲染器一起發(fā)出光源的顏色。很明顯,我們需要做的第一件事就是在延遲渲染方形之上正向渲染所有的光源,它會在延遲渲染管線的最后進行。所以我們只需要像正常情況下渲染立方體,只是會在我們完成延遲渲染操作之后進行。代碼會像這樣:

然而,這些渲染出來的立方體并沒有考慮到我們儲存的延遲渲染器的幾何深度(Depth)信息,并且結(jié)果是它被渲染在之前渲染過的物體之上,這并不是我們想要的結(jié)果。

在延遲渲染之前進行的幾何深度信息處理

我們需要做的就是首先復制出在幾何渲染階段中儲存的深度信息,并輸出到默認的幀緩沖的深度緩沖,然后我們才渲染光立方體。這樣之后只有當它在之前渲染過的幾何體上方的時候,光立方體的片段才會被渲染出來。我們可以使用glBlitFramebuffer復制一個幀緩沖的內(nèi)容到另一個幀緩沖中,這個函數(shù)我們也在抗鋸齒的教程中使用過,用來還原多重采樣的幀緩沖。glBlitFramebuffer這個函數(shù)允許我們復制一個用戶定義的幀緩沖區(qū)域到另一個用戶定義的幀緩沖區(qū)域。

我們儲存所有延遲渲染階段中所有物體的深度信息在gBuffer這個FBO中。如果我們僅僅是簡單復制它的深度緩沖內(nèi)容到默認幀緩沖的深度緩沖中,那么光立方體就會像是場景中所有的幾何體都是正向渲染出來的一樣渲染出來。就像在抗鋸齒教程中介紹的那樣,我們需要指定一個幀緩沖為讀幀緩沖(Read Framebuffer),并且類似地指定一個幀緩沖為寫幀緩沖(Write Framebuffer):

在這里我們復制整個讀幀緩沖的深度緩沖信息到默認幀緩沖的深度緩沖,對于顏色緩沖和模板緩沖我們也可以這樣處理?,F(xiàn)在如果我們接下來再渲染光立方體,場景里的幾何體將會看起來很真實了,而不只是簡單地粘貼立方體到2D方形之上:

復制幀緩沖信息到默認幀緩沖

你可以在這里找到Demo的源代碼:https://learnopengl.com/code_viewer.php?code=advanced-lighting/deferred_light_cube

還有光立方體的頂點著色器:https://learnopengl.com/code_viewer.php?code=advanced-lighting/deferred_light_cube&type=vertex

片段著色器:https://learnopengl.com/code_viewer.php?code=advanced-lighting/deferred_light_cube&type=fragment

有了這種方法,我們就能夠輕易地結(jié)合延遲著色法和正向著色法了。這真是太棒了,我們現(xiàn)在可以應用混合或者渲染需要特殊著色器效果的物體了,這在延遲渲染中是不可能做到的。

延遲渲染一直被稱贊的原因就是它能夠渲染大量的光源而不消耗大量的性能。然而,延遲渲染它本身并不能支持非常大量的光源,因為我們?nèi)匀槐仨氁獙鼍爸忻恳粋€光源計算每一個片段的光照分量。真正讓大量光源成為可能的是我們能夠?qū)ρ舆t渲染管線引用的一個非常棒的優(yōu)化:光體積(Light Volumes)

通常情況下,當我們渲染一個復雜光照場景下的片段著色器時,我們會計算場景中每一個光源的貢獻,不管它們離這個片段有多遠。很大一部分的光源根本就不會到達這個片段,所以為什么我們還要浪費這么多光照運算呢?

隱藏在光體積背后的想法就是計算光源的半徑,或是體積,也就是光能夠到達片段的范圍。由于大部分光源都使用了某種形式的衰減(Attenuation),我們可以用它來計算光源能夠到達的最大路程,或者說是半徑。我們接下來只需要對那些在一個或多個光體積內(nèi)的片段進行繁重的光照運算就行了。這可以給我們省下來很可觀的計算量,因為我們現(xiàn)在只在需要的情況下計算光照。

這個方法的難點基本就是找出一個光源光體積的大小,或者是半徑。

計算一個光源的體積或半徑

為了獲取一個光源的體積半徑,我們需要解一個對于一個我們認為是黑暗(Dark)的亮度(Brightness)的衰減方程,它可以是0.0,或者是更亮一點的但仍被認為黑暗的值,像是0.03。為了展示我們?nèi)绾斡嬎愎庠吹捏w積半徑,我們將會使用一個在投光物這節(jié)中引入的一個更加復雜,但非常靈活的衰減方程:

我們現(xiàn)在想要在等于0的前提下解這個方程,也就是說光在該距離完全是黑暗的。然而這個方程永遠不會真正等于0.0,所以它沒有解。所以,我們不會求表達式等于0.0時候的解,相反我們會求當亮度值靠近于0.0的解,這時候它還是能被看做是黑暗的。在這個教程的演示場景中,我們選擇作為一個合適的光照值;除以256是因為默認的8-bit幀緩沖可以每個分量顯示這么多強度值(Intensity)。

我們使用的衰減方程在它的可視范圍內(nèi)基本都是黑暗的,所以如果我們想要限制它為一個比更加黑暗的亮度,光體積就會變得太大從而變得低效。只要是用戶不能在光體積邊緣看到一個突兀的截斷,這個參數(shù)就沒事了。當然它還是依賴于場景的類型,一個高的亮度閥值會產(chǎn)生更小的光體積,從而獲得更高的效率,然而它同樣會產(chǎn)生一個很容易發(fā)現(xiàn)的副作用,那就是光會在光體積邊界看起來突然斷掉。

我們要求的衰減方程會是這樣:


在這里,是光源最亮的顏色分量。我們之所以使用光源最亮的顏色分量是因為解光源最亮的強度值方程最好地反映了理想光體積半徑。

從這里我們繼續(xù)解方程:

最后的方程形成了的形式,我們可以用求根公式來解這個二次方程:

它給我們了一個通用公式從而允許我們計算的值,即光源的光體積半徑,只要我們提供了一個常量,線性和二次項參數(shù):

它會返回一個大概在1.0到5.0范圍內(nèi)的半徑值,它取決于光的最大強度。

對于場景中每一個光源,我們都計算它的半徑,并僅在片段在光源的體積內(nèi)部時才計算該光源的光照。下面是更新過的光照處理階段片段著色器,它考慮到了計算出來的光體積。注意這種方法僅僅用作教學目的,在實際場景中是不可行的,我們會在后面討論它:

這次的結(jié)果和之前一模一樣,但是這次物體只對所在光體積的光源計算光照。

Demo最終的源碼:https://learnopengl.com/code_viewer.php?code=advanced-lighting/deferred_final

光照渲染階段片段著色器:https://learnopengl.com/code_viewer.php?code=advanced-lighting/deferred_final&type=fragment


真正使用光體積

上面那個片段著色器在實際情況下不能真正地工作,并且它只演示了我們可以不知怎樣能使用光體積減少光照運算。然而事實上,你的GPU和GLSL并不擅長優(yōu)化循環(huán)和分支。這一缺陷的原因是GPU中著色器的運行是高度并行的,大部分的架構(gòu)要求對于一個大的線程集合,GPU需要對它運行完全一樣的著色器代碼從而獲得高效率。這通常意味著一個著色器運行時總是執(zhí)行一個if語句所有的分支從而保證著色器運行都是一樣的,這使得我們之前的半徑檢測優(yōu)化完全變得無用,我們?nèi)匀辉趯λ泄庠从嬎愎庹眨?/span>

使用光體積更好的方法是渲染一個實際的球體,并根據(jù)光體積的半徑縮放。這些球的中心放置在光源的位置,由于它是根據(jù)光體積半徑縮放的,這個球體正好覆蓋了光的可視體積。這就是我們的技巧:我們使用大體相同的延遲片段著色器來渲染球體。因為球體產(chǎn)生了完全匹配于受影響像素的著色器調(diào)用,我們只渲染了受影響的像素而跳過其它的像素。下面這幅圖展示了這一技巧:


通過渲染實際球體來覆蓋光源半徑

它被應用在場景中每個光源上,并且所得的片段相加混合在一起。這個結(jié)果和之前場景是一樣的,但這一次只渲染對于光源相關(guān)的片段。它有效地減少了從nr_objects * nr_lightsnr_objects + nr_lights的計算量,這使得多光源場景的渲染變得無比高效。這正是為什么延遲渲染非常適合渲染很大數(shù)量光源。

然而這個方法仍然有一個問題:面剔除(Face Culling)需要被啟用(否則我們會渲染一個光效果兩次),并且在它啟用的時候用戶可能進入一個光源的光體積,然而這樣之后這個體積就不再被渲染了(由于背面剔除),這會使得光源的影響消失。這個問題可以通過一個模板緩沖技巧來解決。

渲染光體積確實會帶來沉重的性能負擔,雖然它通常比普通的延遲渲染更快,這仍然不是最好的優(yōu)化。另外兩個基于延遲渲染的更流行(并且更高效)的拓展叫做延遲光照(Deferred Lighting)切片式延遲著色法(Tile-based Deferred Shading)。這些方法會很大程度上提高大量光源渲染的效率,并且也能允許一個相對高效的多重采樣抗鋸齒(MSAA)。


延遲渲染 vs 正向渲染

僅僅是延遲著色法它本身(沒有光體積)已經(jīng)是一個很大的優(yōu)化了,每個像素僅僅運行一個單獨的片段著色器,然而對于正向渲染,我們通常會對一個像素運行多次片段著色器。當然,延遲渲染確實帶來一些缺點:大內(nèi)存開銷,沒有MSAA和混合(仍需要正向渲染的配合)。

當你有一個很小的場景并且沒有很多的光源時候,延遲渲染并不一定會更快一點,甚至有些時候由于開銷超過了它的優(yōu)點還會更慢。然而在一個更復雜的場景中,延遲渲染會快速變成一個重要的優(yōu)化,特別是有了更先進的優(yōu)化拓展的時候。

最后我仍然想指出,基本上所有能通過正向渲染完成的效果能夠同樣在延遲渲染場景中實現(xiàn),這通常需要一些小的翻譯步驟。舉個例子,如果我們想要在延遲渲染器中使用法線貼圖(Normal Mapping),我們需要改變幾何渲染階段著色器來輸出一個世界空間法線(World-space Normal),它從法線貼圖中提取出來(使用一個TBN矩陣)而不是表面法線,光照渲染階段中的光照運算一點都不需要變。如果你想要讓視差貼圖工作,首先你需要在采樣一個物體的漫反射,鏡面,和法線紋理之前首先置換幾何渲染階段中的紋理坐標。一旦你了解了延遲渲染背后的理念,變得有創(chuàng)造力并不是什么難事。


附加資源

  • Tutorial 35: Deferred Shading - Part 1:OGLDev的一個分成三部分的延遲著色法教程。在Part 2和3中介紹了渲染光體積?

    https://www.intel.com/content/www/us/en/developer/overview.html#gs.mtgohh

  • Deferred Rendering for Current and Future Rendering Pipelines:Andrew Lauritzen的幻燈片,討論了高級切片式延遲著色法和延遲光照。

    https://ogldev.org/


延遲渲染是如何做到渲染一幀的的評論 (共 條)

分享到微博請遵守國家法律
郧西县| 唐海县| 武山县| 定日县| 城步| 道真| 温宿县| 韩城市| 西和县| 罗城| 南郑县| 昭通市| 正蓝旗| 湾仔区| 托里县| 瑞丽市| 安陆市| 乐业县| 松潘县| 霞浦县| 沁源县| 禄丰县| 汕头市| 瓮安县| 许昌县| 益阳市| 吉首市| 雷州市| 洛宁县| 清新县| 昌邑市| 静安区| 美姑县| 灵丘县| 广宁县| 武城县| 东光县| 明溪县| 合川市| 曲阳县| 宁强县|