본문 바로가기
Computer/WebGL

007. WebGL - Prac06 - Animation

by DogBull 2010. 4. 7.
   이번에는 WebGL을 이용한 Animation을 표현해 보도록하겠다. OpenGL이든 Direct3D이든 Geometry에 대한 연산을 행렬 형태로 표현하고 있음을 잘 알고 있을 것이다. 이동/회전/크기변환 등을 행렬로 표현하면 여러가지 장점이 있어 사용할 뿐, 필수 사항은 아닐 것이다. 이번장에서는 행렬을 사용하지 않고 아주 간단한 Animation을 표현할 것인데, 그 이유는, 행렬을 표현함에 있어 javascript를 이용해야 하기 때문이다. 본 문서들은 javascript에 익숙하지 않다는 가정을 하고 있으므로, javascript로 행렬을 표현하는 방법은 차후 다루기로 하겠다.
   WebGL에서 Animation을 표현하는 방법은 GLSL을 이용한 방법외에는 존재하지 않다라고 해도 무방할 것이다. 그 이유는 다음의 예를 보면 알 수 있을 것이다. 삼각형에 대한 이동을 수행하겠다고 가정하자. 가능한 방법은, 첫째 WebGL을 표현하는 Canvas를 직접 이동시키거나(이것은 어이없는 행동이 될 것이다.), 둘째 삼각형의 각 버텍스를 javascript를 이용하여 옮기거나(버텍스의 값이 변경 될 때 마다 GLSL로 넘어갈 버퍼의 갱신이 이루어 지므로 비효율), 셋째 javascript가 GLSL에게 삼각형의 이동을 위한 값(3차원 좌표, 더 나은 방법은 행렬)을 넘기는 방법(가장 이상적) 들이 있다. 본 예제에서는 당연히 세번째 방법을 사용할 것이다.

이전의 예제에서 아래와 같은 Vertex Shader 코드를 사용했음을 기억할 것이다.
이 코드에서 "버텍스의 위치값"에 해당하는 변수명이 "aPos"이다. 바로  "aPos"의 값을 적당히 변경하면 삼각형의 이동이 이루어 질 것이다. 그렇다면, 어떻게 적당히 변경할 것이가?
[code WebGL]
<script id="VertexShader" type="x-shader/x-vertex">
    attribute vec3 aPos;
    attribute vec4 aColor;

    varying vec4 vColor;

    void main(){
        gl_Position= vec4(aPos, 1.0);
        vColor= aColor;
    }
</script>
[/code]
한 예로, gl_Position= vec4(aPos, 1.0)를 gl_Position= vec4(aPos, 1.0) + vec4(0.5, 0, 0, 0)으로 변경하면, 삼각형이 오른쪽으로 0.5만큼 이동할 것이며, 그 결과는 아래와 같을 것이다.

[code WebGL]
<script id="VertexShader" type="x-shader/x-vertex">
    attribute vec3 aPos;
    attribute vec4 aColor;

    varying vec4 vColor;

    void main(){
        gl_Position= vec4(aPos, 1.0) + vec4(0.5, 0, 0, 0);
        vColor= aColor;
    }
</script>
[/code]


    이는 삼각형이 이동만 했을 뿐 당연히 Animation이라고 부를 수 없다. Animation이라 부를 수 있으려면 적어도 움직인다라는 느낌이 들 수 있어야 하므로, 적당한 매 시간마다 이동의 값을 바꾸어가며 화면을 갱신할 필요가 있다. javascript에서는 setInterval(...)이라는 함수를 이용하여, '사용자가 설정한 매 시간마다, 특정 함수를 호출'하는 기능을 수행할 수 있다. 이 함수에서 삼각형의 위치값을 변경하고, 변경된 값을 GLSL로 넘긴 후, 화면의 갱신을 수행하게 한다면, Animation 표현이 가능하게 될 것이다. 주의할 점은, Animation 표현을 위해 매 시간마다(이하 매 프레임 마다) 불려져야할 구문들과 단 한번만 불려져도 되는 구문들을 구분해야 한다는 것이다. 매 프레임마다 버퍼를 생성한다거나, Shader Script를 읽기/컴파일/링킹등을 수행하는 것과 같은 불필요한 작업은 반드시 피해야 한다.

  (지금부터 변수명을 조금 수정해 보도록하겠다. GLSL에서의 변수명 "aPos"에 대한 핸들을 var vertexPositionAttribute; 로 선언 했었는데, 이를 g_hVertPos로 변경하고, "aColor" 핸들에 대한 변수명을 g_hVertColor로 변경한다. "g_"는 전역에 위치한 변수라는 뜻이고, h는 핸들, 그 다음은 변수 고유의 이름)

앞서 살펴보았던 Vertex Shader코드를 다음과 같이 변경한다.
[code WebGL]
<script id="VertexShader" type="x-shader/x-vertex">
    attribute vec3 aPos;
    attribute vec4 aColor;

    uniform vec4 trans;

    varying vec4 vColor;

    void main(){
        gl_Position= vec4(aPos, 1.0) + trans;
        vColor= aColor;
    }
</script>
[/code]
   위의 코드는 GLSL의 Vertex Shader로 넘어오는 mesh의 각 Vertex의 위치에 trans값을 더해 줌을 볼 수 있다. 따라서 mesh의 모든 Vertex의 위치값에 trans를 더해 줌으로서 mesh의 이동이 수행되는 것이다. 이제, javascript에서 변수명 "trans"의 핸들을 얻을 차례이다. 이 핸들이 있어야만 GLSL에 값을 넘길 수 있기 때문이다.(==Vertex Shader에 값을 넘길 수 있기 때문이다.)

[code WebGL]
//GLSL에서 "trans"라는 변수명의 핸들을 얻음
g_hTrans=    gl.getUniformLocation(shaderProgram, "trans");
if( g_hTrans==-1 ){
    alert("Unable to find 'trans' Uniform Location.");
    return false;
}
[/code]
javascript의 변수명 "g_hTrans"가 GLSL의 "trans"의 핸들이다. "trans"가 uniform으로 선언되어 있으므로, 핸들을 얻는 함수역시 getUniformLocation이다. 이제 g_hTrans을 이용하여, GLSL에 값을 넘길 차례이다.

아래는 g_hTrans핸들을 통해 GLSL에 값을 전달하는 함수이다.
[code WebGL]
gl.uniform4f(g_hTrans, 0.5, 0, 0, 0);
[/code]
g_hTrans핸들은 GLSL에서의 uniform vec4 trans;를 참조하고 있으므로, 값을 넘길 때 uniform4f를 사용해야 한다. 여기까지 수행 하면 앞서 보였던 결과와 같은 결과를 얻을 수 있다.

소소코드는 아래와 같다.
[code WebGL]
<html>

<head>
    <title>WebGL - Prac06 - Animation</title>
    <script type="text/javascript">
        var gl;
        var g_hVertPos;
        var g_hVertColor;
        var g_hTrans;

        function start(){
            if( !initWebGL() ){
                return;
            }

            if( !initShader() ){
                return;
            }

            var verAry=    [
                0.0, 0.0, 0.0,
                0.5, 0.0, 0.0,
                0.0, 1.0, 0.0
            ];

            var colorAry= [
                1.0, 0.0, 0.0, 1.0,
                0.0, 1.0, 0.0, 1.0,
                0.0, 0.0, 1.0, 1.0
            ];

            var glBufVert=    gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, glBufVert);
            gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verAry), gl.STATIC_DRAW);

            var glBufColor=    gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, glBufColor);
            gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colorAry), gl.STATIC_DRAW);

            gl.clearColor(0.4, 0.5, 0.6, 1.0);

            gl.bindBuffer(gl.ARRAY_BUFFER, glBufVert);
            gl.vertexAttribPointer(g_hVertPos, 3, gl.FLOAT, false, 0, 0);

            gl.bindBuffer(gl.ARRAY_BUFFER, glBufColor);
            gl.vertexAttribPointer(g_hVertColor, 4, gl.FLOAT, false, 0, 0);

            gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT);
           
            //x좌표 값을 변경 시킨 후 GLSL에 넘긴다.
            gl.uniform4f(g_hTrans, 0.5, 0, 0, 0);
           
            gl.drawArrays(gl.TRIANGLES, 0, 3);
        }

        function initWebGL(){
            var canvas=    document.getElementById("WebGL-Canvas");
            if( !canvas ){
                alert("Can't find WebGL-Canvas.");
                return false;
            }

            try{
                gl=    canvas.getContext("experimental-webgl");
            }catch(e){
            }

            if( !gl ){
                alert("Unable to initialize WebGL. Your browser may not support it.");
                return false;
            }

            return true;
        }

        function initShader(){
            var vertexShaderDesc=    getShader("VertexShader");
            var vertexShader=        gl.createShader(gl.VERTEX_SHADER);
            gl.shaderSource(vertexShader, vertexShaderDesc);
            gl.compileShader(vertexShader);
            if( !gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS) ){
                alert("Error(VertexShader): " + gl.getShaderInfoLog(vertexShader));
                return false;
            }

            var fragmentShaderDesc=    getShader("FragmentShader");
            var fragmentShader=        gl.createShader(gl.FRAGMENT_SHADER);
            gl.shaderSource(fragmentShader, fragmentShaderDesc);
            gl.compileShader(fragmentShader);
            if( !gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS) ){
                alert("Error(FragmentShader): " + gl.getShaderInfoLog(fragmentShader));
                return false;
            }

            var shaderProgram=    gl.createProgram();
            gl.attachShader(shaderProgram, vertexShader);
            gl.attachShader(shaderProgram, fragmentShader);
            gl.linkProgram(shaderProgram);

            if( !gl.getProgramParameter(shaderProgram, gl.LINK_STATUS) ){
                alert("Unagle to initialize the shader program.");
                return false;
            }

            gl.useProgram(shaderProgram);

            g_hVertPos=    gl.getAttribLocation(shaderProgram, "aPos");
            if( g_hVertPos==-1 ){
                alert("Unable to find Vertex Attribute Location.");
                return false;
            }
            gl.enableVertexAttribArray(g_hVertPos);

            g_hVertColor=    gl.getAttribLocation(shaderProgram, "aColor");           
            if( g_hVertColor==-1 ){
                alert("Unable to find Vertex Color Attribute Location.");
                return false;
            }
            gl.enableVertexAttribArray(g_hVertColor);

            //GLSL에서 "trans"라는 변수명의 핸들을 얻음
            g_hTrans=    gl.getUniformLocation(shaderProgram, "trans");
            if( g_hTrans==-1 ){
                alert("Unable to find 'trans' Uniform Location.");
                return false;
            }

            return true;
        }

        function getShader(id){
            var shaderScript=    document.getElementById(id);
            if( !shaderScript ){
                alert("Unable to get shader script. id='" + id + "'");
                return null;
            }

            var shaderDesc=    "";
            var currentChild=    shaderScript.firstChild;
            while( currentChild ){
                if( currentChild.nodeType==3 ){
                    shaderDesc+=    currentChild.textContent;
                }
                currentChild=    currentChild.nextSibling;
            }

            return shaderDesc;
        }

    </script>

    <script id="VertexShader" type="x-shader/x-vertex">
        attribute vec3 aPos;
        attribute vec4 aColor;

        uniform vec4 trans;

        varying vec4 vColor;

        void main(){
            gl_Position= vec4(aPos, 1.0) + trans;
            vColor= aColor;
        }
    </script>

    <script id="FragmentShader" type="x-shader/x-vertex">
        precision mediump vec4;
        varying vec4 vColor;
        void main(){
            gl_FragColor= vColor;
        }
    </script>

</head>

<body onload="start()">
    <canvas id="WebGL-Canvas" style="border: 1px solid green" width="400" height="300">
        Your browser doesn't appear to support the HTML5 <code>&lt;canvas&gt;</code> element.
    </canvas>
</body>

</html>
[/code]


이제 javascript의 setInterval(...)함수를 이용하여, 매 프레임을 갱신하고, 삼각형의 위치를 변경시켜 보도록 하겠다. 본 예제에서는 아주 단순히 삼각형의 x좌표의 위치만 변경시켜 보도록 하겠다. 삼각형의 x좌표는 매 프레임 마다 어떤 함수에서 갱신될 것이므로 x좌표에 대한 변수는 전역으로 선언해 두고 값을 갱신해 나가야 할 것이다.

소스코드는 아래와 같다.
[code WebGL]
<html>

<head>
    <title>WebGL - Prac06 - Animation</title>
    <script type="text/javascript">
        var gl;
        var g_hVertPos;
        var g_hVertColor;
        var g_hTrans;

        var g_transPosX=0;

        function start(){
            if( !initWebGL() ){
                return;
            }

            if( !initShader() ){
                return;
            }

            var verAry=    [
                0.0, 0.0, 0.0,
                0.5, 0.0, 0.0,
                0.0, 1.0, 0.0
            ];

            var colorAry= [
                1.0, 0.0, 0.0, 1.0,
                0.0, 1.0, 0.0, 1.0,
                0.0, 0.0, 1.0, 1.0
            ];

            var glBufVert=    gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, glBufVert);
            gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verAry), gl.STATIC_DRAW);

            var glBufColor=    gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, glBufColor);
            gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colorAry), gl.STATIC_DRAW);

            gl.clearColor(0.4, 0.5, 0.6, 1.0);

            gl.bindBuffer(gl.ARRAY_BUFFER, glBufVert);
            gl.vertexAttribPointer(g_hVertPos, 3, gl.FLOAT, false, 0, 0);

            gl.bindBuffer(gl.ARRAY_BUFFER, glBufColor);
            gl.vertexAttribPointer(g_hVertColor, 4, gl.FLOAT, false, 0, 0);

            //0.05초 마다 drawScene 호출
            setInterval(drawScene, 50);
        }

        function drawScene(){
            gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT);
           
            //x좌표 값을 변경 시킨 후 GLSL에 넘긴다.
            g_transPosX+=    0.01;
            if( g_transPosX>0.5 )    g_transPosX=    0;
            gl.uniform4f(g_hTrans, g_transPosX, 0, 0, 0);
           
            gl.drawArrays(gl.TRIANGLES, 0, 3);
        }

        function initWebGL(){
            var canvas=    document.getElementById("WebGL-Canvas");
            if( !canvas ){
                alert("Can't find WebGL-Canvas.");
                return false;
            }

            try{
                gl=    canvas.getContext("experimental-webgl");
            }catch(e){
            }

            if( !gl ){
                alert("Unable to initialize WebGL. Your browser may not support it.");
                return false;
            }

            return true;
        }

        function initShader(){
            var vertexShaderDesc=    getShader("VertexShader");
            var vertexShader=        gl.createShader(gl.VERTEX_SHADER);
            gl.shaderSource(vertexShader, vertexShaderDesc);
            gl.compileShader(vertexShader);
            if( !gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS) ){
                alert("Error(VertexShader): " + gl.getShaderInfoLog(vertexShader));
                return false;
            }

            var fragmentShaderDesc=    getShader("FragmentShader");
            var fragmentShader=        gl.createShader(gl.FRAGMENT_SHADER);
            gl.shaderSource(fragmentShader, fragmentShaderDesc);
            gl.compileShader(fragmentShader);
            if( !gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS) ){
                alert("Error(FragmentShader): " + gl.getShaderInfoLog(fragmentShader));
                return false;
            }

            var shaderProgram=    gl.createProgram();
            gl.attachShader(shaderProgram, vertexShader);
            gl.attachShader(shaderProgram, fragmentShader);
            gl.linkProgram(shaderProgram);

            if( !gl.getProgramParameter(shaderProgram, gl.LINK_STATUS) ){
                alert("Unagle to initialize the shader program.");
                return false;
            }

            gl.useProgram(shaderProgram);

            g_hVertPos=    gl.getAttribLocation(shaderProgram, "aPos");
            if( g_hVertPos==-1 ){
                alert("Unable to find Vertex Attribute Location.");
                return false;
            }
            gl.enableVertexAttribArray(g_hVertPos);

            g_hVertColor=    gl.getAttribLocation(shaderProgram, "aColor");           
            if( g_hVertColor==-1 ){
                alert("Unable to find Vertex Color Attribute Location.");
                return false;
            }
            gl.enableVertexAttribArray(g_hVertColor);

            //GLSL에서 "trans"라는 변수명의 핸들을 얻음
            g_hTrans=    gl.getUniformLocation(shaderProgram, "trans");
            if( g_hTrans==-1 ){
                alert("Unable to find 'trans' Uniform Location.");
                return false;
            }

            return true;
        }

        function getShader(id){
            var shaderScript=    document.getElementById(id);
            if( !shaderScript ){
                alert("Unable to get shader script. id='" + id + "'");
                return null;
            }

            var shaderDesc=    "";
            var currentChild=    shaderScript.firstChild;
            while( currentChild ){
                if( currentChild.nodeType==3 ){
                    shaderDesc+=    currentChild.textContent;
                }
                currentChild=    currentChild.nextSibling;
            }

            return shaderDesc;
        }

    </script>

    <script id="VertexShader" type="x-shader/x-vertex">
        attribute vec3 aPos;
        attribute vec4 aColor;

        uniform vec4 trans;

        varying vec4 vColor;

        void main(){
            gl_Position= vec4(aPos, 1.0) + trans;
            vColor= aColor;
        }
    </script>

    <script id="FragmentShader" type="x-shader/x-vertex">
        precision mediump vec4;
        varying vec4 vColor;
        void main(){
            gl_FragColor= vColor;
        }
    </script>

</head>

<body onload="start()">
    <canvas id="WebGL-Canvas" style="border: 1px solid green" width="400" height="300">
        Your browser doesn't appear to support the HTML5 <code>&lt;canvas&gt;</code> element.
    </canvas>
</body>

</html>
[/code]