YUV⇒RGB変換(WebGL編)

Canvas WebGL Contextによる描画(RGBテクスチャ)

<canvas>要素に”2D Context”を使用して描画を行えるようになりましたので、次は”WebGL Context”を使用して描画を行いたいと思います。

既にローカルに存在するYUVファイルからのデータの読み込みやYUV⇒RGB変換等のコードは作成していますので、WebGLで<canvas>上に四角を表示し、そこにテクスチャとしてRGBA画像を貼り付けます。

index.htmlへの各要素の追加配置1

“WebGL Context”を使用した描画用に、“2D Context”を使用した描画用とは別に、以下の要素を追加しておきます。

要素 type id 概要
canvas canvasGLRGB 画像表示用 CIFサイズ
button button btnOpenFileGLRGB ファイルオープン
button button btnPlayGLRGB 再生開始
input text txtFileNameGLRGB ファイル名表示 readonly
input file fileOpenDialogGLRGB ファイル選択 hidden

下記のHTMLを<main>要素の子要素として追加します。

<h2>&ltcanvas&gtにWebGLでYUV⇒RGB変換後の画像を表示(RGB)</h2>
<div class="d-flex bd-highlight mb-3 mt-5 justify-content-center">
  <canvas id="canvasGLRGB" width="352" height="288">
    CIFサイズのYUV画像を表示します。
  </canvas>
</div>
<div class="d-flex bd-highlight mb-3 justify-content-center">
  <button type="button" class="btn btn-primary mx-2" disabled id="btnPlayGLRGB">Play</button>
  <button type="button" class="btn btn-primary mx-1" id="btnOpenFileGLRGB">File...</button>
  <input type="text" id="txtFileNameGLRGB" readonly>
</div>

<!-- ファイルオープンダイアログのためのボタン(見えない) -->
<input type="file" name="file-open" id="fileOpenDialogGLRGB" accept=".yuv" hidden>
    

WebGL対応

HTMLの要素のイベント対応において、“index.html”に記載したHTML要素に対し、クリックや値の変更等のイベントが発生した際の共通する動作をEvents4Elementsクラスに記述しました。

WebGL対応を行うにあたり、先ずは”WebGL Context”に対する共通の動作をEvents4Elementsクラスを継承したEvents4ElementsGLクラスとして作成します。
なお、実際の描画のコードについては、テクスチャにRGBAデータを渡すか、YUVデータを渡すかによって変わりますので、Events4ElementsGLも抽象クラスとし、描画コード等については別途更なる継承クラスに記述する事にします。

先ず、Events4ElementsGLクラスのファイル”events4elementsgl.ts”を作成します。
Events4ElementsGLEvents4Elementsクラスを継承した抽象クラスとして定義します。
なお、他のクラスによって継承されますので、export修飾子を付加しておきます。

import { Element, Events4Elements } from "./events4elements";

export abstract class Events4ElementsGL extends Events4Elements {
        :
        :
}
    

Events4ElementsGLクラスのコンストラクタ

Events4ElementsGLクラスのコンストラクタは以下の通りです。

constructor(ids: Map<Element, string>) {
    super(ids);
    const gl: WebGL2RenderingContext | undefined = this.getContextGL();
    if (gl != undefined) {
        gl.clearColor(0.0, 0.0, 1.0, 1.0);
        gl.clear(gl.COLOR_BUFFER_BIT);
    }
}
      

Events4ElementsGLクラスのコンストラクタでは、Events4Elementsクラスと同様、引数として”index.html”に配置した<canvas><button>, <input>要素等の”id”属性の値を受け取ります。
受け取ったMapオブジェクトは、そのまま親クラスであるEvents4Elementsのコンストラクタに渡します。

その後、後ほど記述するgetContextGLメソッドによって”WebGL Context”を取得し、clearColorメソッドで<canvas>要素のクリア色を青色に設定し、clearメソッドでクリアしておきます。

Events4ElementsGLクラスのファイナライザ

Events4ElementsGLクラスのファイナライザは以下の通りです。

public finalize() {
    const gl: WebGL2RenderingContext | undefined = this.getContextGL();
    if (gl != undefined) {
        this.deleteAllShaders(gl);
    }
}
      

WebGLによる描画ではシェーダープログラムを使用するのですが、使用したシェーダープログラム等は明示的に破棄する方が良いかと思います。
ファイナライザでは、後ほど記述するdeleteAllShadersメソッドによりシェーダープログラムの破棄を行います。

getContextGLメソッド

getContextGL<canvas>要素のWebGLコンテキストを取得するためのメソッドです。

HTMLCanvasElementオブジェクトのgetContextメソッドに引数として”webgl2”を指定する事で取得できます。

protected getContextGL(): WebGL2RenderingContext | undefined {
    const canvas: HTMLCanvasElement | undefined = this.getCanvas();
    var gl: WebGL2RenderingContext | undefined;
    if (canvas != undefined) {
        gl = canvas.getContext('webgl2') as WebGL2RenderingContext;
    }
    return gl;
}

なお、今回はWebGL 1.0・・ではなく、WebGL 2.0・・を使用します。
なので、getContextメソッドの引数にはWebGL 1.0を示す”webgl”ではなく、WebGL 2.0を示す”webgl2”を渡します。

WebGL 1.0はOpenGL ES 2.0の派生規格なのですが、テクスチャを使用する場合、テクスチャのサイズが2のべき乗(2, 4, 8, 16, 32, 64, 128, 256, 512, …)に制限される場合があるようです。
今回、テクスチャにセットする画像はCIFサイズ(352×288)ですので、2のべき乗に合わせるためには、それなりの工夫が必要となります。

一方、OpenGL ES 3.0の派生規格であるにWebGL 2.0は、テクスチャのサイズを2のべき乗にする必要はないようです。

また、シェーダープログラムの言語GLSL ESについても、WebGL 1.0では”バージョン1.0”までの対応なのに対し、WebGL 2.0では”バージョン3.0 ES”まで対応していますので、色々と使い勝手が良い事もあります。

シェーダープログラムのコンパイル、リンク、破棄

シェーダープログラムの内容については色々と違いがありますが、コンパイル、リンクおよび破棄については変わりがありませんので、Events4ElementsGLに配置します。

なお、コンパイルおよびリンクしたシェーダープログラムについては、使用後に破棄しますので、プライベートフィールドに保持しておきます。

        :
        :
private vertexShader: WebGLShader | null = null;
private fragmentShader: WebGLShader | null = null;
private _shaderProgram: WebGLProgram | null = null;
        :
        :
public get shaderProgram(): WebGLProgram | null {
    return this._shaderProgram;
}
        :
        :

なおシェーダープログラムのプライベートフィールド”_shaderProgram”は描画時に参照する必要がありますので、getterを追加しておきます。

シェーダープログラムの破棄

コンパイルおよびリンクしたシェーダープログラムの破棄を行うためdeleteAllShadersメソッドを追加します。

protected deleteAllShaders(gl: WebGL2RenderingContext) {
    gl.deleteShader(this.vertexShader);
    this.vertexShader = null;
    gl.deleteShader(this.fragmentShader);
    this.fragmentShader = null;
    gl.deleteProgram(this._shaderProgram);
    this._shaderProgram = null;
}
        

なお、deleteShaderおよびdeleteProgramメソッドにつきましては、引数として”null”をセットする事ができますので、事前にチェックする必要はありません。

シェーダープログラムのリンク

コンパイルしたシェーダープログラムのリンクを行うためinitShaderProgramメソッドを追加します。
引数にはWebGLコンテキストやバーテックスシェーダープログラム、フラグメントシェーダープログラムをセットします。

protected initShaderProgram(gl: WebGL2RenderingContext, vertexShaderPrg: string, fragmentShaderPrg: string) {
    this.vertexShader = this.loadShader(gl, gl.VERTEX_SHADER, vertexShaderPrg);
    this.fragmentShader = this.loadShader(gl, gl.FRAGMENT_SHADER, fragmentShaderPrg);
    if ((this.vertexShader != null) && this.fragmentShader != null) {
        this._shaderProgram = gl.createProgram();
        if (this._shaderProgram != null) {
            gl.attachShader(this._shaderProgram, this.vertexShader);
            gl.attachShader(this._shaderProgram, this.fragmentShader);
            gl.linkProgram(this._shaderProgram);

            if (!gl.getProgramParameter(this._shaderProgram, gl.LINK_STATUS)) {
                alert('Unable to initialize the shader program: ' + gl.getProgramInfoLog(this._shaderProgram));
                this.deleteAllShaders(gl);
            }
        } else {
            this.deleteAllShaders(gl);
        }
    } else {
        this.deleteAllShaders(gl);
    }
}
        

まず、次に追加するloadShaderメソッドでバーテックスシェーダープログラムおよびフラグメントシェーダープログラムをコンパイルしておきます。
コンパイルが成功したら、createProgramメソッドで作成したシェーダープログラムオブジェクトにバーテックスシェーダーオブジェクトとフラグメントシェーダーオブジェクトをattachShaderメソッドで追加した後、linkProgramメソッドでリンクします。

リンクが成功したかどうかは、linkProgramメソッドのコール直後に第2引数に”LINK_STATUS”をセットしたgetProgramParameterメソッドをコールする事で確認できます。
失敗した場合にはgetProgramInfoLogメソッドでエラー内容を取得し、alertで表示するようにしています。

シェーダープログラムのコンパイル

シェーダープログラムのコンパイルを行うため、loadShaderメソッドを追加します。

第2引数にはプログラムのタイプ(“VERTEX_SHADER”もしくは”FRAGMENT_SHADER”)を指定します。
第3引数にはバーテックスシェーダープログラムかフラグメントシェーダープログラムをセットします。

protected loadShader(gl: WebGL2RenderingContext, type: number, source: string): WebGLShader | null {
    var shader: WebGLShader | null = gl.createShader(type);
    if (shader != null) {
        gl.shaderSource(shader, source);
        gl.compileShader(shader);

        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
            alert('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader));
            shader = null;
        }
    }

    return shader;
}
        

先ず、プログラムのタイプを引数にcreateShaderによりシェーダーオブジェクトを作成します。
次に、shederSourceメソッドで、コンパイルするシェーダープログラムをセットします。
最後にcompileShaderメソッドで実際のコンパイルを行います。

コンパイルが成功したかどうかは、compileShaderメソッドのコール直後に第2引数に”COMPILE_STATUS”をセットしたgetShaderParameterメソッドをコールする事で確認できます。
失敗した場合にはgetShaderInfoLogメソッドでエラー内容を取得し、alertで表示するようにしています。

頂点バッファの作成

今回は四角を描画し表面にテクスチャを貼り付けるだけですので、頂点座標を保持するバッファとテクスチャ座標を保持するバッファを作成しておきます。
そのため、以下のようにcreateBuffersメソッドや頂点データ等を追加します。

static readonly TEXTURE_DATA = [
    0.0, 1.0,
    0.0, 0.0,
    1.0, 0.0,
    1.0, 1.0,
];

static readonly VERTEX_DATA = [
    - 1.0, -1.0, 0.0,
    -1.0, 1.0, 0.0,
    1.0, 1.0, 0.0,
    1.0, -1.0, 0.0,
];
        :
        :
private _vertexBuffer: WebGLBuffer | null = null;
private _textureBuffer: WebGLBuffer | null = null;
        :
        :
public get vertexBuffer(): WebGLBuffer | null {
    return this._vertexBuffer;
}

public get textureBuffer(): WebGLBuffer | null {
    return this._textureBuffer;
}
        :
        :
protected createBuffers(gl: WebGL2RenderingContext) {
    this._vertexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer);
    const vd = new Float32Array(Events4ElementsGL.VERTEX_DATA);
    gl.bufferData(gl.ARRAY_BUFFER, vd, gl.STATIC_DRAW);

    this._textureBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, this._textureBuffer);
    const td = new Float32Array(Events4ElementsGL.TEXTURE_DATA);
    gl.bufferData(gl.ARRAY_BUFFER, td, gl.STATIC_DRAW);
}
      

四角の頂点は”VERTEX_DATA”で(x, y, z)を指定しています。
なお、平面ですので、z成分は全て0.0となっています。

テクスチャの頂点は”TEXTURE_DATA”で[u, v]を指定しています。

イメージ的には以下の通りです。

頂点バッファは、先ずcreateBufferメソッドで作成します。
作成した頂点バッファは、bindBufferメソッドで結合しておきます。

頂点座標のデータやテクスチャ座標のデータは、32ビットの浮動小数点配列Float32ArrayにしてbufferDataメソッドで頂点バッファにセットしておきます。

なお、頂点バッファについては後で使用しますので、プライベートフィールドとして保持しておくと共に、getterを作成しておきます。

テクスチャ作成 / セット

此処でRGB画像からテクスチャを作成し、セットするクラスRGBTextureを作成しておきます。

使用するRGB画像は、2Dコンテキストで使用したRGBImageオブジェクトで作成するImageBitmapオブジェクトを使用します。
なので、RGBTextureクラスはRGBImageクラスを継承します。

import { RGBImage } from "./rgbimage";

export class RGBTexture extends RGBImage {
    private texture: WebGLTexture | null = null;

    constructor(file: File, gl: WebGL2RenderingContext) {
        super(file);
        this.texture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, this.texture);
        gl.pixelStorei(gl.UNPACK_ALIGNMENT, 4);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    }

    public finalize(gl: void): void;
    public finalize(gl: WebGL2RenderingContext): void;

    public finalize(gl: any) {
        super.finalize();

        if (typeof gl === 'object') {
            gl.deleteTexture(this.texture);
            this.texture = null;
        }
    }

    public async setNextTexture(gl: WebGL2RenderingContext, texlocation: WebGLUniformLocation) {
        const bmp: ImageBitmap | undefined = await this.getNextBitmap();
        if (bmp != undefined) {
            gl.uniform1i(texlocation, 0);
            gl.activeTexture(gl.TEXTURE0);
            gl.bindTexture(gl.TEXTURE_2D, this.texture);
            gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, bmp);
        } else {
            throw new Error('No more image');
        }
    }
}
    

RGBTextureクラスのコンストラクタ

RGBTextureクラスのコンストラクタでは、引数にFileオブジェクトを取り、そのまま親クラスであるRGBImageのコンストラクタに渡します。

その後、引数で渡された”WebGL Content”を使用し、createTextureメソッドでテクスチャオブジェクトを作成します。
なお、作成したテクスチャオブジェクトは他のメソッドで使用しますので、プライベートフィールドとして保持しておきます。

bindTextureメソッドで作成したテクスチャオブジェクトをバインドした後、メモリアライメントと画像の拡縮時の補間方法の設定をしておきます。

RGBTextureクラスのファイナライザ

RGBTextureクラスのファイナライザは、引数にWebGLコンテキストを取り、親クラスRGBImageのファイナライザをコール後にdeleteTextureメソッドでテクスチャオブジェクトを破棄します。

なお、finalizeメソッド(引数あり)は、親クラスRGBImagefinalizeメソッド(引数なし)のオーバーロードとなります。
ただ、TypeScriptでは、C++Java等におけるオーバーロードの仕組みが無いため、オーバーロードと同等の機能を実現する場合には少々工夫が必要となります。

先ず、引数なしのfinalizeメソッドと引数ありのfinalizeメソッドを宣言します。
なお”引数なし”を表現するには、引数の型の指定をvoidにします。

その後、引数の型をanyとしてfinalizeメソッドを定義します。

因みに、typeofは引数が何かのクラスの場合はobjectを返し、引数がない場合にはundefinedを返します。

setNextTextureメソッド

setNextTextureメソッドではテクスチャに画像データを割り当ててセットします。

なお、テクスチャはシェーダープログラム中でユニフォーム変数として使用されますので、テクスチャに画像データを割り当てる場合には、ユニフォーム変数を表すIDが必要となります。
setNextTextureメソッドではWebGLコンテキストと共に引数としてユニフォーム変数を表すID”texlocation”を渡しています。

setNextTextureメソッドでは先ずgetNextBitmapメソッドでImageBitmapオブジェクトとして次のフレームのビットマップ画像を取得します。
なお、getNextBitmapメソッドは非同期関数ですので、setNextTextureメソッドも非同期関数となり、async修飾子を付加する必要があります。

ビットマップ画像の取得を成功したら、テクスチャに画像データを割り当てます。

なお、既にImageBitmapオブジェクトには画像サイズが含まれますので、texImage2Dメソッドの引数に画像の幅や高さを指定する必要はありません。
また、ImageBitmapオブジェクトの画像はRGBAの形式ですので、画像フォーマットおよび内部フォーマットは”RGBA”を指定します。
更にタイプは”UNSIGNED_BYTE”を指定する必要があります。

因みにビットマップ画像の取得を失敗した場合には、例外を投げるようにしています。

Canvas WebGL Contextの要素のイベント対応(RGBテクスチャ)

canvas要素にWebGLコンテキストを使用し、RGBA画像をテクスチャとして描画する際のイベント対応をEvents4ElementsGLRGBクラスとして纏めます。
各イベントに対する動作のテンプレートは抽象クラスEvents4Elementsを継承したEvents4ElementsGLクラスに纏めていますので、Events4ElementsGLRGBクラスはEvents4ElementsGLクラスを継承するようにします。

import { Element } from "./events4elements";
import { Events4ElementsGL } from "./events4elementsgl";
import { RGBTexture } from "./rgbtexture";

export class Events4ElementsGLRGB extends Events4ElementsGL {
    :
    :
}
    

Events4ElementsGLRGBクラスのシェーダープログラム

シェーダープログラムは文字列のクラス定数として定義します。

なお、シェーダープログラム内では敢て#versionディレクティブを記載していません。
これにより"#version 1.10を指定したのと同等となります。

WebGL 2.0 ではGLSL ES 3.0以前のバージョンに対応しているはずですので問題なく使用できます。

なおWebGL 1.0ではGLSL ES 1.0のみの対応となりますので、シェーダープログラムの最初に#version 100ディレクティブを指定します。

Events4ElementsGLRGBクラスのバーテックスシェーダープログラム

Events4ElementsGLRGBクラスのバーテックスシェーダープログラムは以下の通りです。

static readonly VERTEX_SOURCE = `
    attribute vec3 position;
    attribute vec2 vertexUV;
    varying mediump vec2 uv;
    void main(void)
    {
        gl_Position.xyz = position;
        gl_Position.w = 1.0;
        uv = vertexUV;
    }`;
        

図形の回転や移動、変形、更に視点の移動や視野角の指定等はマルっと・・・・無視していますので、3次元ベクタとして”position”にセットされた頂点座標データをそのまま座標データの組み込み変数”gl_Position”に出力しています。

2次元ベクタとして”vertexUV”にセットされたテクスチャ座標データは、そのまま”uv”としてフラグメントシェーダープログラムに渡しています。

なお、フラグメントシェーダープログラムに渡す変数に対する精度指定は必須のようで、mediumpの他にheighp, lowpを指定する事ができます。

Events4ElementsGLRGBクラスのフラグメントシェーダープログラム

Events4ElementsGLRGBクラスのフラグメントシェーダープログラムは以下の通りです。

static readonly FRAGMENT_SOURCE = `
    varying mediump vec2 uv;
    uniform sampler2D textureSampler;
    void main(void)
    {
        gl_FragColor = texture2D(textureSampler, uv);
    }`;
        

texture2D関数で”textureSampler”で指定されるテクスチャから、“uv”で指定される座標の色データを取得し、フラグメントカラーの組み込み変数”gl_FragColor”に出力します。

なお、バーテックスシェーダープログラムから渡される変数に対する精度指定は必須のようで、mediumpの他にheighp, lowpを指定する事ができます。

Events4ElementsGLRGBクラスのコンストラクタ

Events4ElementsGLRGBクラスのコンストラクタは以下の通りです。

static readonly MAP_GLRGB = new Map<Element, string>([
    ['canvas', 'canvasGLRGB'],
    ['filebtn', 'btnOpenFileGLRGB'],
    ['playbtn', 'btnPlayGLRGB'],
    ['filetxt', 'txtFileNameGLRGB'],
    ['dialog', 'fileOpenDialogGLRGB']
]);
        :
        :
constructor() {
    super(Events4ElementsGLRGB.MAP_GLRGB);
}
        

Events4ElementsGLRGBクラスのコンストラクタでは、“index.htmlに配置した要素の”id”のMapオブジェクトを引数に、親クラスEvents4ElementsGLのコンストラクタをコールします。

Events4ElementsGLRGBクラスのファイナライザ

private texImage: RGBTexture | undefined;
        :
        :
public finalize() {
    super.finalize();
    const gl: WebGL2RenderingContext | undefined = this.getContextGL();
    if ((gl != undefined) && (this.texImage != undefined)) {
        this.texImage.finalize(gl);
        this.texImage = undefined;
    }
}
        

Events4ElementsGLRGBクラスのファイナライザfinalizeメソッドでは、親クラスEvents4ElementsGLのファイナライザをコール後に、プライベートフィールド”texImage”に保持しているRGBTextureオブジェクトをfinalizeメソッドにより破棄します。

Events4ElementsGLRGBクラスのsetFileNameメソッド

Events4Elementsクラス内で抽象メソッドとして宣言されていたsetFileNameを実装します。

    :
    :
private vertexPosition: number = 0;
private vertexUV: number = 0;
private texture: WebGLUniformLocation | null = null;
        :
        :
protected setFileName(files: FileList | null | undefined) {
    if (this.texImage != undefined) {
        this.finalize();
    }
    const file: File | null = this.extractFileFromFileList(files);
    if (file != null) {
        const gl: WebGL2RenderingContext | undefined = this.getContextGL();
        if (gl != undefined) {
            this.initShaderProgram(gl, Events4ElementsGLRGB.VERTEX_SOURCE, Events4ElementsGLRGB.FRAGMENT_SOURCE);
            if (this.shaderProgram != null) {
                this.vertexPosition = gl.getAttribLocation(this.shaderProgram, 'position');
                this.vertexUV = gl.getAttribLocation(this.shaderProgram, 'vertexUV');
                this.texture = gl.getUniformLocation(this.shaderProgram, 'textureSampler');
            };
            this.createBuffers(gl);
            this.texImage = new RGBTexture(file, gl);
        }
    }
}
        

setFileNameメソッド内では、先ず、既に割り当てられているRGBTextureオブジェクトが存在する場合、破棄しておきます。

次に、引数として渡されたファイルリストからextractFileFromFileListメソッドでFileオブジェクトを取得します。
取得したFileオブジェクトを使用し、新たにRGBTextureオブジェクトを作成し、プライベートフィールド”texImage”にセットします。

同時にバーテックスシェーダープログラム”VERTEX_SOURCE”とフラグメントシェーダープログラム”FRAGMENT_SOURCE”を引数にしてinitShaderProgramメソッドをコールする事でシェーダープログラムのコンパイル、リンク等の準備を行います。

シェーダープログラムの作成に成功したら、シェーダープログラム中のアトリビュート変数”position”と”vertexUV”のIDをgetAttribLocationメソッドで、ユニフォーム変数”textureSampler”のIDをgetUniformLocationメソッドで取得し、其々プライベートフィールドに保存しておきます。

最後にcreateBuffersメソッドで頂点座標データとテクスチャ座標データの頂点バッファを作成しておきます。

Events4ElementsGLRGBクラスのshowNextImageメソッド

Events4Elementsクラス内で抽象メソッドとして宣言されていたshowNextImageを実装します。

protected showNextImage() {
    const gl: WebGL2RenderingContext | undefined = this.getContextGL();
    if ((gl != undefined) && (this.texImage != undefined) && (this.texture != null)) {
        gl.useProgram(this.shaderProgram);
        this.texImage.setNextTexture(gl, this.texture).then(() => {
            gl.clear(gl.COLOR_BUFFER_BIT);

            gl.enableVertexAttribArray(this.vertexPosition);
            gl.enableVertexAttribArray(this.vertexUV);

            gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
            gl.vertexAttribPointer(
                this.vertexPosition,
                3, // サイズ
                gl.FLOAT, // タイプ
                false, // 正規化?
                0, // ストライド
                0 //vertexBuffer // 配列バッファ
            );

            gl.bindBuffer(gl.ARRAY_BUFFER, this.textureBuffer);
            gl.vertexAttribPointer(
                this.vertexUV,
                2, // サイズ
                gl.FLOAT, // タイプ
                false, // 正規化?
                0, // ストライド
                0 //textureBuffer // 配列バッファ
            );

            gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);

            gl.disableVertexAttribArray(this.vertexUV);
            gl.disableVertexAttribArray(this.vertexPosition);
        }).catch(() => {
            this.stopPlay();
        });
    }
}
        

OpenGLで図形を描画する典型的なコードです。

先ず、useProgramメソッドで使用するシェーダープログラムを選択します。

次に、プライベートフィールド”texImage”を使用し、setNextTextureメソッドで次のフレームの画像をテクスチャにセットします。
なお、setNextTextureメソッドは非同期関数ですので、thenにより画像がテクスチャにセットされるのを待ってから描画を継続します。

描画については、enableVertexAttribArray, bindBuffer, vertexAttribPointerメソッドで頂点座標データとテクスチャ座標データをセットした後、drawArraysで3角形を2枚描画する事で四角の描画を行います。

もし、次のフレームの画像が無い等、例外が投げられた場合には、catch内でstopPlayをコールし、動画再生の停止処理等を行います。

Events4ElementsGLRGBクラスのrewindメソッド

Events4Elementsクラス内で抽象メソッドとして宣言されていたrewindを実装します。

RGBTextureオブジェクトのrewindメソッドをコールします。

protected rewind() {
    if (this.texImage != undefined) {
        this.texImage.rewind();
    }
}
        

index.tsのWebGLコンテキスト(RGB)対応

“index.ts”にEvents4ElementsGLRGBクラスを追加します。

import { Events4Elements2D } from "./events4elements2d";
import { Events4ElementsGLRGB } from "./events4elementsglrgb";

var canvas2D: Events4Elements2D | undefined;
var canvasGLRGB: Events4ElementsGLRGB | undefined;

addEventListener('DOMContentLoaded', init);

function init() {
    window.addEventListener('close', finalize);

    canvas2D = new Events4Elements2D();
    canvasGLRGB = new Events4ElementsGLRGB();
}

function finalize() {
    if (canvas2D != undefined) {
        canvas2D.finalize();
        canvas2D = undefined;
    }
    if (canvasGLRGB != undefined) {
        canvasGLRGB.finalize();
        canvasGLRGB = undefined;
    }
}
        

init関数

“index.html”内のキャンバス要素への描画やボタンクリックイベントへの対応を記述したクラスEvents4ElementsWebGLRGBのオブジェクトの作成を追加します。

finalize関数

finalize関数内では、Events4ElementsGLRGB.finalizeメソッドで、作成したクラスオブジェクト内の不要なオブジェクトを明示的に削除する処理を追加しています。

Canvas WebGL Contextによる描画(RGBテクスチャ)の実行

この時点でコンパイルと実行を行い、動作を確認する事ができるはずです。

ここまでのコードはこちらからダウンロードできます。

なお、実際の動作はこちらで確認できます。