一般
概要
このプロジェクトは、純粋な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を継承し独自拡張可能。
- シーン構成/ユーティリティ: 数学系やカメラ制御等もモジュール分離で独自実装しやすい。
モジュール依存関係と流れ
- S3Systemがすべてのファクトリ・管理・計算の中核となり、インスタンス生成も担当。
- S3Sceneが、各モデル・ライト・カメラを保持し、
S3System
からの描画要求で内容を渡す。 - S3MeshLoaderを通じて、外部3Dデータを読み込み、
S3Mesh
へ展開。 - 各モデルは自身の
S3Mesh
を持ち、ワールド変換行列はS3System
で算出。 - 描画時は、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 をすべて管理
データフロー/描画パイプライン例
- S3GLSystem:
setCanvas
,setProgram
,setBackgroundColor
などシステム初期化 - メッシュ・モデル生成:
createMesh
,createModel
、ファイル/データからメッシュ生成(例: MQOローダ) - マテリアル・テクスチャ・ライトのセット
- シーン生成/モデル・ライト登録
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);
}