Article:

直近の作業でgltfのモデルをwebブラウザで表示する必要があってwebglを利用したので、サンプル単位のレポジトリを作成しました。

基本的にはレポジトリのコードだけ見れば事足りる話ではあるのですが、細かい補足等をメモ程度にこの記事に残しておこうと思います。

レポジトリ: https://github.com/totegamma/gltf-viewer-sample

実行すると、モデルが画面上でくるくる回転して表示されます。

利用ライブラリ

glTF Transform

gltfを読み込むのに、glTF Transformを利用しました。

サンプルでは、ネット上のURLからgltfファイルを読み込むために、WebIOを利用しています。

1
2
3
4
import { WebIO } from '@gltf-transform/core';

const io = new WebIO();
const document = await io.read('lowpolyfoxwithcolor.glb');

gl-matrix

行列計算のためにgl-matrixを利用しました。

というのも、実際のOpenGL周りの計算ではglmを使うと思うのですが、webglの場合は公式でそういったライブラリが提供されていません。

いくらか探してみたのですが、gl-matrixが一番それっぽかったので今回はこれを採用してみることにしました。

コード本体

モデルの読み込み

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const io = new WebIO();
const document = await io.read('lowpolyfoxwithcolor.glb');

for (const node of document.getRoot().listNodes()) {
    console.log("node name: ", node.getName());
    const mesh = node.getMesh();
    if (!mesh) continue
    for (const prim of mesh.listPrimitives()) {
        for (const semantic of prim.listSemantics()) {
            const accessor = prim.getAttribute(semantic);
            console.log(`${semantic}: ${accessor?.getCount()} ${accessor?.getType()}`);
        }
    }
}

const position = document.getRoot().listNodes()[0].getMesh()?.listPrimitives()[0].getAttribute('POSITION')?.getArray();
if (!position) return;

const color_int = document.getRoot().listNodes()[0].getMesh()?.listPrimitives()[0].getAttribute('COLOR_0')?.getArray();
if (!color_int) return;

const color = new Array(color_int.length);
for (let i = 0; i < color_int.length; i++) {
    color[i] = color_int[i] / 65535.0;
}

const index = document.getRoot().listNodes()[0].getMesh()?.listPrimitives()[0].getIndices()?.getArray();
if (!index) return;

基本的にはgltf-structureの図とにらめっこすることになると思います。

https://github.com/KhronosGroup/glTF-Tutorials/blob/main/gltfTutorial/images/gltfJsonStructure.png

レポジトリのサンプルではポジションと頂点カラーのみですが、normalやuvが欲しい場合はこのようにしてindexを取得できます

1
2
3
4
const normal = glb.getRoot().listNodes()[0].getMesh()?.listPrimitives()[0].getAttribute('NORMAL')?.getArray();
if (!normal) return;
const uv = glb.getRoot().listNodes()[0].getMesh()?.listPrimitives()[0].getAttribute('TEXCOORD_0')?.getArray();
if (!uv) return;

描画周り

基本的にはwgld.orgさんの記事を大いに参考にさせていただいております。これだけの情報がまとめられているのは本当にありがたい…。

差分としては、主に①typescript対応のために型を付けているところ、②webgl2にしているところ、③独自の行列計算ライブラリをgl-matrixに置き換えているところくらいになります。

メインのレンダリングループは、requestAnimationFrame APIを使って呼び出してみました。

この例では簡単にcountをループごとにインクリメントして時間として使っていますが、本当は差分の時間を見てフレームを決定したほうがベターですね。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
(function render() {
    requestAnimationFrame(render);
    if (!gl || !index) return

    gl.clearColor(0.0, 182/255, 1.0, 1.0);
    gl.clearDepth(1.0);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    var rad = (count % 360) * Math.PI / 180;

    mat4.identity(mMatrix);
    mat4.rotate(mMatrix, mMatrix, rad, [0, 1, 1]);
    mat4.multiply(mvpMatrix, tmpMatrix, mMatrix);
    gl.uniformMatrix4fv(uniLocation, false, mvpMatrix);
    gl.drawElements(gl.TRIANGLES, index.length, gl.UNSIGNED_SHORT, 0);

    gl.flush();
    count++;
})();