OpenGL 程式設計/Glescraft 3

之前兩個教程中的體素看起來很無聊,而且很難區分它們,因為它們具有統一的顏色,並且沒有嘗試使用光照效果。在本教程中,我們將瞭解如何僅使用我們擁有的一個紋理座標為體素賦予不同的紋理。此外,我們將以微小的方式調整片段著色器,以在場景照明方面獲得很大的改進。
當我們想在體素上放置紋理時,我們不想給一個塊中的所有體素賦予相同的紋理。但是,如果我們一次性渲染塊中的所有三角形,我們就無法在體素之間切換紋理。因此,我們將不得不使用 紋理圖集 將我們想要在體素上繪製的所有影像儲存在一個 OpenGL 紋理中。我們只有一個紋理座標可用,它只能有 256 個可能的值。如果我們可以使用它來指向紋理圖集中的 256 個可能的子影像之一,那就太好了。
假設我們有一個包含 16 個子影像的紋理圖集。所有子影像都具有相同的大小(SW x SH),但確切的大小並不重要。紋理圖集將在一行中包含所有子影像,因此紋理圖集將具有 (SW * 16) x SH 個畫素。Theblk[x][y][z]陣列現在應包含 0 到 15 範圍內的值。在片段著色器中,我們現在必須從varying vec4 texcoord建立真實的紋理座標,這些座標是從頂點著色器獲得的。顯然,我們從 0 到 15 的整數值是不夠的。但是,我們可以使用 x、y 和 z 座標。由於這些是“變化的”,它們將不包含來自 VBO 的整數值,而是可以在整數值之間具有任何值,具體取決於片段在頂點之間的距離。特別是,如果我們從 (0, 0, 0) 到 (1, 1, 0) 繪製一個四邊形,則 z 座標將始終為 0,但 x 和 y 座標可以在其之間取任何值。為了紋理化這個四邊形,我們可以使用以下片段著色器
#version 120
varying vec4 texcoord;
uniform sampler2D texture;
void main(void) {
gl_FragColor = texture2D((texcoord.x + texcoord.w) / 16.0, texcoord.y);
}
除以 16 是因為我們的紋理圖集在同一行中包含 16 個子影像。此外,由於 w 座標對於四邊形中的所有頂點都是相同的,因此它將在片段著色器中具有恆定值。請記住,texcoord只是制服的副本coord在應用 MVP 矩陣之前,因此紋理不會受到 MVP 任何變化的影響。
當然,您可能已經注意到此著色器不適用於大多數其他可能座標的四邊形。首先,為了解決任何角落為 (x, y, *) 到 (x + 1, y + 1, *) 的四邊形,其中 x 和 y 是整數座標,* 表示任何可能的 z,我們可以使用fract()函式將 x 座標映射回 0 到 1 的範圍
gl_FragColor = texture2D((fract(texcoord.x) + texcoord.w) / 16.0, texcoord.y);
我們不必使用fract()ontexcoord.y,因為我們的紋理圖集只有一行影像,OpenGL 將負責在垂直方向上進行紋理環繞。此著色器適用於指向正向或負向 z 方向的任何體素面。但是,對於這些面,面的所有頂點都具有相同的 /整數/ z 座標值。因此,在應用之前,我們可以安全地將其新增到 x 座標中fract()function
gl_FragColor = texture2D((fract(texcoord.x + texcoord.z) + texcoord.w) / 16.0, texcoord.y);
相同的引數適用於指向正向或負向 y 方向的體素面,因此您可以對這些面使用相同的著色器!但是,我們不能在其中也放入 y 座標。正確渲染指向正向或負向 y 方向的面唯一方法是,要麼使用兩個片段著色器,分別渲染 x 和 z 面,要麼向頂點新增一些額外資訊,以便能夠在片段著色器中區分這兩種情況。由於我們只在紋理中使用 16 個子影像,因此我們只使用 w 座標的 4 位。事實上,我們可以使用另一個位作為非常基本的“法線”向量。我們將使用負 w 座標來指示指向 y 方向的面
if(texcoord.w < 0)
gl_FragColor = texture2D((fract(texcoord.x) + texcoord.w) / 16.0, texcoord.z);
else
gl_FragColor = texture2D((fract(texcoord.x + texcoord.z) + texcoord.w) / 16.0, texcoord.y);
我們不必擔心 w 座標的負值,只要在建立 VBO 時,我們從blk[x][y][z]的值中減去 16 的倍數,使其變為負數。這樣,紋理座標將超出 0..1 的範圍,但會由 OpenGL 正確地包裹到紋理圖集中的正確位置。
練習
- 在之前的教程中,我們已經看到可以合併相鄰面以減少需要繪製的三角形數量。上面的著色器在這種情況下仍然有效嗎?
即使使用了紋理,也很難區分體素。為了使場景看起來更自然,並更容易區分我們正在檢視體素的哪一面,我們將使用上一節中介紹的“法線”位使體素的側面略微變暗。這模擬了正午時太陽直射時的真實世界陰影。
if(texcoord.w < 0)
gl_FragColor = texture2D((fract(texcoord.x) + texcoord.w) / 16.0, texcoord.z);
else
gl_FragColor = texture2D((fract(texcoord.x + texcoord.z) + texcoord.w) / 16.0, texcoord.y) * 0.85;
在現實世界中,遠處物體看起來比您面前的物體更暗淡,顏色也更少。這是由於大氣對光線的散射造成的。這幾乎與霧相同,主要區別在於強度。我們可以在片段著色器中實現這一點,如下所示
#version 120
varying vec4 texcoord;
uniform sampler2D texture;
const vec4 fogcolor = vec4(0.6, 0.8, 1.0, 1.0);
const float fogdensity = .00003;
void main(void) {
vec4 color;
if(texcoord.w < 0)
color = texture2D((fract(texcoord.x) + texcoord.w) / 16.0, texcoord.z);
else
color = texture2D((fract(texcoord.x + texcoord.z) + texcoord.w) / 16.0, texcoord.y) * 0.85;
float z = gl_FragCoord.z / gl_FragCoord.w;
float fog = clamp(exp(-fogdensity * z * z), 0.2, 1);
gl_FragColor = mix(fogcolor, color, fog);
}
在片段著色器頂部,我們定義了兩個常量。霧的顏色是物體在很遠的地方時會呈現的顏色。霧的密度控制霧的效果強度。非常小的值代表大氣散射或輕微的薄霧,較大的值代表濃霧。
片段到攝像機的距離可以透過將gl_FragCoord.z除以gl_FragCoord.w來計算。霧的效果隨距離呈指數增長。變數fog表示應用霧後“剩餘”的真實顏色的比例。最終的片段顏色是原始顏色和霧顏色的混合。
最後,您可以製作具有透明畫素的紋理。雖然您可以使用混合來應用透明紋理,但也可以在片段著色器中模擬完全透明的畫素。在計算color之後,您可以根據 alpha 值丟棄片段
if(color.a < 0.5)
discard;
Thediscard關鍵字會導致片段程式停止進一步處理(它類似於 C 中的“return”語句)。與混合相比,這種方法的優勢在於,z 緩衝區的值不會為透明畫素更新。使用混合,如果在靠近攝像機的位置渲染一個透明三角形,然後在它後面渲染一個不透明三角形,則不透明三角形將不會被繪製,因為它將無法透過深度測試。
練習
- 向一些子影像新增透明度,並檢視結果。
- 改變chunk::update()來自之前教程的函式,以正確處理部分透明的面。
- 嘗試使用混合而不是丟棄關鍵字。