본문 바로가기

WebGL

WebGL Fundamentals > Point Lighting

https://webglfundamentals.org/webgl/lessons/webgl-3d-lighting-point.html

 

WebGL 3D - Point Lighting

How to implement point lighting in WebGL

webglfundamentals.org

https://github.com/Myoungmin/WebGL_Fundamentals

 

GitHub - Myoungmin/WebGL_Fundamentals: WebGL 학습 프로젝트

WebGL 학습 프로젝트. Contribute to Myoungmin/WebGL_Fundamentals development by creating an account on GitHub.

github.com

 

 

WebGL : Point Lighting

Directional 조명과 다르게 조명의 방향을 설정하는 대신 조명의 위치로 3D 공간의 한 점을 선택한다.

 

그럼 각 표면의 임의의 지점에서 조명까지의 방향이 Directional 조명과 다르게 각각의 값을 갖게된다.

 

표면 법선과 표면에서 조명을 향하는 각 벡터의 스칼라곱을 구하면,

표면의 각 지점에서 다른 값을 얻을 수 있다.

 

 

Specular Highlighting 적용하기 전 Point 조명

조명의 위치가 필요하다.

uniform vec3 u_lightWorldPosition;

표면의 월드 위치를 계산하는 방법 : 위치에 월드 행렬을 곱한다.

uniform mat4 u_world;
 
//...
 
// 표면의 월드 위치 계산
vec3 surfaceWorldPosition = (u_world * a_position).xyz;

표면의 모든 위치에서 조명까지의 벡터를 계산한다.

v_surfaceToLight = u_lightWorldPosition - surfaceWorldPosition;

위 내용을 모두 종합한 Vertex Shader

attribute vec4 a_position;
attribute vec3 a_normal;
 
uniform vec3 u_lightWorldPosition;
 
uniform mat4 u_world;
uniform mat4 u_worldViewProjection;
uniform mat4 u_worldInverseTranspose;
 
varying vec3 v_normal;
 
varying vec3 v_surfaceToLight;
 
void main() {
  // 위치에 행렬 곱하기
  gl_Position = u_worldViewProjection * a_position;
 
  // 법선의 방향을 정하고 프래그먼트 셰이더로 전달
  v_normal = mat3(u_worldInverseTranspose) * a_normal;
 
  // 표면의 월드 위치 계산
  vec3 surfaceWorldPosition = (u_world * a_position).xyz;
 
  // 표면 -> 빛 벡터를 계산하고 프래그먼트 셰이더로 전달
  v_surfaceToLight = u_lightWorldPosition - surfaceWorldPosition;
}

표면에서 빛까지의 벡터는 단위 벡터가 아니라, Fragment Shader에서 정규화해야 한다.

참고로 Vertex Shader에서 정규화할 수 있지만 Varying이기 때문에 위치 사이를 선형적으로 보간하여, Vertex Shader에서 정규화하면 완전한 단위벡터가 아니게 된다.

 

precision mediump float;
 
// 정점 셰이더에서 전달됩니다.
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
 
uniform vec4 u_color;
 
void main() {
  // v_normal은 베링이기 때문에 보간되므로 단위 벡터가 아닙니다.
  // 정규화하면 다시 단위 벡터가 됩니다.
  vec3 normal = normalize(v_normal);
 
  vec3 surfaceToLightDirection = normalize(v_surfaceToLight);
 
  float light = dot(normal, surfaceToLightDirection);
 
  gl_FragColor = u_color;
 
  // 색상 부분(알파 제외)에만 광량 곱하기
  gl_FragColor.rgb *= light;
}

u_world와 u_lightWorldPosition의 위치를 찾는다.

  var lightWorldPositionLocation = gl.getUniformLocation(program, "u_lightWorldPosition");
  var worldLocation = gl.getUniformLocation(program, "u_world");

u_world와 u_lightWorldPosition를 설정한다.

  // 행렬 설정
  gl.uniformMatrix4fv(
    worldLocation,
    false,
    worldMatrix
  );
  gl.uniformMatrix4fv(
    worldViewProjectionLocation,
    false,
    worldViewProjectionMatrix
  );
 
  //...
 
  // 조명 방향 설정
  gl.uniform3fv(reverseLightDirectionLocation, m4.normalize([0.5, 0.7, 1]));
  // 조명 위치 설정
  gl.uniform3fv(lightWorldPositionLocation, [20, 30, 50]);

 

Point 조명에 Specular Highlighting 적용하기

현실의 물체를 보면 먼 곳에서 조명이 비칠 때 거울처럼 빛을 반사한다.

 

해당 효과는 스칼라곱을 구하여 빛이 눈에 반사되는지 계산하여 시뮬레이션 할 수 있다.

 

모델의 표면에서 조명을 향하는 방향을 알고,

표면에서 카메라로 향하는 방향을 안다면,

두 벡터를 더하고 정규화해서,

이들 사이 중간에 있는 halfVector를 계산할 수 있다.

 

이 계산한 halfVector가 표면의 법선과 일치한다면, 빛을 카메라로 반사하기 완벽한 각도이다.

halfVector와 Surface normal의 스칼라곱을 구했을 때 1이 나오면 일치하는 것이고, 0이나오면 수직-1이 나오면 반대방향이라는 사실을 알 수 있다.

 

 

이러한 원리를 통하여 Specular Highlighting을 적용할 수 있다.

스칼라곱을 거듭제곱해서 밝기를 수정할 수 있다.

이는 반사되는 가장 밝은 부분을 선형적 falloff에서 지수적 falloff로 더 작게 만든다.

 

 

빨간 선이 그래프 위쪽에 가까울수록 반사광이 추가되어 더 밝아진다.

거듭제곱하는 값인 power를 높이면 밝아지는 범위가 오른쪽으로 축소된다.

밝기를 조절하고 싶다면 이걸 shininess라고 명명하고 셰이더에 추가해 계산해줄 필요가 있다.

 

스칼라곱은 음수가 될 수 있지만 음수를 거듭제곱하는 것은 WebGL에 정의되어 있지 않아 스칼라곱이 음수가 된다면 specular를 0.0으로 유지해준다.

pow(negative, power)는 정의되어 있지 않다.

먼저 Vertex Shader로 카메라의 위치를 전달하고, 표면에서 뷰를 향하는 벡터를 계산한 다음, Fragment Shader로 전달한다.

attribute vec4 a_position;
attribute vec3 a_normal;
 
uniform vec3 u_lightWorldPosition;
uniform vec3 u_viewWorldPosition;
 
uniform mat4 u_world;
uniform mat4 u_worldViewProjection;
uniform mat4 u_worldInverseTranspose;
 
varying vec3 v_normal;
 
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
 
void main() {
  // 위치에 행렬 곱하기
  gl_Position = u_worldViewProjection * a_position;
 
  // 법선의 방향을 정하고 프래그먼트 셰이더로 전달
  v_normal = mat3(u_worldInverseTranspose) * a_normal;
 
  // 표면의 월드 위치 계산
  vec3 surfaceWorldPosition = (u_world * a_position).xyz;
 
  // 표면 -> 빛 벡터를 계산하고 프래그먼트 셰이더로 전달
  v_surfaceToLight = u_lightWorldPosition - surfaceWorldPosition;
 
  // 표면 -> 뷰/카메라 벡터를 계산하고 프래그먼트 셰이더로 전달
  v_surfaceToView = u_viewWorldPosition - surfaceWorldPosition;
}

 

Fragment Shader에서 표면에서 뷰로 향하는 벡터와 표면에서 빛으로 향하는 벡터 사이의 halfVector를 계산한다. 그런 다음 halfVector와 법선의 스칼라곱을 구하여, 빛이 뷰로 반사되는지 확인할 수 있다.

// 정점 셰이더에서 전달됩니다.
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
 
uniform vec4 u_color;
uniform float u_shininess;
 
void main() {
  // v_normal이 베링이기 때문에 보간되므로 단위 벡터가 아닙니다.
  // 정규화하면 다시 단위 벡터가 됩니다.
  vec3 normal = normalize(v_normal);
 
  vec3 surfaceToLightDirection = normalize(v_surfaceToLight);
  vec3 surfaceToViewDirection = normalize(v_surfaceToView);
  vec3 halfVector = normalize(surfaceToLightDirection + surfaceToViewDirection);
 
  float light = dot(normal, surfaceToLightDirection);
  float specular = 0.0;
  // 스칼라곱을 거듭제곱해서 밝기를 수정한다.  
  if (light > 0.0) {
    // 음수를 거듭제곱하는 건 WebGL에 정의되어 있지 않아 스칼라곱이 음수가 된다면 specular를 0.0으로 남겨둔다.
    specular = pow(dot(normal, halfVector), u_shininess);
  }

 
  gl_FragColor = u_color;
 
  // 색상 부분(알파 제외)에만 광량 곱하기
  gl_FragColor.rgb *= light;
 
  // 반사율 더하기
  gl_FragColor.rgb += specular;
}

u_viewWorldPosition을 찾아 설정한다.

var lightWorldPositionLocation =
    gl.getUniformLocation(program, "u_lightWorldPosition");
var viewWorldPositionLocation =
    gl.getUniformLocation(program, "u_viewWorldPosition");
 
//...
 
// 카메라 행렬 계산
var camera = [100, 150, 200];
var target = [0, 35, 0];
var up = [0, 1, 0];
var cameraMatrix = makeLookAt(camera, target, up);
 
// 카메라/뷰 위치 설정
gl.uniform3fv(viewWorldPositionLocation, camera);

밝기를 조절하기 위해 u_shininess를 찾아 설정한다.

var shininessLocation = gl.getUniformLocation(program, "u_shininess");
 
//...
 
// 광택 설정
gl.uniform1f(shininessLocation, shininess);

 

 

 

 

 

 

https://myoungmin.github.io/WebGL_Fundamentals/

 

WebGL_Fundamentals

WebGL이란? WebGL은 Web Graphics Library의 약자로 웹상에서 2D 및 3D 그래픽을 렌더링하기 위한 로우 레벨 Javascript API. 학습내용 블로그 정리

myoungmin.github.io