WebGL を使用すると、ブラウザで素晴らしいリアルタイム 3D グラフィックを表示できますが、多くの人が知らないのは、WebGL は実際には 3D API ではなく 2D API であるということです。説明しましょう。
WebGL が気にするのは 2 つのことだけです。2D のクリップ空間座標と色。WebGL を使用するプログラマーの仕事は、WebGL にこの 2 つを提供することです。これを行うには、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);
長方形を定義するコードを関数にして、さまざまなサイズの長方形に対して呼び出せるようにしましょう。ついでに、色を設定できるようにします。 まず、フラグメント シェーダーが色の uniform 入力を受け取るようにします。
<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 になります。描画するのは 1 つの長方形(2 つの三角形)だけなので、長方形の各点がテクスチャのどの場所に対応するかを WebGL に伝える必要があります。この情報は、「varying」と呼ばれる特別な種類の変数を使用して、頂点シェーダーからフラグメント シェーダーに渡します。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 のテクスチャ座標でテクスチャを参照するため、1 ピクセルを移動する量を onePixel = 1.0 / textureSize という簡単な計算で算出できます。以下は、テクスチャ内の各ピクセルの左右のピクセルの平均を求めるフラグメント シェーダーです。
<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_ は頂点シェーダーからフラグメント シェーダーに渡され、描画されるピクセルごとに頂点間で補間(または変化)される値であるバリアント用です。
複数のエフェクトを適用する
画像処理に関する次の質問は、複数のエフェクトを適用する方法です。
シェーダーをオンザフライで生成することもできます。ユーザーが使用するエフェクトを選択できる UI を提供し、すべてのエフェクトを実行するシェーダーを生成します。これは必ずしも可能とは限りませんが、この手法はリアルタイム グラフィックのエフェクトを作成するためによく使用されます。より柔軟な方法は、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
これを行うには、フレームバッファを作成する必要があります。WebGL と OpenGL では、フレームバッファは実際には不適切な名前です。WebGL / OpenGL フレームバッファは、実際には状態のコレクションにすぎず、バッファではありません。しかし、テクスチャをフレームバッファにアタッチすることで、そのテクスチャにレンダリングできます。 まず、古いテクスチャ作成コードを関数に変換しましょう。
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"
];
最後に、それぞれを適用し、レンダリングするテクスチャをピンポンします。
// 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 を呼び出すと、フレームバッファの 1 つではなく、キャンバスにレンダリングすることを WebGL に指示します。WebGL はクリップ空間からピクセルに変換する必要があります。これは、gl.viewport の設定に基づいて行われます。gl.viewport の設定は、WebGL の初期化時にデフォルトでキャンバスのサイズになります。レンダリング先のフレームバッファはキャンバスとはサイズが異なるため、ビューポートを適切に設定する必要があります。最後に、WebGL の基礎の例では、レンダリング時に Y 座標を反転しました。これは、WebGL では、2D の一般的な左上ではなく、左下を 0,0 としてキャンバスが表示されるためです。フレームバッファにレンダリングする場合は、これは必要ありません。フレームバッファは表示されないため、どの部分が上部でどの部分が下部かは関係ありません。重要なのは、フレームバッファのピクセル 0,0 が計算の 0,0 に対応していることだけです。これに対処するため、シェーダーに入力を 1 つ追加して、反転するかどうかを設定できるようにしました。
<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 プログラムが必要になります。色相、彩度、輝度を調整するためのプログラム。もう 1 つは明るさとコントラストです。1 つは反転用、もう 1 つはレベル調整用などです。GLSL プログラムを切り替えて、その特定のプログラムのパラメータを更新するには、コードを変更する必要があります。その例を書くことも考えましたが、それは読者の皆さんにお任せすることにしました。複数の GLSL プログラムがそれぞれ独自のパラメータを必要とする場合、すべてがスパゲッティのように複雑にならないようにするには、大幅なリファクタリングが必要になる可能性があるためです。 この例と前の例で、WebGL が少し身近に感じられるようになったのではないでしょうか。2D から始めることで、WebGL が少し理解しやすくなったと思います。時間があれば、3D の実装方法や、WebGL の内部的な仕組みについて、さらに記事を書きたいと思います。
WebGL とアルファ
OpenGL デベロッパーの中には、WebGL がバックバッファ(キャンバス)でアルファを処理する方法に問題があることに気づいている人もいるようです。そこで、アルファに関連する WebGL と OpenGL の違いについて説明します。
OpenGL と WebGL の最大の違いは、OpenGL は他のものと合成されないバックバッファにレンダリングされるため、OS のウィンドウ マネージャーによって他のものと合成されないため、アルファ値が何であっても問題ありません。
WebGL はブラウザによってウェブページと合成され、デフォルトでは、透明度と 2D キャンバスタグを持つ .png <img> タグと同じように、事前に乗算されたアルファ値を使用します。WebGL には、これを OpenGL に近づける方法がいくつかあります。
#1. 非乗算済みアルファで合成することを WebGL に指示する
gl = canvas.getContext("experimental-webgl", {premultipliedAlpha: false});
デフォルトは true です。もちろん、結果はキャンバスの下にある背景色(キャンバスの背景色、キャンバスのコンテナの背景色、ページの背景色、キャンバスの z-index が 0 より大きい場合はキャンバスの背後にあるものなど)でページ上に合成されます。つまり、CSS がウェブページのその領域に定義する色です。アルファの問題があるかどうかを確認する非常に良い方法は、キャンバスの背景を赤などの明るい色に設定することです。何が起きているのかをすぐに確認できます。
<canvas style="background: red;"></canvas>
黒に設定して、アルファの問題を非表示にすることもできます。
#2. バックバッファでアルファを使用しないことを WebGL に伝える
gl = canvas.getContext("experimental-webgl", {alpha: false});
バックバッファに RGB のみが含まれるため、OpenGL のような動作になります。優れたブラウザであれば、アルファがないことを認識し、WebGL の合成方法を実際に最適化できるため、おそらくこれが最適なオプションです。もちろん、バックバッファにアルファ値が含まれないため、何らかの目的でバックバッファのアルファ値を使用している場合は、この方法は適さない可能性があります。バックバッファでアルファを使用するアプリはほとんどありません。これはデフォルトにすべきだったと言えるでしょう。
#3. レンダリングの最後にアルファをクリアする
..
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 ライブラリは、デフォルトでこのメソッドを使用するはずです。実際にアルファ版を合成効果に使用している少数のデベロッパーは、この機能をリクエストできます。それ以外の場合は、最高のパフォーマンスが得られ、予期せぬ事態を最小限に抑えることができます。
#4. アルファを 1 回クリアしてから、レンダリングしない
// 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);
もちろん、独自のフレームバッファにレンダリングする場合は、アルファへのレンダリングを再度有効にしてから、キャンバスへのレンダリングに切り替えるときに再度無効にする必要があります。
#5. 画像の処理
また、アルファ付きの PNG ファイルをテクスチャに読み込む場合、デフォルトではアルファが事前に乗算されます。これは、ほとんどのゲームで行われる方法ではありません。この動作を防ぐには、WebGL に次のように指示する必要があります。
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
#6. 乗算済みアルファで機能するブレンド式を使用する
私が作成または作業した OpenGL アプリのほぼすべてが、
gl.blendFunc(gl.SRC_ALPHA, gl_ONE_MINUS_SRC_ALPHA);
これは、事前乗算されていないアルファ テクスチャで機能します。実際に乗算済みアルファ テクスチャを使用する場合は、
gl.blendFunc(gl.ONE, gl_ONE_MINUS_SRC_ALPHA);
私が知っている方法は以上です。他にもご存じのことがあれば、以下に投稿してください。