WebGL 基礎知識
WebGL 能讓您在瀏覽器中顯示令人驚豔的即時 3D 圖形,但許多人不瞭解 WebGL 實際上是 2D API,而非 3D API。讓我解釋一下
WebGL 只會處理 2 件事。2D 中的剪輯區座標和顏色。使用 WebGL 的程式設計師必須為 WebGL 提供這兩項資訊。您可以提供 2 個「著色器」來執行這項操作。頂點著色器提供剪輯空間座標,而片段著色器提供顏色。無論畫布的大小為何,剪輯空間座標一律會從 -1 到 +1。以下是簡單的 WebGL 範例,可顯示 WebGL 最簡單的形式。
// Get A WebGL context
var canvas = document.getElementById("canvas");
var gl = canvas.getContext("experimental-webgl");
// setup a GLSL program
var vertexShader = createShaderFromScriptElement(gl, "2d-vertex-shader");
var fragmentShader = createShaderFromScriptElement(gl, "2d-fragment-shader");
var program = createProgram(gl, [vertexShader, fragmentShader]);
gl.useProgram(program);
// look up where the vertex data needs to go.
var positionLocation = gl.getAttribLocation(program, "a_position");
// Create a buffer and put a single clipspace rectangle in
// it (2 triangles)
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([
-1.0, -1.0,
1.0, -1.0,
-1.0, 1.0,
-1.0, 1.0,
1.0, -1.0,
1.0, 1.0]),
gl.STATIC_DRAW);
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
// draw
gl.drawArrays(gl.TRIANGLES, 0, 6);
以下是 2 個著色器
<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;
void main() {
gl_Position = vec4(a_position, 0, 1);
}
</script>
<script id="2d-fragment-shader" type="x-shader/x-fragment">
void main() {
gl_FragColor = vec4(0,1,0,1); // green
}
</script>
同樣地,無論畫布的大小為何,剪輯空間座標一律會從 -1 到 +1。在上述情況中,您可以看到我們只會直接傳遞位置資料。由於位置資料已在剪輯空間中,因此無需執行任何作業。如果您想要 3D 圖形,請自行提供從 3D 轉換為 2D 的著色器,因為 WebGL 是 2D API!對於 2D 內容,您可能會傾向以像素而非剪輯區域進行作業,因此請變更著色器,以便我們提供以像素為單位的矩形,並將其轉換為剪輯區域。以下是新的頂點著色器
<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;
uniform vec2 u_resolution;
void main() {
// convert the rectangle from pixels to 0.0 to 1.0
vec2 zeroToOne = a_position / u_resolution;
// convert from 0->1 to 0->2
vec2 zeroToTwo = zeroToOne * 2.0;
// convert from 0->2 to -1->+1 (clipspace)
vec2 clipSpace = zeroToTwo - 1.0;
gl_Position = vec4(clipSpace, 0, 1);
}
</script>
我們現在可以將資料從剪輯區域變更為像素
// set the resolution
var resolutionLocation = gl.getUniformLocation(program, "u_resolution");
gl.uniform2f(resolutionLocation, canvas.width, canvas.height);
// setup a rectangle from 10,20 to 80,30 in pixels
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
10, 20,
80, 20,
10, 30,
10, 30,
80, 20,
80, 30]), gl.STATIC_DRAW);
您可能會發現矩形位於該區域的底部附近。WebGL 會將左下角視為 0,0。為了讓它成為 2D 圖形 API 使用的較傳統的左上角,我們只需翻轉 y 座標即可。
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
我們將定義矩形的程式碼做為函式,以便針對不同大小的矩形呼叫該函式。在這個階段,我們會變更可設定的顏色。 首先,我們讓片段著色器採用顏色統一輸入。
<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;
uniform vec4 u_color;
void main() {
gl_FragColor = u_color;
}
</script>
以下是新的程式碼,可在隨機位置和隨機顏色中繪製 50 個矩形。
...
var colorLocation = gl.getUniformLocation(program, "u_color");
...
// Create a buffer
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
// draw 50 random rectangles in random colors
for (var ii = 0; ii < 50; ++ii) {
// Setup a random rectangle
setRectangle(
gl, randomInt(300), randomInt(300), randomInt(300), randomInt(300));
// Set a random color.
gl.uniform4f(colorLocation, Math.random(), Math.random(), Math.random(), 1);
// Draw the rectangle.
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
}
// Returns a random integer from 0 to range - 1.
function randomInt(range) {
return Math.floor(Math.random() * range);
}
// Fills the buffer with the values that define a rectangle.
function setRectangle(gl, x, y, width, height) {
var x1 = x;
var x2 = x + width;
var y1 = y;
var y2 = y + height;
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
x1, y1,
x2, y1,
x1, y2,
x1, y2,
x2, y1,
x2, y2]), gl.STATIC_DRAW);
}
希望您能發現 WebGL 其實是相當簡單的 API。雖然 3D 處理作業可能會變得更複雜,但您 (程式設計師) 會以更複雜的著色器形式加入這些複雜性。WebGL API 本身是 2D,且相當簡單。
type="x-shader/x-vertex" 和 type="x-shader/x-fragment" 分別代表什麼意思?
<script>
標記預設會包含 JavaScript。您可以不指定類型,也可以指定 type="javascript"
或 type="text/javascript"
,瀏覽器會將內容解讀為 JavaScript。如果您放入其他內容,瀏覽器會忽略指令碼標記的內容。
我們可以使用這項功能,在指令碼標記中儲存著色器。更棒的是,我們可以自行建立類型,並在 JavaScript 中尋找該類型,決定要將著色器編譯為頂點著色器還是片段著色器。
在此情況下,createShaderFromScriptElement
函式會尋找具有指定 id
的指令碼,然後查看 type
以決定要建立的著色器類型。
WebGL 圖像處理
WebGL 能輕鬆處理圖片。您覺得這有多簡單?請參閱下文。
如要在 WebGL 中繪製圖片,必須使用紋理。與 WebGL 在算繪時預期的裁剪空間座標 (而非像素) 類似,WebGL 在讀取紋理時會預期紋理座標。無論紋理的尺寸為何,紋理座標都會從 0.0 到 1.0。由於我們只繪製單一矩形 (或許是 2 個三角形),因此需要告訴 WebGL 矩形中每個點對應的紋理位置。我們會將這項資訊從頂點著色器傳送至片段著色器,方法是使用名為「varying」的特殊變數。之所以稱為變數,是因為它會變動。WebGL 會在使用片段著色器繪製每個像素時,對頂點著色器中提供的值進行內插。使用上一節結尾的頂點著色器,我們需要新增屬性來傳入紋理座標,然後將這些傳遞給片段著色器。
attribute vec2 a_texCoord;
...
varying vec2 v_texCoord;
void main() {
...
// pass the texCoord to the fragment shader
// The GPU will interpolate this value between points
v_texCoord = a_texCoord;
}
接著,我們會提供片段著色器,以便從紋理中查詢顏色。
<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;
// our texture
uniform sampler2D u_image;
// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;
void main() {
// Look up a color from the texture.
gl_FragColor = texture2D(u_image, v_texCoord);
}
</script>
最後,我們需要載入圖片、建立紋理,並將圖片複製到紋理中。由於我們是在瀏覽器中以非同步方式載入圖片,因此必須稍微重新排列程式碼,等待紋理載入。載入後,我們將繪製該圖片
function main() {
var image = new Image();
image.src = "http://someimage/on/our/server"; // MUST BE SAME DOMAIN!!!
image.onload = function() {
render(image);
}
}
function render(image) {
...
// all the code we had before.
...
// look up where the texture coordinates need to go.
var texCoordLocation = gl.getAttribLocation(program, "a_texCoord");
// provide texture coordinates for the rectangle.
var texCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
0.0, 1.0,
1.0, 0.0,
1.0, 1.0]), gl.STATIC_DRAW);
gl.enableVertexAttribArray(texCoordLocation);
gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);
// Create a texture.
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
// Set the parameters so we can render any size image.
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
// Upload the image into the texture.
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
...
}
這並不是太令人興奮的內容,因此我們來修改圖片。那麼換成紅色和藍色如何?
...
gl_FragColor = texture2D(u_image, v_texCoord).bgra;
...
如果我們想進行圖像處理,實際查看其他像素,該怎麼做?由於 WebGL 會在紋理座標中參照紋理,而紋理座標的範圍為 0.0 到 1.0,因此我們可以使用簡單的數學 onePixel = 1.0 / textureSize
計算 1 個像素的移動量。以下是片段著色器,可計算紋理中每個像素的左側和右側像素平均值。
<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;
// our texture
uniform sampler2D u_image;
uniform vec2 u_textureSize;
// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;
void main() {
// compute 1 pixel in texture coordinates.
vec2 onePixel = vec2(1.0, 1.0) / u_textureSize;
// average the left, middle, and right pixels.
gl_FragColor = (
texture2D(u_image, v_texCoord) +
texture2D(u_image, v_texCoord + vec2(onePixel.x, 0.0)) +
texture2D(u_image, v_texCoord + vec2(-onePixel.x, 0.0))) / 3.0;
}
</script>
接著,我們需要從 JavaScript 傳入紋理的大小。
...
var textureSizeLocation = gl.getUniformLocation(program, "u_textureSize");
...
// set the size of the image
gl.uniform2f(textureSizeLocation, image.width, image.height);
...
現在我們已知道如何參照其他像素,接著要使用卷積核心進行大量圖片處理作業。本例將使用 3x3 核心卷積核只是 3x3 矩陣,矩陣中的每個項目代表要將要算繪像素周圍的 8 個像素乘以多少。接著,我們會將結果除以核函式權重 (核函式中所有值的總和) 或 1.0 (兩者取較大者)。請參閱這篇實用文章。這裡還有另一篇文章,說明如何在 C++ 中手動編寫此程式碼。在本例中,我們會在著色器中執行這項作業,因此請參閱新的片段著色器。
<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;
// our texture
uniform sampler2D u_image;
uniform vec2 u_textureSize;
uniform float u_kernel[9];
// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;
void main() {
vec2 onePixel = vec2(1.0, 1.0) / u_textureSize;
vec4 colorSum =
texture2D(u_image, v_texCoord + onePixel * vec2(-1, -1)) * u_kernel[0] +
texture2D(u_image, v_texCoord + onePixel * vec2( 0, -1)) * u_kernel[1] +
texture2D(u_image, v_texCoord + onePixel * vec2( 1, -1)) * u_kernel[2] +
texture2D(u_image, v_texCoord + onePixel * vec2(-1, 0)) * u_kernel[3] +
texture2D(u_image, v_texCoord + onePixel * vec2( 0, 0)) * u_kernel[4] +
texture2D(u_image, v_texCoord + onePixel * vec2( 1, 0)) * u_kernel[5] +
texture2D(u_image, v_texCoord + onePixel * vec2(-1, 1)) * u_kernel[6] +
texture2D(u_image, v_texCoord + onePixel * vec2( 0, 1)) * u_kernel[7] +
texture2D(u_image, v_texCoord + onePixel * vec2( 1, 1)) * u_kernel[8] ;
float kernelWeight =
u_kernel[0] +
u_kernel[1] +
u_kernel[2] +
u_kernel[3] +
u_kernel[4] +
u_kernel[5] +
u_kernel[6] +
u_kernel[7] +
u_kernel[8] ;
if (kernelWeight <= 0.0) {
kernelWeight = 1.0;
}
// Divide the sum by the weight but just use rgb
// we'll set alpha to 1.0
gl_FragColor = vec4((colorSum / kernelWeight).rgb, 1.0);
}
</script>
在 JavaScript 中,我們需要提供卷積核。
...
var kernelLocation = gl.getUniformLocation(program, "u_kernel[0]");
...
var edgeDetectKernel = [
-1, -1, -1,
-1, 8, -1,
-1, -1, -1
];
gl.uniform1fv(kernelLocation, edgeDetectKernel);
...
希望這篇文章能讓您瞭解 WebGL 中的圖像處理作業相當簡單。接下來,我將說明如何為圖片套用多個效果。
GLSL 中變數前置詞 a、u 和 v_ 的含義為何?
這只是命名慣例a_
適用於緩衝區提供的資料。u_
是著色器的輸入值,v_
是從頂點著色器傳遞至片段著色器的值,並在繪製每個像素的頂點之間進行插補 (或變化)。
套用多種效果
接下來,最明顯的圖片處理問題就是如何套用多種效果?
您可以嘗試即時產生著色器。提供使用者介面,讓使用者選取要使用的效果,然後產生可執行所有效果的著色器。雖然這項技術通常用於為即時動畫製作效果,但不一定能達到預期效果。更靈活的方法是使用 2 個額外的紋理,並依序算繪至每個紋理,來回彈跳,並在每次應用下一個效果。
Original Image -> [Blur] -> Texture 1
Texture 1 -> [Sharpen] -> Texture 2
Texture 2 -> [Edge Detect] -> Texture 1
Texture 1 -> [Blur] -> Texture 2
Texture 2 -> [Normal] -> Canvas
為此,我們需要建立 framebuffer。在 WebGL 和 OpenGL 中,Framebuffer 其實是一個不恰當的名稱。WebGL/OpenGL Framebuffer 其實只是狀態集合,並非任何類型的緩衝區。不過,只要將紋理附加至框架緩衝區,我們就能在該紋理中進行轉譯。首先,我們要將舊的紋理建立程式碼轉換為函式
function createAndSetupTexture(gl) {
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
// Set up texture so we can render any size image and so we are
// working with pixels.
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
return texture;
}
// Create a texture and put the image in it.
var originalImageTexture = createAndSetupTexture(gl);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
接下來,我們將使用該函式製作另外 2 個紋理,並將其附加至 2 個 framebuffer。
// create 2 textures and attach them to framebuffers.
var textures = [];
var framebuffers = [];
for (var ii = 0; ii < 2; ++ii) {
var texture = createAndSetupTexture(gl);
textures.push(texture);
// make the texture the same size as the image
gl.texImage2D(
gl.TEXTURE_2D, 0, gl.RGBA, image.width, image.height, 0,
gl.RGBA, gl.UNSIGNED_BYTE, null);
// Create a framebuffer
var fbo = gl.createFramebuffer();
framebuffers.push(fbo);
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
// Attach a texture to it.
gl.framebufferTexture2D(
gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
}
現在,我們來建立一組核心,然後列出要套用的核心。
// Define several convolution kernels
var kernels = {
normal: [
0, 0, 0,
0, 1, 0,
0, 0, 0
],
gaussianBlur: [
0.045, 0.122, 0.045,
0.122, 0.332, 0.122,
0.045, 0.122, 0.045
],
unsharpen: [
-1, -1, -1,
-1, 9, -1,
-1, -1, -1
],
emboss: [
-2, -1, 0,
-1, 1, 1,
0, 1, 2
]
};
// List of effects to apply.
var effectsToApply = [
"gaussianBlur",
"emboss",
"gaussianBlur",
"unsharpen"
];
最後,讓我們套用每個紋理,並 ping pong 我們要算繪的紋理
// start with the original image
gl.bindTexture(gl.TEXTURE_2D, originalImageTexture);
// don't y flip images while drawing to the textures
gl.uniform1f(flipYLocation, 1);
// loop through each effect we want to apply.
for (var ii = 0; ii < effectsToApply.length; ++ii) {
// Setup to draw into one of the framebuffers.
setFramebuffer(framebuffers[ii % 2], image.width, image.height);
drawWithKernel(effectsToApply[ii]);
// for the next draw, use the texture we just rendered to.
gl.bindTexture(gl.TEXTURE_2D, textures[ii % 2]);
}
// finally draw the result to the canvas.
gl.uniform1f(flipYLocation, -1); // need to y flip for canvas
setFramebuffer(null, canvas.width, canvas.height);
drawWithKernel("normal");
function setFramebuffer(fbo, width, height) {
// make this the framebuffer we are rendering to.
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
// Tell the shader the resolution of the framebuffer.
gl.uniform2f(resolutionLocation, width, height);
// Tell webgl the viewport setting needed for framebuffer.
gl.viewport(0, 0, width, height);
}
function drawWithKernel(name) {
// set the kernel
gl.uniform1fv(kernelLocation, kernels[name]);
// Draw the rectangle.
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
我要講解一些事情。
使用 null
呼叫 gl.bindFramebuffer
,可讓 WebGL 知道您要將圖像算繪至畫布,而非其中一個 framebuffer。WebGL 必須從剪輯區轉換回像素。這項作業會根據 gl.viewport
的設定進行。初始化 WebGL 時,gl.viewport
的設定預設為畫布的大小。由於我們要轉譯的 framebuffer 大小與畫布不同,因此我們需要適當設定檢視區域。最後,在 WebGL 基礎範例中,我們在算繪時翻轉 Y 座標,因為 WebGL 會在顯示畫布的左下角時切換 Y 座標,而非傳統的 2D 左上方較為傳統。不過,如果要轉譯成 framebuffer,就不需要這麼做。由於 framebuffer 從未顯示,因此頂端和底部的位置並不重要。重要的是,在計算中,幀緩衝區中的 0,0 像素會對應至 0,0。為解決這個問題,我新增了一個輸入至著色器,讓您可以設定是否要翻轉。
<script id="2d-vertex-shader" type="x-shader/x-vertex">
...
uniform float u_flipY;
...
void main() {
...
gl_Position = vec4(clipSpace * vec2(1, u_flipY), 0, 1);
...
}
</script>
然後在使用 Renderer 時設定
...
var flipYLocation = gl.getUniformLocation(program, "u_flipY");
...
// don't flip
gl.uniform1f(flipYLocation, 1);
...
// flip
gl.uniform1f(flipYLocation, -1);
為了讓這個範例簡單易懂,我使用了可產生多種效果的單一 GLSL 程式。如果您想進行完整的圖片處理作業,可能需要許多 GLSL 程式。調整色調、飽和度與亮度調整程式。另一種設定亮度和對比。一個用於反轉,另一個用於調整音量等。您需要變更程式碼,才能切換 GLSL 程式並更新該特定程式的參數。我考慮要寫這個範例,但這項練習最好交由讀者自行完成,因為多個 GLSL 程式各自有其參數需求,因此可能需要進行重大重構,才能避免程式變得雜亂無章可循。希望這篇文章和前面的範例能讓您更容易上手 WebGL,並且從 2D 開始學習,讓您更容易理解 WebGL。如果有時間,我會再寫幾篇文章,說明如何處理 3D 問題,以及 WebGL 在幕後實際執行的操作。
WebGL 和 Alpha
我發現部分 OpenGL 開發人員在 WebGL 處理後置緩衝區 (即畫布) 中的 alpha 時遇到問題,因此我想介紹 WebGL 和 OpenGL 在 alpha 方面的差異。
OpenGL 與 WebGL 的最大差異在於,OpenGL 會算繪到未與任何內容合併的後緩衝區,或實際上不會與 OS 的視窗管理員結合,因此不論 Alpha 值為何都不重要。WebGL 是由具有網頁的瀏覽器構成,而預設使用具有透明度和 2d 的 .png <img>
標記。WebGL 有幾種方式可以使此作業更接近 OpenGL。
#1) 告訴 WebGL 您想要以未經預乘 Alpha 值進行合成
gl = canvas.getContext("experimental-webgl", {premultipliedAlpha: false});
預設值為 true。當然,結果仍會在網頁上合成,並使用畫布下方的背景顏色 (畫布的背景顏色、畫布的容器背景顏色、網頁的背景顏色、畫布後方的內容 (如果畫布有 z-index > 0) 等),也就是 CSS 為網頁該區域定義的顏色。建議您將畫布的背景設為紅色 (例如紅色),找出是否發生 Alpha 問題。你會立即看到發生什麼事。
<canvas style="background: red;"></canvas>
您也可以將其設為黑色,這樣就能隱藏所有 alpha 問題。
#2) 告訴 WebGL 您不想在後端緩衝區中使用 alpha
gl = canvas.getContext("experimental-webgl", {alpha: false});
由於背景緩衝區只有 RGB,這會使它看起來更像 OpenGL。這可能是最佳選項,因為優質瀏覽器可以看到您沒有 alpha,並實際最佳化 WebGL 的組合方式。當然,這也表示它實際上不會在後端緩衝區中顯示 alpha 值,因此如果您在後端緩衝區中使用 alpha 值,可能無法達到預期效果。據我所知,只有少數應用程式會在後端緩衝區使用 alpha 值。我認為這應該是預設值。
#3) 在轉譯結束時清除 alpha
..
renderScene();
..
// Set the backbuffer's alpha to 1.0
gl.clearColor(1, 1, 1, 1);
gl.colorMask(false, false, false, true);
gl.clear(gl.COLOR_BUFFER_BIT);
清除作業通常非常快速,因為大多數硬體都有專屬的清除作業。我在大多數的示範中都這麼做。如果我很聰明,我會改用上述方法 2。或許我會在發布這則訊息後立即採取行動。大部分的 WebGL 程式庫應該都預設使用此方法。只有少數開發人員會使用 Alpha 來合成特效,因此只有這類開發人員可以提出申請。其餘的則會獲得最佳效能,並減少意外情況。
#4) 清除 alpha 一次,然後不再對其算繪
// At init time. Clear the back buffer.
gl.clearColor(1,1,1,1);
gl.clear(gl.COLOR_BUFFER_BIT);
// Turn off rendering to alpha
gl.colorMask(true, true, true, false);
當然,如果您是向自己的 framebuffer 算繪,可能就需要重新開啟算繪 Alpha 版,然後在切換至畫布模式時再次關閉算繪功能。
#5) 處理圖片
此外,如果您要將含有 Alpha 的 PNG 檔案載入紋理,預設情況下,系統會預先乘上 Alpha,但這通常不是大多數遊戲的做法。如要避免這種行為,您需要透過以下方式告知 WebGL:
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
#6) 使用可與預先相乘的 alpha 運算的混合方程式
我編寫或處理過的幾乎所有 OpenGL 應用程式都使用
gl.blendFunc(gl.SRC_ALPHA, gl_ONE_MINUS_SRC_ALPHA);
這適用於未預先相乘的 Alpha 紋理。如果您實際上想使用預先相乘的 Alpha 紋理,那麼您可能會想要
gl.blendFunc(gl.ONE, gl_ONE_MINUS_SRC_ALPHA);
這些是我知道的方法。如果你知道更多資訊,請在下方貼文。