WebGL 벡터란 ?
프로그래밍 관점에서 보면, WebGL 에서 사용하는 벡터란 ,
대부분 x,y 로 표기되는 원소가 두개인 배열 혹은 x,y,z 으로 표기 되는 원소가 3개인 배열 정도로 이해 할 수 있을 것 같습니다.
배열을 기준으로 보면 1차원 배열의 원소의 갯수만 차이가 있는 것이고, 데이터가 [1,2,3] 이 있을 때 행(Row) 을 기준으로 보면 행백터 이고,
열(Column) 을 기준 으로 보면 열백터로 지칭하고 있습니다. 아래의 예에서와 같이 세로, 가로로 이해하면 편할것 같습니다.
[1 열백터 [1,2,3] 행백터
2
3],
WegGL 에서는 열기준의 계산이 이뤄지기 때문에 향후 행렬 계산할 때 약간의 주의가 필요합니다. 행렬은 추후 이야기 하겠지만, 가로 세로가 복수개로 구성된 배열이기 때문에 2차원 배열 Matrix 를 생각하시면 되고, 벡터는 가로 혹은 세로가 1인 1차원 배열을 생각 하시면 될 것 같습니다.
벡터 예시
X,Y축을 기준으로 평면에 있는 어떤점을 한번 생각해 보겠습니다.
2차원 평면이 X:0, Y:0 점을 기준으로 [1,0] 의 좌표(일반적으로 첫번째 원소가 X, 다음 원소가 Y) 는 X축으로 1만큼
움직이고, Y 방향으로 0만큼 움지임(안움직임)을 의미 한다고 생각 할 수 있습니다.
이때 [1,0] 좌표의 길이는 Y방향으로 움직이지 않았고 X 방향으로 1만큼 움직였으니 길이는 1이라는 것을 알 수 있습니다.
아래의 수식에서와 같이 2차원 평면에서의 길이는 x,y 의 제곱을 더해 루트로 구한 값이고,
3차원 공간에서의 길이는 x,y,z 의 제곱을 구해 모두 더한 값에 루트로 값을 구할 수 있습니다.
$$
2차원 = \sqrt{x^2+y^2}, 3차원 = \sqrt{x^2+y^2+z^2}
$$
약간만 더 생각해 보면, 2차원 평면에서 a 좌표가 [1, 0] 이고 b 좌표가 [0, 1] 이라면, a 좌표는 x축으로 1만큼 이동한 것이고,
b 좌표는 x가 0 이므로 Y 축으로 1 이동한 좌표를 연상할 수 있습니다.
직관적으로 두 좌표 모두 길이가 1이라는 것을 알 수 있습니다. 이제 좌표 [2, 2] 인 c 점을 상상해 보겠습니다.
x 방향으로 2, y 방향으로 2 움직인 좌표라는 것을 알 수 있습니다.
a, b 좌표가 각각 x축 방향, y 축 방향으로만 움직였으니, a와 b 좌표는 직각을 이루고 있을 것입니다. c 는 [2, 2] 이기 때문에
45도 각도로 우상향하는 지점에 위치해 있는 좌표라고 생각 할 수 있습니다.
길이는 2.828427 … 이고 각 x,y 좌표값을 길이로 나눠주면 0.707106… 의 값이 나옵니다.
c의 좌표를 길이로 나눈 좌표는 [0.707106… , 0.707106…] 의 값이고 이것의 길이를 구하면 1이 나옵니다.
이렇게 좌표의 길이가 1이 되도록 구성해 주는 것을 단위 벡터라고 하고
WebGL 의 연산에서 많이 사용되고 있습니다.
이런 방식을 normalize 한다고 이야기 하고 있습니다.
$$ \sqrt{2^2 + 2^2} = 2.828427 …, {2 \over \sqrt{8}} = 0.707106… $$
Javascript 로 Vector 의 길이 정규화(Normalize) 구현
백터의 길이 구하기
/**
* 백터의 길이를 계산하기 위한 함수
* @param {*} v1
* @returns
*/
export const getVectorLength = ( v1 ) => {
if ( !isValidArrayValues(v1) )
return undefined;
let sum = 0.0;
for ( let i = 0; i < v1.length; i++ ) {
sum += (v1[i]*v1[i]);
}
return Math.sqrt(sum);
};
백터 정규화 ( Unit Vector 만들기 )
/**
* Normalize 하기 위한 함수
* @param {*} v1
* @param {*} needRound
* @returns
*/
export const makeNormalizeVector = ( v1, needRound ) => {
if ( !isValidArrayValues(v1) )
return undefined;
const len = v1.length;
const result = new Float32Array(len);
const lv = getVectorLength(v1);
alert ( lv );
if ( lv == 0 ) {
return result;
}
if ( needRound ) {
for ( let i = 0; i < len; i++ ) {
result[i] = makeRoundValues(v1[i]/lv);
}
} else {
for ( let i = 0; i < len; i++ ) {
result[i] = v1[i]/lv;
}
}
return result;
};
사용한 함수
const isValidArrayValues = ( v1 ) => {
if ( !v1 || !v1.length )
return false;
return true;
};
/**
* 2차원 배열이라도( Matrix ) Typed Array 로 사용될 경우 1차원 형식으로 구성
* 그렇기 때문에 1차원 배열로 간주함
* @param {*} mat
* @param {*} roundValue
* @param {*} needClone
* @returns
*/
export const makeRoundValues = ( mat, roundValue, needClone ) => {
if ( !isValidArrayValues(mat)) {
return mat;
}
if ( !roundValue || roundValue < 0 ) {
roundValue = 100000; // javascript 에서 부동 소수점 연산 오류를 피하기 위한 값
}
const len = mat.length;
let result = mat;
if ( needClone ) {
result = new Float32Array(len);
if ( mat.rows ) {
result.rows = mat.rows;
}
if ( mat.cols ) {
result.cols = mat.cols;
}
}
for ( let i = 0; i < len; i++ ) {
result[i] = Math.round(mat[i]*roundValue)/roundValue;
}
return result;
};
페이지에서 사용하기
<script type="module">
import * as TypedMatrixUtils from "../js/TypedMatrixUtils.js"
const v1 = new Float32Array([1,2,3]);
const normal = TypedMatrixUtils.makeNormalizeVector(v1,true);
const nLen = TypedMatrixUtils.getVectorLength(normal);
console.log ( v1 + "\n" + normal + "\n" + nLen );
</script>