https://webglfundamentals.org/webgl/lessons/webgl-2d-matrices.html
https://github.com/Myoungmin/WebGL_Fundamentals
WebGL 2D : Matrices
앞서 지오메트리 평행 이동, 지오메트리 회전, 지오메트리 스케일에 대해 학습하였다.
각각의 변환은 셰이더의 변경이 필요하고 3개의 변환은 각각의 순서에 따라 달라진다.
만약 이 순서를 다르게 하면 다른 결과를 얻게된다.
이 순서를 다르게하여 값을 얻고 싶다면 이전 방법으로는 원하는 새로운 순서로 평행 이동, 회전, 스케일을 적용한 다른 셰이더를 작성해야 한다.
하지만 행렬을 사용하면 셰이더를 다시 작성해야 하는 번거로움이 사라진다.
행렬을 함께 곱하고 모든 변환을 한 번에 적용한다.
두 행렬을 받아서 곱하고 결과를 반환하는 함수 m3.multiply 정의
var m3 = {
multiply: function(a, b) {
var a00 = a[0 * 3 + 0];
var a01 = a[0 * 3 + 1];
var a02 = a[0 * 3 + 2];
var a10 = a[1 * 3 + 0];
var a11 = a[1 * 3 + 1];
var a12 = a[1 * 3 + 2];
var a20 = a[2 * 3 + 0];
var a21 = a[2 * 3 + 1];
var a22 = a[2 * 3 + 2];
var b00 = b[0 * 3 + 0];
var b01 = b[0 * 3 + 1];
var b02 = b[0 * 3 + 2];
var b10 = b[1 * 3 + 0];
var b11 = b[1 * 3 + 1];
var b12 = b[1 * 3 + 2];
var b20 = b[2 * 3 + 0];
var b21 = b[2 * 3 + 1];
var b22 = b[2 * 3 + 2];
return [
b00 * a00 + b01 * a10 + b02 * a20,
b00 * a01 + b01 * a11 + b02 * a21,
b00 * a02 + b01 * a12 + b02 * a22,
b10 * a00 + b11 * a10 + b12 * a20,
b10 * a01 + b11 * a11 + b12 * a21,
b10 * a02 + b11 * a12 + b12 * a22,
b20 * a00 + b21 * a10 + b22 * a20,
b20 * a01 + b21 * a11 + b22 * a21,
b20 * a02 + b21 * a12 + b22 * a22,
];
}
}
평행 이동, 회전, 스케일을 위한 행렬을 만드는 함수
var m3 = {
translation: function(tx, ty) {
return [
1, 0, 0,
0, 1, 0,
tx, ty, 1,
];
},
rotation: function(angleInRadians) {
var c = Math.cos(angleInRadians);
var s = Math.sin(angleInRadians);
return [
c,-s, 0,
s, c, 0,
0, 0, 1,
];
},
scaling: function(sx, sy) {
return [
sx, 0, 0,
0, sy, 0,
0, 0, 1,
];
},
};
행렬 연산을 사용하게된 셰이더. 행렬을 사용하면 이전 셰이더보다 훨씬 간단해진다.
<script id="vertex-shader-2d" type="x-shader/x-vertex">
attribute vec2 a_position;
uniform vec2 u_resolution;
uniform mat3 u_matrix;
void main() {
// 위치에 행렬 곱하기
vec2 position = (u_matrix * vec3(a_position, 1)).xy;
//...
자바스크립트에서 사용
// 장면 그리기
function drawScene() {
//,,,
// 행렬 계산
var translationMatrix = m3.translation(translation[0], translation[1]);
var rotationMatrix = m3.rotation(angleInRadians);
var scaleMatrix = m3.scaling(scale[0], scale[1]);
// 행렬 곱하기
// scale -> rotation -> translation 순서
var matrix = m3.multiply(translationMatrix, rotationMatrix);
matrix = m3.multiply(matrix, scaleMatrix);
// 행렬 설정
gl.uniformMatrix3fv(matrixLocation, false, matrix);
// 사각형 그리기
gl.drawArrays(gl.TRIANGLES, 0, 18);
}
순서를 변경하려는 경우 새로운 셰이더를 작성하지 않아도 된다.
자바스크립트에서 행렬 곱셈 수식의 순서만 변경하면 된다.
//...
// 행렬 곱하기
// translation -> rotation -> scale 순서로 변경
var matrix = m3.multiply(scaleMatrix, rotationMatrix);
matrix = m3.multiply(matrix, translationMatrix);
//...
행렬을 적용할 수 있다는 것은 신체의 팔, 태양 주변에 있는 행성의 위성, 나무의 가지같은 계층적 애니메이션에 특히 중요하다.
행렬을 이용하여 원점을 이동하고, vertex shader를 간단하게 바꾸기
포토샵같은 프로그램이 회전점을 이동하여 어떤 지점에서든 회전이나 크기를 조정하는 것도 행렬을 통해 간단하게 가능하다.
행렬 수학을 적용하면 여러 단계의 다소 복잡한 셰이더를 단계가 하나인 간단한 셰이더로 변경이 가능하다.
'F'의 원점을 중심으로 이동할 행렬 만들기
// 'F'의 원점을 중심으로 이동할 행렬 만들기
var moveOriginMatrix = m3.translation(-50, -75);
//...
// 행렬 곱하기
var matrix = m3.multiply(translationMatrix, rotationMatrix);
matrix = m3.multiply(matrix, scaleMatrix);
matrix = m3.multiply(matrix, moveOriginMatrix);
셰이더에서 수행했던 픽셀을 클립 공간으로 변환하는 코드를 행렬로 변경
기존 셰이더에서 수행했던 픽셀을 클립 공간으로 변환하는 코드
//...
// 사각형을 픽셀에서 0.0에서 1.0사이로 변환
vec2 zeroToOne = position / u_resolution;
// 0->1에서 0->2로 변환
vec2 zeroToTwo = zeroToOne * 2.0;
// 0->2에서 -1->+1로 변환 (클립 공간)
vec2 clipSpace = zeroToTwo - 1.0;
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
직접 주어진 해상도에 대한 '투영 행렬'을 생성하는 함수
var m3 = {
projection: function(width, height) {
// 참고: 이 행렬은 Y축을 뒤집어서 0이 위쪽에 있도록 합니다.
return [
2 / width, 0, 0,
0, -2 / height, 0,
-1, 1, 1
];
},
//…
자바스크립트에서 생성한 투영 행렬 곱한다.
// 장면 그리기
function drawScene() {
//...
// 행렬 계산
var projectionMatrix = m3.projection(gl.canvas.clientWidth, gl.canvas.clientHeight);
//...
// 행렬 곱하기
var matrix = m3.multiply(projectionMatrix, translationMatrix);
matrix = m3.multiply(matrix, rotationMatrix);
matrix = m3.multiply(matrix, scaleMatrix);
//...
}
투영 행렬을 적용하였기에 vertex shader를 단순하게 변경할 수 있다.
<script id="vertex-shader-2d" type="x-shader/x-vertex">
attribute vec2 a_position;
uniform mat3 u_matrix;
void main() {
// 위치에 행렬 곱하기
gl_Position = vec4((u_matrix * vec3(a_position, 1)).xy, 0, 1);
}
</script>
해상도를 설정하는 코드 또한 셰이더에서 삭제되었고 모두 행렬을 통해 적용된다.
행렬을 적용하는 순서
projectionMat * translationMat * rotationMat * scaleMat * position
행렬은 교환 법칙이 성립하지 않고, 결합 법칙은 성립한다.
교환 법칙이 성립하지 않기 때문에 원하는 값을 얻으려면 적용하는 순서가 중요하다.
스케일, 회전, 이동, 투영 순으로 적용된다.
오른쪽에서 시작하여 왼쪽으로 계산
scale을 적용하여 scaledPosition을 얻는다.
scaledPosition = scaleMat * position
scaledPosition에 rotation을 적용하여 rotatedScaledPosition을 얻는다.
rotatedScaledPosition = rotationMat * scaledPosition
rotatedScaledPosition에 translation을 적용하여 translatedRotatedScaledPosition를 얻는다.
translatedRotatedScaledPosition = translationMat * rotatedScaledPosition
마지막으로 최종적인 결과물인 클립 공간의 위치를 얻기 위해 translatedRotatedScaledPosition에 투영 행렬을 곱한다.
clipspacePosition = projectioMatrix * translatedRotatedScaledPosition
결합 법칙이 성립하기 때문에 모든 좌표계 변환 행렬 계산을 미리하고, vertex 좌표에 적용하기 때문에 shader가 간단해지고 변환을 따로 따로 vertex마다 적용할 필요가 없어, 컴퓨터 리소스를 절약할 수 있다.
(변환 행렬 묶음) * position
https://myoungmin.github.io/WebGL_Fundamentals/
'WebGL' 카테고리의 다른 글
WebGL Fundamentals > WebGL 3D : Perspective (0) | 2022.07.02 |
---|---|
WebGL Fundamentals > WebGL 3D : Orthographic (0) | 2022.07.01 |
WebGL Fundamentals > WebGL 2D : Scale (0) | 2022.06.30 |
WebGL Fundamentals > WebGL 2D : Rotation (0) | 2022.06.30 |
WebGL Fundamentals > WebGL 2D : Translation (0) | 2022.06.30 |