メインコンテンツへ移動

テクスチャマッピング(贴图)_WebGL ノート 8

無料2016-01-01#JS#WebGL#webgl texture#webgl image map#webgl贴图#webgl多张图片纹理#webgl纹理映射

贴图とは画像から色を抽出し、それをフラグメントに代入することですが、もちろん、実際の操作は非常に面倒です

はじめに

[前回のノート](/articles/varying 变量与内插-webgl 笔记 7/) では、varying 変数を利用して色値をフラグメントシェーダーに渡し、グラデーション効果を実現しました。より有用な色設定方法が贴图(テクスチャマッピング)です

一.原理

贴图とは画像中の色を幾何図形にマッピングすることで、ステップは以下の通り:

  1. 頂点シェーダー中で各頂点にテクスチャ座標を指定

  2. その後フラグメントシェーダー中で各フラグメントのテクスチャ座標に基づいてテクスチャ画像からテクセル色を抽出

ここでも varying 変数を利用してテクスチャ座標を伝達します。しかも内挿プロセスがあるため、画像を完璧に幾何図形に被せることができます

テクスチャ座標は新しい座標系で、canvas と WebGL 座標系のどちらとも異なります。原点は canvas 左下角にあり、s 軸は右向きが正、t 軸は上向きが正で、canvas 右上角の座標は (1.0, 1.0) です

二.テクスチャマッピング

特に注意:画像フォーマットに制限はなく、ブラウザがサポートする任意のフォーマット画像を使用できますが、テクスチャとしての画像のサイズは 2^mx2^n でなければなりません。否则エラーが発生します:

WebGL: drawArrays: texture bound to texture unit 0 is not renderable. It maybe non-power-of-2 and have incompatible texture filtering or is not 'texture complete'. Or the texture is Float or Half Float type with linear filtering while OES_float_linear or OES_half_float_linear extension is not enabled.

これはNPOT 問題(Non Power Of Two)と呼ばれ、贴图の常識です。非 2^mx2^n サイズの図も貼れますが、通常はより複雑でよりパフォーマンスを消費します

##1.テクスチャ座標の設定

テクスチャ座標と WebGL 座標の联系を確立し、頂点に対応するテクスチャ座標を設定し、その後フラグメントシェーダー実行時に gl_FragColor = texture2D(u_Sampler, v_TexCoord); でテクセルを抽出してフラグメント色を設定し、図が貼られます

シェーダーソースプログラムは以下の通り:

// 顶点着色器源程序
var vsSrc = 'attribute vec4 a_Position;' +
    'attribute vec2 a_TexCoord;' +  // 接受纹理坐标
    'varying vec2 v_TexCoord;' +    // 传递纹理坐标
    'void main() {' +
    'gl_Position = a_Position;' +   // 设置坐标
    'v_TexCoord = a_TexCoord;' +    // 设置纹理坐标
'}';
// 片元着色器源程序
//!!! 需要声明浮点数精度,否则报错 No precision specified for (float) 
var fsSrc = 'precision mediump float;' +
    'uniform sampler2D u_Sampler;' +    // 取样器
    'varying vec2 v_TexCoord;' +        // 接受纹理坐标
    'void main() {' +
    'gl_FragColor = texture2D(u_Sampler, v_TexCoord);' + // 设置颜色
'}';

その中で sampler2D はサンプラタイプで、画像テクスチャは最終的にこのタイプのオブジェクトに保存されます

フラグメントシェーダー中では texture2D(sampler2D sampler, vec2 coord) でテクセル色を抽出し、第 1 パラメータはテクスチャユニット番号、第 2 パラメータはテクスチャ座標���、戻り値は gl.texImage2D に传入した internalformat パラメータによって決定され、もしテクスチャ画像が利用不可であれば、vec4(0.0, 0.0, 0.0, 1.0) を返します

##2.テクスチャの配置とロード

画像をロードする方式は依然として new Image ですが、WebGL はクロスドメインテクスチャ画像の使用を許可しません。具体的なステップは以下の通り:

  1. テクスチャオブジェクトを作成

    // 创建 texture
    var texture = gl.createTexture();   // 创建纹理对象
    
  2. image を作成

    var image = new Image();
    
  3. image に load イベントハンドラを追加し、イベントハンドラ中でテクスチャを配置

    image.onload = function() {
        //---加载纹理
        // 1.对纹理图像进行 y 轴反转
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
        // 2.开开启 0 号纹理单元
        gl.activeTexture(gl.TEXTURE0);
        // 3.向 target 绑定纹理对象
        gl.bindTexture(gl.TEXTURE_2D, texture);
        // 4.配置纹理参数
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        // 用图片边缘颜色填充空白区域
        // gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        // 镜像填充(轴对称)
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.MIRRORED_REPEAT);
        // 5.配置纹理图像
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image);
        // 6.将 0 号纹理传递给着色器
        gl.uniform1i(u_Sampler, 0);
    
        // 绘制矩形
        gl.clear(gl.COLOR_BUFFER_BIT);
        gl.drawArrays(gl.TRIANGLE_STRIP, 0, arrVtx.length / 4);
    };
    

テクスチャの配置プロセスは比較的複雑で、後で詳しく展開します

  1. image.src に値を代入して画像をロード

    image.src = 'miao256x128.png';
    

画像サイズに注意、エラー情報中の non-power-of-2 はテクスチャとして 2^mx2^n サイズの画像を使用しなければならないことを教えてくれます

##3.内部状態

図の通り:

[caption id="attachment_917" align="alignnone" width="605"]webgl-texture webgl-texture[/caption]

三.テクスチャの配置

###1.テクスチャ画像の y 軸反転

// 1.对纹理图像进行 y 轴反转
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);

反転しないと貼った画像が逆さまになっていることがわかります。「福」の字を貼るのと同じで、WebGL テクスチャ座標系中の t 軸と画像座標系の y 軸は方向が逆だからです。画像座標系は私たちが遭遇した第 4 の座標系で、座標軸/原点は canvas 座標系と同じです

もちろん、手動で y 座標を反転することもでき、フラグメントシェーダー中で y = 1.0 - y とすればよいです。または、逆さまに貼ることもできます。。しかし、なぜこんなに便利な関数を使わないのでしょうか?

gl.pixelStorei(pname, param)
---
pname
    gl.UNPACK_FLIP_Y_WEBGL 对图像进行 y 轴反转
    gl.UNPACK_PERMULTIPLY_ALPHA_WEBGL 给图像 rgb 色值的每一个分量乘以 A
param
    0 または非 0 整数

注意:パラメータ値は整数でなければならず、true/false ではありません

###2.x 号テクスチャユニットを开启

// 2.开开启 0 号纹理单元
gl.activeTexture(gl.TEXTURE0);

WebGL はテクスチャユニットの機制を通じて複数のテクスチャを同時に使用します。各テクスチャユニットは 1 枚のテクスチャ画像を管理し、gl.TEXTURE0~7 で合計 8 個あり、つまり同時に管理できるテクスチャ画像は最大 8 枚までです

テクスチャユニットを使用する前に、gl.activeTexture(gl.TEXTURE0); でそれを激活する必要があります

###3.テクスチャオブジェクトをバインド

// 3.向 target 绑定纹理对象
gl.bindTexture(gl.TEXTURE_2D, texture);

WebGL システムにテクスチャオブジェクトがどのタイプのテクスチャを使用しているかを伝えます。2 種類のテクスチャタイプをサポート:gl.TEXTURE_2Dgl.TEXTURE_CUBE_MAPバッファ操作に類似しており、同様に target を指定し、パラメータ数及び意味も類似しています

同様に、WebGL 中ではテクスチャオブジェクトを直接操作できず、テクスチャオブジェクトをテクスチャユニットにバインドし、その後テクスチャユニットを操作してテクスチャオブジェクトを操作する必要があります

###4.テクスチャパラメータの配置

// 4.配置纹理参数
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
// 用图片边缘颜色填充空白区域
// gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
// 镜像填充(轴对称)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.MIRRORED_REPEAT);

テクスチャ画像が画像にマッピングされる具体的な方式を設定し、どのようにテクスチャ座標に基づいてテクセル色を取得するか?どの方式でテクスチャを繰り返し填充するか?つまりどのように内挿してフラグメントを生成するか

texParameteri(target, pname, param)
---
target
    テクスチャタイプ、値は gl.TEXTURE_2D または gl.TEXTURE_CUBE_MAP
pname
    テクスチャパラメータ、値は gl.TEXTURE_MAG_FILTER でテクスチャサイズが図形サイズより小さい時にどのようにテクスチャを拡大するか、デフォルト値は gl.LINEAR
        または gl.TEXTURE_MIN_FILTER でテクスチャサイズが図形サイズより大きい時にどのようにテクスチャを縮小するか、デフォルト値は gl.NEAREST_MIPMAP_LINEAR
        または gl.TEXTURE_WRAP_S でどのようにテクスチャ画像の左側または右側の領域を填充するか、デフォルト値は gl.REPEAT
        または gl.TEXTURE_WRAP_T でどのようにテクスチャ画像の上方と下方の領域を填充するか、デフォルト値は gl.REPEAT
param
    パラメータ値、gl.TEXTURE_MAG_FILTER と gl.TEXTURE_MIN_FILTER の選択可能値は gl.NEAREST と gl.LINEAR、前者はテクスチャ上でマッピング後のピクセル中心に最も近いピクセルの色値を使用し、後者は新ピクセル中心に最も近い 4 つのピクセルの色値の加重平均を使用し、効果はより良いが、オーバーヘッドが比較的大きい。これら 2 種は非ピラミッドテクスチャで、ピラミッドテクスチャ(あまり使用しない、紹介しない)もある
    gl.TEXTURE_WRAP_S と gl.TEXTURE_WRAP_T の選択可能値は gl.REPEAT 平铺、gl.MIRRORED_REPEAT 鏡像平铺、gl.CLAMP_TO_EDGE テクスチャ画像縁辺値を使用

gl.TEXTURE_MIN_FILTER のデフォルト値は特殊なピラミッドテクスチャなので、修正する必要があります gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);。修正しないとエラーが発生し、テクスチャが表示されません

###5.テクスチャ画像の配置

// 5.配置纹理图像
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image);

テクスチャ画像をテクス���ャオブジェクトに割り当て、同時に WebGL システムにテクスチャ画像についてのいくつかの特性を伝えます

gl.texImage2D(target, level, internalformat, format, type, image)
---
target
    テクスチャタイプ、値は gl.TEXTURE_2D または gl.TEXTURE_CUBE_MAP
level
    0 を传入、該パラメータはピラミッドテクスチャのために準備されており、一般にこのパラメータは使用しない
internalformat
    画像の内部フォーマット、format 値と同じでなければならない
format
    画像の外部フォーマット、画像フォーマットに基づいて決定 jpg/bmp は gl.RGB、png は gl.RGBA、グレースケール図は gl.LUMINANCE または gl.LUMINANCE_ALPHA、その他に gl.ALPHA もある
type
    テクスチャデータのタイプ、通常は gl.UNSIGNED_BYTE を使用、選択可能値には gl.UNSIGNED_SHORT_5_6_5、gl.UNSIGNED_SHORT_4_4_4_4、gl.UNSIGNED_SHORT_5_5_5_1 もある
image
    テクスチャ画像を含む image オブジェクト

###6.x 号テクスチャユニットをフラグメントシェーダー中のサンプラ変数に伝達

// 6.将 0 号纹理传递给着色器
gl.uniform1i(u_Sampler, 0);

本例中サンプラは uniform sampler2D タイプとして宣言され、uniform はテクスチャ画像がフラグメントに伴って変化しないため、sampler2Dgl.TEXTURE_2D に対応し、samplerCubegl.TEXTURE_CUBE_MAP タイプテクスチャに使用されます

gl.uniform1i(u_Sampler, 0); で sampler2D 変数に値を代入し、1 は传入する成分数を示し、i は成分が int タイプであることを示します。これは WebGL の慣用的な命名方式で、例えば gl.uniform4fv(name, value) は value の値は 4 次元ベクトル(4 つの要素を持つ型付き配列)であることを示します

四.DEMO

上記のコードを含む完全な例については、以下を参照してください:

多画像テクスチャとは複数の画像を同一の領域に貼ることで、例えば:

[caption id="attachment_918" align="alignnone" width="218"]webgl-texture-multi-image-example webgl-texture-multi-image-example[/caption]

重ね合わせ効果は 2 つのテクセル色値成分を乗算した結果で、vec4(r1, g1, b1, a1) * vec4(r2, g2, b2, a2)、結果ベクトルは vec4(r1 * r2, g1 * g2, b1 * b2, a1 * a2) なので、色値の左乗右乗効果は同じです

多画像テクスチャの内部状態は図の通り:

[caption id="attachment_919" align="alignnone" width="604"]webgl-texture-multi-image webgl-texture-multi-image[/caption]

五.まとめ

テクスチャマッピングの原理は非常に簡単:テクスチャ画像から色を読み取り、それをフラグメントに代入し、varying 変数の内挿が着色の均一を保証します

しかし操作が比較的複雑なので、私たちは再び util をカプセル化しました。インターフェース説明は以下の通り:

/**
 * load texture image
 * by default, configure gl.TEXTURE_MIN_FILTER as gl.LINEAR
 *!!! can be overrode at callback
 * @param  {String}   imgPath  texture image path
 * @param  {Function} callback(image, unit) args: image object and texture unit number
 * @return
 * @throws {Error} If all texture unit was active now
 */
function loadTexture(imgPath, callback) {
    //...
}

callback を利用して柔軟性を保証し、具体的な実装詳細は gl-util.js を参照してください

これで、WebGL の最も基礎的なものが終了しました。下一篇のノートは GLSL ES についての詳細な説明で、その後は無限の行列渦巻です。。

参考資料

  • WebGL プログラミングガイド》

コメント

コメントはまだありません

コメントを書く