본문 바로가기
3D웹 프로그래밍/WebGl

[Webgl] webgl을 이용한 삼각형 만들기

by 디찌s 2020. 11. 10.
728x90
반응형

 

WEBGL을 이용하여 삼각형을 만들어보자

 

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>

  
</head>

<body onload="webglstart()">
    <canvas id="myCanvas" width="500" height="500"></canvas>
</body>

</html>

 

index.html 파일을 생성하고 그안에 body와 canvas를 만들자.

 

webgl은 canvas안에 그림을 그린다. body 태그안에 정의한 onload이벤트 핸들러는 webglstart()메서드를 호출한다.

 

현재는 webglstart함수가 없기에 에러가 뜰것이다.

 

onload이벤트는 유저가 브라우저를 통해 웹 페이지에 진입하고 모든 소스 코드의 내용을 완전히 로딩하면 onload이벤트를 시작한다.

 

canvas안에 id 값을 넣어줌으로써 자바스크립트 코드로 접근이 용이하도록 만들어 놓는다.

 

<script>     
 		var gl;
        var canvas;
        var shaderProgram;
        var vertexBuffer;
    
        function createGLContext(canvas){
            var names = ["webgl","experimental-webgl"];
            var context = null;
            names.forEach(name =>{
                try{
                    context = canvas.getContext(names[name])
                }catch(e){}
                if (context){
                break;
            }
            })
           
            if(context){
                context.viewportWidth = canvas.width;
                context.viewportHeight = canvas.height;
            }else{
                alert("Failed to create WebGL context");
            }
            return context;
        }
        function webglstart(){
            var canvas = document.getElementById("myCanvas");
            var gl = createGLContext(canvas);
        }
    
    </script>
  

script문을 열고 webglstart 함수를 생성!

document.getElementById를 통해 일전에 canvas에 넣어놓았던 id값을 가져와 canvas를 조작하도록 변수에 넣는다.

 

가져온 canvas를 createGLContext 함수에 넣어 webgl context를 가져온뒤 gl 변수에 넣어준다.

 

모든 소스 코드에서는 WebGLRenderingContext는 gl이라는 변수에 할당된다.

 

따라서 gl.someMethod()와 같은 형태의 함수 호출을 보게된다면, 이는 WebGLRenderingContext 인터페이스의 someMethod()란 함수를 사용하는것이다.

 

앞으로 웹지엘 API의 메소드를 호출할때는 보통 접두사 gl을 붙일것이다.

 

 

  <script>
  		function setupShaders(){
        var vertexShaderSource = "attribute vec3 aVertexPosition; \n"+
        "void main() { \n"+
        " gl_Position = vec4(aVertexPosition, 1.0);\n"+
        "} \n";

        var fragmentShaderSource ="precision mediump float; \n"+
        "void main() \n"+
        "gl_FragColor = vec4(1.0,1.0,1.0,1.0); \n"+
        "} \n"
		}
    </script>

버텍스 셰이더

모든 웹지엘 프로그램은 버텍스 셰이더와 프래그먼트 셰이더가 필요하다. 위에 코드는 셰이더 코드이다.

 

버텍스 셰이더 첫 부분인 aVertexPosition은 3개의 원소로 구성된 벡터이다.

 

타입의 앞부분에는 이 변수가 애트리뷰트임을 지정하고있다.

 

애튜리뷰트는 웹지엘 API에서 버텍스 셰이더로 데이터를 보내기위해 사용하는 특별한 입력 변수이다.

 

aVertexPosition 애트리뷰트는 삼각형을 그리는데 필요한 각각의 버텍스의 좌표 정보를 전송하는데 사용한다.

 

API를 호출해 버텍스 데이터를 이 애트리뷰트 변수에 전송하도록 필요한 버퍼를 설정하고 이 버퍼를 avertextPosition 애트리뷰트와 연결한다.

 

이 두 단계는 앞으로 살펴볼것이다.

 

main 함수는 c언어처럼 버텍스 셰이더 실행의 진입점이다. 코드 속의 gl_Postion이라 불리는 변수는 버텍스 셰이더 단계가 끝났을 때의 버텍스의 좌표를 저장하며, 웹지엘 파이프라인의 다음 단계로 전송된다

프레그먼트 셰이더

gl_position이 vec4 타입(동촤 좌표계를 사용하기에) 으로 정의되었기때문에 4번째 요소가 1로 설정되어 3D상의 점을 동차 좌표계의 점으로 치환한다.

 

동차 좌표계는 아래링크에서 확인하자

hipdizzy.tistory.com/50

 

3D에서 사용하는 동차 좌표계란 무엇인가?

투영변환이란 무엇일까? 실세계의 한점 Q =(X,Y,Z)는 3차원의 점이다. 이점이 투영 스크린 상의 한점 (X,Y)로 즉 2차원점으로 변환되는 관계를 투영 변환이라한다. 이렇게 투영변환을 사용할 때는 동

hipdizzy.tistory.com

 

var fragmentShaderSource ="precision mediump float; \n"+
        "void main() \n"+
        "gl_FragColor = vec4(1.0,1.0,1.0,1.0); \n"+
        "} \n"

 

 

  

프래그먼트 셰이더 또한 + 연산자로 연결된 하나의 문자열로 구성된다.

 

첫번째 라인은 정밀도 지정자를 선언해 프래그먼트 셰이더에서 사용하는 float 변수가 중간 정밀도를 사용함으로 지정한다.

 

vec4로 표현된 백색의 색상값을 내장된 변수인 gl_FragColor에 기록한다 (1.0,1.0,1.0,1.0)은 백색이다.

 

이 gl_FragColor 변수는 프래그먼트 셰이더가 종료 될때 RGBA 형식의 출력 색상 값을 가지는 4개의 원소로 구성된 벡터이다.

 

셰이더 컴파일

GPU에서 렌더링에 필요한 셰이더를 만들고 업로드하려면, 먼저 셰이더 객체를 만들고 소스 코드를 객체에 로드한 후 컴파일하고, 프로그램 객체에 셰이더를 링크해야한다.

   function loadShader(type,shaderSource){
   			//1
            var shader = gl.createShader(type);
            //2
            gl.shaderSource(shader,shaderSrouce);
            //3
            gl.compileShader(shader);
            
			//4
            if(!gl.getShaderParmeter()){
                alert("Error compiling shader" + gl.getShaderInfoLog(shader));
                gl.deleteShader(shader);
                return null;
            }
            return shader;
        }

1. 셰이더 객체가 gl.createShader()함수에 의해 생성된다.(이 함수의 인자는 만들고자 하는 셰이더 종류에따라 gl.VERTEX_SHADER 나 gl.FRAGMENT_SHADER중 하나가된다)

 

2. gl_shaderSource()에 의해 셰이더 코드가 셰이더 객체에 로드된다.(첫번째 인자는 셰이더객체 두번째 인자는 셰이더 소스 코드이다.)

 

3. gl.compleShader()에 의해 shader가 컴파일 된다. 

 

4. 컴파일 후에는 gl.getShaderParameter()를 이용해 컴파일 상태를 체크할수있다. 만약 컴파일 에러가 발생하면 에러 경고창이 뜨는 코드이다.

 

프로그램 객체 생성과 셰이더 링크

	function setupShaders(){
        
        ...
        
        var vertexShader = loadShader(gl.VERTEX_SHADER,vertexShaderSource);
        var fragmentShader = loadShader(gl.FRAGMENT_SHADER,fragmentShaderSource);

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

        if(!gl.getProgramParameter(shaderProgram,gl.LINK_STATUS)){
            alert("Failed");
        }
        
        gl.useProgram(shaderProgram);
        shaderProgram.vertexPositionAttribute=gl.getAttribLocation(shaderProgram,"aVertexPosition");
        
        


		}

loadShader함수를 사용하여 셰이더코드를 셰이더객체에 로드시킨다.

gl.createProgram함수를 사용하여 프로그램 객체를 만든다.

그후 gl.attachSahder로 버텍스 셰이더와 프래그먼트 셰이더를 프로그램 객체에 붙인다.

그리고 난후, gl.linkProgram함수를 호출해 링크 과정을 수행한다. 링크가 성공하면 프로그램 객체를 소유하게 되고 gl.useProgram()을 호출해 웹지엘에서 이 프로그램객체를 렌더링에 사용하도록 지정한다.

링크가 끝나면 버텍스 셰이더의 애티르뷰트 변수 위치를 제네릭 애트리뷰트 인덱스에 할당한다.

 

웹지엘에는 고정된 수량의 애트리뷰트 슬롯이있으며, 제네릭 애티르뷰트 인덱스는 이러한 슬롯을 구분하는 데 사용한다. 버텍스 셰이더의 각각의 애트리뷰트를 구분하는 데 제네릭 애트리뷰트 인덱스를 알고 있어야 그리기 과정에서 버퍼의 버텍스 데이터를 버텍스 셰이더의 애트리뷰트와 일치시킬수있다.

 

인덱스 정보를 얻는법은 두가지 전략이있다.

1.gl.bindAttribLocation()을 이용해 링크 전에 어떤 인덱스를 애트리뷰트에 바인딩하는지 지정한다.

2.웹지엘이 애트리뷰트의 인덱스를 자동 지정하고,링크가 끝나면gl.getAttribLocation()메소드를 이용해서 특정 애트리뷰트에 어떤 제네릭 애트리뷰트 인덱스가 쓰였는지 얻어온다.

 

버퍼 설정

 

셰이더를 만들고나면 버텍스 데이터를 저장할  버퍼를 설정하는 단계이다.

 

   function setupBuffer(){
            vertexBuffer = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER,vertexBuffer);
            var traiangleVertices = [
                0.0,0.5,0.0,
                -0.5,-0.5,0.0,
                0.5,-0.5,0.0
            ]

            gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(traiangleVertices),gl.STATIC_DRAW);
            vertexBuffer.itemSize=3;
            vertexBuffer.numberOfItems=3;

        }
  

 

gl.createBuffer()를 호출해 WebGlBUffer객체를 생성, vertexBuffer변수에 넣는다. 그리고 만든 vertexBuffer를 gl.ARRAY_BUFFER에 바인딩한다.

 

이번 예제에서 버텍스 셰이더는 버텍스에 어떠한 변환도 수행하지 않는다.

입력 버텍스를 행렬 연산없이 내장된 변수인 gl_Position에 할당할뿐이다.

이는 기본적으로 오른손 좌표계를 사용하는것을 의미한다.

 

 

 

vertexBuffer에 itemsize는 애트리뷰트에 얼마나 많은 원소가 존재하는지 알려주고  numberOfItems는 이 버퍼에 몇개의 아이템 혹은 버텍스가 있는지 알려준다.

 

장면 그리기

function draw(gl){
            gl.viewport(0,0,gl.viewportWidth,gl.viewportHeight);
            gl.clear(gl.COLOR_BUFFER_BIT);

            gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute,vertexBuffer.itemSize,gl.FLOAT,false,0,0);
            gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
            gl.drawArrays(gl.TRIANGLES,0,vertexBuffer.numberOfItems);
            
        }

 

첫 번째로 뷰포트를 설정하고 뷰포트는 그리기 버퍼 안에 그려지는 렌더링 결과의 위치를 결정한다. 웹지엘 컨텍스트가 생성될 때, 뷰포트는 원점이 (0,0)이고 너비와 높이가 캔버스와 일치하는 크기의 사각형 영역으로 초기화된다.

gl.clear는 gl.clearColor()로 결정된 색상으로 색상버퍼를 채운다.

 

그리고 실행하면 삼각형이 나올것이다

 

아래는 풀코드이다.

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>


    <script>

        var gl;
        var canvas;
        var shaderProgram;
        var vertexBuffer;
        function webglstart(){
            var canvas = document.getElementById("myCanvas");
            gl = createGLContext(canvas);
           
            setupShaders();
            setupBuffer();
            gl.clearColor(0.0, 0.0, 0.0, 1.0);
            draw();

        }
       
        function createGLContext(canvas){
            var names = ["webgl","experimental-webgl"];
            var context = null;

            for (var i=0;i<names.length;i++){
                try{
                    context = canvas.getContext(names[i]);

                }catch(e){}
                if(context){
                    break;
                }
            }
           
            if(context){
                context.viewportWidth = canvas.width;
                context.viewportHeight = canvas.height;
            }else{
                alert("Failed to create WebGL context");
            }
            return context;
        }
    
        
        
        function loadShader(type,shaderSource){
            var shader = gl.createShader(type);
            gl.shaderSource(shader,shaderSource);;
            gl.compileShader(shader);
            

            if(!gl.getShaderParameter(shader,gl.COMPILE_STATUS)){
                alert("Error compiling shader" + gl.getShaderInfoLog(shader));
                gl.deleteShader(shader);
                return null;
            }
            return shader;
        }

        function setupShaders(){
        var vertexShaderSource = "attribute vec3 aVertexPosition; \n"+
        "void main() { \n"+
        " gl_Position = vec4(aVertexPosition, 1.0);\n"+
        "} \n";

        var fragmentShaderSource ="precision mediump float; \n"+
        "void main() {\n"+
        "gl_FragColor = vec4(1.0,1.0,1.0,1.0); \n"+
        "} \n"
        
        var vertexShader = loadShader(gl.VERTEX_SHADER,vertexShaderSource);
        var fragmentShader = loadShader(gl.FRAGMENT_SHADER,fragmentShaderSource);
        
        shaderProgram = gl.createProgram();
        gl.attachShader(shaderProgram,vertexShader);
        gl.attachShader(shaderProgram,fragmentShader);
        gl.linkProgram(shaderProgram);

        if(!gl.getProgramParameter(shaderProgram,gl.LINK_STATUS)){
            alert("Failed");
        }
        
        gl.useProgram(shaderProgram);
        shaderProgram.vertexPositionAttribute=gl.getAttribLocation(shaderProgram,"aVertexPosition");

        


		}
        
        function setupBuffer(){
            vertexBuffer = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER,vertexBuffer);
            var traiangleVertices = [
                0.0,0.5,0.0,
                -0.5,-0.5,0.0,
                0.5,-0.5,0.0
            ]

            gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(traiangleVertices),gl.STATIC_DRAW);
            vertexBuffer.itemSize=3;
            vertexBuffer.numberOfItems=3;

        }
        
        function draw(){
            gl.viewport(0,0,gl.viewportWidth,gl.viewportHeight);
            gl.clear(gl.COLOR_BUFFER_BIT);

            gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute,vertexBuffer.itemSize,gl.FLOAT,false,0,0);
            gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
            gl.drawArrays(gl.TRIANGLES,0,vertexBuffer.numberOfItems);

        }
        
    </script>
  
</head>

<body onload="webglstart();">
    <canvas id="myCanvas" width="500" height="500"></canvas>
</body>

</html>

728x90
반응형

댓글