Flash Advent Calendar 8日目 - WebGL導入への道のり01 -


これまで、Canvas2Dで描画部分を作っていたのですが
やはり、速度面で問題があり、WebGLを導入する事にしました。

とはいえ、全くやった事がないので、何から始めるか悩んだのですが
まずはCanvas2Dの機能をラッピングする事からはじめて見ました。

が、そんな簡単な話ではなかったので、何日かに分けて掲載できればと思います。
ますは、基盤を作っていこうかと思います。

目次

  • Programを作る
  • Shaderで必要なUniformを動的に取得する

Programを作る

ProgramにセットするShaderは後日記載できればと思います。
なので、Shaderがある程で書いていきます。


/**
 * @param {WebGLRenderingContext} gl
 * @constructor
 * @public
 */ 
constructor (gl) 
{
    // 他の関数でコンテキスト使えるようにしておき
    this.gl = gl;
}

/**
 * @param  {string} vertex_source
 * @param  {string} fragment_source
 * @return {WebGLProgram}
 * @public
 */ 
createProgram (vertex_source, fragment_source) 
{
    // 新規のprogramを作成
    const program = this.gl.createProgram();

    // 頂点シェーダーを作成
    const vertexShader = this.gl.createShader(this.gl.VERTEX_SHADER);
    this.gl.shaderSource(vertexShader, vertex_source);
    this.gl.compileShader(vertexShader);

    // 頂点シェーダーのエラーを感知する。
    // ただ処理が重いので本番用のビルド時は対象外にする。
    if (!this.gl.getShaderParameter(vertexShader, this.gl.COMPILE_STATUS)) {
        console.log(this.gl.getShaderInfoLog(vertexShader));
    }

    // フラグメントシェーダーを作成
    const fragmentShader = this.gl.createShader(this.gl.FRAGMENT_SHADER);
    this.gl.shaderSource(fragmentShader, fragment_source);
    this.gl.compileShader(fragmentShader);

    // フラグメントシェーダーのエラーを感知する。
    // ただ処理が重いので本番用のビルド時は対象外にする。
    if (!this.gl.getShaderParameter(fragmentShader, this.gl.COMPILE_STATUS)) {
        console.log(this.gl.getShaderInfoLog(fragmentShader));
    }

    // 頂点シェーダーをプログラムにアタッチ
    this.gl.attachShader(program, vertexShader);

    // フラグメントシェーダーをプログラムにアタッチ
    this.gl.attachShader(program, fragmentShader);

    // プログラムを起動 
    this.gl.linkProgram(program);

    // プログラムの起動時のエラーを感知する。
    // ただ処理が重いので本番用のビルド時は対象外にする。
    if (!this.gl.getProgramParameter(program, this.gl.LINK_STATUS)) {
        console.log(this.gl.getProgramInfoLog(program));
    }

    // プログラムを起動した後は不要になるので、デタッチしておく
    this.gl.detachShader(program, vertexShader);
    this.gl.detachShader(program, fragmentShader);

    // デタッチ後は削除して綺麗にしておく
    this.gl.deleteShader(vertexShader);
    this.gl.deleteShader(fragmentShader);

    return program;
}

これで、汎用的にプログラムを生成させる事ができます。

Shaderで必要なUniformを動的に取得する

Shaderで記載したUniformを毎回個別に書くのは大変なので
生成したプログラムから必要なUniformやpositionなどを動的に取得する。


/**
 * @param {WebGLProgram}
 * @public
 */
createUniform (program) 
{
    const uniformInfo = {};

    // シェーダーで使ってるUniformを取得
    const activeUniforms = this.gl.getProgramParameter(program, this.gl.ACTIVE_UNIFORMS);
    for (let idx = 0; idx < activeUniforms; idx++) {

        // Uniformの情報をセット
        // 変数名や引き渡しに必要な関数を動的に取得する
        const info  = this.gl.getActiveUniform(program, idx);
        const names = info.name.split(".");

        // 配列の変数も扱えるようにする
        const name = (names[0].endsWith("[0]")) 
            ? names[0].slice(0, -3) 
            : names[0];

        const block    = {};
        block.location = this.gl.getUniformLocation(program, name);
        block.type     = info.type;

        // typeから必要な関数に対して、positionをセットする
        switch (info.type) {

            case this.gl.FLOAT:
                block.method = this.gl.uniform1f;
                break;
            // ...他の関数も同様にセットしていく。
        }

        uniformInfo[name] = block;
    }

    return uniformInfo;
}

途中の処理を省いてますが、最終的にシェーダーで利用するUniformの命名はu_変名で運用する。
これで、最低限必要な引き渡しの環境は整ったので、次はシェーダー側への引き渡し部分を記載できたらと思います。