https://webglfundamentals.org/webgl/lessons/webgl-3d-perspective.html
https://github.com/Myoungmin/WebGL_Fundamentals
WebGL 3D : Perspective
현재 샘플에서 더 멀리 있는 것들이 더 작게 보이도록 만드는 쉬운 방법은 클립 공간의 X와 Y를 Z로 나누는 것이다
Z가 증가할수록, 멀어질수록, 더 작게 그려지게 된다.
나누기 전에 Z를 fudgeFactor와 곱하면 주어진 거리에 따라 얼마의 비율로 작게 할지 조정할 수 있다.
"fudgeFactor"를 곱한 뒤 Z로 나누도록 정점 셰이더를 수정
<script id="vertex-shader-3d" type="x-shader/x-vertex">
//...
uniform float u_fudgeFactor;
//...
void main() {
// 위치에 행렬 곱하기
vec4 position = u_matrix * a_position;
// 나누려는 z 조정
// Z가 -1에서 +1인 클립 공간에 있기 때문에, 나눌 값을 0에서 +2로 조정하기 위해 1을 더해준다.
float zToDivideBy = 1.0 + position.z * u_fudgeFactor;
// x와 y를 z로 나누기
gl_Position = vec4(position.xy / zToDivideBy, position.zw);
}
</script>
Z가 -1에서 +1인 클립 공간에 있기 때문에,
나눌 값을 0에서 +2로 조정하기 위해 1을 더해준다.
fudgeFactor로 데이터를 넘길 수 있도록 자바스크립트를 작성
//...
var fudgeLocation = gl.getUniformLocation(program, "u_fudgeFactor");
//...
var fudgeFactor = 1;
//...
function drawScene() {
...
// fudgeFactor 설정
gl.uniform1f(fudgeLocation, fudgeFactor);
// 지오메트리 그리기
var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = 16 * 6;
gl.drawArrays(primitiveType, offset, count);
WebGL은 정점 셰이더의 gl_Position에 할당한 x,y,z,w 값을 가져와 자동으로 w로 나눈다.
zToDivideBy로 나누는 작업을 수행하던 vertex shader에서 나누는 작업을 제거하고 gl_Position.w에 zToDivideBy을 넣어도 동일하게 동작하는 것을 확인할 수 있다.
<script id="vertex-shader-3d" type="x-shader/x-vertex">
//...
uniform float u_fudgeFactor;
//...
void main() {
// 위치에 행렬 곱하기
vec4 position = u_matrix * a_position;
// 나누려는 z 조정
float zToDivideBy = 1.0 + position.z * u_fudgeFactor;
// x와 y를 z로 나누는 것 제거
//gl_Position = vec4(position.xy / zToDivideBy, position.zw);
// x, y, z를 "zToDivideBy"로 나누기
// w를 zToDivideBy 설정하기만 하면 WebGL이 자동으로 나눈다.
gl_Position = vec4(position.xyz, zToDivideBy);
}
</script>
WebGL이 자동으로 W로 나눈다는 사실이 유용한 이유.
z를 w에 복사하기 위해 또 다른 행렬을 사용할 수 있기 때문이다.
WebGL이 편리하게 Z로 나누는 작업을 해주기 때문에 행렬만 사용하도록 수정할 수 있다.
행렬 연산으로 w_out = z_in * fudgeFactor + 1;이 적용되도록 설정한다.
Z → W 행렬을 만드는 함수를 만든다.
function makeZToWMatrix(fudgeFactor) {
return [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, fudgeFactor,
0, 0, 0, 1,
];
}
그걸 사용하도록 자바스크립트를 설정
//...
// 행렬 계산
var matrix = makeZToWMatrix(fudgeFactor);
matrix = m4.multiply(matrix, m4.projection(gl.canvas.clientWidth, gl.canvas.clientHeight, 400));
matrix = m4.translate(matrix, translation[0], translation[1], translation[2]);
matrix = m4.xRotate(matrix, rotation[0]);
matrix = m4.yRotate(matrix, rotation[1]);
matrix = m4.zRotate(matrix, rotation[2]);
matrix = m4.scale(matrix, scale[0], scale[1], scale[2]);
//...
지금까지 내용으로 기본적으로 모든 내용들은 Z로 나누는 게 원근감을 준다는 것과,
WebGL이 편리하게 Z로 나누는 작업을 해준다는 사실을 알 수 있다.
Perspective를 적용한 이후 3D 객체의 Z를 조정하면 일찍 사라지는 현상을 방지하기
WebGL은 X와 Y 혹은 +1에서 -1까지 클리핑하는 것처럼 Z도 클리핑을 한다.
Z에 하나의 행렬 연산을 수행하여 원하는 범위를 -1에서 +1사이로 다시 매핑하면 일찍 사라지는 현상을 방지할 수 있다.
fudgeFactor 대신에 fieldOfView를 결정하고, 이를 위한 올바른 값을 계산한다.
행렬을 만드는 함수
var m4 = {
perspective: function(fieldOfViewInRadians, aspect, near, far) {
var f = Math.tan(Math.PI * 0.5 - 0.5 * fieldOfViewInRadians);
var rangeInv = 1.0 / (near - far);
return [
f / aspect, 0, 0, 0,
0, f, 0, 0,
0, 0, (near + far) * rangeInv, -1,
0, 0, near * far * rangeInv * 2, 0
];
},
//...
행렬의 값이 결정되는 과정
- zNear와 fieldOfView가 주어지면, zNear의 항목이 Z = -1이 되도록필요한 값을 계산한다.
- fieldOfView의 절반인 zNear의 위와 아래는 각각 Y = -1과 Y = 1이 된다.
- 일반적으로 디스플레이 영역의 width / height로 aspect가 설정되고, aspect를 이용하여 Y에서 X에 사용할 값을 계산한다.
- 마지막으로 zFar의 항목이 Z = 1이 되도록 하기 위해 Z에서 얼마나 크기를 조정할지 알아낸다.
만든 행렬이 하는 역할
- 절두체가 클립 공간 안에 있도록 하고 zNear과 zFar를 클립공간의 앞과 뒤로 변환한다.
- 각도별로 시야각을 선택한다.
4면 원뿔 모양을 "절두체(frustum)"라고 한다.
행렬은 절두체 안에 있는 공간을 가져와서 클립 공간으로 변환한다.
zNear은 물체의 앞쪽이 잘리는 곳을 정의하고, zFar은 물체의 뒤쪽이 잘리는 곳을 정의한다.
https://myoungmin.github.io/WebGL_Fundamentals/
'WebGL' 카테고리의 다른 글
WebGL Fundamentals > Animation (0) | 2022.07.05 |
---|---|
WebGL Fundamentals > WebGL 3D : Cameras (0) | 2022.07.03 |
WebGL Fundamentals > WebGL 3D : Orthographic (0) | 2022.07.01 |
WebGL Fundamentals > WebGL 2D : Matrices (0) | 2022.07.01 |
WebGL Fundamentals > WebGL 2D : Scale (0) | 2022.06.30 |