本文我將對PowerVR光線追蹤團隊創(chuàng)建的API進行簡要介紹。該API允許開發(fā)人員訪問Wizard 架構(gòu)的PowerVR光線追蹤硬件。我將簡單概述硬件層發(fā)生的物理變化及其相比傳統(tǒng)的GPU所變化的具體內(nèi)容。隨后,我將著重討論新創(chuàng)建的供開發(fā)人員訪問硬件的OpenGL ES 3.1擴展程序。
在2016年度游戲開發(fā)者大會(GDC)上,Imagination的 idc16開發(fā)人員會議對API進行了討論,點擊此處可獲取API討論資訊。
以下內(nèi)容的前提是默認各位讀者已經(jīng)了解光線追蹤的概念。若不了解,可以先查閱光線追蹤的相關(guān)信息。本文不會深入探討每個功能,詳細的信息將在后續(xù)的文章中進行討論。
前一段時間,我們介紹了啟動光線追蹤功能的四核集群PowerVR GR6500 GPU。最近,我們又展示了一些目前在GPU上運行的光線追蹤應(yīng)用程序。
下圖是我們添加的用于加速光線追蹤的硬件功能:
圖表展示了光線追蹤新的硬件模
光線數(shù)據(jù)管理
光線數(shù)據(jù)管理(RDM)模塊將交叉點歸至統(tǒng)一著色集群(USC)中,再由集群進行光線著色
場景層級生成器
場景層級生成器(SHG)模塊即使用標(biāo)準頂點著色器生成的世界坐標(biāo)頂點,在光線交點處理器之后生成加速結(jié)構(gòu)。
相干引擎
該模塊是光線追蹤裝置(RTU)的一部分,并以相同的方式緩沖了一組遍歷場景層級的光線且遵循類似的執(zhí)行路徑。這樣,在內(nèi)存中獲取光線數(shù)據(jù)時便可以避免緩存缺失,并由此減少帶寬和功耗。更多信息將在后續(xù)文章中詳細介紹。同時,您還可以關(guān)注2014GDC論壇獲取更多相干引擎的資訊。
光線交叉處理器
該模塊是RTU的一部分,可以測試SGH生成的位于主內(nèi)存的三角形光線及場景層級光線。
幀累加緩存
該硬件模塊從USC中獲取累積指令,并使用編寫的緩存來加速某些在光線追蹤著色器中可以使用的圖像原子操作。這意味著我們可以發(fā)出只編寫指令,這些指令將異步列隊并執(zhí)行。
PowerVR GR6500 GPU在PCIe卡上進行集成,如下圖所示,使用RTU時其峰值性能為每秒3億光線。SHG的目標(biāo)是生成加速結(jié)構(gòu),使頂點輸出從每秒1億動態(tài)三角形開始加速。
The API
光線跟蹤硬件對典型的GPU設(shè)計進行了延伸,使之不僅僅只是單一的硬件。我們決定通過添加光線追蹤功能來擴展OpenGL ES 3.1,以充分利用這個開發(fā)人員較為熟知的OpenGL ES API。我們選擇使用現(xiàn)代規(guī)范,如直接狀態(tài)訪問、無綁定等,因為預(yù)計這將是未來的API模式。Vulkan API的潛力不容忽視,但Vulkan仍處于待開發(fā)中,本文只介紹OpenGL ES擴展。
● 頂點處理
光線追蹤器輸入與光柵輸入是不同的。光線追蹤器處理的是世界坐標(biāo)中的對象,這是因為光線追蹤器需要訪問整個場景。例如,其場景中的對象反映的攝像機獲取的某個對象,而光柵中這卻很難處理。
正因為如此,PowerVR硬件光線追蹤API處理頂點輸出時是在世界坐標(biāo)而非光柵的剪輯空間中。這意味著我們將要以不同的幀率運行各層的頂點至光柵中——在光柵中,攝像機通常從幀到幀移動。即便不是,大多數(shù)游戲平臺也將重新渲染場景,因為傳輸過程中某些信息發(fā)生了改變。
這不同于光線追蹤器。我們從攝像機運動中解耦,在應(yīng)用程序啟動時運行頂點著色器,隨后,如果場景中沒有任何對象在世界坐標(biāo)中發(fā)生移動,則無需再次運行頂點著色器。當(dāng)然,當(dāng)攝像機移動時,仍然需要在每一幀進行光線遍歷,但為場景靜態(tài)部分生成的場景層級則無需如此。
預(yù)期OpenGL ES的變量及其他特性運行如下:
layout(std140, binding=0) uniform UboPerObjectIn {
highp mat4 worldFromModel;
highp mat4 worldFromModelIT;
} UboPerObject[2];
out gl_PerVertex {
vec4 gl_Position;
float gl_PointSize;
};
out PerVertexData {
vec3 vertexNormal;
};
void main() {
gl_Position = UboPerObject[gl_BuildIDIMG].worldFromModel * vec4(inVertex, 1.0); // Output in world space
vertexNormal = (UboPerObject[gl_BuildIDIMG].worldFromModelIT * vec4(inNormal, 0.0)).xyz
(gl_BuildIDIMG explained below)
然而,由于世界坐標(biāo)中的頂點處理與光柵頂點處理不同,因此我們需要不同的API來進行頂點處理……
● 場景構(gòu)造
對頂點和變量進行處理時將生成對象的坐標(biāo)位置,使用該位置便可以生成場景層級加速結(jié)構(gòu)。光線遍歷階段需要使用此加速結(jié)構(gòu)。計算加速結(jié)構(gòu)工作量相對較大。這就是為何在硬件中設(shè)計了場景層級加速器模塊——可以有效地生成場景層級加速結(jié)構(gòu)。我們還設(shè)計了API,這樣開發(fā)人員就可以將場景分為組件、組件集、場景和場景集。
這樣分解使得開發(fā)人員得以更有效地使用硬件。
當(dāng)我們發(fā)出構(gòu)建命令時,將在場景中指定的部分執(zhí)行三角形的頂點著色器。隨后,便使用該部分的輸出計算加速結(jié)構(gòu)。
Components
將各組件連接到頂點陣列對象。其與調(diào)用glDraw*()的定義類似。不過,并非是立即執(zhí)行該調(diào)用,而是對其進行記錄和延期,直至我們在包含這些組件的組件集中發(fā)出一個構(gòu)建命令。
還可以對該組件設(shè)置其他屬性,以便在光線遍歷階段進行交互:
?正面(順時針或逆時針方向)
?幾何圖形是否遮擋光線
?幾何圖形是否對對某類光線可見
?可見表面(前或后)
生成組件并設(shè)置屬性的例子如下:
glGenVertexArrays(1, &vertexArray);
// ... upload index data and model space vertex data as usual
glCreateComponentsIMG(1, &componentHandle);
glComponentVertexArrayIMG(componentHandle, vertexArray);
glComponentIndexedGeometryIMG(componentHandle, GL_TRIANGLES, numIndices, GL_UNSIGNED_SHORT, 0, 0);
glComponentBufferRangeIMG(componentHandle, GL_UNIFORM_BUFFER, 0, transformUBOHandle, 0, transformUBOSize);
glComponentVisibleFaceIMG(componentHandle, GL_FRONT_AND_BACK);
glComponentOccluderIMG(componentHandle, GL_TRUE);
請注意,GPU尚未有待執(zhí)行的對象。此刻,僅指定組件的數(shù)據(jù)。同樣要注意的是,頂點緩沖區(qū)需要保持激活狀態(tài),因為組件需要對其進行參照——并非復(fù)制。
組件集
組件集是最小的可分割處理單元,可用于創(chuàng)建或合并命令。
glCreateComponentGroupsIMG(1, &componentGroupHandle);
componentGroupHandle = glGetComponentGroupHandleIMG(componentGroupHandle);
glComponentGroupExtentIMG(componentGroupHandle, &extentMin, &extentMax); // Set the extents
我們還可以指定最小/最大幾何圖形區(qū)段,即創(chuàng)建組件集后在先前創(chuàng)建的組件之外對其進行構(gòu)建。這里我們可以將頂點著色器和光線追蹤器與組件聯(lián)系起來。在這個點上界定組件的材質(zhì)。構(gòu)建命令以啟動硬件SHG部分:
componentHandle = glGetComponentProgramHandleIMG(componentHandle, vertexAndRayShaderProgram);
std::vector components = {{componentHandle}};
glBuildComponentGroupIMG(0, componentGroupHandle, components.size(), components.data());
// Create a fence sync for the scene generation so that we know when it's ready
sceneHierarchyGenerationSync = glFenceSync(GL_SYNC_SHG_COMMANDS_COMPLETE_IMG, 0);
同時還可以發(fā)出合并命令。合并命令將兩個組件集合并在一起。這樣開發(fā)人員便可以選擇以不同的幀率重建場景的某些部分。構(gòu)建場景子集,并與已經(jīng)建立的子集合并,盡管要權(quán)衡場景層級的質(zhì)量,但合并相比完全重建子集更加節(jié)省功耗。
以汽車在地面移動為例。在啟動應(yīng)用程序時便創(chuàng)建地面組件集,且使之一直保持不變。隨后,當(dāng)汽車在地面移動時,創(chuàng)建汽車組件集,必要時每一幀畫面都可創(chuàng)建。
std::vector componentGroups = {{...}};
glMergeComponentGroupsIMG(mergedComponentGroupResultHandle, componentGroups.size(), componentGroups.data(), GL_DONT_CARE);
sceneHierarchyGenerationSync = glFenceSync(GL_SYNC_SHG_COMMANDS_COMPLETE_IMG, 0);
上述代碼中,GL fences可供硬件使用來同步訪問內(nèi)存。它還使得多緩沖區(qū)操作變成一種可能。調(diào)用合并/構(gòu)建后,立刻等待fence直至硬件執(zhí)行完命令,且這是在啟動應(yīng)用程序時構(gòu)建場景層級的一種方式。不過這樣CPU side stall在運行時便會出現(xiàn)狀態(tài)不佳。但相反,我們可以多緩存合并/構(gòu)建命令,以便在GPU硬件讀/寫一組對象時在CPU上設(shè)置第二組對象。而為了更容易實現(xiàn)這一點,可以將第一個參數(shù)輸入glBuildComponentGroupIMG()命令中。通過glsl builtin gl_BuildIDIMG將該指數(shù)輸入頂點著色器中,且通過上述示例可見,這將有助于多緩存對象數(shù)據(jù)UBO。這意味著我們可以在世界坐標(biāo)中移動組件集,且無需暫停CPU。
場景陣列
場景陣列包含了一個或更多在單個調(diào)度內(nèi)可遍歷的組件集。我們需要指定每個場景陣列的光線規(guī)格,以上可以通過glSceneArrayRayBlockSizeIMG完成。
GLuint sceneArray = glCreateSceneArrayIMG();
glSceneArrayRayBlockSizeIMG(sceneArray, 0, 0); // Primary rays
glSceneArrayRayBlockSizeIMG(sceneArray, 1, 12); // Shadow rays
glBindSceneArrayComponentGroupIMG(sceneArray, 0, componentGroupHandle);
單個場景陣列中的多個組件集對幾何圖形層次細節(jié) (LOD)如進行光柵化時很用用處。例如,當(dāng)我們光線追蹤具有復(fù)雜幾何圖形的場景時,我們可能需要場景的多個LOD以幫助加速交叉測試。在著色器中可以切換至不同的場景,這樣便可以使用距離等測試切換至更低一層的LOD場景中。
更簡單更具邏輯性的一個例子是具有虛擬監(jiān)控和閉路電視攝像頭的場景。當(dāng)我們用光線追蹤監(jiān)控器時,可以切換到中央電視臺現(xiàn)場并啟動攝像機視角的光線。
glBindSceneArrayComponentGroupIMG(sceneArray, 0, monitorComponentGroup);
glBindSceneArrayComponentGroupIMG(sceneArray, 1, cameraViewComponentGroup);
多類場景需要多個場景陣列,例如在光線追蹤游戲視覺層次的同時又想沿著該層次光線追蹤聲音的路徑。相比視覺信息,聲音場景需要在光線和幾何圖形中存儲不同的信息。我們可以在不同的場景陣列中單獨區(qū)分這些細節(jié):
glBindSceneArrayIMG(sceneArrayVisuals);
glDispatchRaysIMG(0, 0, 0, frame.w, frame.h, GL_NO_WAIT_BIT_IMG);
glBindSceneArrayIMG(sceneArrayAudio);
glDispatchRaysIMG(0, 0, 0, 1, numSounds * 2, GL_NO_WAIT_BIT_IMG);
或者可以使用單個場景陣列和多類光線。
我們將這個綁定的場景陣列也稱之為場景,這樣在本文中涉及到場景時才說的通。
無綁定
在常規(guī)OpenGL中使用紋理時,我們需要首先調(diào)用glActiveTexture,然后是glBindTexture,且只有一組有限的綁定點。這在單一的對象層比較適用,因為對于下一個對象,我們可以再次調(diào)用這些函數(shù)且改變對象的紋理——不需要擔(dān)心其他對象的紋理。
啟動光線追蹤時,我們需要知道所有對象的紋理和著色器。在光線遍歷時我們無法變更綁定,但又不想僅限于少量的紋理。因此需要另一種機制將這些紋理綁定到對象著色器中。這就是新的IMG_bindless_texture擴展??梢允褂靡韵潞瘮?shù)替代上述的gl調(diào)用來綁定紋理:
GLuint64 textureHandle = glGetTextureHandleIMG(textureObject);
// Similar to glUniform
glProgramUniformHandleui64IMG(rayProgramHandle, samplerLocationInShader, textureHandle);
// Now in glsl we can access the texture:
layout(bindless_sampler) uniform sampler2D sTexture;
光線遍歷
光線追蹤流程
通過glDispatchRaysIMG調(diào)用進入光線遍歷部分。我們需要定義一組glRayBounceLimitIMG光線反射數(shù)量的最大值。還需要其他一些有助于提高硬件執(zhí)行效率的輔助工具。glProgramMaxRayEmitsIMG限制了發(fā)送的光線數(shù)量。對于陰影光線,將此值設(shè)置為1,例如燈光照射得到的硬陰影。這將有助于硬件了解是否在執(zhí)行光線著色器,且最多可以發(fā)送一條陰影光線。
評論
查看更多