のぐそんブログ

暗いおじさんがシコシコ書くブログです。

法線でライトを表現する

WebGLでライトを実装する為に最低限理解しておく必要基礎知識でライトと法線の関係について記載しましたが、実際に法線をつかってライトを表現してみようと思います。

ライトベクトル

ライトの向きのことをライトベクトルと言います。

ライトと記載していますが、ライトの実態は「平行光源」です。 平行光源は3D空間の全てに一様の向きで当たりますので、ライトの向きだけを利用します。

その為、ライトベクトルの値は法線と違い空間上で1つです。どの頂点からみてもライトベクトルの値は同じになります。

Kobito.xSNfYZ.png

ライトベクトルの正規化

ライトベクトルは向きしか利用しない為、正規化して利用します。 正規化には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.

法線をオブジェクトの回転にあわせて回転させることで、影の方向が一定になりました。

まとめ

とにかくシェーダーは難しいです。慣れるしかなさそうです。。。