Home Manual Reference Source

一般

概要

このプロジェクトは、純粋なJavaScriptで3DCG描画やモデルデータの管理、各種ファイルフォーマット対応(OBJ, MQO, JSON等)を可能にするシンプルな3DCGライブラリです。 主な構成要素は、ジオメトリ(メッシュ・頂点・面)、マテリアル、ライト、カメラ、シーン管理、システムユーティリティ、各種データローダー/エクスポーターなどです。

アーキテクチャ全体図(概要)

[S3System] ──┬─> [S3Scene] ─┬─> [S3Model] ──> [S3Mesh]
             │               │                ├─> [S3Vertex]
             │               │                ├─> [S3TriangleIndex]
             │               │                └─> [S3Material]
             │               ├─> [S3Light]
             │               └─> [S3Camera]
             ├─> [S3MeshLoader] <─ [OBJ/MQO/JSON File]
             └─> [Utility: Math, Vector, Matrix, Angles]

コアモジュールの責務

S3System

  • システムの中核クラス。

    • 各種ファクトリ(シーン/モデル/メッシュ/頂点/マテリアル/ライト/カメラ等の生成)。
    • 座標変換(平行移動、回転、スケール、射影、ビューポート等)、MVP計算、カリングなど。
    • 2D Canvas描画のサポート(ラインレンダ、軸描画など)。
    • 描画モードや座標系(OpenGL/DirectX系)切り替え等のシステム設定。

S3Scene

  • シーン(描画単位)の管理クラス。

    • 複数モデル・ライト・カメラの登録、シーン全体の保持。
    • シーンの空化やモデル/ライト追加、カメラ切替が可能。

S3Model

  • ワールド座標変換を含む3Dオブジェクト単位。

    • 位置・回転(オイラー角)・スケール・メッシュ情報を保持。

S3Mesh

  • ジオメトリ(立体形状)のまとまり。

    • 頂点(S3Vertex)、面(S3TriangleIndex)、マテリアル(S3Material)をまとめる。
    • 面の反転や複数素材対応、確定化フラグなど。

S3Vertex / S3TriangleIndex

  • 頂点座標、面情報の基本クラス。

    • 頂点(S3Vertex):位置(S3Vector)だけを持つシンプルなimmutableオブジェクト。
    • 三角形面(S3TriangleIndex):3頂点インデックス、UV座標、マテリアルインデックスを保持。

S3Material

  • マテリアル(材質・テクスチャ等)のプロパティを管理。

    • 拡散色、環境光、鏡面、発光、反射率、テクスチャ画像等。

S3Texture

  • 画像やテクスチャデータの管理。

    • 各種Image型やURLの対応、2の累乗への自動変換等。

S3Light

  • シーン内の光源情報管理。

    • 種類(環境光/平行光源/点光源)、強度、方向、色などを保持。

S3Camera

  • 視点情報・射影/ビュー/ビューポート行列管理。

    • 視点、注視点、視野角、ニア・ファークリップ、各種行列取得APIあり。

数学系ユーティリティ

  • S3Vector / S3Matrix / S3Angles など

    • ベクトル・行列計算、オイラー角変換、線形補間など汎用的な3DCG数値処理を提供。

メッシュローダー(データ入出力)

S3MeshLoader(Facadeパターン的役割)

  • 各種3Dフォーマット(OBJ/MQO/JSON)を一元的にS3Meshインスタンスへ変換(インポート)、逆に各形式へのエクスポート(出力)を提供。

S3MeshLoaderOBJ

  • Wavefront OBJ形式の読み書き。テキスト→メッシュへの変換、各要素の解析。

S3MeshLoaderMQO

  • Metasequoia MQO形式の読み書き。テキスト→メッシュ、素材パラメータやUVも一部対応。

S3MeshLoaderJSON

  • シンプルなJSONメッシュデータ対応。自前データ構造用。

拡張性について

  • 追加フォーマット: S3MeshLoaderに新しいローダーを追加すれば良い設計。
  • レンダラ置換: デフォルトは2D Canvas描画だが、S3GLSystemなどでWebGL拡張も容易。
  • 独自モデル・マテリアル: S3Model/S3Material/S3Meshを継承し独自拡張可能。
  • シーン構成/ユーティリティ: 数学系やカメラ制御等もモジュール分離で独自実装しやすい。

モジュール依存関係と流れ

  1. S3Systemがすべてのファクトリ・管理・計算の中核となり、インスタンス生成も担当。
  2. S3Sceneが、各モデル・ライト・カメラを保持し、S3Systemからの描画要求で内容を渡す。
  3. S3MeshLoaderを通じて、外部3Dデータを読み込み、S3Meshへ展開。
  4. 各モデルは自身のS3Meshを持ち、ワールド変換行列はS3Systemで算出。
  5. 描画時は、S3Systemがモデル・カメラ・シーン情報からMVP行列等を組み立て、Canvasにライン/シェーディング等を描く。

シンプルな利用例

import S3 from "./S3.js";
const sys = new S3.System();
const scene = sys.createScene();
const camera = sys.createCamera();
scene.setCamera(camera);

const mesh = sys.createMesh();
// ... meshに頂点・面・マテリアル追加 ...

const model = sys.createModel();
model.setMesh(mesh);
scene.addModel(model);

sys.setCanvas(document.getElementById("canvas"));
sys.drawScene(scene);

まとめ

  • 責務分離・シンプル設計。
  • 数値系/ジオメトリ/シーン管理/各種ローダーは独立性高く拡張容易。
  • WebGL化や独自レンダラ、拡張フォーマット対応も視野に入れた柔軟アーキテクチャ。

WebGL 拡張

概要

WebGL 実装系 (S3GL* クラス群) について説明します。

  • 純粋な JS のクラス構成(ES Modules 構成)
  • 抽象レイヤー(基本形状・メッシュ・カメラ・ライト・マテリアル etc...)
  • WebGL レイヤー(GL バッファ/シェーダ/テクスチャ/描画パイプライン管理)

アーキテクチャ全体図

+-----------------------------------------------------------+
|                        S3GLSystem                        |
| (WebGL全体の管理/描画パイプライン・GLContext管理)        |
+---------------------+---------+--------------------------+
                      |         |
           +----------+         +--------------+
           |                                    |
    +------v-------+                      +-----v------+
    |  S3GLScene   |                      |  S3GLMesh  |
    |(Scene全体管理|                      |(メッシュ/VBO|
    +------+-------+                      +-----+------+
           |                                    |
    +------v-----------+                +-------v---------+
    |  S3GLModel       |                | S3GLTriangle... |
    |(モデル/形状/変換) |                |(三角形インデックス)|
    +------------------+                +-----------------+
           |                                    |
    +------v----------+                +--------v-------+
    |  S3GLMaterial   |                |  S3GLVertex    |
    |  (材質・uniform) |                |  (頂点情報)    |
    +-----------------+                +----------------+

レイヤ・主な責務

1. 抽象・システムレイヤ(S3Systemなど)

  • シーン管理(S3Scene)・モデル/カメラ/ライト生成
  • 行列演算・座標変換(モデル/ビュー/プロジェクション)
  • 頂点・メッシュ・マテリアルのファクトリ機能

2. WebGLレイヤ(S3GL* クラス群)

  • GLContext の初期化・管理S3GLSystem
  • GLバッファ生成/VBO/IBO 化S3GLMesh
  • GLシェーダ・プログラム管理S3GLShader, S3GLProgram
  • シーン/モデル/マテリアル/ライトの uniform 変換getUniforms 系)
  • GLリソース(テクスチャ/バッファ)管理・破棄

ファイル構成

  • S3GLSystem.js: WebGL描画システム本体
  • S3GLMesh.js: GL用メッシュ(VBO/IBO管理、属性生成)
  • S3GLModel.js: GL用モデル(uniforms生成)
  • S3GLMaterial.js: GL用マテリアル(uniforms生成・テクスチャ管理)
  • S3GLScene.js: GL用シーン管理
  • S3GLProgram.js: シェーダ・GLプログラム管理
  • S3GLVertex.js, S3GLTriangleIndex.js, S3GLTriangleIndexData.js: 頂点・面・属性生成
  • S3GLArray.js: GL用データ変換・型管理
  • S3GLTexture.js: GL用テクスチャ
  • S3GLLight.js: GL用ライト

主要クラスの役割

S3GLSystem

  • WebGL 描画全体の管理者

    • GLContext の初期化/プログラム切り替え
    • 描画モード・カリング・深度・背景色管理
    • プログラムへのバインド・描画ループ処理
    • 各種ファクトリ(メッシュ/モデル/カメラ etc...)

S3GLScene

  • シーン単位のデータ統括

    • モデル・カメラ・ライトの管理
    • 全 uniform 生成(getUniforms:カメラ方向やライト配列を uniform 形式で返す)

S3GLModel

  • オブジェクト(形状)の位置・スケール・回転・メッシュ参照

    • 複数のマテリアルを集約し、uniform を生成
    • ワールド座標変換行列もここで算出

S3GLMesh

  • WebGLに適したメッシュ(頂点配列・インデックス配列)

    • VBO/IBO 生成・バッファ管理
    • 各三角形ごとに法線/接線/従法線なども計算
    • 頂点ハッシュにより、異なる属性毎に頂点をユニーク化

S3GLTriangleIndex / S3GLTriangleIndexData

  • 三角形インデックス情報(各面ごとのUV, マテリアル等を含む)

    • 面単位/頂点単位で法線・接線等も格納

S3GLVertex

  • 各頂点の 3D 位置ベクトルを GL フォーマットで管理

    • attribute へのバインド用データ(Float32Array化など)

S3GLMaterial

  • 色/拡散/反射/発光/環境光/テクスチャ等の uniform 化

    • テクスチャ有無を含むデータをまとめて返す
    • GLSL シェーダ側の uniform 変数名と一致

S3GLTexture

  • 画像から WebGL テクスチャ生成/管理

S3GLLight

  • 平行光源/点光源/環境光などの uniform 生成

S3GLProgram

  • 頂点・フラグメントシェーダの管理/GLSL uniform・attribute バインド管理

    • GLSL 変数名ごとに型・ロケーション・バインド関数を自動管理
    • VBO/IBO の bind, uniform の bind をすべて管理

データフロー/描画パイプライン例

  1. S3GLSystem: setCanvas, setProgram, setBackgroundColor などシステム初期化
  2. メッシュ・モデル生成: createMesh, createModel、ファイル/データからメッシュ生成(例: MQOローダ)
  3. マテリアル・テクスチャ・ライトのセット
  4. シーン生成/モデル・ライト登録
  5. drawScene(scene) でシーン全体描画

    • (a) Uniforms: シーン/カメラ/ライト/マテリアルの uniform をプログラムにバインド
    • (b) VBO/IBO バインド: メッシュごとに属性・インデックスバッファをGLへ
    • (c) Draw Call: drawElements 実行で WebGL へ描画指示
const s3 = new S3GLSystem();
const program = s3.createProgram();
program.setVertexShader("...");
program.setFragmentShader("...");
s3.setProgram(program);

const mesh = s3.createMesh(); // 頂点・インデックス・マテリアル追加
const model = s3.createModel();
model.setMesh(mesh);

const scene = s3.createScene();
scene.setCamera(s3.createCamera());
scene.addModel(model);

s3.drawScene(scene); // ⇒ 描画パイプラインがすべて自動処理

モジュール依存関係

  • math/ : S3Vector, S3Matrix, S3Math など数学系
  • basic/ : S3Vertex, S3Material, S3TriangleIndex, S3Mesh, S3Model, S3Scene, S3Camera など
  • GL用: S3GLSystem, S3GLMesh, S3GLModel, S3GLScene, S3GLMaterial, S3GLTexture, S3GLTriangleIndex, S3GLVertex, S3GLLight, S3GLProgram, S3GLShader, S3GLArray, S3GLTriangleIndexData

設計の特徴・意図

  • 抽象(basic)とWebGL層(GL)を明確に分離

    • 抽象的な3D形状操作(座標/変換/論理)と、WebGL用最適化データ(バッファ/テクスチャ/シェーダ/GL管理)を分けて設計
  • 可搬性/汎用性

    • 例えば「CPU計算のみで画像を出す」用途や、「将来の他API対応」も見据えている
  • データの immutable 準拠(頂点/三角形/マテリアル)と、mutableな Scene/Model/Camera
  • GLSLシェーダ変数との自動バインド管理(S3GLProgramで自動解析)

通常の3D描写のデモ

以下で実際を確認できます。

sample1.mjs

// --- ライブラリの読み込み ---
// UI操作用ライブラリ
import GuiBlocks from "./libs/GuiBlocks.min.js";
// 入力デバイス(マウスやタッチ)操作用ライブラリ
import InputDetect from "./libs/InputDetect.min.js";
// 3DCGエンジン本体(あなたが作ったS3ライブラリ)
import S3 from "./libs/S3.min.js";

/**
 * S3ライブラリ内の数値計算クラスのデモ関数
 * 行列の作成・計算・逆行列など、基本的な数値処理のサンプルを表示します。
 */
const testMath = function() {
    console.log("Math のサンプル");

    // 4x4行列の生成
    const m4 = new S3.Matrix(
        3, -2, -6, 4,
        -7, -6, 8, 21,
        -4, -7, 9, 11,
        2, -3, -5, 8
    );

    console.log("行列を作成");
    console.log(m4.toString());

    // 4x4行列の行列式を計算(det4)
    console.log("4x4行列の行列式");
    console.log(m4.det4().toString());

    // 4x4行列の逆行列を計算(inverse4)
    console.log("4x4行列の逆行列");
    console.log(m4.inverse4().toString());

    // 行列同士の掛け算
    console.log("行列の掛け算");
    console.log(m4.mulMatrix(m4).toString());

    // 3x3行列の生成
    const m3 = new S3.Matrix(
        1, 2, 1,
        2, 1, 0,
        1, 1, 2
    );

    // 3x3行列の行列式
    console.log("3x3行列の行列式");
    console.log(m3.det3().toString());

    // 3x3行列の逆行列
    console.log("3x3行列の逆行列");
    console.log(m3.inverse3().toString());
};

/**
 * メイン処理(ウィンドウ表示・3D描画)
 */
const main = function() {
    testMath(); // まず数値計算デモを実行

    console.log("S3 クラスのサンプル");

    // スクロールを抑止(マウスやタッチ操作が3D操作用イベントとして優先されるようにする)
    InputDetect.noScroll();

    // --- GUIパネル(描画用キャンバス)を作成 ---
    // 640x480ピクセルの描画パネル(HTML canvas)を作成
    const panel = new GuiBlocks.Canvas();
    // id="scomponent" の要素内にこのパネルを追加
    panel.putMe("scomponent", GuiBlocks.PUT_TYPE.IN);
    // サイズ単位をピクセルで設定
    panel.setUnit(GuiBlocks.UNIT_TYPE.PX);
    panel.setPixelSize(640, 480);  // 実際のピクセルサイズ
    panel.setSize(640, 480);       // 論理サイズ

    {
        // HTMLCanvasElementを取得
        const canvas = panel.getCanvas();

        // S3システム本体(描画エンジン管理クラス)を作成
        const sys = new S3.System();
        // カメラ操作用コントローラを作成
        const controller = new S3.CameraController();
        // カメラ(視点)を生成
        const camera = sys.createCamera();

        // S3システムにcanvasを登録(描画の出力先を指定)
        sys.setCanvas(canvas);
        // カメラ操作コントローラにもcanvasを登録(マウス操作取得)
        controller.setCanvas(canvas);

        // システムの座標系モードをOpenGL互換に設定(右手系、逆順カリングなど)
        sys.setSystemMode(S3.SYSTEM_MODE.OPEN_GL);
        // DirectX互換の場合はこちら
        // sys.setSystemMode(S3.SYSTEM_MODE.DIRECT_X);

        // --- 3Dモデルデータの作成と読み込みデモ ---
        console.log("json形式での読み書きのテスト");
        // サンプル用の簡単なポリゴン(四面体っぽい)データ
        const meshdata = {
            Indexes:{
                body:[
                    [ 0, 1, 2],
                    [ 3, 1, 0],
                    [ 3, 0, 2],
                    [ 3, 2, 1]
                ]
            },
            Vertices:[
                [  0,  0,  -5],
                [  0, 20,  -5],
                [ 10,  0,  -5],
                [  0,  0, -20]
            ]
        };

        let mesh;
        // JSONデータからS3Meshにインポート
        console.log(".json");
        mesh = S3.MeshLoader.inputData(sys, meshdata, "json");
        // S3MeshをJSON形式でエクスポート(逆変換)
        console.log(S3.MeshLoader.outputData(mesh, "json"));

        // MQO形式(Metasequoia 3Dモデラー用)のエクスポートテスト
        console.log("MQOでの出力テスト");
        console.log(".mqo");
        console.log(S3.MeshLoader.outputData(mesh, "mqo"));

        // MQOファイルのインポートテスト(ファイルは ./resource/teapot.mqo を想定)
        console.log("MQOでの入力テスト");
        mesh = S3.MeshLoader.inputData(sys, "./resource/teapot.mqo");

        // --- モデル(位置・回転・スケールを持つ3Dオブジェクト)を作成 ---
        const model = sys.createModel();
        model.setMesh(mesh);    // 形状データをセット
        model.setScale(5);      // 全体スケールを5倍に設定

        // --- カメラ(視点)の初期設定 ---
        camera.setEye(new S3.Vector( 20,  30,  50));   // 視点(カメラ位置)を設定
        camera.setCenter(new S3.Vector( 0,  0,  0));   // 注視点(カメラが向く中心)を設定
        controller.setCamera(camera);                  // カメラコントローラにカメラを登録

        // --- シーン(3D空間)を作成してモデルとカメラを配置 ---
        const scene = sys.createScene();
        scene.setCamera(camera);        // シーンにカメラをセット
        scene.addModel(model);          // モデルをシーンに追加

        /**
         * 画面を定期的に再描画する関数(アニメーションループ)
         * モデルを回転させたり、カメラコントローラで動的に視点操作できる
         */
        const redraw = function() {
            // コントローラの入力に合わせてカメラを更新
            scene.setCamera(controller.getCamera());
            sys.clear();           // 描画バッファをクリア
            model.addRotateY(3);   // モデルをY軸回転させる(毎フレーム3度)
            sys.drawAxis(scene);   // XYZ軸を描画(デバッグ・可視化用)
            sys.drawScene(scene);  // シーン全体を描画(全モデルを表示)
        };

        console.log(model);

        // 50ミリ秒ごとにredraw()を実行(1秒あたり約20回の描画更新=アニメーション)
        setInterval(redraw, 50);
    }
};

// メイン関数実行(最初に呼ばれる)
main();

WebGLの3D描写のデモ

以下で実際を確認できます。

sample2.mjs

// 外部UIライブラリやS3エンジンの読み込み
import GuiBlocks from "./libs/GuiBlocks.min.js";
import InputDetect from "./libs/InputDetect.min.js";
import S3 from "./libs/S3.min.js";

/**
 * WebGLによる3D描画テスト用クラス
 * S3エンジンとGUI部品を組み合わせて動作するデモアプリです。
 */
class S3DGLTest {

    constructor() {
        // S3のWebGLシステム(レンダリング管理)を初期化
        this.s3 = new S3.GLSystem();

        // カメラ操作用コントローラーを初期化
        this.controller = new S3.CameraController();

        // カメラ、メッシュ、モデルの参照を初期化
        this.camera = null;
        this.mesh = null;
        this.model = null;
    }

    /**
     * WebGL描画用Canvasの初期化処理
     * @param {HTMLCanvasElement} canvas - 描画対象のCanvas要素
     */
    initCanvas(canvas) {
        const s3 = this.s3;
        // 背景色を黒に設定
        s3.setBackgroundColor(new S3.Vector(0, 0, 0));
        // WebGLコンテキストをCanvasにセット
        s3.setCanvas(canvas);
        // カメラ操作用のCanvasも登録
        this.controller.setCanvas(canvas);

        // 3Dカメラを生成
        this.camera = s3.createCamera();

        // シェーダープログラム(GLSL)を作成・セット
        const program = s3.createProgram();
        program.setFragmentShader("./libs/S3GL.frag"); // フラグメントシェーダ
        program.setVertexShader("./libs/S3GL.vert");   // バーテックスシェーダ
        s3.setProgram(program);

        // 描画方式などの初期パラメータ
        s3.setSystemMode(S3.SYSTEM_MODE.OPEN_GL);       // OpenGL方式
        s3.setFrontMode(S3.FRONT_FACE.CLOCKWISE);       // 面の表裏判定を時計回りに

        // カメラの視点と注視点を設定
        this.camera.setEye(new S3.Vector(20, 30, 50));  // カメラの位置
        this.camera.setCenter(new S3.Vector(0, 0, 0));  // 注視点(原点)

        // コントローラーにカメラをセット(ユーザー操作反映用)
        this.controller.setCamera(this.camera);
    }

    /**
     * 現在のモデル・メッシュをクリア(削除)する
     * 破棄・参照切り離しでリソースリークを防ぐ
     */
    clearModel() {
        if (this.model) {
            this.model = null;
        }
        if (this.mesh) {
            this.mesh.dispose(); // GLリソース解放
            this.mesh = null;
        }
    }

    /**
     * モデル(3D形状データ)を読み込んでセットする
     * @param {string} url - MQOファイルなどのリソースパス
     */
    setModel(url) {
        const s3 = this.s3;
        // 新しい3Dモデルオブジェクト生成
        const newmodel = s3.createModel();
        // メッシュローダーでファイルからメッシュデータ生成
        const newmesh = S3.MeshLoader.inputData(s3, url);
        // モデルにメッシュを登録
        newmodel.setMesh(newmesh);

        // 既存のモデルをクリア
        this.clearModel();

        // 読み込んだモデル・メッシュを保持
        this.model = newmodel;
        this.mesh = newmesh;
    }

    /**
     * シーンの描画処理
     * カメラ、ライト、モデルをまとめてS3エンジンでレンダリング
     */
    draw() {
        const s3 = this.s3;
        // シーン(Scene)を作成
        const scene = s3.createScene();

        // コントローラーからカメラ取得し、シーンに登録
        scene.setCamera(this.controller.getCamera());

        // モデルがセットされていればシーンに追加し、動きをつける
        if (this.model !== null) {
            scene.addModel(this.model);
            this.model.setScale(5);      // モデルを拡大
            this.model.addRotateY(3);    // モデルをY軸周りに回転
        }

        // 環境に3つのライト(光源)を追加
        // 1. 上方向からの平行光源
        const light_down = s3.createLight();
        light_down.setMode(S3.LIGHT_MODE.DIRECTIONAL_LIGHT);
        light_down.setColor(new S3.Vector(0.6, 0.6, 1.0));
        light_down.setDirection(new S3.Vector(0, -1, 0));
        scene.addLight(light_down);

        // 2. 環境光
        const light_ambient = s3.createLight();
        light_ambient.setMode(S3.LIGHT_MODE.AMBIENT_LIGHT);
        light_ambient.setColor(new S3.Vector(0.0, 0.1, 0.05));
        scene.addLight(light_ambient);

        // 3. ポイントライト(位置指定の点光源)
        const light_point = s3.createLight();
        light_point.setMode(S3.LIGHT_MODE.POINT_LIGHT);
        light_point.setColor(new S3.Vector(0.9, 0.9, 1.0));
        light_point.setPosition(new S3.Vector(100, 0, 0));
        light_point.setRange(200); // 有効範囲
        scene.addLight(light_point);

        // バッファ等の初期化
        s3.clear();

        // シーンを描画
        s3.drawScene(scene);
    }

}

// このクラスのインスタンスを生成しグローバル参照
const gl = new S3DGLTest();

/**
 * WebGL描画パネル(Canvasと連携したUI部品)の生成
 * - 画面上に1280x720のWebGLキャンバスを配置
 * - テスト用の3Dモデル(teapot.mqo)を初期表示
 * - 50msごとに描画を更新
 */
const createWebGLPanel2 = function() {
    const panel = new GuiBlocks.Canvas();
    panel.putMe("webglpanel", GuiBlocks.PUT_TYPE.IN);
    panel.setUnit(GuiBlocks.UNIT_TYPE.PX);
    panel.setPixelSize(1280, 720);

    const canvas = panel.getCanvas();

    gl.initCanvas(canvas); // WebGLセットアップ
    gl.setModel("./resource/teapot.mqo"); // ティーポット3Dモデル読み込み

    const redraw = function() {
        gl.draw(); // 画面更新
    };

    // 描画ループ(50ms間隔)
    setInterval(redraw, 50);
    // ※setTimeoutによる単発描画も可能
};

/**
 * 操作用パネルの生成
 * - ファイル選択用のコンボボックス(mqoモデルのリスト)
 * - 「load」ボタンでモデルの切り替えが可能
 */
const createOperationPanel = function() {
    const filepanel = new GuiBlocks.Panel("ファイル");
    filepanel.putMe("operationpanel", GuiBlocks.PUT_TYPE.IN);

    const filebox = new GuiBlocks.ComboBox([
        "./resource/teapot.mqo",
        "./resource/bumptest.mqo"
    ]);
    filebox.putMe(filepanel, GuiBlocks.PUT_TYPE.IN);

    const loadbutton = new GuiBlocks.Button("load");
    loadbutton.putMe(filebox, GuiBlocks.PUT_TYPE.NEWLINE);
    loadbutton.addListener(function () {
        const filename = filebox.getSelectedItem();
        console.log(filename);
        gl.setModel(filename); // 新しいモデルに切り替え
    });
};

/**
 * メイン処理
 * - コンソール出力
 * - スクロール抑止
 * - WebGLパネルと操作パネルのセットアップ
 */
const main = function() {
    console.log("S3DGL クラスのサンプル");

    // スマホ等でのスクロールを抑制
    InputDetect.noScroll();

    // WebGL描画パネル生成
    createWebGLPanel2();
    // 操作パネル生成
    createOperationPanel();
};

// アプリ起動
main();

S3GL.vert

// ========================================================================
// バーテックス(頂点)シェーダ
// このシェーダは、3Dオブジェクトの各頂点ごとに呼び出され、
// 頂点位置の座標変換や、法線・テクスチャ座標などの各種属性を
// フラグメントシェーダ(ピクセルシェーダ)に渡します。
// ========================================================================

// ---------- 頂点属性(JavaScriptからバインドされる) -------------------

// 頂点法線ベクトル(照明計算や法線マップで使う)
attribute vec3 vertexNormal;

// 接線・従法線(タンジェント/バイノーマル)
// 法線マッピング(ノーマルマップ)で必要
attribute vec3 vertexBinormal;
attribute vec3 vertexTangent;

// 頂点の3D空間位置(ワールド空間での頂点座標)
attribute vec3 vertexPosition;

// テクスチャのUV座標(2次元、0.0~1.0など)
attribute vec2 vertexTextureCoord;

// マテリアル(材質)のインデックス番号(float型で持つ)
// 面ごとに異なるマテリアル情報を持つ場合に利用
attribute float vertexMaterialFloat;

// ---------- 行列(uniform: 全頂点で共通の定数値) ----------------------

// モデル座標→カメラ→射影空間への変換行列(最終的にgl_Positionに使う)
uniform mat4 matrixLocalToPerspective4;

// モデル座標→ワールド空間への変換行列
uniform mat4 matrixLocalToWorld4;

// ---------- シェーダ間の受け渡し(varying: 頂点→フラグメント) --------

// マテリアル番号
varying float interpolationMaterialFloat;

// 法線・接線・従法線(各頂点のものをフラグメントに渡す)
varying vec3 interpolationNormal;
varying vec3 interpolationBinormal;
varying vec3 interpolationTangent;

// ワールド空間での頂点位置(ピクセルシェーダのライティングなどに使う)
varying vec3 interpolationPosition;

// テクスチャ座標(フラグメントシェーダでピクセルごとのUV計算に使う)
varying vec2 interpolationTextureCoord;

// ---------- メイン関数 ----------------------------------------------

void main(void) {
    // マテリアル番号をそのまま渡す(int化はfsで行う)
    interpolationMaterialFloat = vertexMaterialFloat;

    // 各種ベクトルもフラグメントシェーダへ伝達
    interpolationNormal   = vertexNormal;
    interpolationBinormal = vertexBinormal;
    interpolationTangent  = vertexTangent;

    // ワールド空間の頂点位置を計算して渡す(照明/反射などで使う)
    interpolationPosition = (matrixLocalToWorld4 * vec4(vertexPosition, 1.0)).xyz;

    // UV座標
    interpolationTextureCoord = vertexTextureCoord;

    // 頂点を最終的にどこに描画するか計算(射影空間座標)
    gl_Position = matrixLocalToPerspective4 * vec4(vertexPosition, 1.0);

    // ※このgl_Positionの値が、画面上の頂点の位置になる
}

S3GL.frag

// ========================================================================
// フラグメント(ピクセル)シェーダ
// このシェーダは、各ピクセル(画面上の1点)ごとに実行されます。
// ライティング、テクスチャ、マテリアル、ノーマルマップなどの情報を使って
// 最終的な色(gl_FragColor)を決定します。
// ========================================================================

// ---------- 精度の指定(必須:ESのGLSL) ------------------------------
precision mediump float;

// ---------- 材質(マテリアル)関連のuniform変数 -------------------------
// JavaScript側から各マテリアルごとにセットされる(最大4つまで)

#define MATERIALS_MAX 4

uniform vec4 materialsColorAndDiffuse[MATERIALS_MAX];
//  [R, G, B, 拡散率](カラー+ディフューズ)

uniform vec4 materialsSpecularAndPower[MATERIALS_MAX];
//  [R, G, B, 光沢度](スペキュラ+パワー)

uniform vec3 materialsEmission[MATERIALS_MAX];
//  発光色(エミッシブ)

uniform vec4 materialsAmbientAndReflect[MATERIALS_MAX];
//  [R, G, B, 反射率](アンビエント+リフレクト)

uniform vec2 materialsTextureExist[MATERIALS_MAX];
//  [カラーテクスチャの有無, ノーマルテクスチャの有無](1:あり, 0:なし)

uniform sampler2D materialsTextureColor[MATERIALS_MAX];
//  カラーテクスチャ(画像)

uniform sampler2D materialsTextureNormal[MATERIALS_MAX];
//  ノーマルマップ(法線マップ用画像)

// ---------- 変換行列のuniform(主に法線変換で使う) ----------------------
uniform mat4 matrixWorldToLocal4; // ワールド→ローカル変換(逆行列など)
uniform mat3 matrixLocalToWorld3; // ローカル→ワールド変換(回転のみ3x3行列)

// ---------- ライト(光源)情報のuniform ---------------------------------

#define LIGHTS_MAX 4
#define LIGHT_MODE_NONE        0
#define LIGHT_MODE_AMBIENT     1
#define LIGHT_MODE_DIRECTIONAL 2
#define LIGHT_MODE_POINT       3

uniform int lightsLength;                 // 実際に使う光源の数
uniform vec4 lightsData1[LIGHTS_MAX];     // 光源の種類・レンジ・方向/位置(XY)
uniform vec4 lightsData2[LIGHTS_MAX];     // 方向/位置(Z), 光源色(RGB)

// ---------- カメラ情報(視線ベクトル) ---------------------------------
uniform vec3 eyeWorldDirection;

// ---------- 頂点シェーダから渡された補間値 ------------------------------
varying float interpolationMaterialFloat;    // マテリアル番号(float型)
varying vec3 interpolationNormal;           // 法線ベクトル
varying vec3 interpolationBinormal;         // バイノーマル
varying vec3 interpolationTangent;          // タンジェント
varying vec3 interpolationPosition;         // ワールド空間での頂点位置
varying vec2 interpolationTextureCoord;     // テクスチャUV座標

// ========================================================================
// メイン処理開始
// ========================================================================
void main(void) {

    // ----- 定数定義(色やノーマルの基準値) ----------------------------
    const vec4 ZERO      = vec4(0.0, 0.0, 0.0, 0.0);
    const vec4 ONE       = vec4(1.0, 1.0, 1.0, 1.0);
    const vec4 WHITE     = ONE;
    const vec3 NORMALTOP = vec3(0.5, 0.5, 1.0); // 法線マップのデフォルト

    // ----- 1. 頂点シェーダから受け取った値の初期処理 ------------------

    int   vertexMaterial = int(interpolationMaterialFloat); // マテリアル番号をintに変換
    vec3  vertexNormal   = normalize(interpolationNormal);
    vec3  vertexBinormal = normalize(interpolationBinormal);
    vec3  vertexTangent  = normalize(interpolationTangent);

    // ----- 2. 面ごとのマテリアル情報(配列から抽出) ------------------

    // 各種マテリアル属性値(色、反射率、テクスチャ有無など)を取得
    vec3  materialColor;
    float materialDiffuse;
    vec3  materialSpecular;
    float materialPower;
    vec3  materialEmission;
    vec3  materialAmbient;
    float materialReflect;
    float materialRoughness;
    vec4  materialTextureColor;
    vec3  materialTextureNormal;
    bool  materialIsSetNormal;

    {
        // 材質配列(最大4つ)から該当インデックスを選択
        if (vertexMaterial < 4) {
            // 材質番号ごとに各種パラメータを取り出す
            // 下記のような分岐は、配列に対してインデックスでアクセスしやすくするため
            if(vertexMaterial < 2) {
                if(vertexMaterial == 0) {
                    materialColor         = materialsColorAndDiffuse[0].xyz;
                    materialDiffuse       = materialsColorAndDiffuse[0].z;
                    materialSpecular      = materialsSpecularAndPower[0].xyz;
                    materialPower         = materialsSpecularAndPower[0].w;
                    materialEmission      = materialsEmission[0];
                    materialAmbient       = materialsAmbientAndReflect[0].xyz;
                    materialReflect       = materialsAmbientAndReflect[0].w;
                    materialTextureColor  = materialsTextureExist[0].x > 0.5 ?
                        texture2D(materialsTextureColor[0], interpolationTextureCoord) : WHITE;
                    materialIsSetNormal   = materialsTextureExist[0].y > 0.5;
                    materialTextureNormal = materialIsSetNormal ?
                        texture2D(materialsTextureNormal[0], interpolationTextureCoord).xyz : NORMALTOP;
                }
                else {
                    // 1番のマテリアル
                    materialColor         = materialsColorAndDiffuse[1].xyz;
                    materialDiffuse       = materialsColorAndDiffuse[1].z;
                    materialSpecular      = materialsSpecularAndPower[1].xyz;
                    materialPower         = materialsSpecularAndPower[1].w;
                    materialEmission      = materialsEmission[1];
                    materialAmbient       = materialsAmbientAndReflect[1].xyz;
                    materialReflect       = materialsAmbientAndReflect[1].w;
                    materialTextureColor  = materialsTextureExist[1].x > 0.5 ?
                        texture2D(materialsTextureColor[1], interpolationTextureCoord) : WHITE;
                    materialIsSetNormal   = materialsTextureExist[1].y > 0.5;
                    materialTextureNormal = materialIsSetNormal ?
                        texture2D(materialsTextureNormal[1], interpolationTextureCoord).xyz : NORMALTOP;
                }
            }
            else {
                if(vertexMaterial == 2) {
                    // 2番のマテリアル
                    materialColor         = materialsColorAndDiffuse[2].xyz;
                    materialDiffuse       = materialsColorAndDiffuse[2].z;
                    materialSpecular      = materialsSpecularAndPower[2].xyz;
                    materialPower         = materialsSpecularAndPower[2].w;
                    materialEmission      = materialsEmission[2];
                    materialAmbient       = materialsAmbientAndReflect[2].xyz;
                    materialReflect       = materialsAmbientAndReflect[2].w;
                    materialTextureColor  = materialsTextureExist[2].x > 0.5 ?
                        texture2D(materialsTextureColor[2], interpolationTextureCoord) : WHITE;
                    materialIsSetNormal   = materialsTextureExist[2].y > 0.5;
                    materialTextureNormal = materialIsSetNormal ?
                        texture2D(materialsTextureNormal[2], interpolationTextureCoord).xyz : NORMALTOP;
                }
                else {
                    // 3番のマテリアル
                    materialColor         = materialsColorAndDiffuse[3].xyz;
                    materialDiffuse       = materialsColorAndDiffuse[3].z;
                    materialSpecular      = materialsSpecularAndPower[3].xyz;
                    materialPower         = materialsSpecularAndPower[3].w;
                    materialEmission      = materialsEmission[3];
                    materialAmbient       = materialsAmbientAndReflect[3].xyz;
                    materialReflect       = materialsAmbientAndReflect[3].w;
                    materialTextureColor  = materialsTextureExist[3].x > 0.5 ?
                        texture2D(materialsTextureColor[3], interpolationTextureCoord) : WHITE;
                    materialIsSetNormal   = materialsTextureExist[3].y > 0.5;
                    materialTextureNormal = materialIsSetNormal ?
                        texture2D(materialsTextureNormal[3], interpolationTextureCoord).xyz : NORMALTOP;
                }
            }
        }
        // ラフネス(ざらざら感)は光沢度から計算
        // ラフネス値(0...1)を暫定計算(大きいほどざらざらしている)
        materialRoughness = (100.0 - materialPower) * 0.01;
    }

    // ----- 3. テクスチャ反映・ノーマルマッピング ------------------------
    {
        // カラーテクスチャで色を掛ける
        materialColor *= materialTextureColor.xyz;

        // ノーマルマップ(法線テクスチャ)を使う場合
        if(materialIsSetNormal) {
            // [0,1] -> [-1,1]の範囲へ変換
            materialTextureNormal = (materialTextureNormal * 2.0 - 1.0);
            // 接線空間からワールド空間に変換
            vertexNormal = normalize(
                -materialTextureNormal.x * vertexTangent +
                materialTextureNormal.y * vertexBinormal +
                materialTextureNormal.z * vertexNormal);
        }
    }

    // ----- 4. 反射ベクトルや視線方向の計算 ------------------------------

    // 反射ベクトル
    vec3 vertexReflectVector = reflect(eyeWorldDirection, normalize(matrixLocalToWorld3 * vertexNormal));

    // カメラが向いている方向を取得
    vec3 eyeDirection = normalize(matrixWorldToLocal4 * vec4(eyeWorldDirection, 0.0)).xyz;

    // ----- 5. 出力色の初期化 --------------------------------------------
    vec3 destDiffuse  = materialColor * materialEmission;
    vec3 destSpecular = ZERO.xyz;
    vec3 destAmbient  = materialAmbient * 0.2;

    // 光源全体の平均色(反射表現などに使う)
    vec3 averageLightsColor = ZERO.xyz;

    // ----- 6. ライティング計算 ------------------------------------------
    {
        // 全ライトについて計算
        for(int i = 0; i < LIGHTS_MAX; i++) {
            int   lightMode   = int(lightsData1[i].x);
            float lightRange  = lightsData1[i].y;
            vec3  lightVector = vec3(lightsData1[i].zw, lightsData2[i].x);
            vec3  lightColor  = lightsData2[i].yzw;

            // 平行光源・点光源
            if((lightMode == LIGHT_MODE_DIRECTIONAL) || (lightMode == LIGHT_MODE_POINT)) {
                bool is_direction = lightMode == LIGHT_MODE_DIRECTIONAL;
                // 光源の向きや位置を求める
                // 光源の種類によって、ピクセルと光への方向ベクトルの計算を変える
                // lightsVector は、点光源なら位置を、平行光源なら方向を指す値
                vec3 lightDirection = is_direction ?
                    normalize(matrixWorldToLocal4 * vec4(lightVector, 0.0)).xyz :
                    normalize(matrixWorldToLocal4 * vec4(interpolationPosition - lightVector, 1.0)).xyz;
                float d = is_direction ? -1.0 : length(lightVector - interpolationPosition);

                if(d < lightRange) {
                    // 距離減衰
                    float rate = is_direction ? 1.0 : pow(1.0 - (d / lightRange), 0.5);
                    // 拡散反射(ランバート反射)
                    float diffuse = clamp(((dot(vertexNormal, lightDirection) * 0.9) + 0.1) * materialDiffuse, 0.0, 1.0);
                    destDiffuse  += lightColor * materialColor.xyz * diffuse * rate;
                    // 鏡面反射(ブリン・フォン)
                    vec3  halfLightEye = normalize(lightDirection + eyeDirection);
                    float specular     = pow(clamp(dot(vertexNormal, halfLightEye), 0.0, 1.0), materialPower);
                    destSpecular      += lightColor * materialSpecular.xyz * specular * rate;
                }
            }
            // アンビエントライト(環境光)
            else if(lightMode == LIGHT_MODE_AMBIENT) {
                destDiffuse += lightColor * materialColor.xyz;
                destAmbient += lightColor * materialAmbient.xyz;
            }

            // 光の平均色(反射・環境エフェクト用)
            averageLightsColor += lightColor;

            // 実際に使う光源数分だけループ
            if(i == lightsLength) {
                break;
            }
        }
        // 平均色を正規化
        if(0 < lightsLength) {
            averageLightsColor /= vec3(lightsLength, lightsLength, lightsLength);
        }
    }

    // ----- 7. 反射表現(リフレクションの模様作成)-----------------------
    vec3 destColor = ZERO.xyz;

    {
        // アンビエント光
        destColor += destAmbient;

        // 拡散反射(反射率が高いほど弱くする)
        destColor += clamp(destDiffuse * (1.0 - materialReflect * 0.8), 0.0, 1.0);

        // 鏡面反射
        destColor += destSpecular;

        // 反射のエフェクト(物体に周囲の色が写り込む)
        if(materialReflect > 0.0001) {
            // リフレクト用の簡易ノイズ模様を計算(ラフネスで模様のボケ方を制御)
            float x = vertexReflectVector.y;
            float x1 = mix(-0.1, -0.5, materialRoughness);
            float x2 = mix(0.01,  0.5, materialRoughness);
            float c1 = mix(-0.3,  0.5, materialRoughness);
            float c2 = mix( 0.9,  0.6, materialRoughness);
            float c3 = mix( 0.3,  0.4, materialRoughness);
            float c4 = mix( 1.2,  0.8, materialRoughness);
            float c5 = mix( 0.3,  0.5, materialRoughness);

            x = x < x1 ? mix( c1, c2, (x + 1.0) * (1.0 / (1.0 + x1))) :
                x < 0.0 ? mix( c2, c3, (x - x1) * (1.0 / -x1)) :
                x < x2  ? mix( c3, c4, x * (1.0 / x2)) :
                          mix( c4, c5, (x - x2) * (1.0 / (1.0 - x2))) ;

            // 光沢・ラフネスの影響も反映した色模様
            //  リフレクトが大きいほどはっきり強くうつりこむ
            //  映り込む模様は、周りの光の色に影響する
            vec3 reflectColor = vec3(x, x, x) * materialReflect * averageLightsColor;

            // 映り込む模様の色
            //  ラフネスが大きいほど、物体の色で映り込む
            //  ラフネスが小さいほど、スペキュラの色で映り込む
            reflectColor *= materialRoughness * materialColor + (1.0 - materialRoughness) * materialSpecular;

            destColor += reflectColor;
        }
    }

    // ----- 8. 出力(ピクセル色の最終決定)-------------------------------
    gl_FragColor = vec4(destColor, 1.0);
}