こんにちは、B4魚田です。風鈴の音が心地よくなる季節になりました。
今回は、私の制作に関連する、ShaderとThree.jsでのグラフィックの制作の過程の一部をまとめてみます。(前回の予告ではP5.jsを使う予定でしたが、変更しました。申し訳ございません。)
Shaderと Three.jsとは?
Shader
Shaderとは3DCGの描画処理プログラムのことです。オブジェクトに陰影をつけたり、表面の質感や凹凸の設定、各画素の色を決定してくれます。それぞれの機能を、オブジェクトの頂点の位置を決定するVertex Shaderと面を構成する各画素の色を決定するFragment Shaderの2つに分けて記述します。
Three.js
Three.jsとは、3DCGを作成するためのJavaScriptライブラリです。Web上でコンピュータグラフィックを描画する低レベルAPIであるWebGLを、JavaScriptで記述できるように開発されています。シーン、カメラ、オブジェクト、ライトの要素から3DCGを描画し、アニメーションやインタラクティブ機能を追加することも可能です。
グラフィックの生成
- 単色の球
はじめにシンプルな球を描画してみます。
まず、球の位置を決めます。Vertex Shaderは3D オブジェクトの頂点の位置を計算するためのプログラムです。
vertexShader: `
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
gl_Position = ...
の部分が頂点の最終的な位置を決定します。それぞれの値はvec4(X座標、Y座標、Z座標、同次座標)となっています
次に、球の色を決めます。
このFragment Shaderは、3Dオブジェクトの表面の色を決定する役割を果たします。
fragmentShader: `
precision mediump float;
void main() {
// オレンジ色のRGB値を定義 (1.0, 0.5, 0.0)
vec3 orangeColor = vec3(1.0, 0.5, 0.0);
// 最終的に出力される色に代入
gl_FragColor = vec4(orangeColor, 1.0);
}
`,
gl_FragColor = ...
の部分が最終的な色を決定します。それぞれの値はvec4(赤、緑、青、透明度)となっています。
このシェーダーは3Dオブジェクトの表面全体を均一なオレンジ色で塗りつぶします。
2.グラデーション
次に、球にグラデーションを付けてみます。
Vertex Shaderでは、uv
(テクスチャ座標)をFragment Shaderに渡せるようにします。
fragmentShader: `
//varyingは頂点シェーダーからフラグメントシェーダーにuv座標の値を渡すための変数。
varying vec2 vUv;
void main() {
//vUvにuv座標を代入する。
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
Fragment Shaderでは、vUv
を受け取り、gradient
(グラデーションの値)として代入しています。gradient
は0.0~1.0の値をとるので、gl_FragColor
にそのまま代入して使うことができます。
fragmentShader: `
precision mediump float;
//vUvを受け取る。
varying vec2 vUv;
void main() {
//グラデーションの方向を決める
float gradient = vUv.y;
//gradientは0.0~1.0の値をとるので、そのまま代入する。
gl_FragColor = vec4(1.0, gradient, 0.0, 1.0);
`
このように書き替えることでy軸方向のグラデーションを描画できます。
また、mix関数を使っても、グラデーションを描画することができます。
fragmentShader: `
precision mediump float;
varying vec2 vUv;
void main() {
//グラデーションの方向を決める
float gradient = vUv.y;
// オレンジと青の色味を決める
vec3 blueColor = vec3(0.3, 0.3, 1.0);
vec3 orangeColor = vec3(1.0, 0.5, 0.0);
//グラデーションの色を決める
vec3 finalColor = mix(orangeColor, blueColor, gradient);
gl_FragColor = vec4(finalColor,1.0)
`
このコードだと以下のような青→オレンジのグラデーションになります。
3.ノイズ
ノイズ的な表現を加えることもできます。Fragment Shaderへの書き加えることで実装します。
fragmentShader: `
precision mediump float;
varying vec2 vUv;
//2つの2次元ベクトルの内積、三角関数とマジックナンバーを使ったノイズ関数
float noise(vec2 st) {
return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);
}
void main() {
//グラデーションの方向を決める
float gradient = vUv.y;
vec3 blueColor = vec3(0.3, 0.3, 1.0);
vec3 orangeColor = vec3(1.0, 0.5, 0.0);
vec3 finalColor = mix(orangeColor, blueColor, gradient);
//ノイズ関数を使って、表現を付け加える。
float noiseValue = noise(vUv * 10.0);
//以下の式はfinalColor各要素にnoiseValueを加えている。
finalColor += noiseValue;
gl_FragColor = vec4(finalColor,1.0)
`
このコードでは、球の表面に白いノイズを重ねることができます。
4.照明効果
Three.jsでは予め、オブジェクトにあてるライトの設定ができますが、Shaderを使って、照明を追加することもできます。
まず、Vertex Shaderからは、頂点の位置と法線のベクトルを渡します。
vertexShader: `
varying vec2 vUv;
//頂点の位置を取得
varying vec3 vNormal;
varying vec3 vPosition;
void main() {
//uv座標を頂点シェーダーに渡す
vUv = uv;
vNormal = normalize(normalMatrix * normal);
vPosition = vec3(modelViewMatrix * vec4(position, 1.0));
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
Fragment Shaderでは、vNormal
、vPosition
を受け取り、照明の位置(lightPosition
)、 照明の色(lightColor
)を設定します。finalColor
にdiffuse
を乗算することで、照明をあてたfinalColor
を描画することができます。
fragmentShader: `
precision mediump float;
varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vPosition;
const vec3 lightPosition = vec3(200.0, 200.0, 200.0);
const vec3 lightColor = vec3(1.0, 1.0, 1.0);
void main() {
//照明計算
vec3 lightDir = normalize(lightPosition - vPosition);
vec3 normal = normalize(vNormal);
float diff = max(dot(normal,lightDir),0.0);
vec3 diffuse = lightColor * diff;
float gradient = vUv.y;
vec3 blueColor = vec3(0.3, 0.3, 1.0);
vec3 orangeColor = vec3(1.0, 0.5, 0.0);
vec3 finalColor = mix(orangeColor, blueColor, gradient);
//照明効果を乗算する。
finalColor = finalColor * (diffuse);
gl_FragColor = vec4(finalColor,1.0)
`
その他の照明効果として、明るい部分をより明るく、暗い部分をより暗くするグロー効果を実装することもできます。((0.299, 0.587, 0.114)はグロースケールという定数)
// 輝度効果
float luminance = dot(finalColor, vec3(0.299, 0.587, 0.114));
float glowStrength = 5.0;
vec3 glow = vec3(1.0, 0.7, 0.3) * pow(luminance, 3.0) * glowStrength;
gl_FragColor = vec4(finalColor + glow, 1.0);
おわりに
ここまで、Shaderでできる、グラデーション、ノイズ、照明などの描画処理をまとめました。Vertex ShaderとFragment Shaderの組み合わせによっては、さらに複雑な表現ができます。機会があれば、そういった表現もまとめたいと思います。
ご高覧頂き、ありがとうございました。