WebGL Tutorial
- Computer Graphics Final Project
- 소프트웨어학과 201620916 오규석
주제
- WebGL을 기반으로 만든 Cube Object에서 마우스 드래그와 마우스 휠의 조작으로 Camera를 이동시켜 보고, Camera의 이동과 좌표계에 대해 이해한다.
목표
- MVP 기법이 무엇인지 알아본다.
- WebGL에서 일반적인 Transformation Matirx를 적용해 만들어 낸 Cube Object에 MVP 기법을 적용해본다.
- Camera의 위치를 마우스의 드래그와 휠 스크롤로 조정해보고, Camera의 이동과 좌표계와 대해 이해한다.
1.MVP 기법이란
WebGL에서 물체의 이동은 basic transformation matrix(translation, scale, rotation)등에 의해 이루어진다. 따라서 basic transformation matrix를 융합해 한번에 다룰 수 있으면 3D 물체를 편하게 rendering 할 수 있다. 이러한 아이디어에서 기반해 Model, Camera, View의 조작을 한번에 할 수 있게 만드는 기법이 MVP 기법이다. MVP에서 M은 Model Matrix, V는 View Matrix, P는 Projection Matrix를 나타낸다. 각 Matrix의 사용은 다음과 같다.
- Model Matrix : 3D space에서 물체의 이동을 표현
- View Matirx : 3D space에서 카메라의 이동을 표현
- Projection Matrix : 3D space에서 시야를 나타내는 Viewing Frustum을 표현
2.일반적인 WebGL Object Code에 MVP 기법 적용하기
기존의 Transformation Matrix로 만들어진 Code에서 Vertex shader Code는 다음과 같다. 이 코드에서는 Transformation Matrix를 사용하고 있으며, transformation을 적용하기 위해 gl_Position을 Transformation과 vertex position을 곱한 값으로 설정했다.
var vertexShaderSource = '\
attribute highp vec4 myVertex; \
attribute highp vec4 myColor; \
uniform mediump mat4 transformationMatrix; \
varying highp vec4 color;\
void main(void) \
{ \
gl_Position = transformationMatrix * myVertex; \
color = myColor; \
}';
...
MVP 기법을 사용하기 위해서는 Transformation Matrix를 삭제하고, 다음과 같이 Vertex shader Code에 Uniform으로 MVP Matrix 변수를 지정해 주어야 한다. 그리고 gl_Position은 M Matrix, V Matrix, P Matrix를 vertex position에 차례대로 곱해준 값으로 설정한다.
- M Matrix, V Matrix, P Matrix는 반드시 차례대로 vertex position에 곱해져야 한다.
var vertexShaderSource = '\
attribute highp vec4 myVertex; \
attribute highp vec4 myColor; \
uniform mediump mat4 mMat; \
uniform mediump mat4 vMat; \
uniform mediump mat4 pMat; \
varying highp vec4 color;\
void main(void) \
{ \
gl_Position = pMat * vMat * mMat * myVertex; \
color = myColor; \
}';
...
Render Scene에서는 mMat, vMat, pMat을 이용한 함수로 각각 model의 위치, camera 위치, view 범위를 설정한다.
var mMat=[];
var vMat=[];
var pMat=[];
mat4.fromYRotation(mMat,rotY);
if ( flag_animation ){
rotY += 0.01;
}
mat4.lookAt(vMat,[0.0,0.0,2.0],[0.0,0.0,0.0],[0.0,1.0,0.0]);
mat4.perspective(pMat,3.14/2,800.0/600.0,0.5,5);
...
이후에 mMat, vMat, pMat을 Vertex shader, 즉 GPU로 보내주는 code를 작성한다.
var mMatLocation = gl.getUniformLocation(gl.programObject, "mMat");
var vMatLocation = gl.getUniformLocation(gl.programObject, "vMat");
var pMatLocation = gl.getUniformLocation(gl.programObject, "pMat");
gl.uniformMatrix4fv(mMatLocation, gl.FALSE, mMat );
gl.uniformMatrix4fv(vMatLocation, gl.FALSE, vMat );
gl.uniformMatrix4fv(pMatLocation, gl.FALSE, pMat );
...
위의 코드들과 같이 M Matrix, V Matrix, P Matrix를 사용해서 MVP 기법을 적용하고 함수들을 이용해 각각의 Matrix를 사용할 수 있다.
그 중에서도 V Matrix가 Camera의 위치를 표현하고, V Matrix는 lookAt이라는 함수의 Parameter로 사용된다. lookAt 함수는 다음과 같다.
mat4.lookAt(out, eye, center, up);
lookAt 함수의 Parameter는 Out, Eye, Center, Up의 4개를 가지고 있다. Out은 lookAt 함수의 결과가 저장될 배열, Eye는 Camera의 위치 배열, Center는 Camera가 보는 지점 배열, Up은 Camera의 Up vector 방향 배열이다. 이 중에서 우리는 Eye Parameter를 조절해 Camera의 위치를 변경할 수 있다. 따라서 아래에서 설명할 x, y, z좌표의 값은 Eye의 x, y, z값과 같다.
3.Camera의 이동과 좌표계에 대한 이해
아래는 MVP 기법을 적용한 모델(이하 MVP 모델)에서 카메라의 좌표계를 표현한 사진이다.
위 사진과 같이 MVP 모델에서의 좌표계는 오른손 좌표계이다. 즉, x축, y축, z축의 방향은 다음와 같다.
- x축 : 사용자 시점 기준 오른쪽 방향이 양의 방향, 사용자 시점 기준 왼쪽 방향이 음의 방향
- y축 : 사용자 시점 기준 위쪽 방향이 양의 방향, 사용자 시점 기준 아래쪽 방향이 음의 방향
- z축 : 사용자 시점 기준 가까이 오는 방향이 양의 방향, 사용자 시점 기준 멀어지는 방향이 음의 방향
이러한 Camera의 좌표계를 실습 Code를 이용해서 자세하게 알아보자. 아래는 실습 Code의 초기 화면이다. 실습 코드는 마우스의 드래그와 휠 움직임으로 lookAt 함수에서 Eye 배열의 Parameter를, 즉 카메라의 위치를 바꿔볼 수 있는 예제이다
실습 코드는 검정색 Canvas에서 마우스의 드래그, 혹은 마우스 휠 움직임으로 Camera의 위치를 움직일 수 있는 코드이다. Description 버튼은 실습 코드의 설명과 기능에 대해 간략하게 설명하는 창을 띄우는 버튼이다. 그 아래에는 여러가지 버튼이 있는데, 각각의 기능은 다음과 같다.
- Rotate 버튼 : Cube의 Y축 회전을 키고 끄는 버튼
- Reset All 버튼 : Cube의 회전을 멈추고, Cube의 초기 위치와 Camera의 초기 위치로 돌아가는 버튼
- Reset Model Position 버튼 : Cube의 초기 위치로 돌아가는 버튼
- Reset Camera Position 버튼 : Camera의 초기 위치로 돌아가는 버튼
버튼 밑에는 두 개의 Text area가 있는데, 각각의 Text area에 나타나는 정보는 다음과 같다.
- 위의 Text area : 마우스 드래그의 시작 좌표와 종료 좌표, 그리고 마우스가 이동한 거리를 보여준다. 이 정보를 기반으로 Camera가 움직인다.
- 아래의 Text area : Camera의 좌표(x,y,z)를 Camera가 이동할 때마다 갱신해서 보여준다.
직접 마우스 드래그로 Camera를 움직인 결과에 대해 살펴보자. Default Camera의 위치는 [0,0,2.0]으로 설정했다. 즉, 물체에서 Z축으로 2만큼 떨어진 곳에 카메라가 위치한다는 뜻이다. 따라서 초기 카메라의 위치에서 Cube의 모습은 기초 화면에서 보이는 모습과 같다.
아래에 사진은 Canvas에서 왼쪽에서 오른쪽으로 드래그 한 결과이다.
위 사진은 왼쪽에서 오른쪽으로 드래그 한 결과이다. Text area에서 Camera의 위치를 확인해보니 x가 음의 방향으로 1.24만큼 이동한 것을 볼 수 있다.
따라서 사용자 시점에서 왼쪽이 Camera에서는 x의 음의 방향인 것을 확인할 수 있다. 그 반대 방향인 사용자 시점에서 오른쪽은 x의 양의 방향이다다.
또, 아래의 사진은 Canvas에서 위에서 아래로 드래그 한 결과이다.
위 사진은 위에서 아래로 드래그 한 결과이다. y값이 양의 방향으로 0.81 이동한 것을 볼 수 있다.
따라서 사용자 시점에서 위쪽이 Camera에서는 y축의 양의 방향인 것을 확인할 수 있다. 그 반대 방향인 사용자 시점에서 아래쪽은 y의 음의 방향이다.
마지막으로, 아래의 사진은 Canvas에서 마우스 휠을 앞으로 돌린 결과다.
위 사진은 마우스 휠을 앞으로 돌렸을 때의 결과이다. Z값이 2에서 0.0까지 줄어들어서 Cube의 안쪽을 확인할 수 있다.
따라서 사용자 시점에서 멀어지는 방향이 Camera에서는 z의 음의 방향인 것을 확인할 수 있다. 그 반대 방향인 사용자 시점으로 가까워지는 방향이 z의 양의 방향이다.
하지만 lookAt 함수에서 중요한 점은 At을 고정하고 Camera의 위치만 바꾸는 것으로는 정확한 시점을 표현할 수 없다는 점이다. 아래의 사진을 보자.
대각선으로 드래그 한 후 휠을 앞으로 돌렸음에도 불구하고 Cube의 안쪽을 확인할 수 없는 모습을 볼 수 있다.
이와 같은 현상을 해결할려면 Eye Vector와 At Vector를 추가적으로 조정할 필요가 있다. 그 과정에 대해서는 다음 Tutorial에서는 다루도록 하겠다.
실습 Code를 통해 Camera의 x, y, z축 이동에 대해 알아보았다. 다시 한번 Camera의 좌표계에 대해 정리해보면 아래와 같다.
- x축 : 사용자 시점 기준 오른쪽 방향이 양의 방향, 사용자 시점 기준 왼쪽 방향이 음의 방향
- y축 : 사용자 시점 기준 위쪽 방향이 양의 방향, 사용자 시점 기준 아래쪽 방향이 음의 방향
- z축 : 사용자 시점 기준 가까이 오는 방향이 양의 방향, 사용자 시점 기준 멀어지는 방향이 음의 방향
이외에도 여러가지 Camera의 이동, 예를 들면 대각선 드래그, 오른쪽에서 왼쪽 드래그, 아래에서 위 드래그, 마우스 휠 뒤쪽으로 회전 등을 실습 Code에서 직접 진행해 볼 수 있다. 이와 같은 실습으로, Camera의 이동에 대해 더욱 확실하게 이해할 수 있다.
실습 Code 설명
실습 Code는 WebGL Cube Code에서 마우스로 LookAt의 Camera Position을 조절할 수 있도록 코드를 수정했다.
- mMat, vMat, pMat을 renderScene 함수 외부에서 제어하기 위해 전역변수로 선언해준다.
- vMat의 Camera Parameter에서 x, y, z 좌표를 표현하는 vMat_x, vMat_y, vMat_z를 전역변수로 선언한다. 이 때의 초기 Camera Position은 [0,0,2.0]이다.
- lookAt 함수의 Camera Paramter에 들어갈 Camera 배열을 전역변수로 선언하고 vMat_x, vMat_y, vMat_z을 Camera의 x, y, z 좌표로 설정한다.
//MVP array
var mMat = [];
var vMat = [];
var pMat = [];
//vMat initialize
var vMat_x=0.0;
var vMat_y=0.0;
var vMat_z=2.0;
//Array that used in lootAt function. Define camera position
var camera=[vMat_x,vMat_y,vMat_z];
- renderScene 함수에서는 lookAt 함수에서 Camera Parameter에 위에서 선언한 Camera 배열을 넣어준다.
mat4.lookAt(vMat, camera, [0.0,0.0,0.0], [0.0, 1.0, 0.0]);
그 후에 vMat_x, vMat_y, vMat_z를 마우스 드래그를 통해 조작할 수 있는 Code를 작성했다. 우선 마우스의 휠의 움직임으로 vMat_z의 값을 조작할 수 있는 Code이다.
- e.wheelDelta가 0보다 작을 떄, 즉 마우스 휠을 뒤로 돌렸을 때 vMat_z를 증가시킨다. 반대로 마우스 휠을 앞으로 돌렸을 때 vMat_z를 감소시킨다. vMat_z는 최소 0까지 이동할 수 있다.
function wheel(){
canvas.onmousewheel=function(e){
if(e.wheelDelta<0){
//if wheel rolled to backward
//vMat z coord + 0.2
vMat_z+=0.2;
//camera position renewal
camera=[vMat_x,vMat_y,vMat_z];
//camera coord print
coord.value="camera position\nx :" + camera[0] + "\ny : "+camera[1]+"\nz : "+camera[2]+"\r\n";
}
else{
//if wheel rolled to forward
//vMat z coord - 0.2
vMat_z-=0.2;
//restrict minimun z to 0
if(vMat_z<0){
vMat_z=0;
}
//camera position renewal
camera=[vMat_x,vMat_y,vMat_z];
//camera coord print
coord.value="camera position\nx :" + camera[0] + "\ny : "+camera[1]+"\nz : "+camera[2]+"\r\n";
}
}
}
마지막으로 마우스의 드래그를 통해 vMat_x, vMat_y의 값을 조작하는 Code이다.
- 마우스가 드래그한 거리를 계산한 후, x축과 y축으로 이동한 거리를 3000으로 나눈 값만큼 vMat_x, vMat_y를 이동시킨다.
//drag start action
canvas.onmousedown=function(){
//drag starting coord save
clicked_x=event.offsetX;
clicked_y=event.offsetY;
click_check=1;
};
//drag end action
canvas.onmouseup=function(){
click_check=0;
};
canvas.onmousemove=function(){
if(click_check==1){
//drag ending coord save
moved_x=event.offsetX;
moved_y=event.offsetY;
//drag ending cood - drag starting coord
//down to up : camera y move to - direction
//up to down : camera y move to + direction
//left to right : camera x move to - direction
//right to left : camera x move to + direction
//also used to diagonal direction
between_x=-((moved_x-clicked_x)/3000);
between_y=((moved_y-clicked_y)/3000);
//for vMat, vMat_x and vMat_y renewal
vMat_x+=between_x;
vMat_y+=between_y;
//vMat renewal
camera=[vMat_x,vMat_y,vMat_z];
//print moving coord in upper textarea
moving.value="moving distance\nx : " + between_x + ", y : "+between_y;
moving.value="to camera x :" + moved_x + ", y : "+moved_y+"\r\n"+moving.value;
moving.value="from camera x :" + clicked_x + ", y : "+clicked_y+"\r\n"+moving.value;
//print current camera position(print camera array) in lower textarea
coord.value="camera position\nx : " + camera[0] + "\ny : "+camera[1]+"\nz : "+camera[2];
}
}
Reference
WebGL Cube Code 참고
MVP 모델 설명
lookAt 함수 설명
Camera 좌표계 사진 - 오른손 좌표계
Camera 좌표계 사진 - Camera와 Frustum
실습 Code의 Mouse 드래그 이벤트 예시
실습 Code의 Mouse 휠 이벤트 예시