본문 바로가기

WebGL

WebGL Fundamentals > WebGL 3D : Cameras

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

 

WebGL 3D - Cameras

How cameras work 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 3D : Cameras

현실에서는 보통 건물의 사진을 찍기 위해 카메라가 움직인다.

하지만 이전 주제에서 -z축의 원점 앞에 물체를 투영하는 방법을 원근 투영 실습할 때 사용하였다.

이를 위해 원점으로 카메라를 옮기고,

나머지 물체를 절두체 안으로 적절하게 이동하여 카메라를 기준으로 동일한 위치에 있도록 하였다.

 

원근 투영 실습을 할 때 원근 투영을 하기 위해 절두체 안으로 물체를 움직이는 방법을 사용하였기 때문에 사실상 카메라 앞에 있는 월드가 움직이도록 해야 한다.

이렇게 적용하는 가장 쉬운 방법은 “역행렬”을 사용하는 것이다.

카메라를 원점에서 원하는 위치로 이동하고 회전하는 방법을 알려주는 행렬이 있으면,

이 것의 역행렬을 물체에 적용하면,

카메라는 원점에 있도록 하고 그 앞에 있는 모든 것을 움직이도록 설정할 수 있게된다.

 

◈ 'F'가 선회하는 3D 장면을 만들기

투영 행렬 계산

// 투영 행렬 계산
var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
var zNear = 1;
var zFar = 2000;
var projectionMatrix = m4.perspective(fieldOfViewRadians, aspect, zNear, zFar);

카메라가 원점을 중심으로 radius * 1.5의 거리에서 원점을 바라보며 회전하는 행렬

var numFs = 5;
var radius = 200;
 
// 카메라에 대한 행렬 계산
var cameraMatrix = m4.yRotation(cameraAngleRadians);
cameraMatrix = m4.translate(cameraMatrix, 0, 0, radius * 1.5);

카메라 행렬에서 "뷰 행렬" 계산

"뷰 행렬"는 마치 카메라가 원점(0,0,0)에 있는 것처럼, 모든 걸 카메라와 정반대로 움직여 사실상 카메라를 기준으로 만드는 행렬이다.

역행렬을 계산하는 inverse 함수를 사용한다.

inverse 함수에 원점을 기준으로 카메라를 어떤 위치와 오리엔테이션으로 옮기는 행렬이 인풋으로 들어가면,

아웃풋으로 나머지 물체를 움직여 카메라가 원점에 있도록 하는 역행렬이 나오게 된다.

 

// 카메라 행렬로 뷰 행렬 만들기
var viewMatrix = m4.inverse(cameraMatrix);

뷰 행렬과 투영 행렬을 뷰 투영 행렬로 합친다.

// 뷰 투영 행렬 계산
var viewProjectionMatrix = m4.multiply(projectionMatrix, viewMatrix);

F로 이루어진 원을 그린다.

각 F에 대해 뷰 투영 행렬로 시작하여 각도에 따라 각 F에 대한 행렬 계산

 

for (var ii = 0; ii < numFs; ++ii) {
  var angle = (ii * Math.PI * 2) / numFs;
  var x = Math.cos(angle) * radius;
  var y = Math.sin(angle) * radius;
 
  // 뷰 투영 행렬로 시작하여 F에 대한 행렬 계산
  var matrix = m4.translate(viewProjectionMatrix, x, 0, y);
 
  // 행렬 설정
  gl.uniformMatrix4fv(matrixLocation, false, matrix);
 
  // 지오메트리 그리기
  var primitiveType = gl.TRIANGLES;
  var offset = 0;
  var count = 16 * 6;
  gl.drawArrays(primitiveType, offset, count);

 

 

◈ 카메라가 보고 싶은 방향을 향하도록 설정하기

‘F’가 선회할 때 특정 F를 가리키도록 하려면, ‘F’의 원이 회전하는 동안 카메라가 해당 ‘F’를 가리키도록 한다.

원하는 카메라 위치와 가리킬 대상을 결정한 다음,

그곳에 카메라를 배치하는 행렬을 계산할 수 있다.

이 방법은 행렬이 작동하는 방식에 기반하여 쉽게 수행할 수 있다.

 

먼저 카메라 위치를 알아야 한다.

이를 cameraPosition이라 부를 것이다.

그런 다음 보고 싶거나 목표로 하고 싶은 것의 위치를 알아야 한다.

이건 target이라 부를 것이다.

cameraPosition에서 target을 빼면 대상에 도달하기 위해 카메라가 이동해야 하는 방향을 가리키는 벡터가 생긴다.

 

 

이걸 zAxis라 부를 것이다.

결과를 정규화한 다음 행렬의 z부분으로 직접 복사한다.

3D에서는 단위구가 필요하고 정규화된 벡터는 단위구의 한 점을 나타낸다.

 

단일 벡터만으로도 단위구의 한 점을 제공하지만, 정보가 충분하지 않다.

해당 점에서 사물로 향하는 오리엔테이션이 필요한데, 이를 알기 위해서는 행렬의 다른 부분을 채워야 한다.

 

2개의 단위 벡터가 있고 이들을 벡터의 외적을 구하면, 이 두 벡터에 수직인 벡터를 얻는다.

zAxis와 up의 벡터곱을 계산하면 카메라의 xAxis를 얻을 수 있다.

 

 

그리고 이제 xAxis가 있으니 zAxis와 xAxis를 벡터곱해서 카메라의 yAxis를 얻을 수 있다.

 

 

이제 남은 일은 3개의 축을 행렬로 연결하는 것이다.

이 행렬은 cameraPosition에서 target을 가리키고 position만 추가하면 된다.

 

+----+----+----+----+
| Xx | Xy | Xz |  0 |  <- x축
+----+----+----+----+
| Yx | Yy | Yz |  0 |  <- y축
+----+----+----+----+
| Zx | Zy | Zz |  0 |  <- z축
+----+----+----+----+
| Tx | Ty | Tz |  1 |  <- 카메라 위치
+----+----+----+----+

두 벡터의 외적을 계산하는 코드

카메라의 xAxis와 yAxis를 구하는 데 사용된다.

 

function cross(a, b) {
  return [
    a[1] * b[2] - a[2] * b[1],
    a[2] * b[0] - a[0] * b[2],
    a[0] * b[1] - a[1] * b[0],
  ];
}

두 벡터를 뺄셈하는 코드

cameraPosition에서 target을 빼서 대상에 도달하기 위해 카메라가 이동해야 하는 방향을 가리키는 벡터를 구하는 데 사용된다.

 

function subtractVectors(a, b) {
  return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
}

벡터를 정규화하는 코드 (단위 벡터로 만듦)

zAxis를 정규화하는 데 사용한다.

 

function normalize(v) {
  var length = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
  // 0으로 나뉘지 않도록 하기
  if (length > 0.00001) {
    return [v[0] / length, v[1] / length, v[2] / length];
  } else {
    return [0, 0, 0];
  }
}

 

"lookAt" 행렬을 계산하는 코드

var m4 = {
  lookAt: function(cameraPosition, target, up) {
    var zAxis = normalize(
        subtractVectors(cameraPosition, target));
    var xAxis = normalize(cross(up, zAxis));
    var yAxis = normalize(cross(zAxis, xAxis));
 
    return [
       xAxis[0], xAxis[1], xAxis[2], 0,
       yAxis[0], yAxis[1], yAxis[2], 0,
       zAxis[0], zAxis[1], zAxis[2], 0,
       cameraPosition[0],
       cameraPosition[1],
       cameraPosition[2],
       1,
    ];
  }

카메라를 움직일 때 특정 'F'를 가리키도록 만드는 방법

  //...
 
  // 첫 번째 F의 위치 계산
  var fPosition = [radius, 0, 0];
 
  // 원에서 카메라가 있는 위치를 계산하는 행렬 수학 사용
  var cameraMatrix = m4.yRotation(cameraAngleRadians);
  cameraMatrix = m4.translate(cameraMatrix, 0, 0, radius * 1.5);
 
  // 계산한 행렬에서 카메라의 위치 가져오기
  var cameraPosition = [
    cameraMatrix[12],
    cameraMatrix[13],
    cameraMatrix[14],
  ];
 
  var up = [0, 1, 0];
 
  // lookAt을 사용하여 카메라 행렬 계산
  var cameraMatrix = m4.lookAt(cameraPosition, fPosition, up);
 
  // 카메라 행렬로 뷰 행렬 만들기
  var viewMatrix = m4.inverse(cameraMatrix);
 
  //...

 

◈ 카메라 이외에도 "lookAt" 수식을 사용할 수 있다.

일반적인 용도는 누군가를 따라가는 캐릭터의 머리를 만드는 것이다.

목표를 겨냥하는 포탑을 만들 수 있고, 개체가 경로를 따라가게 만들 수도 있다.

 

lookAt 함수를 적용하는 방법

  1. 먼저 경로상에 대상이 있는 위치를 계산한다.
  2. 그런 다음 잠시 후에 경로상에 대상이 어디에 있는지 계산한다.
  3. 두 값을 lookAt 함수로 연결하면 개체가 경로를 따라가고 경로를 향하도록 하는 행렬을 얻을 수 있다.

 

 

 

 

 

 

 

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

 

WebGL_Fundamentals

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

myoungmin.github.io