ここではシェーダの基礎を学びます.
モデル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が用意するデフォルトの関数では実現不可能な表現を行うことができます.
シェーダを書き換えて,オリジナルな表現を行え.
ヒント:[File]→[Examples]の,[Topics]→[Shaders]にいろいろなサンプル有.