WebGL(TypeScript)--- 立方体描画&回転

下記記事にて、WebGLを用いて三角形を描画しました。
WebGL(TypeScript)--- 三角形描画 - 何でもプログラミング

今回は引き続き、立方体を描画&回転を実装してみたいと思います。

上記記事をベースに実装していきますので、解説のない部分は参照をお願いいたします。

Index Buffer作成関数

今回はIndex Bufferを用いて描画を行ってみたいと思います。

ですので、作成関数を準備します。

function createIndexBuffer(gl : WebGLRenderingContext, indices : number[]) : WebGLBuffer {
    const buffer = gl.createBuffer()!;

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);

    return buffer;
}


Matrix4クラス

回転を実現するため、最低限の行列クラスを準備します。

回転行列を作成するrotate関数、転置を行うtranspose関数(WebGLは列優先のため)、左上の3x3行列を取り出すgetMatrix3Data関数(法線変換用)を定義しています。

class Matrix4 {
    constructor(readonly data: number[]) { }

    transposed() : Matrix4 {
        return new Matrix4([
            this.data[0], this.data[4], this.data[8],  this.data[12],
            this.data[1], this.data[5], this.data[9],  this.data[13],
            this.data[2], this.data[6], this.data[10], this.data[14],
            this.data[3], this.data[7], this.data[11], this.data[15],
        ]);
    }

    getMatrix3Data() : number[] {
        return [
            this.data[0], this.data[1], this.data[2],
            this.data[4], this.data[5], this.data[6],
            this.data[8], this.data[9], this.data[10],
        ];
    }

    static rotate(degree : number, x : number, y : number, z : number) : Matrix4 {
        const length = Math.sqrt(x * x + y * y + z * z);
        x /= length;
        y /= length;
        z /= length;
        const radian = degree * Math.PI / 180.0;
        const s = Math.sin(radian);
        const c = Math.cos(radian);
        return new Matrix4([
            x * x * (1 - c) + c,     x * y * (1 - c) - z * s, z * x * (1 - c) + y * s, 0,
            x * y * (1 - c) + z * s, y * y * (1 - c) + c,     y * z * (1 - c) - x * s, 0,
            z * x * (1 - c) - y * s, y * z * (1 - c) + x * s, z * z * (1 - c) + c,     0,
            0,                       0,                       0,                       1
        ]);
    }
}


main関数

ライティングを行うShaderの作成と、立方体のVertex Buffer&Index Bufferの作成を行い、requestAnimationFrameにて回転させつつ描画を行っています。

実行すると下記のような表示になります。(実際はアニメーションします。)
f:id:any-programming:20171214141158p:plain

let gl : WebGLRenderingContext;

function main() : void {
    const canvas = <HTMLCanvasElement>document.getElementById("gl");
    gl = canvas.getContext("webgl")!;

    // Vertex Shader(頂点と法線を変換して次へ)
    const vertexCode = `
        attribute vec4 position;
        attribute vec3 normal;
        uniform mat4 matrix;
        uniform mat3 normalMatrix;
        varying vec3 v_normal;
        void main() {
            gl_Position = matrix * position;
            v_normal = normalMatrix * normal;
        }    
    `;

    // Fragment Shader(ライティングを行って描画)
    const fragmentCode = `
        precision mediump float;
        varying vec3 v_normal;
        void main() {
            vec3 light = vec3(0.0, 0.0, -1.0);            
            float g = dot(light, normalize(v_normal));
            g = max(0.0, g);
            gl_FragColor = vec4(0.0, g, 0.0, 1.0);
        }
    `;

    const program = createProgram(gl, vertexCode, fragmentCode);

    // 立方体定義(頂点、法線)
    const vertices = [
         0.5,  0.5,  0.5,    0.0,  0.0,  1.0,
        -0.5,  0.5,  0.5,    0.0,  0.0,  1.0,
         0.5, -0.5,  0.5,    0.0,  0.0,  1.0,
        -0.5, -0.5,  0.5,    0.0,  0.0,  1.0,

         0.5,  0.5, -0.5,    0.0,  0.0, -1.0,
        -0.5,  0.5, -0.5,    0.0,  0.0, -1.0,
         0.5, -0.5, -0.5,    0.0,  0.0, -1.0,
        -0.5, -0.5, -0.5,    0.0,  0.0, -1.0,

         0.5,  0.5,  0.5,    1.0,  0.0,  0.0,
         0.5, -0.5,  0.5,    1.0,  0.0,  0.0,
         0.5,  0.5, -0.5,    1.0,  0.0,  0.0,
         0.5, -0.5, -0.5,    1.0,  0.0,  0.0,

        -0.5,  0.5,  0.5,   -1.0,  0.0,  0.0,
        -0.5, -0.5,  0.5,   -1.0,  0.0,  0.0,
        -0.5,  0.5, -0.5,   -1.0,  0.0,  0.0,
        -0.5, -0.5, -0.5,   -1.0,  0.0,  0.0,

         0.5,  0.5,  0.5,    0.0,  1.0,  0.0,
         0.5,  0.5, -0.5,    0.0,  1.0,  0.0,
        -0.5,  0.5,  0.5,    0.0,  1.0,  0.0,
        -0.5,  0.5, -0.5,    0.0,  1.0,  0.0,

         0.5, -0.5,  0.5,    0.0, -1.0,  0.0,
         0.5, -0.5, -0.5,    0.0, -1.0,  0.0,
        -0.5, -0.5,  0.5,    0.0, -1.0,  0.0,
        -0.5, -0.5, -0.5,    0.0, -1.0,  0.0,
    ];
    const vertexBuffer = createVertexBuffer(gl, vertices);

    const indices = [
        0,  2,  1,
        1,  2,  3,
        4,  5,  6,
        5,  7,  6,

        8,  10, 9,
        9,  10, 11,
        12, 13, 14,
        13, 15, 14,

        16, 18, 17,
        17, 18, 19,
        20, 21, 22,
        21, 23, 22,
    ];
    const indexBuffer = createIndexBuffer(gl, indices);

    // 初期設定
    gl.useProgram(program);
    const positionAttrib = gl.getAttribLocation(program, "position");
    const normalAttrib = gl.getAttribLocation(program, "normal");
    const matrixUniform = gl.getUniformLocation(program, "matrix")!;
    const normalMatrixUniform = gl.getUniformLocation(program, "normalMatrix")!;

    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
    gl.enableVertexAttribArray(positionAttrib);
    gl.enableVertexAttribArray(normalAttrib);
    gl.vertexAttribPointer(positionAttrib, 3, gl.FLOAT, false, 24, 0);
    gl.vertexAttribPointer(normalAttrib, 3, gl.FLOAT, false, 24, 12);

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);

    gl.enable(gl.DEPTH_TEST);
    gl.enable(gl.CULL_FACE); // デフォルトで反時計回りが表
    
    // 描画関数
    let rotation = 0;
    function step(time : number) : void {
        rotation++;

        const mat = Matrix4.rotate(rotation, 1, 1, 1).transposed();
        gl.uniformMatrix4fv(matrixUniform, false, mat.data);
        gl.uniformMatrix3fv(normalMatrixUniform, false, mat.getMatrix3Data());

        gl.clearColor(0.8, 0.8, 0.8, 1);
        gl.clear(gl.COLOR_BUFFER_BIT);
        gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);

        requestAnimationFrame(step);
    }
    // 描画スタート
    requestAnimationFrame(step);
}

main();