본문 바로가기
Computer/WebGL

008. WebGL - Prac07 - JavaScript 파일

by DogBull 2010. 4. 7.

   WebGL을 위한 JavaScript의 코드가 조금씩 길어짐에 따라, 소스 코드의 관리 및 가독성이 점점 떨어 지고 있다.  따라서 이번 문서에서는 WebGL을 위해 JavaScript 코드를 어떻게 사용할 것인가에 대한 내용을 살펴볼 것이다. 지금 까지는 하나의 *.html 파일에 모든 코드가 들어가 있는데, 이후 부터는 *.html파일과 *.js파일(JavaScript code file)을 구분하여 프로그램을 작성할 것이며, 자주 사용되거나 반복적으로 사용되는 JavaScript 구문들을 따로 묶어내어 코드의 Line 수를 줄임과 동시에, 유지 보수 및 가독성 향상을 용이하게 해 보도록 하겠다.

다음과 같은 방법으로 JavaScript코드와 Html코드를 분리해 낼 수 있다. 또한 각 기능에 따라 JavaScript파일을 여러개로 분리하여 관리할 수 있다.

아래와 같은 두 개의 JavaScript파일과 Html파일이 있다고 가정해보자.
[code WebGL]
//JavaScriptFile.js
function sayHello(){
    alert("Hello");
}
[/code]

[code WebGL]
//Test.html
<html>
<body onload="sayHello()">
</body>
</html>
[/code]

위의 Test.html을 보면 웹페이지의 <body></body>테그 내부의 내용이 로드가 시작 될 경우(onload) JavaScript의 sayHello() 함수를 호출하도록 설정이 되어있다. 하지만, Test.html에는 sayHello()라는 자바스크립트 함수가 존재하지 않고, JavaScriptFile.js라는 파일에 정의되어 있다. 따라서 아래와 같이 *.html파일을 수정한다.
[code WebGL]
//Test.html
<html>
<head>
<script src="JavaScriptFile.js" type="text/javascript"></script>
</head>

<body onload="sayHello()">
</body>
</html>
[/code]
이제 Test.html을 실행해 보면 JavaScriptFile.js에 정의되어 있는 "sayHello()"라는 함수에 의해 "Hello"라는 출력값을 갖는 MessageBox를 볼 수 있을 것이다. 이와 같은 방법으로 *.html파일과 *.js파일을 따로 관리하고, 또 여러 *.js파일을 이용하여 각 기능들을 분리해 낼 수 있다.

    지금까지 WebGL의 예제에서는 행렬관련 부분을 사용하지 않았는데, 그 이유는 JavaScript을 이용하여 WebGL을 위한 행렬을 표현하는 것이, 이전의 예제에서 사용했던 방법보다 어려웠기 때문이다.
   이번 장에서 WebGL에 필요한 행렬에 관련된 기본적인 몇 가지만(Identity, Translate) 구현해 보도록하고 차차 그 기능을 늘려 보도록 하겠다. JavaScript의 자세한 기능은 따로 참고하도록 하고, 여기에서는 단순히 '이렇게 구현하면 된다' 정도로 끝내도록 하겠다.

아래는 JavaScript를 이용하여, Matrix 객체를 정의하고(JavaScript에서 '객체'라는 용어 선택이 적절한지 모르겠다.) makeIdentity함수와 translate ,rotation 함수를 구현한 결과는 아래와 같다.
[code WebGL]
//glMath.js

Matrix= function(){
    elements= new Float32Array(16);
}

Matrix.prototype.flattern= function(){
    return elements;
}

Matrix.prototype.toString= function(){
    return
        elements[0]+", "+elements[1]+", "+elements[2]+", "+elements[3]+"\n" +
        elements[4]+", "+elements[5]+", "+elements[6]+", "+elements[7]+"\n" +
        elements[8]+", "+elements[9]+", "+elements[10]+", "+elements[11]+"\n" +
        elements[12]+", "+elements[13]+", "+elements[14]+", "+elements[15];
}

Matrix.prototype.makeIdentity= function(){
    elements[1]= elements[2]= elements[3]= elements[4]=
    elements[6]= elements[7]= elements[8]= elements[9]=
    elements[11]= elements[12]= elements[13]= elements[14]= 0;
    elements[0]= elements[5]= elements[10]= elements[15]= 1;
}

Matrix.prototype.translate= function(x, y, z){
    elements[1]= elements[2]= elements[3]= elements[4]=
    elements[6]= elements[7]= elements[8]= elements[9]= elements[11]= 0;
    elements[0]= elements[5]= elements[10]= elements[15]= 1;

    elements[12]=x;
    elements[13]=y;
    elements[14]=z;
}

Matrix.prototype.rotateX= function(angle){
    var c= Math.cos(angle);
    var s= Math.sin(angle);
    elements[0]=1;  elements[1]= 0; elements[2]=0;  elements[3]=0;
    elements[4]=0;  elements[5]= c; elements[6]=s;  elements[7]=0;
    elements[8]=0;  elements[9]=-s; elements[10]=c; elements[11]=0;
    elements[12]=0; elements[13]=0; elements[14]=0; elements[15]=1;
}

Matrix.prototype.rotateZ= function(angle){
    var c= Math.cos(angle);
    var s= Math.sin(angle);
    elements[0]= c;  elements[1]=s; elements[2]=0;  elements[3]=0;
    elements[4]=-s;  elements[5]=c; elements[6]=0;  elements[7]=0;
    elements[8]= 0;  elements[9]=0; elements[10]=0; elements[11]=0;
    elements[12]=0; elements[13]=0; elements[14]=0; elements[15]=1;
}
[/code]
Matrix객체는 elements라는 멤버변수를 갖는다. elements라는 멤버의 데이터타입은 WebGLFloatArray이다.

WebGLFloatArray에 specification은 아래와 같다.(http://www.khronos.org/webgl/wiki/Main_Page)

[
    Constructor(in unsigned long length),
    Constructor(in WebGLByteArray array),
    Constructor(in sequence&lt;GLbyte&gt; array),
    Constructor(in WebGLArrayBuffer buffer,
                in optional unsigned long byteOffset, in optional unsigned long length)
]
interface WebGLByteArray : WebGLArray {
    const GLsizei BYTES_PER_ELEMENT = 1;
   
    getter GLbyte get(in unsigned long index);
    setter void set(in unsigned long index, in GLbyte value);
    void set(in WebGLByteArray array, in optional unsigned long offset);
    void set(in sequence&lt;GLbyte&gt; array, in optional unsigned long offset);
};

WebGLFloatArray는 1.0 부터 Deprecated되었다.
Float32Array를 참고하라.


이제 앞서 보였던 '삼각형의 이동' 예제를 행렬을 이용한 형태로 바꾸어 보도록 하겠다.
이전의 예제에서 아래와 같은 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]
이를 아래와 같이 수정한다.
[code WebGL]
<script id="VertexShader" type="x-shader/x-vertex">
    attribute vec3 aPos;
    attribute vec4 aColor;

    uniform mat4 gWorld;

    varying vec4 vColor;

    void main(){
        gl_Position= gWorld * vec4(aPos, 1.0);
        vColor=    aColor;
    }
</script>
[/code]
univorm vec4 trans; 라는 변수를 이용하여, Vertex Position의 이동을 수행했었는데, 지금은 uniform mat4 gWorld라는 변수(행렬)를 이용하여 Vertex Position의 이동을 수행함을 볼 수 있다. 따라서, 행렬의 종류(이동, 회전, 크기변환)에 따라 그에 맞는 Vertex Position의 이동이 결정될 것이다.

이제, GLSL의 변수명 "gWorld"에 대한 핸들을 얻어올 차례이다. 이전의 예제에서는 변수명 "trans"에 대한 핸들을 얻기 위해 아래와 같은 코드를 사용했다.
[code WebGL]
g_hTrans=    gl.getUniformLocation(shaderProgram, "trans");
if( g_hTrans==-1 ){
    alert("Unable to find 'trans' Uniform Location.");
    return false;
}
[/code]
이를 아래와 같이 수정한다.
[code WebGL]
g_hWorld=    gl.getUniformLocation(shaderProgram, "gWorld");
if( g_hWorld==-1 ){
    alert("Unable to find 'gView' uniform location.");
    return false;
}
[/code]
GLSL의 변수명 "gWorld"에 대한 핸들이 JavaScript의 "g_hWorld"라는 이름의 변수에 할당되어 있으므로 g_hWorld 변수를 이용하여 GLSL에 행렬값을 넘겨주어야 할 것이다. 앞서 "Matrix"라는 객체를 정의 하였다. 아래와 같이 이 객체의 인스턴스를 생성하고, 적당한 행렬(본 예제에서는 앞서 보였던 예제와 같은 이동 행렬)을 만든 후 GLSL에 넘기도록 한다.
[code WebGL]
//Matrix 객체의 인스턴스 생성
g_mtxView=    new Matrix();

//이전 예제와 같은 Animation을 위해 매 프레임 마다 값을 변경
g_transPosX+=    0.01;
if( g_transPosX>0.5 )    g_transPosX=    0;

//이동행렬을 만듦
g_mtxView.translate(g_transPosX, 0, 0);

//g_hWorld라는 핸들을 이용해 GLSL에 행렬 값을 넘김
gl.uniformMatrix4fv(g_hWorld, false, g_mtxView.flattern());
[/code]

위 예제의 결과는 앞서 소개한 007. WebGL - Prac06 - Animation과 같은 결과를 보여 준다.

전체 소스코드는 아래와 같다.
5Line에서 볼 수 있듯이, 앞서 보였던 glMath.js라는 JavaScript File을 사용하고 있음에 주의한다.
[code WebGL]
<html>

<head>
    <title>WebGL - Prac06 - Animation</title>
    <script src="glMath.js" type="text/javascript"></script>
    <script type="text/javascript">
        var gl;
        var g_hVertPos;
        var g_hVertColor;
        var g_hWorld;

        var g_transPosX=0;

        var g_mtxView;

        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);

            g_mtxView=    new Matrix();

            //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;

            g_mtxView.translate(g_transPosX, 0, 0);
            gl.uniformMatrix4fv(g_hWorld, false, g_mtxView.flattern());
           
            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);

            g_hWorld=    gl.getUniformLocation(shaderProgram, "gWorld");
            if( g_hWorld==-1 ){
                alert("Unable to find 'gView' 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 mat4 gWorld;

        varying vec4 vColor;

        void main(){
            gl_Position= gWorld * vec4(aPos, 1.0);
            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]