法線でライトを表現する
WebGLでライトを実装する為に最低限理解しておく必要基礎知識でライトと法線の関係について記載しましたが、実際に法線をつかってライトを表現してみようと思います。
ライトベクトル
ライトの向きのことをライトベクトルと言います。
ライトと記載していますが、ライトの実態は「平行光源」です。 平行光源は3D空間の全てに一様の向きで当たりますので、ライトの向きだけを利用します。
その為、ライトベクトルの値は法線と違い空間上で1つです。どの頂点からみてもライトベクトルの値は同じになります。
ライトベクトルの正規化
ライトベクトルは向きしか利用しない為、正規化して利用します。 正規化にはGLSLのビルドイン関数であるnormalize
を利用します。
vec3 lightDirection = normalize(vec3(0.0, 0.0, 1.0));
ライトの影響度を計算
WebGLでライトを実装する為に最低限理解しておく必要基礎知識でも書きましたが、ライトの影響度はライトベクトルと、法線の内積で求められます。
GLSLでは内積ようの関数であるdot
が用意されています。
使い方はdot(A,B)
のように内積を求める値を引数として設定します。
//ライトの向き vec3 lightDirection = normalize(vec3(0.0, 0.0, 1.0)); //normalは法線の値 float diffuse = max(dot(normal,lightDirection),0.0);
実行結果
実際に、ライトベクトルと法線を使って、ライトを実装してみます。
JS
const myVert = require('./../shader/sample.vert'); const myFrag = require('./../shader/sample.frag'); window.onload = () => { let windowWidth = window.innerWidth; let windowHeight = window.innerHeight; // rendererの作成 let renderer = new THREE.WebGLRenderer(); // canvasをbodyに追加 document.body.appendChild(renderer.domElement); // canvasをリサイズ renderer.setSize(windowWidth, windowHeight); // scene作成 let scene = new THREE.Scene(); // camera作成 let camera = new THREE.PerspectiveCamera(75, windowWidth / windowHeight, 0.1, 1000); camera.position.z = 1; // Geometry作成 let geometry =new THREE.SphereBufferGeometry( 0.1, 10, 10 ); // Material作成 let material = new THREE.RawShaderMaterial({ vertexShader: myVert, fragmentShader: myFrag, }); // Mesh作成 let mesh = new THREE.Mesh(geometry, material); // Meshをシーンに追加 scene.add(mesh); // draw render(); var r = 0; //描画 function render() { renderer.render(scene, camera); r += 0.01; mesh.rotation.set(0,r,r); // animation requestAnimationFrame(render); } };
vert(頂点シェーダー)
attribute vec3 normal; attribute vec3 position; uniform mat4 modelViewMatrix; uniform mat4 projectionMatrix; uniform mat3 normalMatrix; varying vec4 vColor; const vec3 lightDirection = normalize(vec3(0.0, 0.0, 1.0)); void main() { float diffuse = max(dot(normal,lightDirection),0.0); vColor = vec4(vec3(0.5),1.0) * vec4(vec3(diffuse), 1.0); gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); }
frag(フラグメントシェーダー)
precision mediump float; varying vec4 vColor; void main(){ gl_FragColor = vColor; }
See the Pen RLVZMQ by nogson (@satofaction) on CodePen.
法線の回転
上記の例の場合、ライトの実装はできていますが、オブジェクトを回転させると影もついていってしまいます。 ライトの位置が変わらない場合は、オブジェクトが回転しても影のつき方は、一定方向になるはずです。
なぜ、影も回転してしまうかというと、オブジェクトの回転に合わせて法線の向きも移動させる必要があるからです。 法線の回転を行う為にThree.jsではnormalMatrix
という行列が用意されています。
vec3 n = normalMatrix * normal; float diffuse = max(dot(n,lightDirection),0.0);
normalMatrix(法線を正しい向きにする変換行列)は何をやっているかと言うと、モデル座標変換行列の逆転置行列
を使うようです。
法線を回転した実行結果
See the Pen wrdrvb by nogson (@satofaction) on CodePen.
法線をオブジェクトの回転にあわせて回転させることで、影の方向が一定になりました。
まとめ
とにかくシェーダーは難しいです。慣れるしかなさそうです。。。