天天看點

WebGL交錯緩沖區【Interleaved Buffer】

作者:新缸中之腦
WebGL交錯緩沖區【Interleaved Buffer】
推薦:用 NSDT設計器 快速搭建可程式設計3D場景。

昨天我在 WebGL 沙箱項目的評論中收到 Jon 的一個問題:

嗨, 布蘭登,以你的示範為起點,我嘗試顯示一個金字塔,但到目前為止我隻能看到它的四個面之一。 如果我使用 gl.LINES 而不是 gl.TRIANGLES,那麼我隻能看到一個三角形。 我對将紋理坐标混合到 vertArray 中的方式也有點困惑。 您能解釋一下這些坐标是如何在着色器中排序的嗎?

老實說,我不知道我是否是世界上解釋這一點的最佳人選,但我會嘗試一下,因為關于此的特定于 WebGL 的資訊似乎非常少。 為了簡單起見,大多數教程更喜歡将數組分開,但這對于性能來說并不是最佳的。 這些概念的工作方式與 OpenGL 幾乎相同,但很高興看到它們在你正在使用的環境中使用。 它需要對通常不适用于 Javascript 的主題有一些基本的了解,例如計算位元組,但是一旦掌握了它的竅門,它就不會太難。

首先要注意的是,WebGL API 不知道任何有關頂點、紋理坐标、法線或其他類似内容的資訊。 它所知道的是數字清單。 它知道如何在圖形記憶體中儲存數字清單,并且知道如何告訴着色器開始渲染這些數字,但實際上僅此而已。 你是通過着色器代碼和 gl.vertexAttribPointer() 告訴系統數字含義的人。

var values = [
    -1.0, -1.0, 0.0, // Vertex 1
    0.0, 1.0, 0.0, // Vertex 2
    1.0, -1.0, 0.0 // Vertex 3
];
           

你們以前可能都見過,對吧? 這次要注意的是,除了注釋和我們建構換行符的方式之外,沒有任何東西可以分隔頂點。 該行可以準确地寫成這樣:

var values = [ -1.0, -1.0, 0.0, 0.0, 1.0, 0.0, 1.0, -1.0, 0.0 ];           

當然,現在對于我們人類來說閱讀起來要困難得多,但是對于計算機來說卻是完全一樣的。 無論哪種情況,我們都将資料推送到頂點緩沖區中,如下所示:

var vertBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(values), gl.STATIC_DRAW);           

請注意 Float32Array 位,我們稍後會讨論它。 不過,最終結果是 vertBuffer 現在指向顯示卡上某處的數字數組。 那麼計算機如何知道如何解釋這些看似随機的值呢? gl.vertexAttribPointer!

在渲染之前,我們必須調用如下代碼:

gl.bindBuffer(gl.ARRAY_BUFFER, vertBuffer);
gl.vertexAttribPointer(shader_attrib_position, 3, gl.FLOAT, false, 12, 0);           

這告訴系統它需要知道的關于如何讀取頂點的一切(更準确地說,如何告訴着色器關于頂點的資訊...)。 這裡我們要注意 4 點: “3”告訴 WebGL 每個點由多少個值組成。 由于位置是由 x、y 和 z 坐标定義的,是以我們将其指定為“3”。 如果你在 WebGL 代碼中進行 2D 渲染,可以輕松地在此處傳遞 2,并且可以根據情況給它 1 到 4 之間的任何值。 接下來,我們告訴它我們正在傳遞什麼樣的數字。 在本例中它們是浮點型,是以我們傳入 gl.FLOAT。 還有 gl.BYTE 和 gl.SHORT (以及其他的,但我們不要把事情搞得太複雜)。 這告訴 WebGL 兩件非常重要的事情:首先,如何将值暴露給着色器,其次每個值有多大。 現在這很重要,對于那些不熟悉位元組打包的人來說,讓我們快速回顧一下:

  • gl.BYTE = 1 位元組(顯然!),可以儲存從 -127 到 127 的值
  • gl.SHORT = 2位元組,取值範圍-32767到32767
  • gl.FLOAT = 4位元組,一大堆的值範圍。

在大多數情況下,除非你有一些非常具體的需求,否則你将希望堅持使用 gl.FLOAT。 這裡使用 Float 也與我們在建立緩沖區時使用 Float32Array 一緻。 如果我們要使用其他類型之一,你将需要使用不同的數組類型建立緩沖區。

重要的是,根據上面的 vertexAttribPointer 調用,我們傳遞了 3 個浮點數。 3 * 4 = 12,是以每個位置占用12個位元組。 而且,嘿! 我們在那次通話中也有 12! 這稱為Stride(步幅),它告訴系統每個頂點的起點相距多遠(以位元組為機關)。 如果我們的頂點包含的資訊不僅僅是位置,這非常有用。 假設由于某種原因我們的頂點被這樣定義:

var values = [
    -1.0, -1.0, 0.0, 999.0, // Vertex 1
    0.0, 1.0, 0.0, 999.0, // Vertex 2
    1.0, -1.0, 0.0, 999.0 // Vertex 3
];           

這些 999 可能代表頂點資料的其他部分,但它們與位置沒有任何關系。 如果我們繼續使用上面相同的 vertexAttribPointer 值,我們的頂點将如下所示:

[-1, -1, 0] [999, 0, 1] [0, 999, 1] [-1, 0, 999]           

我們可以輕松地“忽略”第四個值,進而傳回到我們想要的頂點位置,如下所示:

gl.vertexAttribPointer(shader_attrib_position, 3, gl.FLOAT, false, 16, 0);           

請注意,我們仍然訓示 WebGL 讀取 3 個浮點,但步幅已更改為 16 以考慮第 4 個不需要的浮點(4 個值 * 每個值 4 個位元組 = 16 個位元組)。

最後,我們在末尾添加 0,它告訴 WebGL 開始讀取數組中的值的深度。 這也是以位元組為機關給出的,在本例中為 0,因為我們想從第一個值開始。 如果我們想從第二個數字開始,我們會這樣做:

gl.vertexAttribPointer(shader_attrib_position, 3, gl.FLOAT, false, 16, 4);           

現在我們的頂點位置将如下所示:

[-1, 0, 999] [1, 0, 999] [-1, 0, 999]           

這對于将多個網格打包到單個緩沖區中非常有用,因為我們可以在任何點開始渲染,但它對于交錯(Interleaved)資料也很有用。 “交錯”意味着将多種類型的資料打包到一個數組中,例如位置和紋理坐标資料。 大多數教程會将它們顯示為單獨的數組,如下所示:

var pos = [1, 1, 1, 2, 2, 2, 3, 3, 3]; // (x, y, z), (x, y, z)...
var tex = [0, 1, 0, 1, 0, 1]; // (s, t), (s, t)...           

但将它們全部作為一個數組通常會更高效、更容易,如下所示:

var verts = [
    1, 1, 1, 0, 1, // (x, y, z), (s, t)...
    2, 2, 2, 0, 1,
    3, 3, 3, 0, 1,
];

var vertBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verts), gl.STATIC_DRAW);
           

當我們為此數組設定 vertexPointers 時,我們需要對 vertexAttribPointer 進行兩次調用。

gl.bindBuffer(gl.ARRAY_BUFFER, vertBuffer);
gl.vertexAttribPointer(shader_attrib_position, 3, gl.FLOAT, false, 20, 0);
gl.vertexAttribPointer(shader_attrib_texcoord, 2, gl.FLOAT, false, 20, 12);           

讓我們看看一下這意味着什麼:第一個屬性(頂點位置)由 3 個浮點組成,從位元組 0 開始,間隔 20 個位元組。第二個屬性(頂點UV)由 2 個浮點組成,間隔 20 個位元組,從位元組開始 12 個(或 3 個浮點數)。 就是這樣! 一個緩沖區中有兩種屬性類型! 我們可以根據需要(在合理範圍内)對盡可能多的屬性執行此操作。 包含位置、紋理坐标、法線、副法線和頂點權重的單個緩沖區并不罕見!

我真誠地希望這一切都是有意義的,而不僅僅是一堆圖形胡言亂語。 我在這裡根本沒有讨論着色器方面的内容,但其他教程對此進行了很好的介紹。

原文連結:http://www.bimant.com/blog/webgl-interleaved-buffer/

繼續閱讀