シェーダー

ここではシェーダの基礎を学びます.

準備

モデル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);
}
lightrot.png

シェーダーとは

これまで,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);
fragshader.png

モデルの色を赤と指定しているのに関わらず,モデルが青になりました.シェーダでモデルを描画する際は,画面の画素の色を必ず

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となります.以下の図で示すように,モデル頂点の法線と,光源の向きの反対ベクトルの内積の値をモデルの色に積算することで光の表現が可能となります.

lighting.png

例えば,内積の値を透明度にするよう設定してみます.

  gl_FragColor = vec4(vertColor.xyz * intensity, intensity);

光が当たっている個所は不透明で,当たらない個所は透明となります.

trans.png

このようにシェーダを書き換えることで,Processingが用意するデフォルトの関数では実現不可能な表現を行うことができます.

シェーダ内で使える型

シェーダはProcessingとは異なる言語ですので,使える型も異なります.CGにおいてはベクトルおよび行列を使うことが多いので,それらの型は初めから用意されています.

  • float:Processing同様,少数を含む値一つを扱う型
  • vec2:2次元ベクトル
  • vec3:3次元ベクトル
  • vec4:4次元ベクトル
  • mat2:2x2行列
  • mat3:3x3行列
  • mat4:4x4行列

正確には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)となる

詳細を知りたい場合は以下を参照:

課題の提出先・提出方法

講義中に完成しチェックを受けた課題については提出不要です.

\\nas002.term.usmc.dendai.ac.jp\授業用ワークスペース\千住キャンパス\後期\未来科学部\⊂霾鵐瓮妊ア学科\FI1メディア演習C(CG)(森谷友昭)月34\第06回

指定されたスケッチ名で,スケッチフォルダごと提出してください.

提出締め切り:11月15日(金) 18:00

課題

セピア調(古い写真のような色合い)で描画するシェーダを書け

提出スケッチ名:BASIC6_1 (フォルダごと提出)

オリジナル(シェーダを用いていない):

orig.png

課題の結果例:

sepia.png

ヒント:セピア調への変換式

以下のサイトより引用すると,

まず,オリジナルの画素の色を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回のライティングでは面の法線(表面と裏面)を考慮する必要はなかった.しかし独自にシェーダを記述する場合,面は「頂点時計回りとなる面を表面とする」となっており,それに従った法線がシェーダに渡されてくるので注意する必要がある.

そのため,頂点が反時計回りの面は裏面となり,光を当てても真っ黒のままなので注意する.

任意課題

アニメ調(トゥーンシェーディング)で描画するシェーダを書け

提出スケッチ名:ADVANCE6_1 (フォルダごと提出)

toon.png

ヒント:アニメ調にするには

下図左のように,intensityをそのまま使用した場合,表面の明るさは連続的に変化する.そこで,下図右のように,intensityの値を階段状になるように加工すればよい.これには数式は必要なく,if文で可能である.

int.png

添付ファイル: fileint.png 579件 [詳細] filetoon.png 397件 [詳細] filesepia.png 651件 [詳細] fileorig.png 619件 [詳細] filetrans.png 413件 [詳細] filelightrot.png 356件 [詳細] filelighting.png 537件 [詳細] filefragshader.png 398件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2019-11-05 (火) 12:21:18