Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

本日のおしながき • 宿題解説 • ブラー処理 • シェーダの書き方

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

実装例 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); }

Slide 6

Slide 6 text

今日は… • 前回はテクスチャ座標を「ずらす」ということを学習しました。 • 前回はテクスチャ画像から1回だけデータを拾って出力しました。 (テクスチャを1回だけサンプリング) • では… 1回ではなく複数回サンプリングしたら, どのようなことができるようになるでしょうか?

Slide 7

Slide 7 text

ブラー処理

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

実装してみましょう • テクスチャを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); }

Slide 10

Slide 10 text

結果 明るすぎる!! 単純な加算ではだめなようです。 では… どうすればよいでしょうか?

Slide 11

Slide 11 text

平均をとる • 足し合わせた結果の平均をとってみましょう。 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); }

Slide 12

Slide 12 text

結果

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

放射ブラー • ループ文を使って放射ブラーを実装してみましょう。 ブラー中心 対象ピクセル 向きを作ってブラー 向きは矢印の先端の方の座標から矢印の尻尾の座標を引き算することで作れます。

Slide 15

Slide 15 text

実装コード 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

Slide 16

Slide 16 text

実装コード 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

Slide 17

Slide 17 text

実装コード 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

Slide 18

Slide 18 text

実装コード 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

Slide 19

Slide 19 text

実装コード 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

Slide 20

Slide 20 text

結果

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

いよいよ! • 次回からSubstance Painterでカスタムシェーダを動かします。 そのため,Substance Painter向けにシェーダの書き方を説明しておきます。 • ここでは,「雰囲気的に分かる」ことを優先とするため, 厳密な定義は取り扱いません。 そのため,全然定義違う可能性もあるので注意してください。 • 厳密な定義を知りたい方は,言語仕様書(※)を参照してください。 ※例えば OpenGL Shading Language Specification 4.6など https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.4.60.pdf

Slide 23

Slide 23 text

シェーダ言語について • シェーダを記述するためには, シェーダ言語を用いてプログラミングを行います。 • シェーダ言語は大きく分けて, HLSL (High Level Shader Language) GLSL (Open GL Shader Language) MSL (Metal Shader Language) の3種類が存在します。 • ShaderToyやSubstance Painterでは GLSL が採用されているので, ここではGLSLの文法について簡単に紹介します。

Slide 24

Slide 24 text

プログラミングを始める その前に! • 次回以降,シェーダプログラムを作っていきますが, プログラムを書く際は すべて半角! で,全角の使用は基本的に禁止です。 もちろん空白も全角は絶対にダメ! 全角や日本語文字が許されるのは「コメント」と呼ばれる プログラムとして処理されない部分のみです。 大事なことなのでもう一度言います。 すべて半角で書きましょう。

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

変数(2) • 変数の「名前」には使える文字と使えない文字が存在します。 • ルール • 先頭文字には a-z, A-z,アンダースコア(_) しか使えない。 • 2文字目以降は上記に加えて 数字 が使える。 OKな例: takesann_okanekudasai _ShigotoHerashite4 TEMPRA velocity1_0 hoge_____ _1_2_3_4_5_6

Slide 28

Slide 28 text

データ型(1) • データ型には以下のようなものがあります。 1要素 bool int uint float double N要素 bvec2 / bvec3 / bvec4 ivec2 / ivec3 / ivec4 uvec2 / uvec3 / uvec4 vec2 / vec3 / vec4 dvec2 / devc3 / dvec4

Slide 29

Slide 29 text

データ型(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

Slide 30

Slide 30 text

演算子(1) • 計算するためには,「演算子」と呼ばれる記号を使用します。 • 算術演算子 • + 足し算を行います。 • - 引き算を行います。 • * 掛け算を行います。 • / 割り算を行います。 5 + 2; 5 – 2; 5 * 2; 5 / 2;

Slide 31

Slide 31 text

演算子(2) • 代入演算子 • = 右側の値を左側に代入します。 • += 右側の値で加算してから左側に代入します。 • -= 右側の値で減算してから左側に代入します。 • *= 右側の値で乗算してから左側に代入します。 • /= 右側の値で除算してから左側に代入します。 • ++ 値を1増やします。 • -- 値を1減らします。 float x; x = 2; x += 2; x -= 2; x *= 2; x /= 2; x++; x--;

Slide 32

Slide 32 text

演算子(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);

Slide 33

Slide 33 text

演算子(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;

Slide 34

Slide 34 text

演算子(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;

Slide 35

Slide 35 text

条件分岐 • 条件に応じて処理を変えたいという場面に出くわすかもしれません。 このようなときには,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; }

Slide 36

Slide 36 text

繰り返し処理 • 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; }

Slide 37

Slide 37 text

配列 • いっぱいデータを持ちたい!…みたいなことがたま~にあります。 • 同じデータ型を複数持ちたい場合には配列を利用すると良いです。 データ型 変数名[配列数]; (例) vec2 texture_offset[4] = { vec2(-1, 0), vec2( 1, 0), vec2( 0, -1), vec2( 0, 1) };

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

Substance Painterのシェーダ

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

入力データについて • 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; };

Slide 46

Slide 46 text

組み込み関数 • 計算をする上での便利な機能が「組み込み関数」として提供されています。 https://qiita.com/edo_m18/items/71f6064f3355be7e4f45 • 詳細について知りたい方は, GLSLの仕様書(The OpenGL Shading Language)を参照してください。 https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.4.60.html

Slide 47

Slide 47 text

データの出力方法 • 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);

Slide 48

Slide 48 text

ピクセルカラーの出力 • ピクセルカラーを計算する最も基本的な式は次のようになります。 emissiveColor + albedo * diffuseShading + specularShading void diffuseShadingOutput(vec3); void specularShadingOutput(vec3); void albedoOutput(vec3); void emissiveColorOutput(vec3);

Slide 49

Slide 49 text

本日はここまで! 次回は… カスタムシェーダを作って,Substance Painter上で描画してみます。

Slide 50

Slide 50 text

興味が出てきた人は… (1) • シェーダについて興味が出てきた人は The Book of Shaders https://thebookofshaders.com/00/?lan=jp …などをみると良いかもしません。 • プログラマーであれば, LEARN OpenGL https://learnopengl.com/ …などがライティング勉強におすすめです。

Slide 51

Slide 51 text

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