HTML5의 requestAnimationFrame 함수

HTML5를 지원하는 웹브라우저 기반의 JavaScript의 window 객체에는 requestAnimationFrame 함수가 정의되어 있습니다. 더 정확히는, 이 함수는 WebGL의 지원과 함께 제공됩니다. 이 함수는 지정된 연산(함수)의 호출을 다른 연산에 방해를 주지 않고 최대한 빨리 호출해 주는 함수입니다. 웹GL에서 이 함수의 용도를 충분히 짐작할 수 있습니다.

네, 아래의 애니메이션은 requestAnimationFrame 함수를 사용한 예제 입니다. 이 예제를 통해 requestAnimFrame 함수에 대해 정리해 보겠습니다.

[xyz-ihs snippet=”requestAnimationFrame2″]

위의 예제에 대한 코드를 살펴보겠습니다. 먼저 애니메이션이 이루어지는 캔버스에 대한 정의 코드입니다.


다음은 requestAnimationFrame 함수가 아직 지원이 되지 않을 경우, 또는 브러우저 마다 서로 다른 이름으로 함수로 제공되는 문제에 대한 해법으로 일괄적으로 requestAnimFrame이라는 함수로 지원되도록 하는 코드입니다.

window.requestAnimFrame = (function(callback) {
    return window.requestAnimationFrame || 
    window.webkitRequestAnimationFrame || 
    window.mozRequestAnimationFrame || 
    window.oRequestAnimationFrame || 
    window.msRequestAnimationFrame ||
    function(callback) { window.setTimeout(callback, 1000 / 60); };
})();

위의 코드를 보면 만약 어떠한 requestAnimationFrame 함수도 지원되지 않을 경우 1000/60밀리초 후에 발생하는 Timer를 사용하고 있습니다.

다음은 캔버스에 사각형을 렌더링하고 애니메이션을 수행하는 코드입니다.

var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');

var myRectangle = {
    x: 0,
    y: 75,
    width: 100,
    height: 50,
    borderWidth: 5
};

drawRectangle(myRectangle, context);

setTimeout(function() {
    var startTime = (new Date()).getTime();
        animate(myRectangle, canvas, context, startTime);
    }, 2000);

4번 코드가 캔버스에 렌더링할 사각형의 스펙입니다. 12번 코드를 통해 이 사각형의 스펙대로 렌더링합니다. 그리고 14번 코드에서 2초 후에 animation 함수를 호출하고 있는데요. 바로 이 animation 함수가 실제 requestAnimFrame 함수를 이용하여 애니메이션을 수행하는 함수입니다.

drawRectangle 함수는 다음과 같습니다.

function drawRectangle(rect, context) {
    context.beginPath();
    context.rect(rect.x, rect.y, rect.width, rect.height);
    context.fillStyle = '#8ED6FF';
    context.fill();
    context.lineWidth = rect.borderWidth;
    context.strokeStyle = 'black';
    context.stroke();
}

그리고 animation 함수는 다음과 같습니다.

function animate(rect, canvas, context, startTime) {
    var time = (new Date()).getTime() - startTime;
    var linearSpeed = 100;
    var newX = linearSpeed * time / 1000;

    if(newX < canvas.width - rect.width - rect.borderWidth / 2) {
        rect.x = newX;
    }

    context.clearRect(0, 0, canvas.width, canvas.height);

    drawRectangle(rect, context);

    requestAnimFrame(function() {
        animate(rect, canvas, context, startTime);
    });
}

2~4번 코드는 사각형를 움직이도록 새롭게 설정할 x 좌표값을 계산하여 newX 변수에 할당합니다. 6~8번 코드는 계산한 newX 변수를 사각형의 x 속성에 할당하는 것으로써 만약 사각형이 캔버스 밖으로 벗어나지 않도록 하고 있습니다. 10번 코드는 새로운 위치의 사각형을 그리기에 앞서 화면을 지우는 코드이고 12번 코드는 실제로 화면에 사각형을 그리는 함수의 호출이며, 14~16번이 바로 requestAnimFrame 함수를 통해 animate 함수를 호출해 주는 것입니다. 이처럼 animate 함수의 호출을 requestAnimFrame 함수를 통해 등록해 놓으면 가장 적합하고 빠른 시점에 animate 함수를 호출해주게 됩니다.

법선 벡터의 변환을 위한 법선 행렬

3차원에서, 광원에 의한 물체 표면의 표현을 위해 표면에 수직인 법선 벡터를 고려하게 됩니다. 쉽게 생각해 보면, 물체를 구성하는 좌표에 대한 모델뷰 행렬(M)을 이용해 법선 벡터를 변환하는 것으로 충분할듯하지만 아래처럼 어파인(Affine) 변환 중 y축에 대한 크기 변환을 수행했을 경우, 법선 벡터를 모델뷰 행렬로 변환하면, 표면 벡터(S)와 법선 벡터(N)이 더 이상 수직이 아님을 쉽게 알 수 있습니다.

그렇다면 물체에 대해서 어떠한 어파인 변환을 수행하더라도 법선 백터의 고유한 특성, 즉 표면 벡터와 수직인 성질을 유지할 수 있을까하는 것에 대해 정리를 해 봅니다.

먼저 법선 벡터와 표면 벡터는 수직이므로 이 두 벡터의 내적은 0입니다. 즉, 아래와 같습니다.

물체는 모델뷰 행렬에 의해 변환됩니다. 즉, 물체의 표면 역시 모델뷰 행렬에 의해 정확히 변환됩니다. 모델뷰 행렬이 M, 표면 벡터를 S, 변환된 표면 벡터를 S’라고 하면 다음과 같습니다.

위와 같은 맥락으로, 법선 벡터 N이 법선 벡터로써의 특성을 유지하면서 새롭게 변환된 법선 벡터를 N’라고 할때, 법선 벡터로써의 특성을 유지하면서 변환해 주는 행렬, 즉 법선 행렬을 K라고 하면 다음과 같습니다.

법선 벡터의 특성, 즉 변환된 후의 표면 벡터인 S’와 법선 벡터인 N’는 수직이여야 하므로 다음과 같습니다.

S’=MS 그리고 N’=KN이라고 했으므로 바로 위의 식에 각각 대입하면 다음과 같습니다.

벡터의 기본 성질 중, 벡터의 내적의 결과는 첫번째 벡터의 전치에 의한 벡터의 단순 곱과 같으므로 위의 식은 아래와 같습니다.

벡터 곱에 대한 전치는 각 벡터의 전치에 대한 역순의 곱과 같으므로 다음의 첫번째 식과 같습니다.

위의 첫째 식은 순차적으로 2번째와 세번째 식으로 변환이 가능합니다. 위의 식중 세번째 식을 “주식”이라고 하겠습니다. S와 N의 내적의 표현은 S의 전치와 N의 곱과 같고, S와 N의 내적의 결과는 0이므로 다음과 같습니다.

위의 식과 앞서 “주식”이라고 했던 식을 함께 살펴보면, 다음의 결과과 같은 식을 얻을 수 있습니다.

위의 식에서 양변에 M의 전치 행렬에 대한 역행렬을 곱해 주면 최종적으로 K 행렬을 다음처럼 얻을 수 있습니다.

위의 식은 행렬의 기본 성질에 의해 다음과 같습니다.

즉, 어떤 변환(M)에 의해 법선 벡터가 그 고유한 특성을 유지할 수 있는 변환을 위한 행렬인 법선행렬은 모델뷰 행렬의 역행렬에 대한 전치 행렬과 같다는 것을 알 수 있습니다.