WebGL

[WebGL] Hemisphere Lighting

개발가락 2023. 7. 9. 18:48

 

 

 

About.

https://threejs.org/examples/#webgl_lights_hemisphere

 

three.js examples

 

threejs.org

 

특정 객체가 두 광원 사이에 존재 할 시,  객체의 정점들에 대해 각 광원과 가깝고 마주볼수록 해당 광원에 영향을 많이 받습니다. 이러한 조명을 구현한 것이 Hemisphere lighting 입니다. 일반적으로 특정 객체에 대해 하늘과 땅에 의해 산란되는 빛을 표현하기 위해 사용됩니다.

 

 

Implementation.

 

먼저 Vertex Shader에서 각 정점에 대한 normal Vector(법선 벡터) 정보를 가져와야합니다. 정점의 normal과 광원과의 교차에 따라 산란이 달라지기 때문입니다.

 

varying vec3 vNormal;

void main(){
  
  gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.);
  
  vNormal=(modelMatrix*vec4(normal,0.)).xyz;
}

 

normal 그 자체로는 local space 좌표이기 때문에, model Matrix와 곱을 통해 world space로 변환시킵니다.

 

 

이제 Fragment Shader에서 normal정보를 받습니다.

 

varying vec3 vNormal;

void main(){

  vec3 normal=normalize(vNormal);
  
  vec3 color=normal;
  
  gl_FragColor=vec4(color,1.);
}

 

vNormal을 통해 각 정점에 대한 법선벡터 정보를 받은 후, 정규화를 진행합니다. 

 

각 정점에 대한 법선 벡터를 기준으로 각 정점 사이의 법선벡터가 보간되는데, 이 때 보간되는 법선벡터들이 단위 벡터임을 보장할 수 없기 때문입니다.

 

 

렌더링을 진행하면 위와 같은 결과가 나옵니다.

 

Red = (1,0,0)

Green = (0,1,0)

Blue = (0,0,1)

를 생각했을때, 수잔(원숭이)의 정면(Z축)과 마주볼 수록 파란색, X,Y축도 마찬가지로 색상값을 나타내는것을 볼 수 있습니다.

 

다만, 반대편에 보면 검게 물든 지점은 값들이 음수인 부분으로 당연하지만, 색상 값의 범위에 속하는 부분이 아니기에, 보완이 필요할 듯합니다.

 

 

먼저 목표는 법선벡터의 범위(-1,1)을 (0,1)로 변환 하는 것이므로, 이를 위해 remap 함수를 구현하겠습니다.

 

ex) 0 -> 0.5

1. 0이 기존 범위에서 몇퍼센트 지점에 있는지 구한다.

2.  1에서 구한 값을, remap 범위에 대해 mix한다.

 

1을 구하는 함수를 inverseLerp라 할 때,

 

float inverseLerp(float v,float minValue,float maxValue){
  return(v-minValue)/(maxValue-minValue);
}
float remap(float v,float inMin,float inMax,float outMin,float outMax){
  float t=inverseLerp(v,inMin,inMax);
  return mix(outMin,outMax,t);
}

위와 같이 표현가능합니다.

 

 

이제 remap한 값을 색상에 대입한다면,

 

varying vec3 vNormal;

float inverseLerp(float v,float minValue,float maxValue){
  return(v-minValue)/(maxValue-minValue);
}

float remap(float v,float inMin,float inMax,float outMin,float outMax){
  float t=inverseLerp(v,inMin,inMax);
  return mix(outMin,outMax,t);
}

void main(){
  vec3 baseColor=vec3(.5);
  vec3 lighting=vec3(.1451,.1176,.1176);
  
  vec3 normal=normalize(vNormal);
  vec3 ambient=vec3(.5);
  
  float hemiMix=remap(normal.y,-1.,1.,0.,1.);
  
  color= vec3(hemiMix);
  
  gl_FragColor=vec4(color,1.);
}

 

 

 

잘 해결되었습니다.

 

이제 normal 문제도 해결되었으니, Hemisphere lighting의 두가지 색상(sky, ground color)을 가지고 색상을 입힙니다.

 

void main(){
  vec3 baseColor=vec3(.5);
  vec3 lighting=vec3(.1451,.1176,.1176);
  
  vec3 normal=normalize(vNormal);
  vec3 ambient=vec3(.5);
  
  //hemi
  vec3 skycolor=vec3(0.,.3,.6);
  vec3 groundColor=vec3(.6,.3,.1);
  
  float hemiMix=remap(normal.y,-1.,1.,0.,1.);
  vec3 hemi=mix(groundColor,skycolor,hemiMix);
  
  vec3 color= hemi;
  
  gl_FragColor=vec4(color,1.);
}