Help us understand the problem. What is going on with this article?

WebGLでシェーダーリフレクション

More than 3 years have passed since last update.

この記事は,WebGL Advent Calendar 2016 15日目の記事です.

WebGLでシェーダーリフレクションを利用して,自動的に頂点情報の設定をしてくれる機能を実装してみました.ここでのリフレクションとは,反射のことではなく,実行時に型などの情報を取得したりできる機能のことです.

有効な頂点属性数の取得

WebGLでは,getProgramParamterメソッドにシェーダープログラムオブジェクトと共に,gl.ACTIVE_ATTRIBUTESを指定すると,有効な頂点属性の数を取得できます.

// 有効な頂点属性の数を取得
gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);

有効な頂点属性の情報取得

次に,getActiveAttribメソッドを使うと,頂点属性の名前などが入ったWebGLActiveInfoオブジェクトを取得できます.

// indexは0から上記で取得した数未満の範囲を指定する
var attribute = gl.getActiveAttrib(program, index);

console.log(attribute.name); // 頂点属性の名前
console.log(attribute.size); // GLSL ES 1.0の場合,頂点属性のサイズは常に1
console.log(attribute.type); // FLOAT,FLOAT_VECn,FLOAT_MATnなど (nは2から4)

頂点属性に関する情報の算出

この情報を元に,enableVertexAttribArrayメソッドや,vertexAttribPointerに設定する値を求めていきます.
ただし,stride(1頂点あたりのバイトサイズ)やoffset(1頂点分のデータの先頭からのバイトオフセット)は,
別途設定します.

以下のコードは,頂点属性の情報をまとめて管理するためのクラスです.
setupメソッドにWebGLRenderingContextオブジェクトを渡すと,設定されている値でenableVertexAttribArrayや,vertexAttribPointerを呼び出します.

VertexAttribute.js
class VertexAttribute
{
    // gl      : WebGLRenderingContext
    // program : シェーダープログラム
    // index   : 有効な頂点属性のインデックス
    constructor(gl, program, index)
    {
        // 有効な頂点属性の情報を取得
        var attribute = gl.getActiveAttrib(program, index);

        // 頂点属性の位置を求める
        this.index = gl.getAttribLocation(program, attribute.name);
        // 行列用
        this.count = 1;

        // 型によってsizeやcountを設定する
        switch(attribute.type)
        {
        case gl.FLOAT:
            this.size = 1;
            break;
        case gl.FLOAT_MAT2:
            this.count = 2;
            // fall-through
        case gl.FLOAT_VEC2:
            this.size = 2;
            break;
        case gl.FLOAT_MAT3:
            this.count = 3;
            // fall-through
        case gl.FLOAT_VEC3:
            this.size = 3;
            break;
        case gl.FLOAT_MAT4:
            this.count = 4;
            // fall-through
        case gl.FLOAT_VEC4:
            this.size = 4;
            break;
        }

        // GLSL ESの場合,gl.FLOAT限定
        this.type = gl.FLOAT;
        this.normalized = gl.FALSE;
        this.stride = 0;
        this.offset = 0;
    }

    // 頂点属性の型を元にバイトサイズを返す
    byte_size()
    {
        // FLOATのみでFLOATは4バイトという前提
        return this.size * 4 * this.count;
    }

    // 頂点属性の設定を行う
    setup(gl)
    {
        for(var i = 0; i < this.count; ++i)
        {
            gl.enableVertexAttribArray(this.index + i);
            gl.vertexAttribPointer(
                this.index + i,
                this.size,
                this.type,
                this.normalized,
                this.stride,
                this.offset + 4 * this.size * i
            );
        }
    }
}

頂点属性をまとめて管理する

ここでは,1つのバッファに全ての頂点属性の値がまとまっている前提で,上記のクラスをまとめるMaterialクラスを作ります.

Material.js
class Material
{
    // gl      : WebGLRenderingContext
    // program : シェーダープログラム
    constructor(gl, program)
    {
        // 有効な頂点属性の数を取得
        var n = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);
        // 有効な頂点属性分の配列を用意
        this.attributes = new Array(n);

        // 頂点属性を生成し,strideの計算と同時にoffsetを設定していく
        var stride = 0;
        for(var i = 0; i < n; ++i)
        {
            this.attributes[i] = new VertexAttribute(gl, program, i);
            this.attributes[i].offset = stride;
            stride += this.attributes[i].byte_size();
        }

        // strideを設定する
        for(var i = 0; i < n; ++i)
        {
            this.attributes[i].stride = stride;
        }
    }

    setup(gl)
    {
        // 全ての頂点属性の設定を行う
        this.attributes.forEach(function(attribute){
            attribute.setup(gl);
        });
    }
}

記述例

上記のクラスを使うと,こんな風にWebGLのコードが書けます.

var program = gl.createProgram();
// ...
// シェーダープログラムをリンクしたりする
// ...
var material = new Material(gl, program);

// ...
// 色々処理
// ...

gl.useProgram(program);
// ...
// バッファのバインド
// ...
material.setup(gl);

// ...
// 描画
// ...

まとめ

頂点バッファに頂点属性のデータが連続で格納されている前提になりますが,getActiveAttribメソッドを利用することでシェーダーを変更しても,ほとんどコードを変えずに済みます.

同様に,getProgramParameterメソッドにgl.ACTIVE_UNIFORMSを渡すと有効なユニフォームの数を取得でき,getActiveUniformでユニフォームの情報を取得できるので,そちらも利用すると,更にシェーダの変更に強いコードが書ける・・・かもしれません.

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした