ここではシェーダの基礎を学びます.
モデル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); }
これまで,3DCGを描くため光源の設定などを行いました.例えば,
directionalLight(255, 255, 255, -1, 1, 0);
のように記述しました.
では,実際に配置されたモデルと光源はどのように計算されて色が決定されるのでしょうか?
Processing2.0からはシェーダがその計算を行っています.
シェーダはプログラムから渡されたモデルの情報(頂点の位置,法線,色など),光源の情報(色,向きなど)などを受けとり,GPUに対してどのように計算し,画面の画素の色をどうするか指示するための,もう一つのプログラムです.
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]で追加してください.
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); }
[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);
モデルの色を赤と指定しているのに関わらず,モデルが青になりました.シェーダでモデルを描画する際は,画面の画素の色を必ず
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となります.以下の図で示すように,モデル頂点の法線と,光源の向きの反対ベクトルの内積の値をモデルの色に積算することで光の表現が可能となります.
例えば,内積の値を透明度にするよう設定してみます.
gl_FragColor = vec4(vertColor.xyz * intensity, intensity);
光が当たっている個所は不透明で,当たらない個所は透明となります.
このようにシェーダを書き換えることで,Processingが用意するデフォルトの関数では実現不可能な表現を行うことができます.
シェーダはProcessingとは異なる言語ですので,使える型も異なります.CGにおいてはベクトルおよび行列を使うことが多いので,それらの型は初めから用意されています.
正確にはProcessingでは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)となる
詳細を知りたい場合は以下を参照:
招待されているBoxアップロードフォルダ「第06回」に自身の学籍番号のフォルダ(アルファベット大文字,例:20FI999)を自身で作成し,その中に各課題,指定された名前で完成させたスケッチフォルダごとアップロードしてください.
提出締め切り:12月27日(金) 23:59
BOXアップロードフォルダの招待メールが来ていない,また,WebClassで本講義が見えない方は森谷まで連絡ください.
セピア調(古い写真のような色合い)で描画するシェーダを書け
提出スケッチフォルダ名:BASIC6_1
オリジナル(シェーダを用いていない):
課題の結果例:
以下のサイトより引用すると,
まず,オリジナルの画素の色をcとすると,それをグレースケールgに変換する.
g = 0.29 * c.r + 0.58 * c.g + 0.11 * c.b
カラーをグレースケールへ変換する式は様々な式が提案されており,上記の式はその内の一種である.
グレースケールの値gを基に,セピア調の色を生成する.この式には厳密な定義はない.
画素の色をpとすると,
p.r = 1.07 * g
p.g = 0.74 * g
p.b = 0.43 * g
とすることで,セピア調の色となる.
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);
物体が描画されない箇所はシェーダが適用されないので,background関数の色をセピア調の色に変更しておく.
結果例は,
background(240, 200, 145);
としている.
第4回のライティングでは面の法線(表面と裏面)を考慮する必要はなかった.しかし独自にシェーダを記述する場合,面は「頂点時計回りとなる面を表面とする」となっており,それに従った法線がシェーダに渡されてくるので注意する必要がある.
そのため,頂点が反時計回りの面は裏面となり,光を当てても真っ黒のままなので注意する.
.glslファイル文字コードはUTF-8(BOMなし)である必要がある.BOMありの場合,「ファイル先頭にillegal characterがある(英語)」旨のエラーメッセージが出て実行できない.
一部テキストディタでは「UTF-8」のみの表記がデフォルトで「BOMあり」となっている場合があるので注意する.
例えば,TeraPadにおける「UTF-8」はBOMあり,となっているので「UTF-8N」を選択する必要がある.
アニメ調(トゥーンシェーディング)で描画するシェーダを書け
提出スケッチフォルダ名:ADVANCE6_1
ヒント:アニメ調にするには
下図左のように,intensityをそのまま使用した場合,表面の明るさは連続的に変化する.そこで,下図右のように,intensityの値を階段状になるように加工すればよい.これには数式は必要なく,if文で可能である.