WebGL 正投影 3D

Gregg Tavares
Gregg Tavares

WebGL 正投影 3D

この投稿は、WebGL に関する一連の投稿の続きです。 まずは基礎から 前のものは 2 次元行列に関する 2 次元行列でした。 これらをご覧になっていない場合は、先にご覧ください。 前回の投稿では、2 次元行列の仕組みについて説明しました。お話ししました 変換、回転、スケーリング、さらには投影まで 1 つのマトリクスと魔法を使って 行列計算ですこれから 3D を制作するには、ほんの小さな一歩です。 これまでの 2 次元の例では、2 次元のポイント(x, y)に乗算を行いました。 3x3 マトリックスです。3D を行うには、3 次元の点(x, y, z)と 4x4 の行列が必要です。 最後の例を 3D に変更します。ここでも F キーを使用しますが、今回は 3D の「F」を使用します。 まず、3D を処理するように頂点シェーダーを変更する必要があります。これが古いシェーダーです。

<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;

uniform mat3 u_matrix;

void main() {
// Multiply the position by the matrix.
gl_Position = vec4((u_matrix * vec3(a_position, 1)).xy, 0, 1);
}
</script>

こちらが新しい

<script id="3d-vertex-shader" type="x-shader/x-vertex">
attribute vec4 a_position;

uniform mat4 u_matrix;

void main() {
// Multiply the position by the matrix.
gl_Position = u_matrix * a_position;
}
</script>

さらにシンプルになりました。 次に、3D データを提供する必要があります。

...

gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0);

...

// Fill the buffer with the values that define a letter 'F'.
function setGeometry(gl) {
gl.bufferData(
    gl.ARRAY_BUFFER,
    new Float32Array([
        // left column
        0,   0,  0,
        30,   0,  0,
        0, 150,  0,
        0, 150,  0,
        30,   0,  0,
        30, 150,  0,

        // top rung
        30,   0,  0,
        100,   0,  0,
        30,  30,  0,
        30,  30,  0,
        100,   0,  0,
        100,  30,  0,

        // middle rung
        30,  60,  0,
        67,  60,  0,
        30,  90,  0,
        30,  90,  0,
        67,  60,  0,
        67,  90,  0]),
    gl.STATIC_DRAW);
}

次に、すべての行列関数を 2D から 3D に変更する必要があります。 makeTranslation、makeRotation、makeScale の 2D(前)バージョン

function makeTranslation(tx, ty) {
return [
1, 0, 0,
0, 1, 0,
tx, ty, 1
];
}

function makeRotation(angleInRadians) {
var c = Math.cos(angleInRadians);
var s = Math.sin(angleInRadians);
return [
c,-s, 0,
s, c, 0,
0, 0, 1
];
}

function makeScale(sx, sy) {
return [
sx, 0, 0,
0, sy, 0,
0, 0, 1
];
}

こちらが 3D バージョンの更新です

function makeTranslation(tx, ty, tz) {
return [
    1,  0,  0,  0,
    0,  1,  0,  0,
    0,  0,  1,  0,
    tx, ty, tz, 1
];
}

function makeXRotation(angleInRadians) {
var c = Math.cos(angleInRadians);
var s = Math.sin(angleInRadians);

return [
1, 0, 0, 0,
0, c, s, 0,
0, -s, c, 0,
0, 0, 0, 1
];
};

function makeYRotation(angleInRadians) {
var c = Math.cos(angleInRadians);
var s = Math.sin(angleInRadians);

return [
c, 0, -s, 0,
0, 1, 0, 0,
s, 0, c, 0,
0, 0, 0, 1
];
};

function makeZRotation(angleInRadians) {
var c = Math.cos(angleInRadians);
var s = Math.sin(angleInRadians);
return [
    c, s, 0, 0,
-s, c, 0, 0,
    0, 0, 1, 0,
    0, 0, 0, 1,
];
}

function makeScale(sx, sy, sz) {
return [
sx, 0,  0,  0,
0, sy,  0,  0,
0,  0, sz,  0,
0,  0,  0,  1,
];
}

3 つの回転関数があることに注目してください。2D で必要なのは 1 つだけでした。 実質的には Z 軸を中心に回転するだけでした。3D については、 X 軸と Y 軸を中心に回転させたい場合もあります。マイページ どれも非常によく似ていることが わかりますもし 調整すると、前のように単純化されます。

Z 回転

newX = x * c + y * s;
newY = x * -s + y * c;

Y 回転


newX = x * c + z * s;
newZ = x * -s + z * c;

X 回転

newY = y * c + z * s;
newZ = y * -s + z * c;

射影関数も更新する必要があります。古いのはこちら

function make2DProjection(width, height) {
// Note: This matrix flips the Y axis so 0 is at the top.
return [
2 / width, 0, 0,
0, -2 / height, 0,
-1, 1, 1
];
}

ピクセルからクリップ空間に変換できます。初めて 3D に展開してみましょう。

function make2DProjection(width, height, depth) {
// Note: This matrix flips the Y axis so 0 is at the top.
return [
    2 / width, 0, 0, 0,
    0, -2 / height, 0, 0,
    0, 0, 2 / depth, 0,
-1, 1, 0, 1,
];
}

同様に、x と y についてピクセルからクリップ空間に変換する必要がありましたが、 z 同じことをする必要があります。ここでは Z 空間ピクセルにします。 できます。深度として width に似た値を渡します。 スペースは 0 から幅ピクセル、0 から高さピクセルになりますが、 深さは -depth / 2 ~+depth / 2 です。 最後に、行列を計算するコードを更新する必要があります。

// Compute the matrices
var projectionMatrix =
    make2DProjection(canvas.width, canvas.height, canvas.width);
var translationMatrix =
    makeTranslation(translation[0], translation[1], translation[2]);
var rotationXMatrix = makeXRotation(rotation[0]);
var rotationYMatrix = makeYRotation(rotation[1]);
var rotationZMatrix = makeZRotation(rotation[2]);
var scaleMatrix = makeScale(scale[0], scale[1], scale[2]);

// Multiply the matrices.
var matrix = matrixMultiply(scaleMatrix, rotationZMatrix);
matrix = matrixMultiply(matrix, rotationYMatrix);
matrix = matrixMultiply(matrix, rotationXMatrix);
matrix = matrixMultiply(matrix, translationMatrix);
matrix = matrixMultiply(matrix, projectionMatrix);

// Set the matrix.
gl.uniformMatrix4fv(matrixLocation, false, matrix);

1 つ目の問題は ジオメトリが平面であるため 3D を見づらくなります。この問題を修正するには、ジオメトリを 3D に拡張します。Google 現在の F は 3 つの長方形、それぞれ 2 つの三角形で構成されています。3D にするには、 合計 16 個の長方形が必要ですここに挙げるものはたくさんあります。 長方形 16 個 x 1 つの長方形あたり 2 つの三角形 x 1 つの三角形 3 つの頂点は 96 個 あります。サンプルのすべてのビューソースを確認したい場合は、 もっと頂点を描画する必要があるので

// Draw the geometry.
gl.drawArrays(gl.TRIANGLES, 0, 16 * 6);

スライダーを動かすと、3D かどうか見分けがつきにくくなります。やってみる それぞれの長方形の色を変更できます。そのために、もう一つ 属性を頂点シェーダーに設定し、頂点シェーダーから値を渡すには、 フラグメント シェーダーにマッピングされます。 これが新しい頂点シェーダーです。

<script id="3d-vertex-shader" type="x-shader/x-vertex">
attribute vec4 a_position;
attribute vec4 a_color;

uniform mat4 u_matrix;

varying vec4 v_color;

void main() {
// Multiply the position by the matrix.
gl_Position = u_matrix * a_position;

// Pass the color to the fragment shader.
v_color = a_color;
}
</script>

そして、その色をフラグメント シェーダーで使用する必要があります。

<script id="3d-vertex-shader" type="x-shader/x-fragment">
precision mediump float;

// Passed in from the vertex shader.
varying vec4 v_color;

void main() {
gl_FragColor = v_color;
}
</script>

色を指定して場所を検索してから、別の 色を指定します。

...
var colorLocation = gl.getAttribLocation(program, "a_color");

...
// Create a buffer for colors.
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.enableVertexAttribArray(colorLocation);

// We'll supply RGB as bytes.
gl.vertexAttribPointer(colorLocation, 3, gl.UNSIGNED_BYTE, true, 0, 0);

// Set Colors.
setColors(gl);

...
// Fill the buffer with colors for the 'F'.

function setColors(gl) {
gl.bufferData(
    gl.ARRAY_BUFFER,
    new Uint8Array([
        // left column front
    200,  70, 120,
    200,  70, 120,
    200,  70, 120,
    200,  70, 120,
    200,  70, 120,
    200,  70, 120,

        // top rung front
    200,  70, 120,
    200,  70, 120,
    ...
    ...
    gl.STATIC_DRAW);
}

えーっと、なにか?結局のところ、ML の 3D の「F」記号、前面、背面、側面などが、表示されている順序で描画されます。 使用します。この方法では期待した結果が得られないことがある 後ろの絵が前の絵の後に描画されます。 WebGL の三角形には、前面と背面というコンセプトがあります。 正面三角形の頂点は時計回りです 頂点が反時計回りになっている場合は

曲がりくねった三角形。

WebGL は、前面と背面のみを描画できる 使用できます。この機能を有効にするには

gl.enable(gl.CULL_FACE);

プログラムの開始時に一度だけ行いますこれで 機能がオンの場合、WebGL はデフォルトで「culling」になります。裏向きの三角形です。 "Culling"これは「書いたことがない」ことを表す言葉です。 なお、WebGL を認識していれば、三角形が認識されているかどうかに関係なく、 時計回りか反時計回りかは その三角形の頂点をクリップ空間で作成します。つまり WebGL は 計算を行った後に、三角形が表と裏のどちらであるかを 使用します。たとえば時計回りで X を -1 でスケーリングした三角形は、反時計回りの三角形になります。 X 軸または Y 軸を中心に 180 度回転した時計回りの三角形は、反時計回りの三角形になります。 CULL_FACE が無効になっているため 時計回り(前)と反時計回り(後)の三角形を使用しますこれで、 オンにすれば、正面を向いた三角形が 拡大縮小や回転などの理由で WebGL では描画が行われません。 3D で作品を回転させると 正面の三角形を正面と見なす傾向があります あります。

ねえ!全部の三角形はどこにあるの?その多くが 間違った方向に直面しているからです回転させると見やすくなります 使用します修正は簡単です。どの Pod が 頂点の 2 つを交換します。たとえば 後方三角形は

1,   2,   3,
40,  50,  60,
700, 800, 900,

最後の 2 つの頂点を反転させるだけです。

1,   2,   3,
700, 800, 900,
40,  50,  60,

それに近いのですが、問題がもう 1 つあります。いずれにしても、 正しい向きと裏向きの三角形 取り除かれても 三角形の位置は戻るはず 真正面にあるはずの三角形の上に描画されます。 DEPTH BUFFER を入力します。 Z バッファとも呼ばれる Depth バッファは、depth の長方形です。 ピクセルです。画像の作成に使用されたカラーピクセルごとに 1 つの奥行きピクセルです。として WebGL は、各カラーピクセルを描画します。また、奥行きピクセルを描画することもできます。この処理 Z の頂点シェーダーから返される値に基づきます。私たちが X と Y のクリップ スペースに変換しなければならなかったので、Z はクリップ スペース つまり (-1) になります。 )。この値は、深度空間値(0 ~+1)に変換されます。 WebGL は、カラーピクセルを描画する前に、対応する深度をチェックします。 。描画しようとしているピクセルの奥行きの値が大きければ、 対応する深度ピクセルの値よりも値が小さいと、WebGL は描画されない 作成します。それ以外の場合は、新しいカラーピクセルと 新しい要素で深度ピクセルを描画します。 深さの値。つまり 他のピクセルの背後にあるピクセルは 表示されます。 これは クエリで除外をオンにしたのと 同じように簡単に有効にできます

gl.enable(gl.DEPTH_TEST);

また、描画を開始する前に、深度バッファをクリアして 1.0 に戻す必要があります。

// Draw the scene.
function drawScene() {
// Clear the canvas AND the depth buffer.
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
...

次の投稿では、視点を持たせる方法を説明します。

vec4 属性の gl.vertexAttribPointer のサイズが 3 である理由

詳細志向の皆さんはお気づきかもしれませんが、Google は 2 つの属性を次のように定義しています。

attribute vec4 a_position;
attribute vec4 a_color;

どちらも「vec4」です。バッファからデータを取り出す方法を WebGL に指示する際には、

gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0);
gl.vertexAttribPointer(colorLocation, 3, gl.UNSIGNED_BYTE, true, 0, 0);

その「3」それぞれの例で、属性ごとに 3 つの値を取得するだけです。 これは、頂点シェーダーでは WebGL により、デフォルトで 不要ですデフォルトは 0、0、0、1 です。x = 0、y = 0、z = 0 です。 w = 1 です。そのため、従来の 2D 頂点シェーダーでは、 1 を指定します。x と y を渡しており、z には 1 が必要でしたが、 z のデフォルトは 0 なので、明示的に 1 を指定する必要がありました。3D 用 「w」は渡していませんが、デフォルトは 1 で、 行列計算が機能する必要があります