前言
最近在工作中越來越多地接觸到一些3D以及相比常見特性更酷炫的效果翰灾,因此萌發(fā)了想要自己從0開始打造一個(gè)渲染引擎的念頭穆役,一方面是為了更好地實(shí)現(xiàn)公司業(yè)務(wù)的需求,另一方面則是可以學(xué)到整個(gè)渲染流水線上的方方面面站楚。
因?yàn)橹白龅拇蟛糠侄际窃谔匦н@塊单起,對OpenGL會(huì)比較熟悉一些,但是放大到渲染引擎上就很多方面不熟悉了蛀柴,甚至有些是完全0基礎(chǔ)螃概,在給自己今年定下這么一個(gè)目標(biāo)時(shí)也是一頭霧水,有種瞬間不知道該向哪里邁出第一步的感覺鸽疾。因此這幾天基本都在尋找和渲染引擎甚至游戲引擎相關(guān)的一些基本信息和資料吊洼,自己給自己提問題,再從問題出發(fā)去尋找答案制肮,一步步地去理清自己的思路冒窍,從而形成了這一份文章。
文章里面的很多內(nèi)容都是自己整理各位大神的文章/回答中的摘要而來(所有引用均會(huì)注明出處豺鼻,在此對各位大神表示感謝和仰望)综液,而且只靠一篇文章是肯定不足以完全講述清楚整個(gè)引擎所需要了解到的內(nèi)容的,只是從我自己的角度來說儒飒,這篇文章之于之后構(gòu)建渲染引擎之路來說谬莹,無異于撥云見日的作用。
另外這系列文章假定各位對OpenGL具有一定的基礎(chǔ)桩了,因此不會(huì)過多地介紹OpenGL的一些基礎(chǔ)知識(shí)如渲染流程附帽、著色器用法等。
從問題出發(fā)
且不論游戲引擎這樣的龐然巨物井誉,就只說渲染引擎所需要涉及的內(nèi)容就非常蕉扮,你可能要考慮怎么去封裝渲染API,還要考慮怎么去整合各類效果系統(tǒng)颗圣,初版效果出來了慢显,要不要考慮搞搞Vulkan搞搞Metal爪模,什么樣的方案才是最佳的等等欠啤。
因此如果沒有一個(gè)方向就開始尋找資料的話和大海撈針沒有什么區(qū)別荚藻,也容易因?yàn)橐粫r(shí)間接收的資訊過多進(jìn)而打擊信心和興趣,因此在開始找資料之前我給自己定了幾個(gè)問題洁段,以這個(gè)幾個(gè)問題為方向去找資料為自己解答:
- 一個(gè)渲染引擎是由哪些系統(tǒng)組成的应狱?這些系統(tǒng)都是不可或缺的嗎?
- 實(shí)現(xiàn)一個(gè)渲染引擎需要點(diǎn)亮哪些技能點(diǎn)祠丝?哪些是必須技疾呻?哪些可以邊做邊點(diǎn)亮?
- 渲染引擎的基本渲染流程是什么樣的写半?
- 目前有哪些出色的渲染引擎岸蜗?怎么篩選參考引擎?參考哪些叠蝇?
而在尋找答案的過程中璃岳,自然而然就也會(huì)接觸到其他方面的內(nèi)容,比如一些自己之前見都沒見過的名詞悔捶,或者是大多數(shù)引擎都在用的成熟方案等等铃慷,這些也都會(huì)一并記錄下來。
啟程
其實(shí)這篇文章是有一個(gè)檢驗(yàn)收獲的方式的蜕该,這個(gè)方式在一開始是沒有的犁柜,但是在找資料的過程中看到知乎大佬的回答有所感悟,在這篇文章結(jié)束時(shí)再回過頭來看看下面這句話堂淡,如果大致明白了馋缅,那么這篇文章就起到作用了:
而這段話正是這一輪資料搜尋下來個(gè)人覺得最精煉的對渲染流程的描述了,至于里面具體各個(gè)名詞所指則會(huì)在后面的內(nèi)容進(jìn)行補(bǔ)充绢淀,當(dāng)然各個(gè)渲染引擎的流程在細(xì)節(jié)之處甚至一些環(huán)節(jié)上都會(huì)做出有所區(qū)別萤悴,這些則會(huì)在后面針對一些參考引擎做分析的時(shí)候再逐步整理出來。
那么更啄,啟程稚疹!
渲染引擎的技能樹
先貼一下找到的 知乎上的回答 :
是不是有種看一眼就想放棄的沖動(dòng) ?(╯°Д°)╯︵ ┻━┻
但是正如我們在日常開發(fā)中突然遇到一個(gè)新的技術(shù)需求的時(shí)候一樣祭务,一般我們也不會(huì)把一個(gè)技術(shù)學(xué)到精通來才開始落地内狗,而是會(huì)先預(yù)研一下方案,做下對比义锥,掌握好基礎(chǔ)柳沙,對一些風(fēng)險(xiǎn)提前做好功課,保證好大方向上的正確性拌倍,然后就會(huì)開始逐步落地赂鲤,在過程中慢慢打磨噪径。
類比到上面的內(nèi)容也是如此,按照我自己的想法数初,我覺得首先C++的基本語法找爱、內(nèi)存管理、標(biāo)準(zhǔn)庫的使用等基礎(chǔ)內(nèi)容以及OpenGL的渲染管線和基本的glsl泡孩、矩陣這些是在開始之前必須要先打好底子的车摄,否則可能會(huì)連參考第三方開源引擎都成問題,更不要說自己去寫了仑鸥,而在自己寫項(xiàng)目或?qū)W習(xí)第三方開源項(xiàng)目的過程中則可以對遇到的不明白或者不完全理解的內(nèi)容進(jìn)行進(jìn)一步的學(xué)習(xí)吮播,不求快但求吃透每一點(diǎn),以這樣的方式去持續(xù)擴(kuò)大或加深自己的技能眼俊,在這個(gè)過程中盡量以點(diǎn)及面意狠,每一個(gè)細(xì)節(jié)盡可能多去了解一些邊邊角角涉及到的地方,以便自己能夠盡快完成一個(gè)又一個(gè)的閉環(huán)疮胖,最終對整個(gè)領(lǐng)域的理解越來越清晰环戈。
其次就是合理安排出一部分時(shí)間來進(jìn)行理論方面的系統(tǒng)學(xué)習(xí),比如計(jì)算機(jī)圖形學(xué)等获列,系統(tǒng)性的學(xué)習(xí)個(gè)人還是比較推薦通過書籍來進(jìn)行谷市,雖然周期更長,但是可以讓你對這個(gè)領(lǐng)域的方方面面能夠有一個(gè)更清晰的認(rèn)識(shí)击孩,從而形成一個(gè)更大的閉環(huán)迫悠。
而至于dx、metal巩梢、編譯知識(shí)這些如果暫時(shí)不需要做這方面的需求的話倒不是非常關(guān)鍵创泄,可以放到上面的技能學(xué)習(xí)之后,甚至可能很長一段時(shí)間你都不會(huì)使用到括蝠,當(dāng)然也可能你現(xiàn)在是需要用dx而不需要opengl鞠抑,那么就是dx的基礎(chǔ)知識(shí)是必須的opengl排到后面,總之因人而異吧忌警。
3D引擎著色方式的演化史
這部分內(nèi)容是我在尋找渲染流程過程中的額外收獲搁拙,雖然說的是演化史,但是里面的內(nèi)容正好是對 啟程
章節(jié)里對渲染引擎主要流程的描述的擴(kuò)展法绵,通過對多種著色方式的了解箕速,可以間接對渲染流程里的幾個(gè)主要步驟有一個(gè)初步的感知。
該章節(jié)的內(nèi)容起源是3D渲染引擎著色方式的演化史朋譬,原文對各個(gè)著色方式的介紹比較簡明扼要盐茎,在閱讀完這篇文章后我又自己去對里面的各個(gè)名詞和許多不理解的地方進(jìn)行了搜索,因此大綱上還是會(huì)按照這篇文章的幾大部分進(jìn)行徙赢,在內(nèi)容里面再補(bǔ)上自己找到的額外的資料字柠,主要來源于 實(shí)時(shí)渲染中常用的幾種Rendering Path 和其他針對其中細(xì)節(jié)講解的文章探越。
這部分個(gè)人認(rèn)為在這篇文章的階段不需要深究里面的實(shí)現(xiàn)細(xì)節(jié),這個(gè)可以在后續(xù)分析開源引擎流程以及自主實(shí)現(xiàn)的時(shí)候去深入研究窑业,在這里更多地是對這些Render path
有一個(gè)印象并且知道他們的大致渲染原理钦幔、彼此之間的區(qū)別以及能解決什么樣的問題,從而能在后面的引擎中因地制宜地使用不同的著色方式数冬。
Rendering Path
Rendering Path
其實(shí)指的就是渲染場景中光照的方式节槐。由于場景中的光源可能很多瘫筐,甚至是動(dòng)態(tài)的光源排抬。所以怎么在速度和效果上達(dá)到一個(gè)最好的結(jié)果確實(shí)很困難皮迟。以當(dāng)今的顯卡發(fā)展為契機(jī),人們才衍生出了這么多的 Rendering Path 來處理各種光照秸架。
在介紹各種光照渲染方式之前,首先必須介紹一下現(xiàn)代的圖形渲染管線咆蒿。這是下面提到的幾種 Rendering Path 的技術(shù)基礎(chǔ)东抹。
現(xiàn)代的渲染管線也稱為可編程管線(Programmable Pipeline)
,簡單點(diǎn) 說就是將以前固定管線寫死的部分(比如頂點(diǎn)的處理沃测,像素顏色的處理等等)變成在 GPU 上可以進(jìn)行用戶自定義編程的部分缭黔,好處就是用戶可以自由發(fā)揮的空間增大,缺點(diǎn)就是必須用戶自己實(shí)現(xiàn)很多功能蒂破。
下面簡單介紹下可編程管線的流程馏谨。以 OpenGL 繪制一個(gè)三角形舉例。首先用戶指定三 個(gè)頂點(diǎn)傳給 Vertex Shader
附迷。然后用戶可以選擇是否進(jìn)行Tessellation Shader
(曲面細(xì)分可能會(huì)用到)和 Geometry Shader
(可以在 GPU 上增刪幾何信息)惧互。緊接著進(jìn)行光柵化
,再將光柵化后的結(jié)果傳給 Fragment Shader
進(jìn)行 pixel
級(jí)別的處理喇伯。 最后將處理的像素傳給 FrameBuffer
并顯示到屏幕上喊儡。
名詞解釋
Geometry:即我們所要渲染的一個(gè)幾何圖形
Vertex Shader:頂點(diǎn)著色器,處理每個(gè)頂點(diǎn)稻据,將頂點(diǎn)的空間位置投影在屏幕上艾猜,即計(jì)算頂點(diǎn)的二維坐標(biāo)。
Tessellation Shader:曲面細(xì)分著色器捻悯,是一個(gè)可選的著色器匆赃,用于細(xì)分圖元
Geometry Shader:幾何著色器,是一個(gè)可選的著色器秋度,用于逐圖元的著色炸庞,可以產(chǎn)生更多的圖元
Fragment Shader:片段著色器,也稱為像素著色器(Pixel Shader)荚斯,用于計(jì)算“片段”的顏色和其它屬性埠居,此處的“片段”通常是指單獨(dú)的像素
后面文章內(nèi)容中出現(xiàn)的VS查牌、TS、GS滥壕、FS(PS)即對應(yīng)上圖中的Vertex Shader纸颜、Tessellation Shader、Geometry Shader绎橘、Fragment Shader
FrameBuffer:幀緩沖存儲(chǔ)器胁孙,簡稱幀緩存或顯存,它是屏幕所顯示畫面的一個(gè)直接映象称鳞,又稱為位映射圖(Bit Map)或光柵涮较。幀緩存的每一存儲(chǔ)單元對應(yīng)屏幕上的一個(gè)像素,整個(gè)幀緩存對應(yīng)一幀圖像冈止。
Forward rendering
這是最初始的渲染方式狂票,原理是以mesh
為單位進(jìn)行渲染,在光柵化后熙暴,對每個(gè)PS
進(jìn)行計(jì)算時(shí)闺属,根據(jù)光照
進(jìn)行著色計(jì)算,所以這種方式稱為前向著色
周霉。
Forward Rendering 是絕大數(shù)引擎都含有的一種渲染方式掂器。要使用 Forward Rendering,一般在 Vertex Shader
或 Fragment Shader
階段對每個(gè)頂點(diǎn)或每個(gè)像素進(jìn)行光照計(jì)算俱箱,并且是對每個(gè)光源進(jìn)行計(jì)算產(chǎn)生最終結(jié)果国瓮。下面是 Forward Rendering 的核心偽代碼:
For each light:
For each object affected by the light:
framebuffer += object * light
比如在 Unity3D 4.x 引擎中,對于下圖中的圓圈(表示一個(gè) Geometry
)匠楚,進(jìn)行 Forward Rendering 處理:
將得到下面的處理結(jié)果:
也就是說巍膘,對于 ABCD 四個(gè)光源我們在 Fragment Shader 中我們對每個(gè) pixel 處理光照, 對于 DEFG 光源我們在 Vertex Shader 中對每個(gè) vertex 處理光照芋簿,而對于 GH 光源峡懈,我們采用球調(diào)和(SH)函數(shù)進(jìn)行處理。
這種方式存在以下弊端:
- 如果像素被其他像素遮蔽了与斤,就浪費(fèi)了寶貴的處理結(jié)果
- 光源多起來后管理很麻煩肪康,Shader也不好寫。
因此撩穿,Deferred rendering就應(yīng)運(yùn)而生了磷支。
很明顯,對于 Forward Rendering食寡,光源數(shù)量對計(jì)算復(fù)雜度影響巨大雾狈,所以比較適合戶外這種光源較少的場景(一般只有太陽光)。
但是對于多光源抵皱,我們使用 Forward Rendering 的效率會(huì)極其低下善榛。因?yàn)槿绻?Vertex Shader 中計(jì)算光照辩蛋,其復(fù)雜度將是
O(num_geometry_vertexes ? num_lights)
,而如果在 Fragment Shader 中計(jì)算光照移盆,其復(fù)雜度為O(num_geometry_fragments ? num_lights)
悼院。可見光源數(shù)目和復(fù)雜度是成線性增長的
咒循。對此据途,我們需要進(jìn)行必要的優(yōu)化。比如
多在 Vertex Shader 中進(jìn)行光照處理叙甸,因?yàn)橛幸粋€(gè)幾何體有 10000 個(gè)頂點(diǎn)颖医,那么對于 n 個(gè)光源,至少要在 Vertex Shader 中計(jì)算 10000n 次蚁署。而對于在 Fragment Shader 中進(jìn)行處理便脊,這種消耗會(huì)更多,因?yàn)閷τ谝粋€(gè)普通的 1024x768 屏幕光戈,將近有 8 百萬的像素 要處理。所以如果頂點(diǎn)數(shù)小于像素個(gè)數(shù)的話遂赠,盡量在 Vertex shader 中進(jìn)行光照久妆。
如果要在 fragment shader 中處理光照,我們大可不必對每個(gè)光源進(jìn)行計(jì)算時(shí)跷睦,把所有像素都對該光源進(jìn)行處理一次筷弦。因?yàn)槊總€(gè)光源都有其自己的作用區(qū)域。比如點(diǎn)光源的作用區(qū)域是一個(gè)球體抑诸,而平行光的作用區(qū)域就是整個(gè)空間了烂琴。對于不在此光照作用區(qū)域的像素就不進(jìn)行處理。但是這樣做的話蜕乡,CPU 端的負(fù)擔(dān)將加重奸绷。
對于某個(gè)幾何體,光源對其作用的程度是不同层玲,所以有些作用程度特別小的光源可以不進(jìn)行考慮号醉。典型的例子就是 Unity 中只考慮重要程度最大的 4 個(gè)光源。
名詞解釋
Mesh:網(wǎng)格辛块,任何一個(gè)模型都是由若干網(wǎng)格面組成畔派,而每一個(gè)面又有若干個(gè)三角形組成,也就是說润绵,模型是由若干個(gè)三角形面組成的
Deferred rendering
Deferred Rendering(延遲渲染)顧名思義线椰,就是將光照處理這一步驟延遲一段時(shí)間再處理。
具體做法就是將光照放在已經(jīng)將三維物體生成二維圖片之后進(jìn)行處理尘盼。也就是說將物空間
的光照處理放到了像空間
進(jìn)行處理憨愉。要做到這一步烦绳,需要一個(gè)重要的輔助工具——G-Buffer
。
G-Buffer 主要是用來存儲(chǔ)每個(gè)像素對應(yīng)的 Position莱衩,Normal爵嗅,Diffuse Color 和其他 Material parameters
。根據(jù)這些信息笨蚁,我們就可以在像空間中對每個(gè)像素進(jìn)行光照處理睹晒。
下面是 Deferred Rendering 的核心偽代碼。
For each object:
Render to multiple targets
For each light:
Apply light as a 2D postprocess
這種渲染方式相比 Forward rendering 就是在渲染mesh
時(shí)括细,并不進(jìn)行光照計(jì)算伪很,而是按照以下步驟進(jìn)行:
將深度、法線奋单、Diffuse锉试、Specular等
材質(zhì)屬性
分別輸出到GBuffer
里(其實(shí)就是幾張RT
)然后GBuffer里的深度和法線信息,累加所有光照的強(qiáng)度到一張
光照強(qiáng)度RT
上根據(jù)GBuffer里的Diffuse和Specular信息览濒,以及光照強(qiáng)度RT呆盖,進(jìn)行著色計(jì)算
名詞解釋
GBuffer:指Geometry Buffer,亦即“物體緩沖”贷笛。區(qū)別于普通的僅將顏色渲染到紋理中应又,G-Buffer指包含顏色、法線乏苦、世界空間坐標(biāo)的緩沖區(qū)株扛,亦即指包含顏色、法線汇荐、世界空間坐標(biāo)的紋理洞就。由于G-Buffer需要的向量長度超出通常紋理能包含的向量的長度,通常在游戲開發(fā)中掀淘,使用多渲染目標(biāo)技術(shù)來生成G-Buffer旬蟋,即在一次繪制中將顏色、法線繁疤、世界空間坐標(biāo)分別渲染到三張浮點(diǎn)紋理中
下面簡單舉個(gè)例子:
首先我們用存儲(chǔ)各種信息的紋理圖咖为。比如下面這張 Depth Buffer
,主要是用來確定該像 素距離視點(diǎn)的遠(yuǎn)近的稠腊。
根據(jù)反射光的密度/強(qiáng)度分度圖來計(jì)算反射效果躁染。
下圖表示法向數(shù)據(jù),這個(gè)很關(guān)鍵架忌。進(jìn)行光照計(jì)算最重要的一組數(shù)據(jù)吞彤。
下圖使用了 Diffuse Color Buffer。
這是使用 Deferred Rendering 最終的結(jié)果。
Deferred rendering 的最大的優(yōu)勢就是將光源的數(shù)目和場景中物體的數(shù)目在復(fù)雜度層面上完全分開饰恕,也就是說場景中不管是一個(gè)三角形還是一百萬個(gè)三角形挠羔,最后的復(fù)雜度不會(huì)隨光源數(shù)目變化而產(chǎn)生巨大變化。從上面的偽代碼可以看出 Deferred rendering 的復(fù)雜度為 O(screen_resolution + num_lights)
埋嵌。
這種渲染方式也有一些弊端:
- 由于硬件限制或者性能限制破加,GBuffer里保存的材質(zhì)信息有限,對于特殊材質(zhì)來說雹嗦,例如人的皮膚范舀、翡翠等,渲染結(jié)果很不好
- 延遲計(jì)算光照會(huì)大幅增加紋理帶寬和幀緩沖區(qū)帶寬的開銷
- 當(dāng)光源數(shù)量很多時(shí)了罪,光源會(huì)不斷對光照強(qiáng)度RT進(jìn)行累加锭环,也會(huì)大幅增加幀緩沖區(qū)帶寬開銷
- 由于硬件限制或者性能限制,不能使用硬件支持的MSAA泊藕,只能使用類似后期處理的FXAA或者Temporal AA
名詞解釋
MSAA辅辩、FXAA、Temporal AA都是抗鋸齒(Anti-Aliasing)技術(shù)娃圆,鋸齒的來源是因?yàn)閳鼍暗亩x在三維空間中是連續(xù)的玫锋,而最終顯示的像素則是一個(gè)離散的二維數(shù)組。所以判斷一個(gè)點(diǎn)到底沒有被某個(gè)像素覆蓋的時(shí)候單純是一個(gè)“有”或者“沒有"問題讼呢,丟失了連續(xù)性的信息景醇,導(dǎo)致鋸齒。
具體區(qū)別可見FXAA吝岭、FSAA與MSAA有什么區(qū)別?
Deferred Rendering 局限性是顯而易見的吧寺。比如我在 G-Buffer 存儲(chǔ)以下數(shù)據(jù):
這樣的話窜管,對于一個(gè)普通的 1024x768 的屏幕分辨率≈苫總共得使用 1024x768x128bit=20MB幕帆, 對于目前的動(dòng)則上 GB 的顯卡內(nèi)存可能不算什么,但是使用 G-Buffer 耗費(fèi)的顯存還是很多的赖条。一方面失乾,對于低端顯卡,這么大的顯卡內(nèi)存確實(shí)很耗費(fèi)資源纬乍;另一方面碱茁,如果要渲染更酷的特效,使用的 G-Buffer 大小將增加仿贬,并且其增加的幅度也是很可觀的纽竣;并且存取 G-Buffer 耗費(fèi)的帶寬也是一個(gè)不可忽視的缺陷。
對于 Deferred Rendering 的優(yōu)化也是一個(gè)很有挑戰(zhàn)的問題。 下面簡單介紹幾種降低 Deferred Rendering 存取帶寬的方式蜓氨。最簡單也是最容易想到的就是將存取的 G-Buffer 數(shù)據(jù)結(jié)構(gòu)最小化聋袋,這也就衍生除了 Light Pre-Pass
方法。另一種方式是將多個(gè)光照組成一組穴吹,然后一起處理幽勒,這種方法衍生了 Tile-based deferred Rendering
。
Light Pre-Pass / Deferred Lighting
這個(gè)技術(shù)是CryTek這個(gè)團(tuán)隊(duì)(該團(tuán)隊(duì)開發(fā)了CryENGINE游戲引擎港令,即下面簡稱的CE啥容,如果還不熟悉的話,那么這個(gè)團(tuán)隊(duì)開發(fā)了《孤島危機(jī)》缠借、《孤島驚魂》等游戲)原創(chuàng)的干毅,由 Wolfgang Engel 在他的 博客 中提到的,也用于解決Deferred rendering
渲染方式里的第一個(gè)弊端泼返。原理跟Deferred rendering
差不多硝逢,只是有幾處不同:
GBuffer中只有深度(Z)和法線(Normal)數(shù)據(jù),對比 Deferred Rendering绅喉,少了 Diffuse Color渠鸽, Specular Color 以及對應(yīng)位置的材質(zhì)索引值
在 FS 階段利用上面的 G-Buffer 計(jì)算出所必須的 Light properties,比如 Normal * LightDir, LightColor, Specular 等 Light properties柴罐,將這些計(jì)算出的光照進(jìn)行
alpha-blend
并存入LightBuffer
(就是用來存儲(chǔ) Light properties 的 buffer)著色過程不是Deferred rendering中類似于后處理的方式徽缚,而是渲染mesh,即將結(jié)果送到 Forward rendering 渲染方式計(jì)算最后的光照效果
相對于傳統(tǒng)的 Deferred Render革屠,使用 Light Pre-Pass 可以對每個(gè)不同的幾何體使用不同 的 Shader 進(jìn)行渲染凿试,所以每個(gè)物體的 Material properties 將有更多變化。這里我們可以看出對于傳統(tǒng)的 Deferred Rendering似芝,它的第二步是遍歷每個(gè)光源那婉,這樣就增加了光源設(shè)置的靈活性,而 Light Pre-Pass 第三步使用的其實(shí)是 Forward rendering党瓮,所以可以對每個(gè) mesh 設(shè)置其材質(zhì)详炬,這兩者是相輔相成的,有利有弊寞奸。
另一個(gè) Light Pre-Pass 的優(yōu)點(diǎn)是在使用 MSAA 上很有利呛谜。雖然并不是 100%使用上了 MSAA(除非使用 DX10/11 的特性),但是由于使用了 Z 值和 Normal 值枪萄,就可以很容易找到邊緣隐岛,并進(jìn)行采樣。
下面這兩張圖呻引,上邊是使用傳統(tǒng) Deferred Render 繪制的礼仗,下邊是使用 Light Pre-Pass 繪 制的。這兩張圖在效果上不應(yīng)該有太大區(qū)別。
其實(shí)這種方式也有弊端:
由于不透明物體在主視口中被渲染了兩次元践,會(huì)大幅增加渲染批次韭脊,不過好在CE對狀態(tài)切換管理的非常好,所以渲染批次的承載力很高
由于某些特殊材質(zhì)需要對光照進(jìn)行特殊處理单旁,比如說樹葉的背光面也會(huì)有一定的光照沪羔,所以這種方式也不太完美
印象里貌似CE對主光,例如太陽光象浑,不累加進(jìn)光照強(qiáng)度RT蔫饰,而是著色時(shí)單獨(dú)處理,這樣的話效果會(huì)提升不少愉豺,至少室外場景是完全能夠解決問題的篓吁;而對于點(diǎn)光源比較多的室內(nèi)場景,主光著色好看了就會(huì)效果很好了蚪拦,畢竟其他光照的影響占比比較小杖剪。
Tile-based deferred rendering
這個(gè)方案是對Deferred rendering
渲染方式里的第三個(gè)弊端進(jìn)行優(yōu)化的。原理就是:
- 先將整個(gè)光照強(qiáng)度RT分成很多個(gè)正方形區(qū)域驰贷,計(jì)算每個(gè)區(qū)域受哪些光源影響盛嘿,并保存起來
- 然后以每個(gè)區(qū)域?yàn)閱挝唬谝粋€(gè)批次里累加所有的光照
這樣就能減少對光照強(qiáng)度RT上某個(gè)像素頻繁讀寫的次數(shù)括袒。
TBDR 主要思想就是將屏幕分成一個(gè)個(gè)小塊 tile
次兆,然后根據(jù)這些 Depth 求得每個(gè) tile 的 bounding box
。對每個(gè) tile 的 bounding box 和 light 進(jìn)行求交锹锰,這樣就得到了對該 tile 有作用 的 light 的序列芥炭。最后根據(jù)得到的序列計(jì)算所在 tile 的光照效果。
對比 Deferred Render恃慧,之前是對每個(gè)光源求取其作用區(qū)域 light volume
蚤认,然后決定其作用的的 pixel,也就是說每個(gè)光源要求取一次糕伐。而使用 TBDR,只要遍歷每個(gè) pixel蘸嘶,讓其所屬 tile 與光線求交良瞧,來計(jì)算作用其上的 light,并利用 G-Buffer 進(jìn)行 Shading训唱。一方面這樣做減少 了所需考慮的光源個(gè)數(shù)褥蚯,另一方面與傳統(tǒng)的 Deferred Rendering 相比,減少了存取的帶寬况增。
在 一篇文章 中提到目前所有的移動(dòng)設(shè)備都使用的是 Tile-Based Deferred Rendering(TBDR) 的渲染架構(gòu)赞庶,,里面還提及了使用TBDR的一些注意事項(xiàng),感興趣的可以看看歧强,以及 針對移動(dòng)端TBDR架構(gòu)GPU特性的渲染優(yōu)化 澜薄,移動(dòng)GPU渲染原理的流派——IMR、TBR及TBDR
名詞解釋
tile:區(qū)塊摊册,即將需要渲染的畫面分成一個(gè)個(gè)的區(qū)塊
bounding box:邊界框肤京,是一個(gè)矩形框,可以由矩形左上角的xx和yy軸坐標(biāo)與右下角的xx和yy軸坐標(biāo)確定茅特。從技術(shù)上講忘分,邊界框是包含一個(gè)物體的最小矩形
light volume:體積光,散射是一種非常美麗的自然現(xiàn)象白修,在自然界中光穿過潮濕或者含有雜質(zhì)的介質(zhì)時(shí)產(chǎn)生散射妒峦,散射的光線進(jìn)入人眼,讓這些介質(zhì)看起來像攏住了光線一樣兵睛,也就是所謂的體積光肯骇。可見 游戲開發(fā)相關(guān)實(shí)時(shí)渲染技術(shù)之體積光
Hybrid deferred rendering
為了解決Deferred lighting
里面的第一個(gè)弊端卤恳,從CE3的某個(gè)版本開始累盗,換成了這種方式。理由是突琳,對于大多數(shù)物體來說若债,Deferred rendering
的方式就很好了,而對于特殊材質(zhì)拆融,則使用Deferred lighting
的方式蠢琳。這樣,既能保持很好的渲染效果镜豹,又能避免渲染批次激增傲须。
更詳細(xì)的內(nèi)容可見 Hybrid-Deferred-Rendering.pdf
Forward+
有時(shí)候,你轉(zhuǎn)了很大一個(gè)圈以后趟脂,發(fā)現(xiàn)又回到了原點(diǎn)泰讽。
好,那這就到了終極方式了——前向著色
的改進(jìn)版昔期。這個(gè)方案是ATI(著名顯卡生產(chǎn)商已卸,06年被AMD收購)發(fā)明的,已經(jīng)應(yīng)用于Ogre 2.1(開源的面向?qū)ο蟮?D引擎)硼一。UE4(大名鼎鼎的虛幻引擎)正在針對VR研發(fā)前向著色累澡,不知道是不是也是這個(gè)。
原理也很簡單:
先用
Tile-based deferred rendering
里的方式計(jì)算好每個(gè)區(qū)域受哪些光照影響然后像傳統(tǒng)的前向著色一樣渲染每個(gè)mesh——當(dāng)然般贼,要去光照列表里查找影響當(dāng)前區(qū)域的所有光照愧哟,并著色
這種方式只有上述提到的一個(gè)缺點(diǎn)奥吩,那就是可能和Deferred lighting
一樣需要渲染兩遍場景,不過以后應(yīng)該會(huì)有優(yōu)化的方案蕊梧。優(yōu)點(diǎn)則有:
渲染效果好
帶寬開銷低霞赫,尤其適用于VR這種每幀需要渲染兩遍場景的應(yīng)用
可以使用硬件支持的MSAA,質(zhì)量最高望几。
Forward+的優(yōu)勢還有很多绩脆,其實(shí)大多就是傳統(tǒng) Forward Rendering 本身的優(yōu)勢,所以 Forward+更像一個(gè)集各種 Rendering Path 優(yōu)勢于一體的 Rendering Path橄抹。
Forward+ = Forward + Light Culling
靴迫。Forward+ 很類似 Tiled-based Deferred Rendering。 其具體做法就是先對輸入的場景進(jìn)行 z-prepass楼誓,也就是說關(guān)閉寫入 color玉锌,只向 z-buffer 寫入 z 值。注意此步驟是 Forward+必須的疟羹,而其他渲染方式是可選的主守。接下來的步驟和 TBDR 很類似,都是劃分 tiles榄融,并計(jì)算 bounding box参淫。只不過 TBDR 是在 G-Buffer 中完成這一步驟 的,而 Forward+是根據(jù) Z-Buffer愧杯。最后一步其實(shí)使用的是 Forward rendering 方式涎才,即在 FS 階段對每個(gè) pixel 根據(jù)其所在 tile 的 light 序列計(jì)算光照效果。而 TBDR 使用的是基于 G-Buffer 的 Deferred rendering力九。實(shí)際上耍铜,forward+比 deferred 運(yùn)行的更快。我們可以看出由于 Forward+只要寫深度緩存 就可以跌前,而 Deferred Rendering 除了深度緩存棕兼,還要寫入法向緩存。而在
Light Culling
步驟抵乓, Forward+只需要計(jì)算出哪些 light 對該 tile 有影響即可伴挚。而 Deferred Rendering 還在這一部分把光照處理給做了。而這一部分灾炭,F(xiàn)orward+是放在 Shading 階段做的章鲤。所以 Shading 階段 Forward+ 耗費(fèi)更多時(shí)間。但是對目前硬件來說咆贬,Shading 耗費(fèi)的時(shí)間沒有那么多。
以下是 Forward+ 與 Deferred Rendering 的對比圖:
感興趣的可以再額外看看 forward框架的逆襲:解析forward渲染 這篇文章帚呼。
名詞解釋
Light Culling:剔除光照
渲染/游戲引擎調(diào)查
渲染引擎屬于游戲引擎中的一部分掏缎,本章節(jié)主要簡要整理一下找到的一些渲染引擎和游戲引擎皱蹦,具體內(nèi)在區(qū)別后續(xù)進(jìn)一步深入了解的時(shí)候再整理補(bǔ)上。
渲染引擎
- bgfx
- OGRE 3D
- osg
- The Forge
- gkEngine
- three.js
- pixi.js
- g3d
- OpenSceneGraph
- LiteScene
- webglstudio.js
- sketch.js
- PlayCanvas
游戲引擎
在Wiki上也已經(jīng)有整理了目前為止市面上已有的大量游戲引擎:Game Engine
Github上統(tǒng)計(jì)的開源游戲引擎:game-engines
參考引擎
通過上面的調(diào)查我們發(fā)現(xiàn)現(xiàn)在市面上的大小引擎數(shù)不勝數(shù)眷蜈,一個(gè)個(gè)地去看的話時(shí)間周期估計(jì)要以年為單位沪哺,首先我們要先從自身的需求出發(fā)定出一些對參考引擎所需要具備的特性的要求,然后再根據(jù)要求來篩選出幾個(gè)比較貼合我們需求的深入研究酌儒。
以我自身的角度出發(fā)辜妓,我列出來了以下一些要求:
- 開源,但是項(xiàng)目規(guī)模還未到非常龐大的程度忌怎,避免研究周期過長
- 具備一定規(guī)模的使用人數(shù)和影響力
- 保持更新籍滴,所用方案不至于落后行業(yè)太久
- 使用C++語言編寫,具備跨平臺(tái)特性
- 支持2D/3D渲染榴啸,實(shí)現(xiàn)粒子系統(tǒng)孽惰、光源、動(dòng)畫系統(tǒng)鸥印、后處理等多項(xiàng)功能中的幾種
- 具備多平臺(tái)自動(dòng)切換渲染驅(qū)動(dòng)的話更好
我從上面調(diào)查后的引擎列表里整理出了以下幾個(gè)符合語言勋功、使用人數(shù)、持續(xù)更新库说、支持效果等方面都比較符合的引擎來優(yōu)先作為研究的對象狂鞋,后續(xù)的分析系列文章也會(huì)先以這些引擎來作為目標(biāo):
-
渲染引擎
-
bgfx
- 可實(shí)現(xiàn)2D以及文字繪制,3D渲染潜的,光照等效果
- 可自動(dòng)切換Metal等渲染驅(qū)動(dòng)
-
OGRE 3D
- 老牌渲染引擎骚揍,除了渲染之外還包含動(dòng)畫系統(tǒng)和粒子系統(tǒng)
-
OpenSceneGraph
- 中文文檔,粒子系統(tǒng)等功能可通過第三方插件實(shí)現(xiàn)
-
bgfx
-
游戲引擎
-
godot
- 用的人多夏块,中文文檔疏咐,2D和3D都支持
- What are the best 3D C++ game engines with full source code access?:外網(wǎng)評價(jià)的截止2019最佳游戲引擎,下面的GoDot排第二脐供,第一是Unreal
-
Urho3D
- 歷史久遠(yuǎn)使用人數(shù)比Godot少很多浑塞,各方面表現(xiàn)比較中庸
- 輕量級(jí)項(xiàng)目,支持在該引擎的基礎(chǔ)上方便地?cái)U(kuò)展各種效果組件
-
godot
總結(jié)
至此我們完成了在邁出跨平臺(tái)渲染引擎第一步之前的鋪墊工作政己,我們梳理了渲染引擎的一個(gè)大致流程酌壕,以及這個(gè)流程里面的關(guān)于 Rendering path 等方面的細(xì)節(jié)信息,對這些內(nèi)容有了一個(gè)初步的印象歇由,同時(shí)列舉了以下令人望而卻步的技能樹卵牍,但是我們可以一步一步地吃成胖子,重要地是邁出這第一步沦泌,最后我們整理了一下渲染/游戲引擎列表糊昙,并按照自身要求從中梳理了幾個(gè)引擎來作為下一步分析研究的目標(biāo)。
接下來就是技術(shù)活了谢谦,下一篇《跨平臺(tái)渲染引擎之路:bgfx分析》將針對 bgfx 開始第一步學(xué)習(xí)研究释牺,分享其內(nèi)部的渲染流程以及分析思路等萝衩。