@author: 白袍小道
查看隨意,轉載隨緣
第一部分:
這里主要關心加速算法,和該階段相關的UE模塊的結構和組件的處理。
What-HOW-Why-HOW-What(嘿嘿,老規矩)
1、渲染模塊這里有個主要任務需要完成:將需要在屏幕上(或者某設備)顯示出來的Primitives(幾何體,或者繪制圖元)輸入到pipeline的下一階段。
2、渲染的每幀,計算Camera,Light,Primitives輸出到幾何階段(非幾何着色)
插一句:Geometry State包含了視點變換,頂點着色,投影、裁剪、映射
3、幾個空間數據結構和算法:
層次包圍,入口裁剪、QuadTree, 空間分隔樹, Kd樹,八叉樹,場景圖、細節裁剪
空間數據結構:
是將幾何體組織在N維空間中的一系列層次(上抱下,下抱下下,類推)數據結構
層次包圍體BVH
入口裁剪PortalCulling
細節裁剪( 屏幕尺寸裁剪 )
具有包圍體的問題,將這個包圍體投射到投影平面,然后以像素為單位來估算投影面積,如果像素的數量小於用戶定義的閾值,那么不對這個物體進行進一步處理。
遮擋刪除
遮擋剔除必要性:
不難理解,可見性問題可以通過Z緩沖器的硬件構造來實現,即使可以使用Z緩沖器正確解決可見性問題,但其中Z緩沖並不是在所有方面都不是一個很"聰明"的機制。例如,假設視點正沿着一條直線觀察,其中,在這條直線上有10個球體,雖然這10個球體進行了掃描轉換,同時與Z緩沖器進行了比較並寫入了顏色緩沖器和Z緩沖器,但是這個從這個視點渲染出的圖像只會顯示一個球體,即使所有10個球體都將被光柵化並與Z緩沖區進行比較,然后可能寫入到顏色緩沖區與Z緩沖區。
上圖中間部分顯示了在給定視點處場景的深度復雜度,深度復雜度指的是對每個像素重寫的次數。對於有10個球體的情形,最中間的位置,深度復雜度為10,因為在這個地方渲染了10個球體(假設背面裁剪是關閉的),而且這意味着其中有9次像素寫入是完全沒有必要的。
兩種主要形式的遮擋裁剪算法,分別是基於點的遮擋裁剪和基於單元的遮擋裁剪
偽代碼--------------------------------------------------------------------------------
----------------------------------------------------------------------------------------
下面是常用幾種遮擋算法
1、硬件遮擋查詢(UE中有,也可以自己先寫寫,然后測試對照)
硬件遮擋查詢的基本思想是,當和Z緩沖器中內容進行比較時,用戶可以通過查詢硬件來找到一組多邊形是否可見的,且這些多邊形通常是復雜物體的包圍體(如長方體或者k-DOP)。如果其中沒有多邊形可見,那么便可將這個物體裁剪掉。硬件實現對查詢的多邊形進行光柵化,並且將其深度和Z緩沖器進行比較
2、HZB(同上)
層次Z-緩沖算法用八叉樹來維護場景模型,並將畫面的Z緩沖器作為圖像金字塔(也稱為Z-金字塔(Z-pyramid)),該算法因此在圖像空間中進行操作。其中,八叉樹能夠對場景的遮擋區域進行層次剔除,而Z-金字塔則可以對單個基元和邊界體積進行層次Z緩沖。 因此Z-金字塔可以作為此算法的遮擋表示。
通過從前到后遍歷八叉樹並裁剪遇到的八叉樹節點,此算法可以僅訪問可見的八叉樹節點及其子節點(右上角的節點),
的容器只對可見包圍體中的多邊形進行渲染。
3、遮擋地平線算法
通過從前到后渲染一個場景,我們可以定位到地平線在哪里進行渲染,而任何在當前地平線之后和之下的物體都可以被裁剪掉。
4、遮擋物收縮與視錐擴張算法(也有類似處理)
可以使用基於點的遮擋算法來生成基於單元的可見性,根據給定的量來縮小場景中所有遮擋物來達到延伸有效可見點的目的,
通常與Occluder Shrinking算法一起配合使用
5、LOD這個放到后面細說。【篇幅不少】
6、裁剪圖策略:后面加入
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
第二部分:
下面按照延時渲染來過一下。
涉及主要類
DeferredShadingSceneRenderer
FSceneRender,FSceneViewFamily,FViewInfo,Fscene,FView
FMeshElementCollector
SceneOcclusion
FSceneViewState
TStaticMeshDrawList
FRenderTask\FDrawVisibleAnyThreadTask
DrawingPolicy,FPrimitiveSceneProxy
一、FDeferredShadingSceneRenderer::InitViews
這里主要通過檢測可見性,透明排序等,完成視圖初始化。這里我們關注檢測可見性
1、 燈光信息的可見分配。
2、預處理可見性
位於文件:SceneVisibility.cpp
FSceneRenderer::ComputeViewVisibility(FRHICommandListImmediate& RHICmdList)
2.1 |
得到當前預處理可見性的數據 |
FSceneViewState::GetPrecomputedVisibilityData
完成任務:
*返回給定視圖位置的可見性數據數組(數據),如果不存在,則返回NULL。
*數據位通過場景中每個原語的VisibilityId進行索引。
*此方法在必要時解壓縮數據,並基於視圖狀態中的bucket和chunk(優化時候也需要注意)索引緩存數據
(這里若需要查看可以通過幾個調試選項【詳細在后面備注】,然后烘培)
如何完成:
a、計算盒子的ViewOrigin是否在格子中,這里利用反過來將盒子散列到bucket去減少了計算量。
這里盒子偏移,Bucket索引計算可以看看。
b、若有必要解壓數據
2.1后(構建有時會覆蓋截錐體,就是迭代覆蓋視口的圖元可見性Map【view.PrimitiveVisibilityMap】)
2.2 |
更新HLOD轉換/可見性狀態 |
這里開啟后是為了允許HLOD在踢襠刪除階段可以使用
2.3 |
計算使用標准視錐體裁剪的圖元的數目---FrustumCull |
接下來是引擎特性(處理view.PrimitiveVisibilityMap)
2.4a |
更新了視圖的原始fade狀態(略)。 |
2.4b |
(掃描VisibilityMap后面就說VMP)如果幾何體標記為Hide,view.PrimitiveVisibilityMap標記 |
2.4c |
視圖屬性標記了只顯示幾何體的,那其他同樣標記 |
2.4d |
反射捕獲(Reflection Captures)的處理:只對接受非移動的. |
2.4e |
剔除線框中的小框對象【就一個投射矩陣判斷】【主要是提高編輯器,因為線框模式的禁止了遮擋,我去】 |
2.4f |
(不在線框中的)進行剔除 |
2.5 |
OcclusionCull |
這里才是算法實現的開始,包括使用前面說的預計算數據,根據特征級別(opengl,dx)做不同的OC處理
a、軟處理(自己整CPU處理)FSceneSoftWareOCclusion。
b、FetchVisibilityForPrimitives處理(還有FHZBOcclusionTester)
c、標記到非OC(沒得整)組
2.5.1 |
FSceneSoftWareOCclusion |
幾個重要事情:
SubmitScene
CollectOccludeeGeom
Sort potential occluders by weight
Add sorted occluders to scene up to GSOMaxOccluderNum
reserve space for occludees vis flags
ProcessOcclusionFrame:
上面基本是數據的規整,這里才是執行算法過程
2.5.1.1 |
ProcessOccluderGeom,對着算法來一遍(嘿嘿) |
每個模型
a\ 轉換模型到裁剪空間【矩陣操作】
const FMatrix LocalToClip = Mesh.LocalToWorld * SceneData.ViewProj;
VectorRegister mRow0 = VectorLoadAligned(LocalToClip.M[0]);
VectorRegister mRow1 = VectorLoadAligned(LocalToClip.M[1]);
VectorRegister mRow2 = VectorLoadAligned(LocalToClip.M[2]);
VectorRegister mRow3 = VectorLoadAligned(LocalToClip.M[3]);
for (int32 i = 0; i < NumVtx; ++i)
{
VectorRegister VTempX = VectorLoadFloat1(&MeshVertices[i].X);
VectorRegister VTempY = VectorLoadFloat1(&MeshVertices[i].Y);
VectorRegister VTempZ = VectorLoadFloat1(&MeshVertices[i].Z);
VectorRegister VTempW;
// Mul by the matrix
VTempX = VectorMultiply(VTempX, mRow0);
VTempY = VectorMultiply(VTempY, mRow1);
VTempZ = VectorMultiply(VTempZ, mRow2);
VTempW = VectorMultiply(GlobalVectorConstants::FloatOne, mRow3);
// Add them all together
VTempX = VectorAdd(VTempX, VTempY);
VTempZ = VectorAdd(VTempZ, VTempW);
VTempX = VectorAdd(VTempX, VTempZ);
// Store
VectorStoreAligned(VTempX, &MeshClipVertices[i]);
uint8 VertexFlags = ProcessXFormVertex(MeshClipVertices[i], W_CLIP);
MeshClipVertexFlags[i] = VertexFlags;
}
每個三角形
b\ 修正三角形:ClippedVertexToScreen,TestFrontface,AddTriangle
【滿足裁頂點加或修正三角形:按深度(獲取離屏最遠的)】
uint16 I0 = MeshIndices[i*3 + 0];
uint16 I1 = MeshIndices[i*3 + 1];
uint16 I2 = MeshIndices[i*3 + 2];
uint8 F0 = MeshClipVertexFlags[I0];
uint8 F1 = MeshClipVertexFlags[I1];
uint8 F2 = MeshClipVertexFlags[I2];
if ((F0 & F1) & F2)
{
// fully clipped
continue;
}
FVector4 V[3] =
{
MeshClipVertices[I0],
MeshClipVertices[I1],
MeshClipVertices[I2]
};
uint8 TriFlags = F0 | F1 | F2;
if (TriFlags & EScreenVertexFlags::ClippedNear)
{
static const int32 Edges[3][2] = {{0,1}, {1,2}, {2,0}};
FVector4 ClippedPos[4];
int32 NumPos = 0;
for(int32 EdgeIdx = 0; EdgeIdx < 3; EdgeIdx++)
{
int32 i0 = Edges[EdgeIdx][0];
int32 i1 = Edges[EdgeIdx][1];
bool dot0 = V[i0].W < W_CLIP;
bool dot1 = V[i1].W < W_CLIP;
if (!dot0)
{
ClippedPos[NumPos] = V[i0];
NumPos++;
}
if (dot0 != dot1)
{
float t = (W_CLIP - V[i0].W) / (V[i0].W - V[i1].W);
ClippedPos[NumPos] = V[i0] + t*(V[i0] - V[i1]);
NumPos++;
}
}
// triangulate clipped vertices
for (int32 j = 2; j < NumPos; j++)
{
FScreenTriangle Tri;
float Depths[3];
bool bShouldDiscard = false;
bShouldDiscard|= ClippedVertexToScreen(ClippedPos[0], Tri.V[0], Depths[0]);
bShouldDiscard|= ClippedVertexToScreen(ClippedPos[j-1], Tri.V[1], Depths[1]);
bShouldDiscard|= ClippedVertexToScreen(ClippedPos[j], Tri.V[2], Depths[2]);
if (!bShouldDiscard && TestFrontface(Tri))
{
// Min tri depth for occluder (further from screen)
float TriDepth = FMath::Min3(Depths[0], Depths[1], Depths[2]);
AddTriangle(Tri, TriDepth, Mesh.PrimId, 1, OutData);
}
}
}
else
{
FScreenTriangle Tri;
float Depths[3];
bool bShouldDiscard = false;
for (int32 j = 0; j < 3 && !bShouldDiscard; ++j)
{
bShouldDiscard|= ClippedVertexToScreen(V[j], Tri.V[j], Depths[j]);
}
if (!bShouldDiscard && TestFrontface(Tri))
{
// Min tri depth for occluder (further from screen)
float TriDepth = FMath::Min3(Depths[0], Depths[1], Depths[2]);
AddTriangle(Tri, TriDepth, Mesh.PrimId, /*MeshFlags*/ 1, OutData);
}
}
2.5.1.2 |
按深度整理【最接近屏幕的在前】 柵格化遮擋刪除 |
2.5.2 |
FetchVisibilityForPrimitives |
這里先略(那啥,放到另外一個地方)
1、若支持並行處理:構建數據FVisForPrimParams,利用上多任務FetchVisibilityForPrimitivesTask處理FHZBBound
2、FOcclusionQueryBatcher::BatchPrimitive (一個算法)
2.6 |
StereoPass |
若視圖開啟了InstancedStereoPass
->確保右眼視圖中的圖元在左眼(實例化)視圖中可見。
->ComputeAndMarkRelevanceForViewParallel;
2.7 |
GatherDynamicMeshElements |
FSceneRender::GatherDynamicMeshElments
3、排序BasePass,實現HiZ剔除
按是否使用線程且還有可用渲染線程
. |
FDeferredShadingSceneRenderer::SortBasePassStaticData |
如果我們不使用深度(EarlyZPassMode==None)僅通過排序靜態繪制列表桶大致從前到后,以最大化HiZ剔除。不會干擾狀態排序,且每個列表都是單獨排序的
. |
FDeferredShadingSceneRenderer::AsyncSortBasePassStaticData |
4、提交視野的自定義數據
(略)
5、RHI資源(構建)
(略)
檢驗:
總結: