Skip to content
Snippets Groups Projects
Select Git revision
  • master default protected
1 result

webgl-tutorial

  • Clone with SSH
  • Clone with HTTPS
  • Name Last commit Last update
    LICENSE
    README.md
    gl-matrix.js
    index.html
    script.js

    WebGL Tutorial

    2021-1 Computer Graphics Final Project - 소프트웨어학과 201920739 박지윤


    Object

    webgl에서 rotate와 view와 관련하여 다양한 함수를 제공하고 있습니다. 하지만 이는 sin, cos 등의 계산과 여러 행렬의 곱으로 결과가 도출되기 때문에, 머릿속으로 3D 상황에서 rotate, view 등의 결과를 예상하기 어렵습니다. 따라서 본 Tutorial은 각 x, y, z 축에 따른 rotate, view의 변화를 시각화하여 webgl을 처음 배우는 사람의 3D에서의 rotate와 view의 개념 이해를 돕고자 하는 것에 있습니다.


    How to make?

    먼저, 이곳에서 이 tutorial의 base가 될 code를 가져옵니다.

    본격적으로 시작하기에 앞서, 여기에서는 MVP 모델을 사용합니다. M은 Model matrix, V는 View Matrix, P는 Projection Matrix입니다. Model Matrix는 3D 공간에서 물체의 이동을 표현할 수 있습니다. View Matrix는 3D 공간에서 카메라의 view를 표현할 수 있습니다. Projection Matrix는 3D 공간에서 일반적인 카메라에서의 표현을 표현할 수 있습니다.

    1. rotate를 수동으로 조절할 수 있도록 코드를 추가합니다.

    rotate는 사용자가 지정한 축을 기준으로 rotate를 합니다. gl-matrix의 rotate 함수를 사용하여 rotate를 구현할 수 있으나, 이번에는 xrotate, yrotate, zrotate로 내가 원하는 축을 선택하여 rotate를 할 수 있도록 해보고자 합니다.
    어느 축을 기준으로 얼마나 rotate할 것인지 사용자로부터 입력을 받을 수 있게 다음과 같은 코드를 index.html 에 추가합니다.

        <label>X rotate : </label>
        <input style="width:400px" id="x_rotate" type="range" min="0" max="360" value="0" oninput="fn_update_xrotate(this.value);"></input>
    	<input style="width:60px" type="text" id="textXRot" value="0">
        <br/>
        <label>Y rotate : </label>
        <input style="width:400px" id="y_rotate" type="range" min="0" max="360" value="0" oninput="fn_update_yrotate(this.value);"></input>
    	<input style="width:60px" type="text" id="textYRot" value="0">
        <br/>
        <label>Z rotate : </label>
        <input style="width:400px" id="z_rotate" type="range" min="0" max="360" value="0" oninput="fn_update_zrotate(this.value);"></input>
    	<input style="width:60px" type="text" id="textZRot" value="0">
        <br/><hr>

    그리고 사용자로부터 받은 값을 변수에 설정하기 위하여 다음과 같은 코드를 script.js에 추가합니다.

    function fn_update_xrotate(val)
    {
        document.getElementById('textXRot').value=val; 
        xRot = val * 3.141592 / 360.0; 
    }
    
    function fn_update_yrotate(val)
    {
        document.getElementById('textYRot').value=val; 
        yRot = val * 3.141592 / 360.0; 
    }
    
    function fn_update_zrotate(val)
    {
        document.getElementById('textZRot').value=val; 
        zRot = val * 3.141592 / 360.0; 
    }

    사용자로부터 받은 값을 변수에 저장할 수 있게 되었습니다. 이제 이 값을 따라 실제로 rotate가 됩니다. 이때, object를 rotate하므로 Model Matrix를 사용합니다. script.js에서 Model Matrix는 mMat이라는 이름으로 쓰고 있습니다. 기존에 받아온 코드에서 다음 부분이 이와 같은 역할을 합니다.

    function renderScene() {
        ...
        mat4.rotateX(mMat, mMat, xRot);
        mat4.rotateY(mMat, mMat, yRot);
        mat4.rotateZ(mMat, mMat, zRot);
        ...
    }

    2. animation에서도 rotate를 하는 축을 정할 수 있도록 설정합니다.

    기존의 코드에서는 rotate를 하면 정확하게 어느 축을 회전시켜서 나오는 결과인지 알기가 어렵습니다. 따라서 어떤 축과 어떤 축을 rotate하는 기준으로 선택했을 때, object가 rotate되는 결과를 얻기 위하여 이 단계를 수행합니다.
    먼저, animate할 때 각 축을 본인이 선택하여 rotate를 할 수 있도록 index.html에 다음과 같은 코드를 추가합니다.

        <button onclick="fn_speed_scale(1.1)"> + </button>
        <button onclick="fn_speed_scale(0.9)"> - </button> 
        // 여기까지 기존 코드
        // 여기서부터 새로 추가된 코드
        <lable> | Animation Rotate :</lable>
        <input type="checkbox" onclick="animate_rotate(1)"><lable>X-axis Rotate  /</lable>
        <input type="checkbox" onclick="animate_rotate(2)"><lable>Y-axis Rotate  /</lable>
        <input type="checkbox" onclick="animate_rotate(3)"><lable>Z-axis Rotate  </lable>
    	<br/><hr>
        // 새로 추가된 코드
        // 기존 코드
        <lable>Draw Mode :</label>
        ...

    사용자가 어떤 축을 선택했는지 알기 위하여 다음과 같은 변수와 함수를 script.js에 추가합니다.

    var xRot_animate = false;
    var yRot_animate = false;
    var zRot_animate = false;
    
    function animate_rotate(val)
    {
      if (val == 1) {
        xRot_animate = !xRot_animate
      }
      else if (val == 2) {
        yRot_animate = !yRot_animate
      }
      else if (val == 3) {
        zRot_animate = !zRot_animate
      }
    }

    사용자가 선택한 축을 기준으로 rotate하는 것을 animate하기 위해 script.jsrenderScene() 에 다음과 같은 함수를 추가합니다. 이때, animate가 활성화 되어있는 경우, rotate하기로 한 축을 기준으로 수동으로 rotate를 조절하는 것을 막기 위한 코드도 추가합니다.

    if (flag_animation == 1)
    	{
        if (xRot_animate) 
        {
          xRot = xRot + speedRot;
          document.getElementById('textXRot').value = "X";
        }
        if (yRot_animate)
        {
          yRot = yRot + speedRot;
          document.getElementById('textYRot').value = "X";
        }
        if (zRot_animate)
        {
          zRot = zRot + speedRot;
          document.getElementById('textZRot').value = "X";
        }
      }

    사용자의 입장에서는 아무런 고지도 없이 animate를 할 때, 수동으로 rotate가 안 된다면 당황스러울 것입니다. 사용자에게 지금 animate 중이고, 만약 사용자가 x축을 기준으로 rotate를 선택했다면 수동으로 x축의 rotate 정도를 조절하는 것이 불가함을 알려주기 위하여 1번에서 추가한 코드를 수정합니다.

    function fn_update_xrotate(val)
    {
      if (!xRot_animate) {
        document.getElementById('textXRot').value=val; 
        xRot = val * 3.141592 / 360.0; 
      }
      else{
        document.getElementById('textXRot').value = "X";
      }
    }
    
    function fn_update_yrotate(val)
    {
      if (!yRot_animate) {
        document.getElementById('textYRot').value=val; 
        yRot = val * 3.141592 / 360.0; 
      }
      else{
        document.getElementById('textYRot').value = "X";
      }
    }
    
    function fn_update_zrotate(val)
    {
      if (!zRot_animate) {
        document.getElementById('textZRot').value=val; 
        zRot = val * 3.141592 / 360.0; 
      }
      else{
        document.getElementById('textZRot').value = "X";
      }
    }

    3. 물체를 각 축을 따라 translate 할 수 있도록 합니다.

    현실 세계에서는 물체를 옮길 일이 많습니다. 그렇다면 CG에서도 물체를 옮기는 것을 구현해보고 싶지 않나요? 우리가 그린 cube를 translate하는 함수를 추가해보도록 하겠습니다.

    일단, 각 x, y, z축을 따라 얼마나 translate할 것인지 사용자가 조절할 수 있도록 다음과 같은 코드를 index.html에 추가합니다.

        ...
        <label>Z rotate : </label>
        <input style="width:400px" id="z_rotate" type="range" min="0" max="360" value="0" oninput="fn_update_zrotate(this.value);"></input>
    	<input style="width:60px" type="text" id="textZRot" value="0">
        <br/><hr> 
        // 여기까지 기존 코드
        // 여기서부터 추가된 코드
        <label>Cube Translate | </label>
        <br/>
        <label>X-axis Translate : </label>
        <input style="width:400px" id="x_move" type="range" min="-100" max="100" value="0" oninput="fn_update_xmove(this.value);"></input>
    	<input style="width:60px" type="text" id="textXMove" value="0">
        <br/>
        <label>Y-axis Translate : </label>
        <input style="width:400px" id="y_move" type="range" min="-100" max="100" value="0" oninput="fn_update_ymove(this.value);"></input>
    	<input style="width:60px" type="text" id="textYMove" value="0">
        <br/>
        <label>Z-axis Translate : </label>
        <input style="width:400px" id="z_move" type="range" min="-100" max="100" value="0" oninput="fn_update_zmove(this.value);"></input>
    	<input style="width:60px" type="text" id="textZMove" value="0">
        <br/><hr>

    사용자가 입력한 translate 값을 우리의 변수에 저장하기 위하여 script.js에 다음과 같은 코드를 추가합니다.

    var xMove = 0.0;
    var yMove = 0.0;
    var zMove = 0.0;
    
    function fn_update_xmove(val)
    {
      val = val / 100.0;
      document.getElementById('textXMove').value = val; 
      xMove = val; 
    }
    
    function fn_update_ymove(val)
    {
      val = val / 100.0;
      document.getElementById('textYMove').value = val; 
      yMove = val; 
    }
    
    function fn_update_zmove(val)
    {
      val = val / 100.0;
      document.getElementById('textZMove').value = val; 
      zMove = val; 
    }

    gl-matrix에서는 각 축으로 얼마나 translate할 건지 알려주면 translate 해주는 함수인 translate(out, a, v)가 있습니다. 여기에서는 그 코드를 사용하여 사용자로부터 받은 값만큼 이동할 수 있도록 script.jsrenderScene()에 다음과 같은 코드를 추가합니다.

    ...
    pMat = mat4.create(); 
    vMat = mat4.create(); 
    mMat = mat4.create(); 
    // 위는 기존에 있던 코드
    // 추가한 코드
    mat4.translate(mMat, mMat, [xMove, yMove, zMove]);
    // 추가된 코드
    // 아래는 기존에 있던 코드
    mat4.rotateX(mMat, mMat, xRot);
    mat4.rotateY(mMat, mMat, yRot);
    mat4.rotateZ(mMat, mMat, zRot);
    ...
    

    4. lookAt 을 통해 Camera view를 조절합니다.

    Lookat은 gl-matrix에서 카메라의 view를 조정할 수 있도록 제공하는 함수입니다. Lookat은 eye vector, center vector, up vector로 이루어져 있습니다. Eye vector는 viewer의 position을 정하는 vector입니다. center vector는 카메라가 바라보고 있는 위치를 표현하는 vector입니다. up vector는 카메라가 어떻게 보고 있는지 알려주는 vector입니다.

    eye vector, center vector, up vector의 x, y, z의 값을 사용자로부터 입력을 받을 수 있게 다음과 같은 코드를 index.html 에 추가합니다.

        <label>lookAt | </label>
        <br/>
        <label>eye vector (x-axis) : </label>
        <input style="width:400px" id="x_eye" type="range" min="-100" max="100" value="0" oninput="fn_update_xeye(this.value);"></input>
    	<input style="width:60px" type="text" id="textXEye" value="0">
        <br/>
        <label>eye vector (y-axis) : </label>
        <input style="width:400px" id="y_eye" type="range" min="-100" max="100" value="0" oninput="fn_update_yeye(this.value);"></input>
    	<input style="width:60px" type="text" id="textYEye" value="0">
        <br/>
        <label>eye vector (z-axis) : </label>
        <input style="width:400px" id="z_eye" type="range" min="-100" max="100" value="100" oninput="fn_update_zeye(this.value);"></input>
    	<input style="width:60px" type="text" id="textZEye" value="2">
        <br/><hr>
        <label>center vector (x-axis) : </label>
        <input style="width:400px" id="x_center" type="range" min="-100" max="100" value="0" oninput="fn_update_xcenter(this.value);"></input>
    	<input style="width:60px" type="text" id="textXCenter" value="0">
        <br/>
        <label>center vector (y-axis) : </label>
        <input style="width:400px" id="y_center" type="range" min="-100" max="100" value="0" oninput="fn_update_ycenter(this.value);"></input>
    	<input style="width:60px" type="text" id="textYCenter" value="0">
        <br/>
        <label>center vector (z-axis) : </label>
        <input style="width:400px" id="z_center" type="range" min="-100" max="100" value="0" oninput="fn_update_zcenter(this.value);"></input>
    	<input style="width:60px" type="text" id="textZCenter" value="0">
        <br/><hr>
        <label>up vector (x-axis) : </label>
        <input style="width:400px" id="x_up" type="range" min="-100" max="100" value="0" oninput="fn_update_xup(this.value);"></input>
    	<input style="width:60px" type="text" id="textXUp" value="0">
        <br/>
        <label>up vector (y-axis) : </label>
        <input style="width:400px" id="y_up" type="range" min="-100" max="100" value="100" oninput="fn_update_yup(this.value);"></input>
    	<input style="width:60px" type="text" id="textYUp" value="1">
        <br/>
        <label>up vector (z-axis) : </label>
        <input style="width:400px" id="z_up" type="range" min="-100" max="100" value="0" oninput="fn_update_zup(this.value);"></input>
    	<input style="width:60px" type="text" id="textZUp" value="0">
        <br/><hr>

    그리고 사용자로부터 받은 값을 변수에 설정하기 위하여 다음과 같은 코드를 script.js에 추가합니다.

    function fn_update_xeye(val)
    {
      val = val / 50.0;
      document.getElementById('textXEye').value = val; 
      xEye = val; 
    }
    
    function fn_update_yeye(val)
    {
      val = val / 50.0;
      document.getElementById('textYEye').value = val; 
      yEye = val; 
    }
    
    function fn_update_zeye(val)
    {
      val = val / 50.0;
      document.getElementById('textZEye').value = val; 
      zEye = val; 
    }
    
    
    function fn_update_xcenter(val)
    {
      val = val / 100.0;
      document.getElementById('textXCenter').value = val; 
      xCenter = val; 
    }
    
    function fn_update_ycenter(val)
    {
      val = val / 100.0;
      document.getElementById('textYCenter').value = val; 
      yCenter = val; 
    }
    
    function fn_update_zcenter(val)
    {
      val = val / 100.0;
      document.getElementById('textZCenter').value = val; 
      zCenter = val; 
    }
    
    function fn_update_xup(val)
    {
      val = val / 100.0;
      document.getElementById('textXUp').value = val; 
      xUp = val; 
    }
    
    function fn_update_yup(val)
    {
      val = val / 100.0;
      document.getElementById('textYUp').value = val; 
      yUp = val; 
    }
    
    function fn_update_zup(val)
    {
      val = val / 100.0;
      document.getElementById('textZUp').value = val; 
      zUp = val; 
    }

    위의 eye vector, center vector, up vector는 x, y, z의 3열로 이루어진 vector입니다. gl-matrix에서는 각 벡터 값에 따라 카메라의 view가 어떻게 되는지 계산해주는 lookAt(out, eye, center, up) 이라는 함수가 있습니다. 우리는 이 함수를 사용하여 사용자로부터 입력받은 각 vector의 x, y, z값에 따라 어떻게 camera의 view가 달라지는지 확인하기 위하여 다음과 같은 코드를 script.js 에 추가합니다.

    ...
    mat4.perspective(pMat, fov_degree * 3.141592 / 180.0 , 8.0/6.0 , 0.5, 6); 
    // 위는 기존 코드
    // 추가한 코드
    mat4.lookAt(vMat, [xEye,yEye,zEye], [xCenter, yCenter, zCenter], [xUp, yUp, zUp]);
    // 추가된 코드
    // 아래는 기존 코드
    if (flag_animation == 1)
    {
        ...

    License

    본 Tutorial은 MIT License 를 따릅니다.


    Reference