Upgrade to Pro — share decks privately, control downloads, hide ads and more …

【STUDDi】WebGL で学ぶ 3D Graphics の概略

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

【STUDDi】WebGL で学ぶ 3D Graphics の概略

STUDDi とは CADDi 株式会社 Tech 本部で行われている社内勉強会の名称です。
その STUDDi の場で 3D グラフィックスの初歩的な内容を説明しました。
※ もし内容に誤りがありましたらご指摘ください。

Avatar for Terada Yuichiro

Terada Yuichiro

July 07, 2020
Tweet

More Decks by Terada Yuichiro

Other Decks in Technology

Transcript

  1. 【STUDDi】 WebGL で学ぶ 3D Graphics の概略 2020-07-07 CADDi / Tech

    / Algo / Y.Terada ※ STUDDi とは CADDi Tech 本部の社内勉強会の名称です
  2. 3つの壁 数学の壁 • 4x4の行列、アフィン変換、射影変換、etc アーキテクチャの壁 • CPUとGPUの違い • VRAM上のオブジェクトの操作 APIの壁

    • 何やら引数の多い関数を沢山呼ばないと動かない • グローバルな状態が遷移するモデルが分かりづらい Hello World が遠い
  3. 方針 • 数学の話は(あまり)しません ◦ 「分かっている人」には簡単で退屈 ◦ 「分からない人」が数分の説明で理解するのは無理 • API よりも原理を説明します

    ◦ 原理が分かるとAPIの意味・意図が分かるようになる ◦ 原理を理解せずにAPIだけ見てもダメ ◦ 急がば廻れ
  4. Default Projection x y z 1 1 1 -1 -1

    -1 • [-1, 1] x [-1, 1] x [-1, 1] の空間 • 要は、この空間に収まるように三角形 の頂点座標を送れば、画面に描画され る • 行列の演算は何のため? • → 座標変換してこのデフォルトの空間 に頂点座標を収めるため!
  5. Rendering Pipeline x y z x y z 1 1

    1 - 1 - 1 - 1 VRAM Framebuffer Vertex Shader (programmable) Fragment Shader (programmable) 座標変換 etc シェーディング etc Rasterization (automatic)
  6. Shader is NOT Only for Shading • Shading … 立体的に見えるように陰影を付けること

    • Shader = Shading するためのプログラム? ◦ ↑そういう側面もあるが、 Shader という名前に囚われないほうが良い ◦ Shader はもっと自由なもの。 Shading 以外にも使える。 • Shader = Rendering Pipeline に差し込めるプログラム ◦ Vertex Shader … 頂点単位で動くプログラム ◦ Fragment Shader … ピクセル単位で動くプログラム
  7. CPU vs GPU つよそうなヤツで比較 CPU GPU 名前 Intel Core i9-10980XE

    NVIDIA GeForce RTX2080Ti クロック数 3.0GHz 1.35GHz コア数 18 4352 メモリ帯域 94 GB/s 616 GB/s > << <
  8. CPU and GPU CPU RAM GPU VRAM 速い めっちゃ速い 遅い

    CPU RAM GPU 統合型 (Intel HD Graphics) WebGL/OpenGL/Direct3D 等は こういったハードウェア特性を 念頭に置いたAPI設計 めっちゃ並列
  9. 描画パフォーマンスのためには Main Memory → VRAM のデータ転送を少なく抑える • データ(頂点、テクスチャ画像、etc)は事前にVRAMに転送しておく • Rendering

    Pipeline は(ほぼ) GPU + VRAM だけで完結させる GPUの並列性を活かす • パイプライン処理 • Vertex Shader • Fragment Shader
  10. 頂点配列をVRAMに転送 Main Memory (x0, y0, z0) (x1, y1, z1) (x2,

    y2, z2) ︙ VRAM (x0, y0, z0) (x1, y1, z1) (x2, y2, z2) ︙ Frame Buffer new Float32Array(...) const buf = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buf); gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW); Rendering Pipeline 1. Vertex Shader 2. Rasterization 3. Fragment Shader 遅い 速い この回数は少なく! 出来るだけVRAMだ けで完結させる
  11. Rendering Pipeline VRAM / GPU (x0, y0, z0) (x1, y1,

    z1) (x2, y2, z2) ︙ Frame Buffer Vertex Shader Vertex Shader Vertex Shader v0 v1 v2 [uniform変数] モデルビュー行 列、etc Rasterize Fragment Shader Fragment Shader Fragment Shader pixel [uniform変数] 色、光源、etc color Main Memory Vertex Array
  12. 隠面消去 ウラに隠れている面は消去したい VRAM / GPU Frame Buffer Depth Buffer Color

    Buffer Depth Test Depth Test Depth Test Fragment Shader Fragment Shader Fragment Shader color depth color 上書き depth 上書き 既存の depth (手前のピクセルのみ) ※ Depth Test は shader を書く必要はなく、 OpenGL/WebGL が行ってくれる
  13. Rendering Context <!DOCTYPE html> <html lang="ja"> <body> <div> <canvas id="canvas1"></canvas>

    </div> <div> <canvas id="canvas2"></canvas> </div> </body> </html> // canvas1 の rendering context const gl1 = canvas1.getContext(“webgl” or “webgl2”); // canvas2 の rendering context const gl2 = canvas2.getContext(“webgl” or “webgl2”); VRAM gl1 gl2 FBO VBO Texture Shader FBO VBO Texture Shader
  14. 描画処理のコード(雰囲気) // Framebuffer の初期化。 // - color buffer を gl.clearColor(...)

    で設定された色で初期化する // - depth buffer を gl.clearDepth(...) で設定されたデプスで初期化する gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // シェーダープログラムを有効にする。シェーダープログラムは事前にコンパイルしておく必要がある。 const program: WebGLProgram = ...; gl.useProgram(program); // シェーダーに uniform 変数を渡す(メインメモリからVRAMへ転送)。 // uniform 変数とは、メインメモリからシェーダープログラムに渡される変数で、複数のシェーダーのスレッドで共有して使用される。 // 座標変換のための行列などが典型的。 const uniModelViewProjMatrix: WebGLUniformLocation = ...; const modelViewProjMatrix: Float32Array = ...; gl.uniformMatrix4fv(uniModelViewProjMatrix, false, modelViewProjMatrix); // VRAM頂点バッファを有効化し、シェーダープログラムの in 変数に割り当てる。 // 頂点データは事前にVRAMに転送してあるので、ここでは転送処理は走らない。 const vertexBuffer: WebGLBuffer = ...; const atrPosition: number = ...; gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); gl.enableVertexAttribArray(atrPosition); gl.vertexAttribPointer(atrPosition, 3, gl.FLOAT, false, 0, 0); // 描画処理の実行。Rendering Pipeline に従って処理が実行される。 gl.drawArrays(gl.TRIANGLES, 0, vertexCount);
  15. (脇道)bind って何なん? • bindXxx() ていう名前の関数が沢山ある ◦ bindBuffer() ◦ bindTexture() ◦

    etc • 単なる変数束縛と思えばだいたいOK const buf = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buf); gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW); // --- 疑似コード --- const buf = gl.createBuffer(); gl.ARRAY_BUFFER = buf; // 変数束縛! gl.ARRAY_BUFFER.setBufferData(data, gl.STATIC_DRAW);
  16. Shader Program の生成 // --- vertex shader の生成 --- const

    vertexShaderSourceString = ` #version 300 es in vec4 position; uniform modelViewProjMatrix; void main() { gl_Position = modelViewProjMatrix * position; }` ; const vertexShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vertexShader, vertexShaderSourceString); gl.compileShader(vertexShader); // --- fragment shader の生成 --- const fragmentShaderSourceString = `#version 300 es precision mediump float; out vec4 color; void main() { color = vec4(1, 0, 0, 1); }` ; const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fragmentShader, fragmentShaderSourceString); gl.compileShader(fragmentShader); // --- program の生成 --- const program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); Shader はソースコード文字列を埋め込んで、 実行時にコンパイルします。 GPUの命令セットなどがハードウェアごとに異 なるため、コンパイル済みのバイナリで配布 することは出来ません。
  17. Simple Shaders x y z x y z 1 1

    1 -1 -1 -1 Rasterization (automatic) #version 300 es in vec4 position; uniform modelViewProjMatrix; void main() { gl_Position = modelViewProjMatrix * position; } Framebuffer #version 300 es precision mediump float; out vec4 color; void main() { color = vec4(1, 0, 0, 1); } Vertex Shader Fragment Shader gl.DrawArrays(gl.TRIANGLES, …) 組み込み変数
  18. Phong Shading Fragment Shader // 拡散反射 float diffuse = max(dot(light,

    nrm), 0.0); // 鏡面反射 vec3 refDir = reflect(-light, nrm); float specular = pow(max(refDir.z, 0.0), shininess); // ピクセルの色を算出 color = vec4((diffuse + ambient) * col + vec3(specular), 1);
  19. 変な(自由な) Shading の例 無理やり作ってみた変な Shading の例。 Phong Shading で使う法線ベクトルを強引に 周期的に並ぶ球面のものに置換。

    float u = 2.0 * (fUV.x / SCALE - round(fUV.x / SCALE)); float v = 2.0 * (fUV.y / SCALE - round(fUV.y / SCALE)); float r2 = u * u + v * v; if (r2 < 1.0) { float n = sqrt(1.0 - r2); nrm = u * fVecU + v * fVecV + n * nrm; }
  20. Vertex Shader でアニメーション #version 300 es in vec4 position; in

    vec3 normal; out vec3 fPos; out vec3 fNrm; uniform mat4 modelViewMatrix; uniform mat4 projMatrix; uniform float seconds; // 経過時刻(秒) uniform float amplitude; // 振幅 const float PERIOD = 2.0; const float PI = 3.141592653589793; void main() { float delta = amplitude * sin(2.0 * PI * seconds / PERIOD); vec4 pos = modelViewMatrix * (position + delta * vec4(normal, 0) ); fPos = pos.xyz; fNrm = mat3(modelViewMatrix) * normal; gl_Position = projMatrix * pos; }
  21. Texture Mapping --- Fragment Shader --- #version 300 es precision

    mediump float; uniform sampler2D tex; in vec2 texCoord; out vec4 fragColor; void main(){ fragColor = texture(tex, texCoord); } VRAM --- JavaScript --- const texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); gl.texImage2D(gl.TEXTURE_2D, ..., image); 画像 テクスチャ 転送
  22. Bump Mapping 普通のテクスチャ画像 法線マップの画像 色 (R, G, B) ↓ 法線

    (X, Y, Z) ↓ Shading の 反射光計算に使う ※引用元 https://docs.unity3d.com/ja/2018.4/Manual/StandardShaderMaterialParameterNormalMap.html Bump Mapping なし Bump Mapping あり