本授業の重点内容#
なぜ WebGL なのか / なぜ GPU なのか?#
- WebGL とは何か?
- GPU ≠ WebGL ≠ 3D
- WebGL はなぜ他のフロントエンド技術のように簡単ではないのか?
現代の画像システム#
- ラスタ(Raster):ほぼすべての現代のグラフィックスシステムはラスタに基づいてグラフィックスを描画します。ラスタとは画像を構成するピクセルの配列を指します。
- ピクセル(Pixel):1 つのピクセルは画像上の 1 点に対応し、通常は画像上の特定の位置の色などの情報を保存します。
- フレームバッファ(Frame Buffer):描画プロセス中に、ピクセル情報はフレームバッファに保存され、フレームバッファはメモリアドレスの一部です。
- CPU(Central Processing Unit):中央処理装置で、論理計算を担当します。
- GPU(Graphics Processing Unit):グラフィックス処理装置で、グラフィックス計算を担当します。
- 上の図のように、現代の画像のレンダリングはこのプロセスに従います。
- 輪郭抽出 / メッシング
- ラスタ化
- フレームバッファ
- レンダリング
パイプライン#
GPU#
- GPU は多数の小さな演算ユニットで構成されています。
- 各演算ユニットは非常に単純な計算のみを処理します。
- 各演算ユニットは互いに独立しています。
- したがって、すべての計算は並行して処理できます。
WebGL と OpenGL の関係#
OpenGL, OpenGL ES, WebGL, GLSL, GLSL ES API Tables (umich.edu)
WebGL の描画手順#
手順
- WebGL コンテキストを作成
- WebGL プログラムを作成
- データをバッファに格納
- バッファデータを GPU に読み込む
- GPU が WebGL プログラムを実行し、結果を出力
図のように、いくつかの単語について説明します:
- Raw Vertices & Primitives 原始頂点 & 原語
- Vertex Processor 頂点シェーダー
- 演算後にフラグメントシェーダーに送信して処理:Fragment Processor
WebGL コンテキストの作成#
const canvas = document.querySelector('canvas');
const gl = canvas.getContext('webgl');
// コンテキストを作成、互換性に注意
function create3DContext(canvas, options) {
const names = ['webgl', 'experimental-webgL','webkit-3d','moz-webgl']; // 特性判断
if(options.webgl2) names.unshift(webgl2);
let context = null;
for(let ii = 0; ii < names.length; ++ii) {
try {
context = canvas.getContext(names[ii], options);
} catch(e) {
// no-empty
}
if(context) {
break;
}
}
return context;
}
WebGL プログラムの作成(シェーダー)#
-
頂点シェーダー(Vertex Shader)
タイプ配列 position を使用して、並行して各頂点の位置を処理します。
attribute vec2 position;// vec2 二次元ベクトル void main() { gl_PointSize = 1.0; gl_Position = vec4(position, 1.0, 1.0); }
-
フラグメントシェーダー(Fragment Shader)
頂点の輪郭で囲まれた領域内のすべてのピクセルに色を付けます。
precision mediump float; void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);//対応するrgba(255,0,0,1.0)、赤色 }
具体的な手順は以下の通りです:
-
頂点シェーダーとフラグメントシェーダーのコードを作成:
// 頂点シェーダープログラムコード const vertexShaderCode = ` attribute vec2 position; void main() { gl_PointSize = 1.0; gl_Position = vec4(position, 1.0, 1.0); } `; // フラグメントシェーダープログラムコード const fragmentShaderCode = ` precision mediump float; void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); } `;
-
**
createShader()
** を使用してシェーダーオブジェクトを作成します。 -
**
shaderSource()
** を使用してシェーダーのプログラムコードを設定します。 -
**
compileShader()
** を使用してシェーダーをコンパイルします。// 頂点シェーダー const vertexShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vertexShader, vertex); gl.compileShader(vertexShader); // フラグメントシェーダー const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fragmentShader, fragment); gl.compileShader(fragmentShader);
-
**
createProgram()
** を使用してWebGLProgram
オブジェクトを作成します。 -
**
attachShader()
** を使用してWebGLProgram
にフラグメントまたは頂点シェーダーを追加します。 -
**
linkProgram()
** を使用して指定されたWebGLProgram
をリンクし、プログラムのフラグメントと頂点シェーダーのために GPU コードを準備します。 -
**
useProgram()
** を使用して定義されたWebGLProgram
オブジェクトを現在のレンダリング状態に追加します。// シェーダープログラムを作成してリンク const program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); gl.useProgram(program);
データをバッファに格納する(Data to Frame Buffer)#
- 座標軸:webGL の座標システムは正規化されており、ブラウザと canvas2Dの座標システムは左上角を座標原点とし、y 軸が下向き、x 軸が右向きで、座標値は原点に対して相対的です。一方、webGLの座標系は描画キャンバスの中心点を原点とし、通常のデカルト座標系です。
頂点を表す頂点配列を使用し、**createBuffer()
を使用して頂点データまたはシェーディングデータを格納するためのWebGLBuffer
オブジェクトを作成し、bufferId
を返します。その後、bindBuffer()
を使用して指定されたbufferId
をターゲットにバインドし、最後にbufferData()
** を使用してデータをバッファにバインドします。
// 頂点データ
const points = new Float32Array([
-1, -1,
0, 1,
1, -1,
]);
// バッファを作成
const bufferId = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, bufferId);
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);
バッファデータを GPU に読み込む(Frame Buffer to GPU)#
getAttribLocation()は、指定された
WebGLProgram
オブジェクト内の特定の属性のインデックスを返します。vertexAttribPointer()は、GPU に現在バインドされているバッファ(bindBuffer () で指定されたバッファ)から頂点データを読み込むよう指示します。
enableVertexAttribArray()は、属性配列リスト内の指定されたインデックスの一般的な頂点属性配列を有効にします。
const vPosition = gl.getAttribLocation(program, 'position'); // 頂点シェーダー内のposition変数のアドレスを取得
gl.vertexAttribPointer(vPosition, 2, gl.FLOAT, false, 0, 0); // 変数に長さとタイプを設定
gl.enableVertexAttribArray(vPosition); // この変数を有効にする
結果を出力する(Output)#
drawArrays()は、ベクトル配列からプリミティブを描画します。
// output
gl.clear(gl.COLOR_BUFFER_BIT); //バッファのデータをクリア
gl.drawArrays(gl.TRIANGLES, 0, points.length / 2);
WebGL は複雑すぎる?他の方法#
canvas 2D#
canvas2D を見てみましょう、同じ三角形を描画します:
// canvasはシンプルで、すでにラッピングされています
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(250, 0);
ctx.lineTo(500, 500);
ctx.lineTo(0, 500);
ctx.fillStyle = 'red';
ctx.fill();
Mesh.js#
mesh-js/mesh.js: A graphics system born for visualization 😘. (github.com)
const {Renderer, Figure2D, Mesh2D} = meshjs;
const canvas = document.querySelector ('canvas');
const renderer = new Renderer(canvas);
const figure = new Figure2D();
figurie.beginPath();
figure.moveTo(250, 0);
figure.lineTo(500,500);
figure.lineTo(0, 500);
const mesh = new Mesh2D(figure, canvas);
mesh.setFill({
color: [1, 0, 0, 1],
});
renderer.drawMeshes([mesh]);
Earcut#
Earcutを使用して三角形分割を行います。
const vertices = [
[-0.7, 0.5],
[-0.4, 0.3],
[-0.25, 0.71],
[-0.1, 0.56],
[-0.1, 0.13],
[0.4, 0.21],
[0, -0.6],
[-0.3, -0.3],
[-0.6, -0.3],
[-0.45, 0.0],
];
const points = vertices.flat();
const triangles = earcut(points)
3D メッシング#
デザイナーが私たちにエクスポートし、そこから抽出します。
SpriteJS/next - The next generation of spritejs.
グラフィックス変換(Transforms)#
これがデジタル画像処理に関連する知識です(学んだことがすべて戻ってきました.jpg)
平行移動#
回転#
拡大縮小#
線形変換(回転 + 拡大縮小)#
線形変換から同次行列へ
先生のもう一つの例:Apply Transforms
3D マトリックス#
3D 標準モデルの4 つの同次行列(mat4)
- 投影行列 Projection Matrix(直交投影と透視投影)
- モデル行列 Model Matrix(頂点を変換する)
- ビュー行列 View Matrix(3D の視点、カメラの視野のように考える)
- 法線行列 Normal Matrix(物体表面に垂直な法線、通常は物体のライティング計算に使用されます)
さらに読む#
- The Book of Shaders (フラグメントシェーダーについて、非常に面白い)
- Mesh.js (低レベルライブラリ、えへへ)
- Glsl Doodle (フラグメントシェーダーの軽量ライブラリ、小さなデモがたくさんあります)
- SpriteJS (月影先生が書いたオープンソースライブラリ orz)
- Three.js(面白い
ゲームプロジェクトがたくさんあります) - Shadertoy BETA(面白いプロジェクトがたくさんあります)
まとめと感想#
この授業では、先生が WebGL の描画と関連ライブラリについて非常に詳しく説明し、多くの面白い WebGL の小プロジェクトを紹介しました~
本文で引用したほとんどの内容は月影先生の授業と MDN からのものです!月影先生、tql!