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

初心者向けシェーダ講習会 第2回

3e9c91b0864d1067e0147c68d71cd44a?s=47 Pocol
April 01, 2020

初心者向けシェーダ講習会 第2回

社内向けにやる予定だったシェーダ講習会の資料です。
ガチ勢お断り。

3e9c91b0864d1067e0147c68d71cd44a?s=128

Pocol

April 01, 2020
Tweet

Transcript

  1. 初心者向け シェーダ講習会 第2回目 Presented By Pocol

  2. 目的 • アーティスト・プログラマー向けにシェーダの基礎を解説します。 • シェーダを触れるテクニカルアーティストや シェーダを触れるプログラマーの増員・増強が目的です。 • 最終目標として, Substance Painterでカスタムシェーダを作れることを目指します。

    • 今回のシェーダ講習では GLSL を使用します。
  3. 本日のおしながき • 宿題解説 • ブラー処理 • シェーダの書き方

  4. 前回の宿題 ・次の動画のように画面端が波のようにうねるUVアニメーションを作ってください。

  5. 実装例 void mainImage( out vec4 fragColor, in vec2 fragCoord )

    { // Normalized pixel coordinates (from 0 to 1) vec2 uv = fragCoord/iResolution.xy; float scaleX = 0.05f; // 揺らし幅. float scaleY = 30.0f; // 縦方向の縮み. float speed = 10.0f; // 揺らしスピード. uv.x += sin(uv.y * scaleY + iTime * speed) * scaleX; vec3 col = texture(iChannel0, uv).rgb; // Output to screen fragColor = vec4(col,1.0); }
  6. 今日は… • 前回はテクスチャ座標を「ずらす」ということを学習しました。 • 前回はテクスチャ画像から1回だけデータを拾って出力しました。 (テクスチャを1回だけサンプリング) • では… 1回ではなく複数回サンプリングしたら, どのようなことができるようになるでしょうか?

  7. ブラー処理

  8. ブラー処理 • テクスチャを複数回サンプリングすることによってブラーが実装できます。

  9. 実装してみましょう • テクスチャを2回サンプリングします。 • 片方は +X方向にちょっとだけずらす。 • もう一方は –X方向にちょっとだけずらす。 •

    2つの結果を足して合成する。 void mainImage( out vec4 fragColor, in vec2 fragCoord ) { // Normalized pixel coordinates (from 0 to 1) vec2 uv = fragCoord/iResolution.xy; vec3 col0 = texture(iChannel0, uv + 0.1).rgb; vec3 col1 = texture(iChannel0, uv - 0.1).rgb; vec3 col = col0 + col1; // Output to screen fragColor = vec4(col,1.0); }
  10. 結果 明るすぎる!! 単純な加算ではだめなようです。 では… どうすればよいでしょうか?

  11. 平均をとる • 足し合わせた結果の平均をとってみましょう。 void mainImage( out vec4 fragColor, in vec2

    fragCoord ) { // Normalized pixel coordinates (from 0 to 1) vec2 uv = fragCoord/iResolution.xy; vec3 col0 = texture(iChannel0, uv + 0.1).rgb; vec3 col1 = texture(iChannel0, uv - 0.1).rgb; vec3 col = (col0 + col1) / 2.0; // Output to screen fragColor = vec4(col, 1.0); }
  12. 結果

  13. ループ文を使えるようになろう! • テクスチャをいっぱいサンプリングしたいときに, さすがに1行ずつ書くのは疲れます。 • 同じような処理を複数回実行したい場合には ループ文を使って繰り返し処理を実行することができます。 for(int i =

    開始番号; i < 終了番号; i += 増やす数) { 繰り返し処理; }
  14. 放射ブラー • ループ文を使って放射ブラーを実装してみましょう。 ブラー中心 対象ピクセル 向きを作ってブラー 向きは矢印の先端の方の座標から矢印の尻尾の座標を引き算することで作れます。

  15. 実装コード const int STEP = 32; void mainImage( out vec4

    fragColor, in vec2 fragCoord ) { // Normalized pixel coordinates (from 0 to 1) vec2 uv = fragCoord/iResolution.xy; vec2 center = vec2(0.5, 0.5); vec2 offset = normalize(uv - center) * (1.0 / iResolution.y); vec3 col = vec3(0); float div = 1.0 / float(STEP); for(int i=0; i<STEP; ++i) { col += texture(iChannel0, uv).rgb * div; uv += offset; } // Output to screen fragColor = vec4(col,1.0); }
  16. 実装コード const int STEP = 32; void mainImage( out vec4

    fragColor, in vec2 fragCoord ) { // Normalized pixel coordinates (from 0 to 1) vec2 uv = fragCoord/iResolution.xy; vec2 center = vec2(0.5, 0.5); vec2 offset = normalize(uv - center) * (1.0 / iResolution.y); vec3 col = vec3(0); float div = 1.0 / float(STEP); for(int i=0; i<STEP; ++i) { col += texture(iChannel0, uv).rgb * div; uv += offset; } // Output to screen fragColor = vec4(col,1.0); } ブラー中心
  17. 実装コード const int STEP = 32; void mainImage( out vec4

    fragColor, in vec2 fragCoord ) { // Normalized pixel coordinates (from 0 to 1) vec2 uv = fragCoord/iResolution.xy; vec2 center = vec2(0.5, 0.5); vec2 offset = normalize(uv - center) * (1.0 / iResolution.y); vec3 col = vec3(0); float div = 1.0 / float(STEP); for(int i=0; i<STEP; ++i) { col += texture(iChannel0, uv).rgb * div; uv += offset; } // Output to screen fragColor = vec4(col,1.0); } ブラー中心 1ピクセルのブラー移動量
  18. 実装コード const int STEP = 32; void mainImage( out vec4

    fragColor, in vec2 fragCoord ) { // Normalized pixel coordinates (from 0 to 1) vec2 uv = fragCoord/iResolution.xy; vec2 center = vec2(0.5, 0.5); vec2 offset = normalize(uv - center) * (1.0 / iResolution.y); vec3 col = vec3(0); float div = 1.0 / float(STEP); for(int i=0; i<STEP; ++i) { col += texture(iChannel0, uv).rgb * div; uv += offset; } // Output to screen fragColor = vec4(col,1.0); } ブラー中心 1ピクセルのブラー移動量 合計が1になるような1回あたりの重み
  19. 実装コード const int STEP = 32; void mainImage( out vec4

    fragColor, in vec2 fragCoord ) { // Normalized pixel coordinates (from 0 to 1) vec2 uv = fragCoord/iResolution.xy; vec2 center = vec2(0.5, 0.5); vec2 offset = normalize(uv - center) * (1.0 / iResolution.y); vec3 col = vec3(0); float div = 1.0 / float(STEP); for(int i=0; i<STEP; ++i) { col += texture(iChannel0, uv).rgb * div; uv += offset; } // Output to screen fragColor = vec4(col,1.0); } ブラー中心 1ピクセルのブラー移動量 合計が1になるような1回あたりの重み 決められた回数だけ繰り返す
  20. 結果

  21. シェーダの書き方 ※ここから先は,文法説明になります。 プログラマーの方は36枚目の「Substance Painterのシェーダ」まで読み飛ばしてもらって構いません。

  22. いよいよ! • 次回からSubstance Painterでカスタムシェーダを動かします。 そのため,Substance Painter向けにシェーダの書き方を説明しておきます。 • ここでは,「雰囲気的に分かる」ことを優先とするため, 厳密な定義は取り扱いません。 そのため,全然定義違う可能性もあるので注意してください。

    • 厳密な定義を知りたい方は,言語仕様書(※)を参照してください。 ※例えば OpenGL Shading Language Specification 4.6など https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.4.60.pdf
  23. シェーダ言語について • シェーダを記述するためには, シェーダ言語を用いてプログラミングを行います。 • シェーダ言語は大きく分けて, HLSL (High Level Shader

    Language) GLSL (Open GL Shader Language) MSL (Metal Shader Language) の3種類が存在します。 • ShaderToyやSubstance Painterでは GLSL が採用されているので, ここではGLSLの文法について簡単に紹介します。
  24. プログラミングを始める その前に! • 次回以降,シェーダプログラムを作っていきますが, プログラムを書く際は すべて半角! で,全角の使用は基本的に禁止です。 もちろん空白も全角は絶対にダメ! 全角や日本語文字が許されるのは「コメント」と呼ばれる プログラムとして処理されない部分のみです。

    大事なことなのでもう一度言います。 すべて半角で書きましょう。
  25. 計算結果をとっておきたい。 • 計算した数値などを保存したり,取り出したりする箱のような役割を持 つものが必要となります。 • 何が入っているかなどを示すためにラベルや種類も必要そうです。 いるもの いらないもの

  26. 変数(1) • 「変数」は計算結果を格納する箱のような役割を持ちます。 • 変数には「名前」とデータの種類を判別するための「データ型」が必要です。 • GLSL内で変数を使用するためには,「データ型」と「名前」を予め決めておく 必要があり,これを「変数の宣言」と呼びます。 • 具体的には次のような書式になります。

    データ型 変数名; データ型 変数名 = 値; (例) float scale; float speed = 1.0;
  27. 変数(2) • 変数の「名前」には使える文字と使えない文字が存在します。 • ルール • 先頭文字には a-z, A-z,アンダースコア(_) しか使えない。

    • 2文字目以降は上記に加えて 数字 が使える。 OKな例: takesann_okanekudasai _ShigotoHerashite4 TEMPRA velocity1_0 hoge_____ _1_2_3_4_5_6
  28. データ型(1) • データ型には以下のようなものがあります。 1要素 bool int uint float double N要素

    bvec2 / bvec3 / bvec4 ivec2 / ivec3 / ivec4 uvec2 / uvec3 / uvec4 vec2 / vec3 / vec4 dvec2 / devc3 / dvec4
  29. データ型(2) N要素 dmat2 / dmat3 / dmat4 dmat2x2 / dmat2x3

    / dmat2x4 dmat3x2 / dmat3x3 / dmat3x4 dmat4x2 / dmat4x3 / dmat4x4 M要素 mat2 / mat3 / mat4 mat2x2 / mat2x3 / mat2x4 mat3x2 / mat3x3 / mat3x4 mat4x2 / mat4x3 / mat4x4
  30. 演算子(1) • 計算するためには,「演算子」と呼ばれる記号を使用します。 • 算術演算子 • + 足し算を行います。 • -

    引き算を行います。 • * 掛け算を行います。 • / 割り算を行います。 5 + 2; 5 – 2; 5 * 2; 5 / 2;
  31. 演算子(2) • 代入演算子 • = 右側の値を左側に代入します。 • += 右側の値で加算してから左側に代入します。 •

    -= 右側の値で減算してから左側に代入します。 • *= 右側の値で乗算してから左側に代入します。 • /= 右側の値で除算してから左側に代入します。 • ++ 値を1増やします。 • -- 値を1減らします。 float x; x = 2; x += 2; x -= 2; x *= 2; x /= 2; x++; x--;
  32. 演算子(3) • 比較演算子 • == 左側と右側の値が同じであれば true, そうでなければ false .

    • != 左側と右側の値が異なれば true, そうでなければ false . • > 左側の値が大きければ true, そうでなければ false . • < 左側の値が小さければ true, そうでなければ false . • >= 左側の値が右側以上であれば true, そうでなければ false . • <= 左側の値が右側以下であれば true, そうでなければ false . int x = 2; bool y; y = (x == 2); y = (x != 2); y = (x > 3); y = (x < 3); y = (x >= 2); y = (x <= 3);
  33. 演算子(4) • 論理演算子 • && 左側と右側が両方とも true なら true, そうでなければ

    false. (AND演算) • || 左側あるいは右側が true なら true, そうでなければ false. (OR演算) • ! 逆の bool 値を返します. (NOT演算). bool x = true; bool y = false; bool z; z = (x && y); z = (x || y); z = !x; z = !y;
  34. 演算子(4) • 条件演算子 • w = (x) ? y :

    z; • 条件 x が true の場合に y を代入。 false の場合は z を代入します。 (例) int x = 10 / 3; int y = (x > 3) ? 5 : 6; float x = 10.0 / 3.0; float y = (x > 3.0) ? 5.0 : 6.0;
  35. 条件分岐 • 条件に応じて処理を変えたいという場面に出くわすかもしれません。 このようなときには,if 文 や else 文を使用します。 if (

    条件 ) { やりたい処理; } (例) int x = 1 + 3; bool y = false; if ( x >= 2 ) { y = true; } if ( 条件 ) { やりたい処理A; } else { やりたい処理B; } if ( 条件1 ) { やりたい処理A; } else if ( 条件2 ) { やりたい処理B; } else { やりたい処理C; }
  36. 繰り返し処理 • 1から10000までの値を足した結果を求めたいような場合が出てきたらどうすればよい でしょうか? • 地道に1行ずつ書いていくのは心が折れそうです。 こうした場合にはループ文を使うと良いです。 while ( 条件

    ) { 繰り返しやりたい処理; } for ( カウンター; 条件; カウンターの増減 ) { 繰り返しやりたい処理; } (例) int counter = 0; int sum = 0; while (counter < 100) { sum += 2; counter++; } (例) int sum = 0; for( int i = 0; i < 100; i += 2) { sum += 2; }
  37. 配列 • いっぱいデータを持ちたい!…みたいなことがたま~にあります。 • 同じデータ型を複数持ちたい場合には配列を利用すると良いです。 データ型 変数名[配列数]; (例) vec2 texture_offset[4]

    = { vec2(-1, 0), vec2( 1, 0), vec2( 0, -1), vec2( 0, 1) };
  38. 関数 • ほぼ同じ処理で,入力データのみを差し替えれば,まとめられるのに… …といった場面に出くわすかもしれません。 • 計算プロセスは同一で,入力データや出力データが異なる場合は 計算プロセスを「関数」として纏めておくことが可能です。 (例) average関数としてまとめる。 float

    avarage(float inputs, int count) { float sum = 0.0f; for(int i=0; i<count; ++i) { sum += inputs[i]; } sum /= (float)count; return sum; }
  39. Substance Painterのシェーダ

  40. プログラムの大枠 すごい処理 入力データ 出力データ 大抵は,入力と出力があります。

  41. 描画フロー 頂点シェーダ ピクセルシェーダ 頂点バッファ 加工済み頂点 ピクセルカラー Substance Painterでは ユーザーカスタマイズできない ユーザーカスタマイズ可能

  42. シェーダの処理フロー はじまり すごい計算 結果を出力 おしまい ここを自由に書く 書式が決まってる

  43. シェーダ処理の はじまり と おわり • Substance Painterの場合は shade() という関数内をユーザーが自由に カスタマイズできる仕様になっています。

    void shade(V2F inputs) { } はじまり おしまい すごい計算
  44. シェーダ処理の はじまり と おわり • Substance Painterの場合は shade() という関数内をユーザーが自由に カスタマイズできる仕様になっています。

    void shade(V2F inputs) { } はじまり おしまい すごい計算 入力データ 加工済み頂点データ
  45. 入力データについて • Substance Painterのピクセルシェーダ(フラグメントシェーダ)の 入力データは次のように定められています。 struct V2F { vec3 normal;

    // 補間済み法線ベクトル vec3 tangent; // 補間済み接線ベクトル vec3 bitangent; // 補間済み従接線ベクトル vec3 position; // 補間済み位置座標 vec4 color[1]; // 補間済み頂点カラー (color0) vec2 tex_coord; // 補間済みテクスチャ座標 (uv0) SparseCoord sparse_coord; // textureSparse() サンプリング関数によって使用される補間済み離散テクスチャ座標 vec2 multi_tex_coord[8]; // 補間済みテクスチャ座標 (uv0-uv7) }; struct SparseCoord { vec2 tex_coord; vec2 dfdx; vec2 dfdy; float lod; uint material_lod_mask; };
  46. 組み込み関数 • 計算をする上での便利な機能が「組み込み関数」として提供されています。 https://qiita.com/edo_m18/items/71f6064f3355be7e4f45 • 詳細について知りたい方は, GLSLの仕様書(The OpenGL Shading Language)を参照してください。

    https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.4.60.html
  47. データの出力方法 • Substance Painterではデータの出力方法が決められています。 具体的には以下の関数を用いて出力を行います。 // fragment opacity. default value:

    1.0 void alphaOutput(float); // diffuse lighting contribution. default value: vec3(0.0) void diffuseShadingOutput(vec3); // specular lighting contribution. default value: vec3(0.0) void specularShadingOutput(vec3); // color emitted by the fragment. default value: vec3(0.0) void emissiveColorOutput(vec3); // fragment color. default value: vec3(1.0) void albedoOutput(vec3); // subsurface scattering properties, see lib-sss.glsl for details. default value: vec4(0.0) void sssCoefficientsOutput(vec4);
  48. ピクセルカラーの出力 • ピクセルカラーを計算する最も基本的な式は次のようになります。 emissiveColor + albedo * diffuseShading + specularShading

    void diffuseShadingOutput(vec3); void specularShadingOutput(vec3); void albedoOutput(vec3); void emissiveColorOutput(vec3);
  49. 本日はここまで! 次回は… カスタムシェーダを作って,Substance Painter上で描画してみます。

  50. 興味が出てきた人は… (1) • シェーダについて興味が出てきた人は The Book of Shaders https://thebookofshaders.com/00/?lan=jp …などをみると良いかもしません。

    • プログラマーであれば, LEARN OpenGL https://learnopengl.com/ …などがライティング勉強におすすめです。
  51. 興味が出てきた人は… (2) ・アーティスト向けであれば, 「モンスターハンター:ワールド」アーティストによるシェーダ作成のノウハウ https://cedil.cesa.or.jp/cedil_sessions/view/1892 の講演資料 CEDiL_1892_2.zip などを参照するのがお勧めです。