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

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

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

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

4c41dd5087889e60e220abd377b52c4a?s=128

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. Goal • 三角形が描画される原理を理解する • シェーダーとは何かを理解する • Rendering Pipeline を理解する •

    CPU と GPU の違いを理解する
  3. 3つの壁 数学の壁 • 4x4の行列、アフィン変換、射影変換、etc アーキテクチャの壁 • CPUとGPUの違い • VRAM上のオブジェクトの操作 APIの壁

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

    ◦ 原理が分かるとAPIの意味・意図が分かるようになる ◦ 原理を理解せずにAPIだけ見てもダメ ◦ 急がば廻れ
  5. Framebuffer VRAM Framebuffer 要は、Framebuffer に三角形を描画できれば OK

  6. Default Projection x y z 1 1 1 -1 -1

    -1 • [-1, 1] x [-1, 1] x [-1, 1] の空間 • 要は、この空間に収まるように三角形 の頂点座標を送れば、画面に描画され る • 行列の演算は何のため? • → 座標変換してこのデフォルトの空間 に頂点座標を収めるため!
  7. 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)
  8. Shader is NOT Only for Shading • Shading … 立体的に見えるように陰影を付けること

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

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

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

    Pipeline は(ほぼ) GPU + VRAM だけで完結させる GPUの並列性を活かす • パイプライン処理 • Vertex Shader • Fragment Shader
  12. 頂点配列を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だ けで完結させる
  13. 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
  14. 隠面消去 ウラに隠れている面は消去したい 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 が行ってくれる
  15. 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
  16. 描画処理のコード(雰囲気) // 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);
  17. (脇道)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);
  18. 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の命令セットなどがハードウェアごとに異 なるため、コンパイル済みのバイナリで配布 することは出来ません。
  19. 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, …) 組み込み変数
  20. 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);
  21. なんちゃって Toon Shading 拙作 Wikipedia より ピクセルの明るさを閾値によって段階的に制御

  22. 変な(自由な) 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; }
  23. 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; }
  24. 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); 画像 テクスチャ 転送
  25. Bump Mapping 普通のテクスチャ画像 法線マップの画像 色 (R, G, B) ↓ 法線

    (X, Y, Z) ↓ Shading の 反射光計算に使う ※引用元 https://docs.unity3d.com/ja/2018.4/Manual/StandardShaderMaterialParameterNormalMap.html Bump Mapping なし Bump Mapping あり
  26. CPU GPU Rendering Pipeline おしまい ※ この勉強会は7月7日に開催されました