![](/img/trans.png)
[英]What OpenGL functions modify vertex positions prior to the vertex shader?
[英]What is Octahedral Compression of Vertex Arrays?
我在 Godot 程序中生成 3d 個網格。 Mesh
class提供:
- ARRAY_FLAG_USE_OCTAHEDRAL_COMPRESSION = 2097152 --- 用於標記數組使用法線和切線向量的八面體表示而不是笛卡爾的標志。
使用ArrayMesh
子類生成網格時,此標志設置為默認值。
我從谷歌獲得的“八面體壓縮”的最佳結果是這個着色器示例,但我不明白它的作用。
有人可以解釋一下八面體壓縮的作用和它的好處嗎?
在這種情況下,“八面體表示”指的是一種編碼法線向量的方式,與通常的 XYZ 編碼相比,它恰好占用更少的空間(以精度為代價)。 由於它占用的空間更少,我們也可以說這是一種(有損)向量壓縮。
通過 XYZ 編碼,我的意思是每個向量由三個float
分量 XYZ 表示。 這是默認設置。
因此,八面體表示節省了 memory 和 memory 帶寬(渲染時視頻 memory 和頂點 memory 帶寬)。 交換編碼時間(在創建網格時發生)和解碼時間(在 GPU 中渲染時發生),並且會損失一些精度。 精度和 GPU 性能的損失並不像其他替代方案(例如使用球坐標)那么糟糕。
該方法於 2014 年發表在Survey of Efficient Representations for Independent Unit Vectors上。它基於Octahedron Environment Maps 。 這是基於八面體的 map 環境紋理(因此它是cube-mapping的替代方案)的一種方式,其中紋理是這樣分割的:
正如你所看到的,我們現在有一個 2D 空間(可以調整到紋理 UV 的范圍),其中每個點對應於八面體表面中的一個點,因此每個 2D 點對應於中心的一個方向八面體。 因此,這些二維坐標是一種編碼方向的方法。
因此,為了對我們的法線進行編碼,我們將其解釋為單位球體上的一個點,然后:
Map 八面體
因為我們想保持方向,所以我們要做的是縮放矢量,使其位於八面體上。
如您所知,八面體由 3D 空間的八個象限中的每一個組成一個平面。 因此,馬上,通過用輸入向量的分量的絕對值制作一個向量,我們可以在第一象限上工作:
l = vec3(abs(v.x), abs(v.y), abs(v.z))
從 線面相交我們有:
d = ((pₒ - lₒ)·n)/(l·n)
p = lₒ + ld
其中lₒ
是直線的一個點。 我們將把它放在原點,留下我們:
p = l * (pₒ·n)/(l·n)
其中n
是平面的法線……所以n = (1, 1, 1)
很方便。 pₒ
是平面上的一個點…… pₒ = (1, 0, 0)
也很方便。 因此:
p = l * 1/(l·n)
這與:
p = l * 1/((l.x * n.x) + (l.y * n.y) + (l.z * n.z))
由於n = (1, 1, 1)
這與:
p = l * 1/(l.x + l.y + l.z)
啊,但還記得我們因為八面體的對稱性而做了絕對值嗎? 我們需要返回標志:
p = v * 1/(abs(v.x) + abs(v.y) + abs(v.z))
因此,對於從球體到八面體的 go,我們將向量除以其分量的絕對值之和。
Map 到二維空間
然后我們需要折疊具有負 z 的法線。 澄清一下:正 z 的法線位於中心(靠近原點),負 z 的法線位於外角。 因此,當 z 為正時,我們可以保持 x 和 y 坐標不變。 但是當z為負數時我們需要將它們map到相應的角。 這是這樣的:
vec2 output = vec2(
(1.0 - abs(input.x)) * sign(input.x),
(1.0 - abs(input.y)) * sign(input.y)
);
要理解這一點,請看絕對值會將 [-1, 0] 范圍內的數字折疊到 [0, 1] 范圍內(翻轉)。 然后通過執行1 - x
我們翻轉范圍 [0, 1] 以便接近 0 現在接近 1,反之亦然。 然后我們乘以符號展開。 因為我們對x
和y
都這樣做,所以我們將靠近原點的點映射到靠近正方形角落的點。
Map轉UV空間
如果我們正在做環境映射,那么我們將 map 從 [-1.0, 1.0] 到 [0.0, 1.0] 范圍內的坐標,這樣我們就可以將它們用作 UV 坐標來查詢環境紋理 map。 input * 0.5 + 0.5
。
我們可以在源碼中找到函數norm_to_oct
:
// Maps normalized vector to an octahedron projected onto the cartesian plane
// Resulting 2D vector in range [-1, 1]
// See http://jcgt.org/published/0003/02/01/ for details
Vector2 VisualServer::norm_to_oct(const Vector3 v) {
const float L1Norm = Math::absf(v.x) + Math::absf(v.y) + Math::absf(v.z);
// NOTE: this will mean it decompresses to 0,0,1
// Discussed heavily here: https://github.com/godotengine/godot/pull/51268 as to why we did this
if (Math::is_zero_approx(L1Norm)) {
WARN_PRINT_ONCE("Octahedral compression cannot be used to compress a zero-length vector, please use normalized normal values or disable octahedral compression");
return Vector2(0, 0);
}
const float invL1Norm = 1.0f / L1Norm;
Vector2 res;
if (v.z < 0.0f) {
res.x = (1.0f - Math::absf(v.y * invL1Norm)) * SGN(v.x);
res.y = (1.0f - Math::absf(v.x * invL1Norm)) * SGN(v.y);
} else {
res.x = v.x * invL1Norm;
res.y = v.y * invL1Norm;
}
return res;
}
說服自己這段代碼執行前面描述的操作。
我確實深入了解了將此功能添加到 Godot 的動機,事實證明它旨在節省移動設備中的 memory(特別是頂點 memory 帶寬)。 請參閱八面體法向/切線壓縮。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.