こんにちは、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の組み合わせによっては、さらに複雑な表現ができます。機会があれば、そういった表現もまとめたいと思います。 ご高覧頂き、ありがとうございました。