WebGL (6)


点光源

光が光源から放射状にひろがるため、光の強度は光源からの距離の2乗に反比例する。 面の拡散反射率を $k_d$, 点光源の光度を $I_q$, 光源から点Pまでの距離 $r$、 面の法線ベクトルと入射光のなす角を $\alpha$ とすると、 拡散反射光の強さ $I$ は $\displaystyle I = \frac{k_d I_q}{r^2} \cos \alpha$


フラグメント毎に光が当たった場合の色を計算するためには、 フラグメントのワールド座標系での位置と、 その位置での法線の向きが必要です。 これらは、頂点シェーダで計算して varying 変数経由でフラグメントシェーダに 渡すことで、補間計算を利用して計算できます。

頂点シェーダでvarying変数に代入した値は同じ名前を持つフラグメントシェーダの varying変数に渡されますが、実はそのままの値ではありません。 頂点シェーダで varying 変数に代入された値はラスタライズ処理中に 補間処理され、フラグメントごとに異なる値がフラグメントシェーダに渡されます。 これがvarying変数(=変化する変数)の名前の由来です。

下の例では、教科書の例にしたがって、「点光源の方向」と「面の法線」の $\cos \theta$ に比例する明るさで照されているものとして、 フラグメントシェーダで計算しています。 つまり、明るさが「点光源からの距離の2乗」に比例して弱まることを考慮していない例です。

PointLightCube6.htmlの変更点
*** ParallelLightCube6.html	Fri May 11 12:29:33 2018
--- PointLightCube6.html	Fri May 11 12:48:21 2018
***************
*** 15,39 ****
  attribute vec4 a_Color;\n\
  attribute vec4 a_Normal;\n\
  uniform mat4 u_MvpMatrix;\n\
  uniform mat4 u_NormalMatrix;\n\
- uniform vec3 u_LightColor;\n\
- uniform vec3 u_LightDirection;\n\
- uniform vec3 u_AmbientLight;\n\
  varying vec4 v_Color;\n\
  void main() {\n\
      gl_Position = u_MvpMatrix * a_Position;\n\
!     vec3 normal = normalize(vec3(u_NormalMatrix * a_Normal));\n\
!     float nDotL = max(dot(u_LightDirection,normal),0.0);\n\
!     vec3 diffuse = u_LightColor * a_Color.rgb * nDotL;\n\
!     vec3 ambient = u_AmbientLight * a_Color.rgb;\n\
!     v_Color = vec4(diffuse+ambient,a_Color.a);\n\
  }\n\
  ";
  var FSHADER_SOURCE = "\
  precision mediump float;\n\
  varying vec4 v_Color;\n\
  void main() {\n\
!     gl_FragColor = v_Color;\n\
  }\n\
  ";
  function main() {
--- 15,47 ----
  attribute vec4 a_Color;\n\
  attribute vec4 a_Normal;\n\
  uniform mat4 u_MvpMatrix;\n\
+ uniform mat4 u_ModelMatrix;\n\
  uniform mat4 u_NormalMatrix;\n\
  varying vec4 v_Color;\n\
+ varying vec3 v_Normal;\n\
+ varying vec3 v_Position;\n\
  void main() {\n\
      gl_Position = u_MvpMatrix * a_Position;\n\
!     v_Position = vec3(u_ModelMatrix * a_Position);\n\
!     v_Normal = normalize(vec3(u_NormalMatrix * a_Normal));\n\
!     v_Color = a_Color;\n\
  }\n\
  ";
  var FSHADER_SOURCE = "\
  precision mediump float;\n\
+ uniform vec3 u_LightColor;\n\
+ uniform vec3 u_LightPosition;\n\
+ uniform vec3 u_AmbientLight;\n\
+ varying vec3 v_Normal;\n\
+ varying vec3 v_Position;\n\
  varying vec4 v_Color;\n\
  void main() {\n\
!     vec3 normal = normalize(v_Normal);\n\
!     vec3 lightDirection = normalize(u_LightPosition - v_Position);\n\
!     float nDotL = max(dot(lightDirection, normal), 0.0);\n\
!     vec3 diffuse = u_LightColor * v_Color.rgb * nDotL;\n\
!     vec3 ambient = u_AmbientLight * v_Color.rgb;\n\
!     gl_FragColor = vec4(diffuse + ambient, v_Color.a);\n\
  }\n\
  ";
  function main() {
***************
*** 54,73 ****
      if (! tsuda.setElementArrayBuffer(indices)) return;
  
      var u_MvpMatrix = gl.getUniformLocation(gl.program,'u_MvpMatrix');
      var u_NormalMatrix = gl.getUniformLocation(gl.program,'u_NormalMatrix');
      var u_LightColor = gl.getUniformLocation(gl.program, 'u_LightColor');
!     var u_LightDirection = gl.getUniformLocation(gl.program, 'u_LightDirection');
      var u_AmbientLight = gl.getUniformLocation(gl.program, 'u_AmbientLight');
!     if (! u_MvpMatrix || ! u_LightColor || ! u_LightDirection || ! u_AmbientLight
!         || ! u_NormalMatrix) {
          console.log('failed to get location of uniform variable');
          return;
      }
  
      gl.uniform3f(u_LightColor, 1.0, 1.0, 1.0);
!     var lightDirection = new Vector3([1.0, 0.8, 0.5]); // world coordinates
!     lightDirection.normalize();
!     gl.uniform3fv(u_LightDirection, lightDirection.elements);
      gl.uniform3f(u_AmbientLight, 0.2, 0.2, 0.2);
  
      var modelMatrix = new Matrix4();
--- 62,80 ----
      if (! tsuda.setElementArrayBuffer(indices)) return;
  
      var u_MvpMatrix = gl.getUniformLocation(gl.program,'u_MvpMatrix');
+     var u_ModelMatrix = gl.getUniformLocation(gl.program,'u_ModelMatrix');
      var u_NormalMatrix = gl.getUniformLocation(gl.program,'u_NormalMatrix');
      var u_LightColor = gl.getUniformLocation(gl.program, 'u_LightColor');
!     var u_LightPosition = gl.getUniformLocation(gl.program, 'u_LightPosition');
      var u_AmbientLight = gl.getUniformLocation(gl.program, 'u_AmbientLight');
!     if (! u_MvpMatrix || ! u_ModelMatrix || ! u_LightColor || ! u_LightPosition 
!         || ! u_AmbientLight || ! u_NormalMatrix) {
          console.log('failed to get location of uniform variable');
          return;
      }
  
      gl.uniform3f(u_LightColor, 1.0, 1.0, 1.0);
!     gl.uniform3f(u_LightPosition, 0.5, 0.0, -0.3);
      gl.uniform3f(u_AmbientLight, 0.2, 0.2, 0.2);
  
      var modelMatrix = new Matrix4();
***************
*** 83,88 ****
--- 90,96 ----
      modelMatrix.rotate(45, 0, 0, 1);
      mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix);
      gl.uniformMatrix4fv(u_MvpMatrix,false,mvpMatrix.elements);
+     gl.uniformMatrix4fv(u_ModelMatrix,false,modelMatrix.elements);
      normalMatrix.setInverseOf(modelMatrix);
      normalMatrix.transpose();
      gl.uniformMatrix4fv(u_NormalMatrix,false,normalMatrix.elements);

上記のPointLightCube6.htmlは 点光源からフラグメントまでの距離を考慮していないので、光源からの位置によってはおかしな照明に見える場合があります。 点光源ではないように見える光源の位置の例を示します。

PointLightCube7.htmlの変更点
*** PointLightCube6.html	Fri May 11 12:48:21 2018
--- PointLightCube7.html	Fri May 11 12:47:26 2018
***************
*** 74,80 ****
      }
  
      gl.uniform3f(u_LightColor, 1.0, 1.0, 1.0);
!     gl.uniform3f(u_LightPosition, 0.5, 0.0, -0.3);
      gl.uniform3f(u_AmbientLight, 0.2, 0.2, 0.2);
  
      var modelMatrix = new Matrix4();
--- 74,80 ----
      }
  
      gl.uniform3f(u_LightColor, 1.0, 1.0, 1.0);
!     gl.uniform3f(u_LightPosition, 0.2, 0.5, 0.7);
      gl.uniform3f(u_AmbientLight, 0.2, 0.2, 0.2);
  
      var modelMatrix = new Matrix4();

光源とフラグメントの距離の2乗に比例して、フラグメントの明るさが弱まるように変更してみます。 これだと点光源がどこにあっても、リアルな結果が得られます。

PointLightCube8.htmlの変更点
*** PointLightCube7.html	Fri May 11 12:47:26 2018
--- PointLightCube8.html	Fri May 11 12:45:56 2018
***************
*** 37,45 ****
  varying vec4 v_Color;\n\
  void main() {\n\
      vec3 normal = normalize(v_Normal);\n\
!     vec3 lightDirection = normalize(u_LightPosition - v_Position);\n\
      float nDotL = max(dot(lightDirection, normal), 0.0);\n\
!     vec3 diffuse = u_LightColor * v_Color.rgb * nDotL;\n\
      vec3 ambient = u_AmbientLight * v_Color.rgb;\n\
      gl_FragColor = vec4(diffuse + ambient, v_Color.a);\n\
  }\n\
--- 37,47 ----
  varying vec4 v_Color;\n\
  void main() {\n\
      vec3 normal = normalize(v_Normal);\n\
!     vec3 lightVec = u_LightPosition - v_Position;\n\
!     float len2 = dot(lightVec,lightVec);\n\
!     vec3 lightDirection = normalize(lightVec);\n\
      float nDotL = max(dot(lightDirection, normal), 0.0);\n\
!     vec3 diffuse = u_LightColor * v_Color.rgb * nDotL / len2;\n\
      vec3 ambient = u_AmbientLight * v_Color.rgb;\n\
      gl_FragColor = vec4(diffuse + ambient, v_Color.a);\n\
  }\n\

複数の光源がある場合には、それぞれの光源に対してdiffuse(拡散反射)成分とambient (環境光)成分を計算して、 合計すれば求めることができます。