天天看點

渲染管線與渲染路徑渲染管線幀緩存渲染路徑

文章目錄

  • 渲染管線
    • 前言
    • 流程概述
    • CPU與GPU之間的通信
      • UE4的官方資料
    • GPU流水線
      • 頂點着色器(Vertex Shader)
      • 可選頂點處理(Optional Vertex Processing)
      • 裁剪(Clipping)
      • 螢幕映射(Screen Mapping)
      • 三角形設定(Triangle Setup)
      • 三角形周遊(Triangle Traversal)
      • 像素着色器(Pixel Shader)
      • 合并(Merging)
  • 幀緩存
  • 渲染路徑
    • 前言
    • 概述
    • 前向渲染
      • 補充
    • 延遲渲染
      • G-buffer
      • 補充
    • UE4相關渲染路徑
    • Unity相關渲染路徑
      • Unity中的前向渲染
      • Unity中的延遲渲染
    • 不同渲染路徑的特性
    • 渲染路徑比較
    • 不同渲染路徑優劣
    • 補充:其他渲染路徑

渲染管線

前言

學習完GAMES101後,一直想總結一下渲染管線相關的知識。最近在看樂樂姐的Unity Shader入門精要,覺得講的非常好,結合RTR4的書籍資料,在這裡做出一份總結。

文中的插圖主要來自于《Unity Shader 入門精要》與《RTR4》。

《Unity Shader 入門精要》配套插圖連結:

http://candycat1992.github.io/unity_shaders_book/unity_shaders_book_images.html

流程概述

在RTR3中,渲染管線分為3個階段:

渲染管線與渲染路徑渲染管線幀緩存渲染路徑

而在RTR4中,渲染管線則分為4個階段:

渲染管線與渲染路徑渲染管線幀緩存渲染路徑

可以看到,在應用階段、幾何階段、光栅化階段後,還新添加了像素處理階段。

這些階段中的每一個階段本身可能是一個管道,比如圖中的幾何階段,或者一個階段可能(部分)并行化,比如圖中的像素處理階段。而從圖中來看,應用階段似乎是單個程序,但是實際上這個階段也可以是流水線或者是并行化的。

  • 應用階段

    顧名思義,應用程式階段由應用程式驅動,是以通常在運作于 CPU 上的軟體中實作。當然一般是多核CPU去并行處理。傳統上在 CPU 上執行的一些任務包括碰撞偵測、全局加速算法、動畫、實體模拟等等,這取決于應用程式的類型。

    而這一階段中開發者有三個主要任務:

    準備場景資料(錄影機、視錐體、模型、光源等)、粗粒度剔除(culling,模型層面,剔除不可見物體)、設定每個模型的渲染狀态(每個模型使用的材質、紋理、shader等)。

    應用階段最重要的輸出是渲染所需的幾何資訊,RTR4叫做rendering primitives,中文可以翻譯為渲染圖元。通俗來講,渲染圖元可以是點、線、三角面等。

  • 幾何階段

    RTR4中将這一階段劃分為:頂點着色、投影、裁剪和螢幕映射。這一階段通常在GPU上進行。

    渲染管線與渲染路徑渲染管線幀緩存渲染路徑
    幾何階段負責和每個渲染圖元打交道,進行逐頂點、逐多邊形的操作。幾何階段的一個重要任務就是把頂點坐标變換到螢幕空間中,再交由光栅器進行處理。

這一階段将會輸出螢幕空間的二維頂點坐标、每個頂點對應的深度值、着色等相關資訊,并傳遞給下一個階段。

需要注意的是,在頂點着色器中,我們常常對每個頂點乘上一個MVP矩陣,但其實這一步是轉換到裁剪空間中,而MVP中的P矩陣projection投影矩陣雖然包含“投影”二字,但其實并沒有進行真正的投影工作,而是在為投影做準備。真正的投影其實發生在後面的齊次除法中(homogeneous division)。是以經過投影矩陣的變換後頂點的w分量(齊次坐标)會具有特殊的意義(為了之後進行齊次除法)。

  • 光栅化階段

    光栅化階段通常将三個頂點作為輸入,形成一個三角形,并找到三角形内的所有資料,如何将這些像素轉發到下一個階段。

  • 像素處理階段

    這一階段按每個像素執行程式以确定其顔色,并可以進行深度測試以檢視其是否可見。它還可以執行逐像素操作,例如将新計算的顔色與以前的顔色混合。

渲染管線與渲染路徑渲染管線幀緩存渲染路徑

RTR4的原圖如上,光栅化階段分為三角形設定與三角形周遊,像素處理階段分為像素着色和合并。

可以看到,RTR4隻是拆分成了兩個階段。在以前則隻是把像素處理階段并入光栅化階段了。

光栅化階段還需要對幾何階段傳輸來的一些頂點資料(紋理坐标、頂點顔色等)進行插值,如何再進行逐像素處理。

CPU與GPU之間的通信

渲染管線的起點是CPU,即應用階段,可大緻分為以下3個階段:

1. 把資料加載到顯存中

所有渲染所需的資料都需要從硬碟中加載到系統記憶體中。然後網格、紋理、頂點資訊等等資料又被加載到顯示卡上的存儲空間——顯存(VRAM)中。

渲染管線與渲染路徑渲染管線幀緩存渲染路徑

2. 設定渲染狀态

即指定該模型用什麼材質,光源屬性是什麼樣的之類的。

是以為了合批,我們常把一些資訊放在頂點資訊如TEXCOORD中,而去使用同一種材質。

如下,利用批處理,CPU在RAM把多個網格合并成一個更大的網格,再發送給GPU,然後在一個Draw Call中渲染它們。是以這些網格使用的都是一種狀态,是以材質必須相同。

渲染管線與渲染路徑渲染管線幀緩存渲染路徑

而若是動态物體,由于是不斷運動的,是以要合批的話每一幀都需要進行重新合并,這會有額外的空間和時間開銷。

3. 調用Draw Call

Draw Call就是一個指令,發起方是CPU,接收方是GPU,由于上一階段已經設定好了渲染狀态,是以Draw Call指令隻需要指向一個被渲染的圖元清單,告訴GPU要渲染什麼了。

而給定了一個Draw Call,GPU就會根據渲染狀态(如材質、紋理、着色器等)和所有的頂點資料去進行計算。

注意,指令緩沖區的指令有很多種類,不僅有Draw Call,還有如改變渲染狀态的指令:

渲染管線與渲染路徑渲染管線幀緩存渲染路徑

UE4的官方資料

渲染管線與渲染路徑渲染管線幀緩存渲染路徑

如上圖,從左到右:硬碟、記憶體、CPU/GPU,CPU/GPU計算完都要将資料傳回到記憶體。資料會在CPU和GPU之間來回傳輸,一部分工作在CPU上一部分在GPU上,他們之間是同步的。

線程的執行:

渲染管線與渲染路徑渲染管線幀緩存渲染路徑

GPU流水線

渲染管線與渲染路徑渲染管線幀緩存渲染路徑

頂點着色器(Vertex Shader)

需要強調的是,頂點着色器無法得到頂點與頂點之間的關系。例如無法知道兩個頂點在模型中是否連成線。但是正是這樣的互相獨立性使得這一階段具有非常好的并行性。

這一階段的兩個主要任務:一是去計算每個頂點在裁剪空間中的位置(很多時候我們就乘一個MVP矩陣轉化到齊次裁剪坐标系下,接着通常再由硬體做透視除法後得到歸一化的裝置坐标(NDC));二是去寫這個頂點需要輸出的資料(比如unity shader書寫中的v2f,vertex to fragment結構體),例如法線、紋理坐标(法線變換需要注意的是,所乘的矩陣為MV的逆轉置,用于将法線從模型空間轉換到觀察空間,而非MV)。

當然也可以在頂點着色器中就去逐頂點光照計算每個頂點的顔色,然後存儲起來,在後面的三角形中直接進行插值。傳統上一個物體的大部分着色就是這樣做的。

可選頂點處理(Optional Vertex Processing)

每一個pipeline都會有剛才所述的頂點處理,而處理完後則有一些可選階段,當然這是GPU的流水線概述,是以這些階段都是可以發生在GPU上的。

按照這樣的順序執行:tessella-tion, geometry shading, and stream output(曲面細分,幾何着色和流輸出)

它們的使用既取決于硬體的功能(并非所有 GPU 都有這些功能),也取決于程式員的願望。它們是互相獨立的,一般不常用。

  • 曲面細分着色器

    我了解中就是LOD。讓一個曲面生成适當數量的三角形。比如通過ddx和ddy來進行判斷。

  • 幾何着色器

    書上原話說的是:The next optional stage is the geometry shader. This shader predates the tessellation shader and so is more commonly found on GPUs

    應該是說幾何着色器很早就誕生出來并用于使用了,是以在GPU上更常見,但是流程上應該是在曲面細分着色器之後的。

    它有點像曲面細分着色器,因為它會使用各種不同的渲染圖元去産生新的頂點。

    幾何着色器有多種用途,其中最受歡迎的是生成粒子。想象一下模拟煙花爆炸。

  • 流輸出

    最後一個可選的階段稱為流輸出。這個階段讓我們使用 GPU 作為一個幾何引擎。

    與我們把處理後的頂點資料傳輸到螢幕去渲染不同,這一階段可以選擇把這些頂點資料輸出到一個數組中去進行更進一步的處理,而這些資料則可以在之後的pass中由CPU或者GPU使用。這個階段通常用于粒子模拟,例如我們的焰火例子。

    (這裡我也不太熟悉,不知道自己翻譯的對不對)。

裁剪(Clipping)

一個圖元和錄影機視野(視錐體)的關系有三種:完全在視野内、部分在視野内、完全在視野外。

完全在視野内的圖元就繼續傳遞給下一個流水線階段,完全在視野外的圖元則不會傳遞。是以這一步要處理(裁剪)的其實是部分在視野内的圖元。

裁剪過程示意圖:

渲染管線與渲染路徑渲染管線幀緩存渲染路徑

之前說過,齊次除法後會到一個歸一化的裝置坐标(OpenGL中叫NDC),即變化到一個立方體内,這也是上面裁剪示意圖的英文描述部分的解釋:

渲染管線與渲染路徑渲染管線幀緩存渲染路徑

并且注意上圖,透視投影得到的裁剪空間的近平面為:

(x, y, z, w) = (-Near, -Near, -Near, Near) ~ (Near, Near, -Near, Near)

遠平面為:

(x, y, z, w) = (-Far, -Far, -Far, Far) ~ (Far, Far, Far, Far)

其實際上是由原來的裁剪空間進行了縮放得到。這樣的縮放是為了友善裁剪。

比如此時如果一個頂點在視錐體内,那麼它變換後的坐标就必須滿足:

-w <= x, y, z <= w

否則就不在視錐體内。

螢幕映射(Screen Mapping)

就是把每個圖元的x、y坐标轉換到螢幕坐标系而已。

值得一提的是每個像素的中心對應的浮點數都需要加0.5。是以一個像素範圍[0, 9]其實覆寫了範圍 [0.0, 10.0)

轉換過程:

渲染管線與渲染路徑渲染管線幀緩存渲染路徑

這裡d是像素的離散(整數)索引,c是像素的連續(浮點)值。

三角形設定(Triangle Setup)

就是去計算一個三角網格所需的資訊。因為上一階段輸出的都是三角網格的頂點資訊,這一階段就是由三角形這三個點去計算出三條邊的方程(得到三角形邊界的表示方式),或是一些其他為了三角形計算的資料。

三角形周遊(Triangle Traversal)

這一階段就是根據上一階段的計算結果來判斷一個三角網格覆寫了哪些像素,并使用三角網格的3個頂點的頂點資訊來對整個覆寫區域的像素進行插值。

示意圖:

渲染管線與渲染路徑渲染管線幀緩存渲染路徑

像素着色器(Pixel Shader)

或叫片元着色器。

輸入是上一個階段對頂點着色器輸出的資料進行插值得到的結果。輸出則是一個或多個顔色值。

這一階段可以完成很多重要的渲染技術,比如紋理采樣。

而為了在這一步進行紋理采樣,通常會在頂點着色器的輸出(v2f)中包含每個頂點對應的紋理坐标,這樣就能在插值後得到對應片元的坐标了。

合并(Merging)

每個像素的資訊存儲在顔色緩沖區中(color buffer)。合并階段的職責就是去講像素着色器産生的像素顔色和目前存儲在緩沖區的顔色結合起來。

這一階段也叫做“ROP”,即“raster operations (pipeline)” 或 “render output unit,”

這個階段還可以決定每個片元的可見性。

是以這一階段主要有兩個任務:

  1. 決定每個片元的可見性。涉及到很多測試工作,如深度測試、模闆測試。
  2. 如果一個片元通過了所有的測試,就可以把這個片元的顔色值和顔色緩沖區的顔色進行合并(混合)。

這個階段不是完全可程式設計的,但是是高度可配置的。即我們可以設定每一步的操作細節。

幀緩存

當模型的圖元經過上面的層層計算和測試後,就會顯示到我們的螢幕上。我們的螢幕顯示的就是顔色緩沖區的顔色值。

但是為了避免我們看到那些正在進行光栅化的圖元,GPU會使用雙重緩沖(Double Buffering)的政策,或者叫幀緩存更合适一些。因為現在還有三重緩沖(即加一個中緩存)的情況。

預設情況下有兩個幀緩存,即Back Buffer和Front Buffer,也被我們稱為前緩存和後緩存。

顯示卡在渲染畫面的時候并不會直接交給顯示器去顯示,而是先寫入BackBuffer也就是後緩存中,等待後緩存寫入完畢,前後緩存發生交替,後緩存就變成了前緩存,前緩存就變為了後緩存。這個交替的過程被我們稱為Bufferswap(幀傳遞)。

關于幀緩存與垂直同步,強烈推薦視訊:

https://www.bilibili.com/video/BV1FK4y1x7bk?from=search&seid=3279369838866778797&spm_id_from=333.337.0.0

講的非常棒!

渲染路徑

前言

關于渲染路徑,我主要參考了《大象無形》、《Unity Shader入門精要》和may老師的TA百人計劃:

https://www.bilibili.com/video/BV1244y1i7oV?p=2

概述

渲染路徑(Rendering Path),就是決定光照的實作方式。簡言之,就是目前渲染目标使用光照的流程。

這裡主要談的是延遲渲染(Deferred Rendering)與前向渲染(Forward Rendering)。

前向渲染

流程如下圖所示:

渲染管線與渲染路徑渲染管線幀緩存渲染路徑

流程:待渲染幾何體 → 頂點着色器 → 片元着色器 → 渲染目标

在渲染每一幀的時,每一個頂點/片元都要執行一次片元着色器代碼,這時需要将所有的光照資訊傳到片元着色器中。

并且無論光源影響大不大,計算的時候都會把所有光源計算進去,這樣就會造成一個很大的浪費。

關于更多的學習可以看後文寫的Unity中的前向渲染。

補充

圖檔源自UE4實時渲染官方教程的自截圖:

渲染管線與渲染路徑渲染管線幀緩存渲染路徑

延遲渲染

我了解的延遲渲染,是為了讓物體數量與光照數量解耦。主要用來解決大量光照渲染的方案。

所謂延遲,就是将”光照渲染“延遲。每次渲染都會把物體的BaseColor、粗糙度、表面法線、像素深度等分别渲染成圖檔,然後根據這些圖再逐個計算光照。好處是無論場景中有多少物體,經過光照準備階段後都隻是變成了幾張貼圖,其實質是先找出來你能看到的所有像素,再去疊代光照。

流程如下圖所示:

渲染管線與渲染路徑渲染管線幀緩存渲染路徑

流程為:待渲染幾何體 → 頂點着色器 → MRT → 光照計算 → 渲染目标

比如一個場景有5個物體和6個光源,每個物體都單獨計算光照,那麼計算量将是5 * 6 = 30次光照計算。随着光源數量提升,這樣做的代價就會非常高昂。

而在延遲渲染中,我們首先将場景渲染一次,擷取到的待渲染對象的各種幾何資訊存儲到G-buffer中,然後第二個pass再周遊所有G-buffer中的位置、顔色、法線等參數,執行一次光照計算。

是以這樣做,第一個pass我們把每個物體渲染成多張貼圖,這裡5個物體就需要5次渲染。第二個pass我們逐光源計算光照結果,這裡6個光源則需要6次渲染。

于是就把之前的5 * 6 = 30次渲染計算量降低為5 + 6 = 11次渲染計算量。

G-buffer

G-Buffer,全稱Geometric Buffer ,它主要用于存儲每個像素對應的位置(Position),法線(Normal),漫反射顔色(Diffuse Color)以及其他有用的材質參數。

下圖為一個典型的G-Buffer:

渲染管線與渲染路徑渲染管線幀緩存渲染路徑

補充

圖檔源自UE4實時渲染官方教程的自截圖:

渲染管線與渲染路徑渲染管線幀緩存渲染路徑

UE4相關渲染路徑

虛化引擎對于場景中所有不透明物體的渲染方式,就是延遲渲染。而對于透明物體的渲染方式,則是前向渲染。

這裡搬運書本《大象無形》的插圖,為虛幻引擎延遲渲染過程流程圖:

渲染管線與渲染路徑渲染管線幀緩存渲染路徑

在編輯器中,我們可以按如下設定對一個場景打開緩沖顯示(這裡我的UE4版本是4.26.2,場景為初學者包自帶的一個場景):

渲染管線與渲染路徑渲染管線幀緩存渲染路徑

然後就能看到所有的G-buffer,并且能随着鏡頭晃動而同時更新:

渲染管線與渲染路徑渲染管線幀緩存渲染路徑
渲染管線與渲染路徑渲染管線幀緩存渲染路徑

這裡我放一張中文截圖放一張英文截圖,大家可以看一下G-buffer中存着什麼東西。

Unity相關渲染路徑

可參考官方手冊:

https://docs.unity.cn/cn/current/Manual/RenderingPaths.html

我的Unity版本:2020.1.6f1c1

每個錄影機都能設定該錄影機使用的渲染路徑:

渲染管線與渲染路徑渲染管線幀緩存渲染路徑

Graphics Settings 在 Edit -> Project Settings -> Graphics中:

渲染管線與渲染路徑渲染管線幀緩存渲染路徑

每個Pass中可以使用LightMode标簽實作指定該Pass使用的渲染路徑,如:

渲染管線與渲染路徑渲染管線幀緩存渲染路徑

除了ForwardBase,LightMode還可以選Always、ForwardAdd、Deffered、ShadowCaster、PrepassBase等。

Unity中的前向渲染

Unity中,前向渲染路徑有3種處理光照(即照亮物體)的方式:逐頂點處理、逐像素處理、球諧函數(Spherical Harmonics, SH)處理。而決定一個光源使用哪種處理模式取決于它的類型和渲染模式:

渲染管線與渲染路徑渲染管線幀緩存渲染路徑
渲染管線與渲染路徑渲染管線幀緩存渲染路徑

Unity使用的判斷規則:

  1. 場景中最亮的平行光總是按逐像素處理的
  2. 渲染模式被設定成Not Important的光源,會按逐頂點或SH處理
  3. 渲染模式被設定成Not Important的光源,會按逐像素處理
  4. 如果根據以上規則得到的逐像素光源數量小于Quality Setting中的逐像素光源數量(Pixel Light Count),會有更多的光源以逐像素的方式進行渲染。

這裡的Pixel Light Count(最大像素光照數量)可以在Edit -> Project Settings -> Quality中進行修改:

渲染管線與渲染路徑渲染管線幀緩存渲染路徑

而在Pass中計算光照有Base Pass和Additional Pass:

渲染管線與渲染路徑渲染管線幀緩存渲染路徑

對于前向渲染而言,一個Unity Shader通常會定義一個Base Pass(Base Pass也可以定義多次,例如需要雙面渲染等情況)以及一個Additional Pass。一個Base Pass僅會執行一次(定義了多個Base Pass的情況除外),而一個Additional Pass會根據影響該物體的其他逐像素光源的數目被多次調用,即每個逐像素光源會執行一次Additional Pass。

Unity中的延遲渲染

使用延遲渲染時,Unity要求我們提供兩個Pass:

  1. 第一個Pass用于渲染G-buffer。在這個Pass中我們會把物體的漫反射顔色、高光反射顔色、平滑度、法線、自發光和深度等資訊渲染到螢幕空間的G-buffer中。對于每個物體來說這個Pass隻會執行一次。
  2. 第二個Pass用于計算真正的光照模型。這個Pass會使用上一個Pass中渲染的資料來計算最終的光照顔色,再存儲到幀緩沖中。

不同渲染路徑的特性

1、後處理方式不同

如何需要深度資訊進行後處理的話,前向渲染需要單獨渲染出一張深度圖,而延遲渲染就可以直接從G-buffer中的深度資訊來進行計算

2、着色計算不同(shader)

由于延遲渲染光照計算統一是在LightPass中計算的,是以隻能算一個光照模型(如果需要其他的光照模型,隻能切換pass)

3、抗鋸齒方式不同

比如目前延遲渲染由于一些硬體原因(顯示卡的顯存、帶寬等),一般實作中都不支援MSAA,而改用如TAA的方式。

渲染路徑比較

Unity官方文檔給了如下圖:

https://docs.unity.cn/cn/current/Manual/RenderingPaths.html

渲染管線與渲染路徑渲染管線幀緩存渲染路徑

不同渲染路徑優劣

這部分總結對應may老師視訊的15:38處:

https://www.bilibili.com/video/BV1244y1i7oV?p=2

前向渲染缺點

  1. 光源數量對計算複雜度影響巨大
  2. 通路深度等資料需要額外計算

前向渲染優點

  1. 支援半透明渲染
  2. 支援使用多個光照pass
  3. 支援自定義光照計算方式(延遲渲染因為是用整個Light Pass去計算所有的光照,是以不支援每一個物體用單獨的光照方式計算)

延遲渲染缺點

  1. 對MSAA支援不友好
  2. 透明物體渲染存在問題
  3. 占用大量的顯存帶寬

延遲渲染優點

  1. 大量光照場景優勢明顯
  2. 隻渲染可見像素,節省計算量
  3. 對後處理支援良好
  4. 用更少的shader

補充:其他渲染路徑

  • 延遲光照(Light Pre-Pass / Deferred Lighting)

    減少G-buffer占用的過多開銷,支援多種光照模型

    和延遲渲染的差別:用更少的Buffer資訊,着色計算的時候用的是forward,是以第三步開始都是前向渲染(也就意味着可以對不同的幾何物體進行不同的Shader進行渲染,是以每個物體的材質屬性将有更多的變化)。

  • Forward+(即Tiled Forward Rendering,分塊正向渲染)

    減少帶寬,支援多光源,強制需要一個preZ

    通過分塊索引的方式,以及深度和法線資訊達到需要進行光照計算的片元進行光照計算。因為需要深度和法線資訊是以需要單獨渲染提出來。

    強制使用了一個preZ,進行了一個深度預計算。

  • 群組渲染(Clustered Rendering)

    帶寬相對減少,多光源下效率提升

    其實分為Forward和Deferred兩種

還有一篇知乎文章可以參考一下:https://zhuanlan.zhihu.com/p/54694743

繼續閱讀