- 追加された行はこの色です。
- 削除された行はこの色です。
#author("2023-05-30T16:11:35+09:00","default:kyo-in","kyo-in")
#author("2023-06-01T15:21:25+09:00","default:kyo-in","kyo-in")
**シェーダー [#a723d9d9]
ここではシェーダの基礎を学びます.
** 準備 [#g44eca36]
モデル1個とライトが1つある基本的なシーンを用意します.
void setup()
{
size(640, 480, P3D);
}
void draw()
{
background(127, 127, 127);
camera(100, -100, 100, 0, 0, 0, 0, 1, 0);
axis(100);
fill(255, 0, 0);
noStroke();
directionalLight(255, 255, 255, -1, 1, 0);
sphere(30);
}
void axis(float l)
{
stroke(255, 0, 0);
line(0, 0, 0, l, 0, 0);
stroke(0, 255, 0);
line(0, 0, 0, 0, l, 0);
stroke(0, 0, 255);
line(0, 0, 0, 0, 0, l);
}
#ref(./lightrot.png,50%);
**シェーダーとは [#sd18281c]
これまで,3DCGを描くため光源の設定などを行いました.例えば,
directionalLight(255, 255, 255, -1, 1, 0);
のように記述しました.
では,実際に配置されたモデルと光源はどのように計算されて色が決定されるのでしょうか?~
Processing2.0からはシェーダがその計算を行っています.
シェーダはプログラムから渡されたモデルの情報(頂点の位置,法線,色など),光源の情報(色,向きなど)などを受けとり,GPUに対してどのように計算し,画面の画素の色をどうするか指示するための,もう一つのプログラムです.
**シェーダを使ってみる [#z02f494d]
Processingがデフォルトで使用しているシェーダをやめて,自身でシェーダを用意して使ってみます.まずは同じ結果を出すようにしてみましょう.
PShader shader;
void setup()
{
size(640, 480, P3D);
shader = loadShader("FragmentShader.glsl", "VertexShader.glsl");
}
void draw()
{
background(127, 127, 127);
camera(100, -100, 100, 0, 0, 0, 0, 1, 0);
axis(100);
fill(255, 0, 0);
noStroke();
directionalLight(255, 255, 255, -1, 1, 0);
shader(shader);
sphere(30);
}
void axis(float l)
{
stroke(255, 0, 0);
line(0, 0, 0, l, 0, 0);
stroke(0, 255, 0);
line(0, 0, 0, 0, l, 0);
stroke(0, 0, 255);
line(0, 0, 0, 0, 0, l);
}
シェーダである以下2つのファイル,VertexShader.glsl,FragmentShader.glslを右クリックで「対象をファイルに保存」(ブラウザーによって異なる)でダウンロード,Processingの[Sketch]→[Add File]で追加してください.
#ref(./VertexShader.glsl);
#ref(./FragmentShader.glsl);
VertexShader.glsl:
uniform mat4 transform;
uniform mat3 normalMatrix;
uniform vec3 lightNormal;
attribute vec4 vertex;
attribute vec4 color;
attribute vec3 normal;
varying vec4 vertColor;
varying vec3 vertNormal;
varying vec3 vertLightDir;
void main() {
gl_Position = transform * vertex;
vertColor = color;
vertNormal = normalize(normalMatrix * normal);
vertLightDir = -lightNormal;
}
FragmentShader.glsl:
#ifdef GL_ES
precision mediump float;
precision mediump int;
#endif
#define PROCESSING_LIGHT_SHADER
uniform float fraction;
varying vec4 vertColor;
varying vec3 vertNormal;
varying vec3 vertLightDir;
void main() {
float intensity;
intensity = max(0.0, dot(vertLightDir, vertNormal));
gl_FragColor = vec4(vertColor.xyz * intensity, 1.0);
}
**シェーダの書き換え [#v9abd0ce]
[Sketch]→[Show Sketch Folder]で,現在のSketchのフォルダを開き,フォルダ「Data」を開くと,先ほど追加した二つのシェーダファイルがあるので,これを書き換えていきます.
例えば,モデルを描画する際の,各画素の色はFragmentShader.glsl内で計算されます.具体的には以下のコードです.
gl_FragColor = vec4(vertColor.xyz * intensity, 1.0);
試しに,以下のように変更してみましょう.
gl_FragColor = vec4(0.5, 0.5, 1.0, 1.0);
#ref(./fragshader.png,50%);
モデルの色を赤と指定しているのに関わらず,モデルが青になりました.シェーダでモデルを描画する際は,画面の画素の色を必ず
RGBA=(0.5, 0.5, 1.0, 1.0);
とするようにシェーダを書き換えたためです.
元々のプログラムがどういう意味か解説していきます.
gl_FragColor = vec4(vertColor.xyz * intensity, 1.0);
vertColorにはモデルの色が入っていますRGBA=(1, 0, 0, 1).シェーダ内では色もベクトルとして扱うためXYZWがそれぞれRGBAに対応します.intensityは以下のように計算されています.
intensity = max(0.0, dot(vertLightDir, vertNormal));
vertLightDirは光源の向きの反対ベクトル,vertNormalはモデル頂点の法線です.dotは内積を行う関数,maxは2つの値のうち,大きい値を返す関数です.内積は,2つのベクトルが同じ向きを向いていれば1,直角であれば0,反対を向いていれば-1となります.以下の図で示すように,モデル頂点の法線と,光源の向きの反対ベクトルの内積の値をモデルの色に積算することで光の表現が可能となります.
#ref(./lighting.png,50%);
例えば,内積の値を透明度にするよう設定してみます.
gl_FragColor = vec4(vertColor.xyz * intensity, intensity);
光が当たっている個所は不透明で,当たらない個所は透明となります.
#ref(./trans.png,50%);
このようにシェーダを書き換えることで,Processingが用意するデフォルトの関数では実現不可能な表現を行うことができます.
**シェーダ内で使える型 [#c9ad6958]
シェーダはProcessingとは異なる言語ですので,使える型も異なります.CGにおいてはベクトルおよび行列を使うことが多いので,それらの型は初めから用意されています.
-float:Processing同様,少数を含む値一つを扱う型
-vec2:2次元ベクトル
-vec3:3次元ベクトル
-vec4:4次元ベクトル
-mat2:2x2行列
-mat3:3x3行列
-mat4:4x4行列
正確にはProcessingではGLSLというシェーダ言語なので,使用できる型の詳細は以下を参照.
-https://www.khronos.org/opengl/wiki/Data_Type_(GLSL)
ここではベクトル型の使用例を示します.
vec4 v = vec4(1.0, 0.0, 0.0, 1.0); //4次元ベクトル変数を値初期化して作成
v.x = 2.0; //x成分を変更
v.y = v.z; //y成分をz成分に変更
vec3 a = vec3(3.0, 2.0, 1.0); //3次元ベクトル変数を値初期化して作成
v = vec4(a.xyz, 1.0); //vの値を変更,a.xyzのように複数成分をまとめた書き方も可能
//vは(3.0, 2.0, 1.0, 1.0)となる
vec3 b = vec3(0.3, 0.2, 0.1);
vec3 c = a + b; //ベクトルの足し算となりcは(3.3, 2.2, 1.1)となる
vec3 d = a * b; //成分ごとの掛け算となりdは(0.9, 0.4, 0.1)となる
詳細を知りたい場合は以下を参照:
-https://en.wikibooks.org/wiki/GLSL_Programming/Vector_and_Matrix_Operations
**課題の提出先・提出方法 [#dc136f9e]
招待されているBoxアップロードフォルダ「第06回」に自身の学籍番号のフォルダ(アルファベット大文字,例:20FI999)を''自身で作成し'',その中に各課題,指定された名前で完成させたスケッチフォルダごとアップロードしてください.
''提出締め切り:6月7日(水) 23:59''
BOXアップロードフォルダの招待メールが来ていない,また,WebClassで本講義が見えない方は森谷まで連絡ください.
**課題 [#vdde6c4b]
セピア調(古い写真のような色合い)で描画するシェーダを書け
提出スケッチフォルダ名:BASIC6_1
オリジナル(シェーダを用いていない):
#ref(orig.png);
課題の結果例:
#ref(sepia.png);
***ヒント:セピア調への変換式 [#fe51763e]
以下のサイトより引用すると,
-https://wgld.org/d/webgl/w054.html
まず,オリジナルの画素の色をcとすると,それをグレースケールgに変換する.
>g = 0.29 * c.r + 0.58 * c.g + 0.11 * c.b
カラーをグレースケールへ変換する式は様々な式が提案されており,上記の式はその内の一種である.
-[[https://ja.wikipedia.org/wiki/グレースケール:https://ja.wikipedia.org/wiki/%E3%82%B0%E3%83%AC%E3%83%BC%E3%82%B9%E3%82%B1%E3%83%BC%E3%83%AB]]
グレースケールの値gを基に,セピア調の色を生成する.この式には厳密な定義はない.
画素の色をpとすると,
>p.r = 1.07 * g
>p.g = 0.74 * g
>p.b = 0.43 * g
とすることで,セピア調の色となる.
***ヒント:グレースケールまでの実装例 [#p62479db]
vec3 c = vertColor.xyz * intensity;
float g = 0.29 * c.x + 0.58 * c.y + 0.11 * c.z;
gl_FragColor = vec4(g, g, g, 1.0);
***ヒント:背景の色 [#g67c4f74]
物体が描画されない箇所はシェーダが適用されないので,background関数の色をセピア調の色に変更しておく.
結果例は,
background(240, 200, 145);
としている.
***ヒント:床が真っ黒になる場合 [#h1e53c80]
第4回のライティングでは面の法線(表面と裏面)を考慮する必要はなかった.しかし独自にシェーダを記述する場合,面は「頂点時計回りとなる面を表面とする」となっており,それに従った法線がシェーダに渡されてくるので注意する必要がある.
そのため,頂点が反時計回りの面は裏面となり,光を当てても真っ黒のままなので注意する.
***ヒント:文字コードに注意 [#k6d699d0]
.glslファイル文字コードがはUTF-8(BOMなし)である必要がある.BOMありの場合,「ファイル先頭にillegal characterがある(英語)」旨のエラーメッセージが出て実行できない.
一部テキストディタでは「UTF-8」のみの表記がデフォルトで「BOMあり」となっている場合があるので注意する.
例えば,TeraPadにおける「UTF-8」はBOMあり,となっているので「UTF-8N」を選択する必要がある.
-TeraPadで文字コードにUTF-8(BOMなし)を指定して文字化けしないように保存する完璧な設定方法~
https://creating-homepage.com/archives/97
**任意課題 [#q10e4c95]
アニメ調(トゥーンシェーディング)で描画するシェーダを書け
提出スケッチフォルダ名:ADVANCE6_1
#ref(toon.png);
ヒント:アニメ調にするには
下図左のように,intensityをそのまま使用した場合,表面の明るさは連続的に変化する.そこで,下図右のように,intensityの値を階段状になるように加工すればよい.これには数式は必要なく,if文で可能である.
#ref(./int.png,50%);