Fragment Shader에서 3D 카메라

시작은 다음과 같다.

uniform vec3 uResolution;
uniform float uTime;
uniform vec4 uMouse;

void main() {
   ?
}

프레그먼트의 uv 좌표는 좌측하단이 원점인데, 원점을 화면 중심으로 잡기 위해 다음 코드가 필요하다.

void main() {
  vec2 uv = gl_FragCoord.xy / uResolution.xy;
  uv -= .5;
  uv.x *= uResolution.x / uResolution.y;

  float t = uTime; // 그냥 흘러가는 시간값
}

프레그먼트에서 무언가를 의미있게 표현하기 위해서는 그 무언가가 픽셀에서 얼마나 떨어져있는지의 거리값을 매우 의미있게 사용한다. 이를 위해 Ray가 필요한데, Ray는 시작점(ro)과 방향(rd)이 중요하다.

void main() {
  ..

  float t = uTime; // 그냥 흘러가는 시간값

  // Left-Hand (Z축은 모니터에서 사람을 향하는 방향이 마이너스임)
  vec3 ro = vec3(3. * sin(t), 1., -3. * cos(t)); // Ray의 시작점(카메라의 위치), 일단 시간에 따라 Y축으로 회전시켜본다.
}

Ray의 시작점인 ro는 보이는데, 방향인 rd는 아직 보이지 않는다. rd를 정하기 위해 카메라 개념을 이용한다.

void main() {
  ...

  vec3 ro = vec3(3. * sin(t), 1., -3. * cos(t)); // Ray의 시작점(카메라의 위치), 일단 시간에 따라 Y축으로 회전시켜본다.

  vec3 lookAt = vec3(.0); // 카메라가 바라보는 지점
  vec3 f = normalize(lookAt - ro); // 카메라가 바라보는 방향 벡터
  vec3 u = vec3(0,1,0); // 카메라의 Up 벡터 
  vec3 r = normalize(cross(u, f)); // 카메라의 Right 벡터 

  float zoom = 1.; // 확대 배율
  vec3 c = ro + f * zoom;
  vec3 i = c + uv.x * r + uv.y * u; // 교차하는 지점

  vec3 rd = normalize(i - ro); // Ray의 방향 벡터

이제 정육면체의 구성 정점 8개를 프레그먼트에 시각화하기만 하면 된다.

void main() {
  ...

  vec3 rd = normalize(i - ro); // Ray의 방향 벡터

  float d = 0.;  
  float off = .5;
  d += drawPoint(ro, rd, vec3(0.-off, 0.-off, 0.-off));
  d += drawPoint(ro, rd, vec3(0.-off, 0.-off, 1.-off));
  d += drawPoint(ro, rd, vec3(0.-off, 1.-off, 0.-off));
  d += drawPoint(ro, rd, vec3(0.-off, 1.-off, 1.-off));
  d += drawPoint(ro, rd, vec3(1.-off, 0.-off, 0.-off));
  d += drawPoint(ro, rd, vec3(1.-off, 0.-off, 1.-off));
  d += drawPoint(ro, rd, vec3(1.-off, 1.-off, 0.-off));
  d += drawPoint(ro, rd, vec3(1.-off, 1.-off, 1.-off));

  gl_FragColor = vec4(d);
}

drawPoint는 포인트를 그리는 함수이고 다음과 같다.

float drawPoint(vec3 ro, vec3 rd, vec3 p) {
  float d = distLine(ro, rd, p);
  d = smoothstep(.04, .03, d);
  return d;
}

distLine은 ro와 rd로 정의되는 Ray에서 프로그먼트 좌표 간의 거리를 얻는 함수인데 다음과 같다.

float distLine(vec3 ro, vec3 rd, vec3 p) {
  return length(cross(p - ro, rd)) / length(rd);
}

끝.

위의 코드 작성을 위해 학습한 내용


GLSL, 무지개 색 표현

수평으로 7개의 무지개 색을 표현하는 프레그먼트 쉐어디 코드입니다. 배열 자료형을 사용하고 있는 예제 코드입니다.

uniform vec2 uResolution;
uniform float uTime;
uniform vec2 uMouse;

vec3 rainbow(float t) {
    vec3 colors[7];

    colors[0] = vec3(1.0, 0.0, 0.0); // 빨강
    colors[1] = vec3(1.0, 0.5, 0.0); // 주황
    colors[2] = vec3(1.0, 1.0, 0.0); // 노랑
    colors[3] = vec3(0.0, 1.0, 0.0); // 초록
    colors[4] = vec3(0.0, 0.0, 1.0); // 파랑
    colors[5] = vec3(0.29, 0.0, 0.51); // 남색
    colors[6] = vec3(0.56, 0.0, 1.0); // 보라
    
    int index = int(t * 7.0);
    return colors[index];
}

void main() {
    vec2 st = gl_FragCoord.xy/uResolution.xy;
    vec3 color = rainbow(st.x);
        
    gl_FragColor = vec4(color,1.0);
}

결과는 다음과 같습니다.