WebGL 基礎知識

Gregg Tavares
Gregg Tavares

WebGL 基礎知識

WebGL 可讓您在瀏覽器中顯示令人驚豔的即時 3D 圖形,但許多使用者不知道 WebGL 其實是 2D API,而不是 3D API。讓我來說明。

WebGL 只關心 2 個事物。2D 和顏色的 Clipspace 座標。使用 WebGL 的程式設計人員的工作是提供這 2 個來源的 WebGL。您需要提供 2 個「著色器」來進行這項操作。Vertex 著色器,可提供 clipspace 座標和提供顏色的片段著色器。無論畫布大小為何,Clipspace 座標一律都會從 -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 的著色器,因為 WebGL IS A 2D API 而只能由 3D 轉換為 2D,完全由您決定! 針對 2D 內容,您或許會比較想要以像素為單位,而不是裁切空間。我們現在要變更著色器,讓我們可以提供以像素為單位的矩形,並由我們轉換為 Clipspace。新版頂點著色器隆重登場

<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 預期會用裁剪空間座標的方式,但在讀取紋理時, WebGL 會預期紋理座標。無論紋理尺寸為何,紋理座標都會介於 0.0 到 1.0 之間。由於我們只繪製一個矩形 (例如 2 個三角形),因此需要告知 WebGL 矩形中每個點對應的位置。我們會使用名為「變化式」的特殊變數,將這些資訊從頂點著色器傳遞至片段著色器。名稱不同,因此稱為「變化」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 (值大於 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 中變數中的 、u 和 v_ 前置字元有什麼作用?

這只是一個命名慣例。a_ 用於屬性,也就是緩衝區提供的資料。u_ 是製式的輸入值,可為著色器輸入;v_ 表示不同的變化,也就是從頂點著色器傳遞至片段著色器的值,並在繪製的每個像素的頂點之間插入 (或變化)。

套用多種特效

下一個最顯而易見的問題是,我該如何套用多種效果?

您可以嘗試即時產生著色器。提供 UI 讓使用者選取想要使用的特效,然後產生可執行所有效果的著色器。雖然這是為即時圖像建立特效的技巧,但是您不一定能夠這樣做。 更靈活的方法是使用 2 個更多紋理,然後輪流算繪每個紋理,對過去進行連線偵測 (ping),每次套用下一個效果。

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

為此,我們需要建立 framebuffers。在 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 個影格緩衝區。

// 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) 連線偵測 (ping) 我們同樣算繪的紋理

// 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,您想算繪至畫布,而非其中一個影格緩衝區。 WebGL 必須從 Clipspace 轉換回像素。這項操作是以 gl.viewport 的設定為依據。初始化 WebGL 時,gl.viewport 的設定預設為畫布大小。由於我們算繪的影格緩衝區大小不同,因此我們必須正確設定可視區域。最後在 WebGL 基本原則範例中,我們進行算繪時翻轉 Y 座標,因為 WebGL 顯示畫布的左下角為 0,0,而非傳統的 2D 畫面左下角。算繪至 Framebuffer 時不需要。由於 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>

將容器設定為

...
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 視窗管理員合併的任何物件。因此,WebGL 是瀏覽器與網頁合成。WebGL 是瀏覽器與網頁結合的畫布,預設使用的畫布與 .png <img> 標記相同,具有透明度和 2d 標記。WebGL 提供多種方式,讓瀏覽器更加像 OpenGL。

#1) 告訴 WebGL 您希望以未預先乘法 Alpha 版進行合成

gl = canvas.getContext("experimental-webgl", {premultipliedAlpha: false});

預設值為 true。當然,系統仍會將結果合併在頁面上,任何背景顏色在畫布下 (畫布的背景顏色、畫布的容器背景顏色、頁面背景顏色、畫布後方的內容)、要找出是否有任何 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);

這些是我知道的方法。如果您有興趣瞭解更多,請在下方張貼。