E D R , A S I H C RSS

WebGL


1. 기본 컨셉

OpenGL에서 정말 실무에서 쓰는 부분만 따로 떼어낸 OpenGL ES(Embeded System)의 Javascript 구현체이며 HTML5 Canvas를 통해 나타난다. 따라서 초보자가 쉽게 배우는데에 초점이 맞추어져 있지 않고 오직 전문가가 구현을 하는데에 초점이 맞추어져 있다.

2. 특징

Javascript임에도 불구하고 마치 C프로그래밍 스타일의 함수들이 존재한다. WinAPI가 C스타일의 OOP이듯 WebGL 또한 C스타일의 OOP이다. 모든 함수는 WebGLcontext라는 객체에 있는데 보면 그냥 접두어를 붙이는 느낌이다.
var gl = canvas.getContext("experimental-webgl");
gl.attachShader(shaderProgram, fragmentShader);  
gl.attachShader(shaderProgram, vertexShader);
위의 코드를 보면 이더 프로그램에 fragmentShader와 vertexShader를 Link 시키는 구문인데 주체인 shaderProgram은 첫번쨰 인자이고 gl은 그냥 접두어 처럼 보인다. 저 구문만 그런것이 아니라 다른 모든 함수들이 저 gl 객체에 붙어있다. 하지만 정작 gl이 주체가 아닌 것들이 많다. 따라서 래핑한 객체를 만들어 쓰는 것이 속편한데 어설프게 했다가는 무척 꼬이게 된다.

이 관습은 OpenGL이 기본적으로 C라이브러리이라 그런듯 하다. 실제 래핑을 진행해본결과 마치 MFC를 보는듯한 느낌을 강하게 받고 있다.

2.1. OpenGL과 차이점

  • WebGL은 기존 OpenGL과 다르게 직접 그리기가 지원되지 않는다. 기존의 glBegin()와 glEnd()사이에서 값을 계속적으로 전달하수 없고 오직 glDrawElement()를 통한 배열을 한꺼번에 전달하는 것'만' 지원한다. 초보자들의 첫난관이다.
  • 사각형그리기 및 다각형 그리기가 지원되지 않는다. 실제로 다각형 그리기는 연습시에만 자주 쓰고 실제 코드에서는 삼각형으로 이루어진 모델을 가져다 쓰기 때문인 것으로 보인다. 그리고 다각형은 삼각형의 집합으로 표현할수 있다.
  • 유틸라이브러리로 제공되는 큐브, 구, 실린더, 티포트가 모두 지원되지 않는다. 역시 예제에만 쓰이고 쓰지 않기 때문에 과감히 제거한것으로 보인다.
  • 광원, 카메라 조작, 회전 등이 제공되지 않는다. 모두 자신이 직접 연산을 통해 행렬을 구해주어야 한다. 초보자들의 둘쨰 난관이다.
  • 텍스쳐 모드와 조명 모드가 매우 제한되어 있다.
  • 쉐이더를 짜지않으면 쓸수가 없다. 심지어 텍스쳐를 입히는 것도 쉐이더에서 처리한다. 그냥 단색으로 처리하는 코드도 쉐이더 코드를 짜지 않으면 그냥 햐얀 것만 보게된다. 그리고 그것도 회전시킬수도 없다.

3. 주요요소

3.1. 파이프 라인

WebGL은 일정한 흐름구조를 만들어 두고 그 각부분을 만들수 있도록 해 두었다. 아마 최적화가 쉬운 탓에 그러했으리고 생각된다.
WebGl파이프라인
[PNG image (52.47 KB)]

Attribute는 각 포인트 별로 전달되는 정보이고 uniform 은 전체에서 공통적인 정보이다. 일반적으로 Attribute는 각 정점의 위치 정보와 각 지점의 법선 벡터 정보를을 전달한다. uniform은 일반적으로 카메라의 위치나 환경광의 위치처럼 전체적인 것을 전달한다. Attribute나 uniform은 일종의 변수인데 핸들을 얻어와서 그것을 통해 값을 전달할수 있다. 즉 Atrribute나 Uniform은 Javascript측에서 쉐이더로 정보를 보내는 것이다. varying은 쉐이더 간의 정보 전달에 사용된다. vertex shader에서 fragment shader로 값이 전달되며 반대는 불가능하다(파이프라인 구조상 당연한 것이다). 이때 vertex shader는 각 정점(꼭지점) fragment shader는 각 픽셀에 한번 호출되게 되는데 각 정점 사이의 값들은 간법을 거쳐 전달되게 된다(그라디언트 같은 느낌이다 중간값을 알아서 만들어 준다).

각 쉐이더는 동시에 동작할수 있는데 당연히 이들은 서로간에 독립적이어야 한다.

3.2. 쉐이더

쉐이더는 쉐이더 언어로 따로 짜주고 컴파일 해야하며 심지어 링크까지 시켜주어야 한다. GPU의 강력한 연산 능력을 가져다 쓰기 위해서인것으로 보이는데 이것을 사용하지 않고서는 예제파일도 돌려볼수가 없다. 다행이 언어는 C언어와 매우 유사하고 행렬연산이 모두 있기 때문에 딱히 어렵거나 하진 않다. 다만 어느부분에서 어디와 연결되는지 이해하는데 시간이 걸린다.

3.2.1. vertex shader

각 정점(vertex, 꼭지점)마다 호출되며 주로 꼭지점의 위치를 연산하고 실제 View에 투영하는 연산을 주로 하게 된다. 한마디로 모델의 위치 변환과 카메라 시점에 따른 변환 원근법을 적용하는 변환등을 수행한다.

3.2.2. fragment shader

각 정점 사이에 있는 픽셀 마다 호출된다. 주로 광원효과를 적용한 픽셀의 최종적인 색깔이나 텍스쳐 연산에 사용된다. varying변수를 vertex shader에서 fragment shader로 넘겨주면 각 정점 사이에는 보간법으로 변환된 값이 넘어 온다.

4. 예제코드

아래 코드는 정확하지 않으며 연습 도중의 코드입니다. 또한 WebGL의 특성상 코드가 분산되어 있습니다. 현재 객체 래핑을 진행중입니다.

현재 객체 래핑중 중대한 문제에 봉착. 대부분의 모듈과 세이더 코드는 으로 호출되는데 이것을 적절히 래핑할 방법이 없다. webGL과는 하등 연관이 없는 부분이라서 각자 알아서 구현하도록 해도 되지만 대부분의 경우 같은 코드를 다스 짜고 있는 나를 보게 된다. 이것을 어떻게 해야 잘한 래핑이라 할수 있을까?

4.1. vertexShader

attribute vec3 aVertexPosition;
attribute vec3 aVertexNormal;

uniform mat4 matCamara;
uniform mat4 matProject;

uniform vec3 lightPos;
uniform vec3 lightDirection;
uniform vec4 materialDiffuse;
uniform vec4 lightDiffuse;

varying vec4 vFinalColor;
varying vec3 vNormal;

void main(void){

	vec3 N = normalize((vec4(aVertexNormal, 1.0) * matCamara).xyz);//nomal compute
	vec3 L = normalize(lightDirection); //lightDrection

	float lambertTerm = max(dot(N, -L), 0.0);
	vec4 Id =  lightDiffuse * materialDiffuse * lambertTerm;

	vNormal = normalize((vec4(aVertexNormal, 1.0) * matCamara).xyz);

	vFinalColor = Id;
	vFinalColor.a = 1.0;
	gl_Position = matProject * matCamara * vec4((aVertexPosition), 1.0);
	gl_Position.w = 1.0;
}

4.2. fragmentShader

#ifdef GL_ES
precision highp float;
#endif

uniform vec3 lightPos;
uniform vec3 lightDirection;
uniform vec4 materialDiffuse;
uniform vec4 lightDiffuse;
	
varying vec4 vFinalColor;
varying vec3 vNormal;

void main(void) {

	vec3 L = normalize(lightDirection);

    gl_FragColor = vFinalColor + vec4(0.01,0.01,0.01, 1.0);
}

4.3. javascript

var cube = {
  "vertices": [
     0.2, 0.2, 0.2, //0
     0.2, 0.2,-0.2, //1
     0.2,-0.2, 0.2, //2
     0.2,-0.2,-0.2, //3
    -0.2, 0.2,-0.2, //4
    -0.2, 0.2, 0.2, //2
    -0.2,-0.2,-0.2, //6
    -0.2,-0.2, 0.2  //7
  ],
  "normals": [
     1, 1, 1, //0
     1, 1,-1, //1
     1,-1, 1, //2
     1,-1,-1, //3
    -1, 1,-1, //4
    -1, 1, 1, //5
    -1,-1,-1, //6
    -1,-1, 1  //7

  ],
  "indices" : [
    0,2,3, 0,3,1,
    4,6,7, 4,7,5,
    4,5,0, 4,0,1,
    7,6,3, 7,3,2,
    5,7,2, 5,2,0,
    1,3,6, 1,6,4
  ]
}

var init = function(){
  var gl = getGLContext();


  var cubeBuffer = new GLBuffer(gl, cube);

  var shader;
  async.parallel([
    function(callback){
      var url = document.getElementById("vertexShader").getAttribute("src");
      ajax(url, callback);
    },
    function(callback){
      var url = document.getElementById("fragmentShader").getAttribute("src");
      ajax(url, callback);
    },
  ], function (err, data){
    shader = new GLShader(gl, data[0], data[1]);

    gl.useProgram(shader.program);
    shader.aVertexPosition = gl.getAttribLocation(shader.program, "aVertexPosition");
    shader.aVertexNormal = gl.getAttribLocation(shader.program, "aVertexNormal");

    var cam = gl.getUniformLocation(shader.program, "matCamara");
    
    var camMat = mat4.identity(mat4.create());

    mat4.translate(camMat, camMat, [0, 0, 0.1]);
    mat4.rotate(camMat, camMat, Math.PI/4, [1,0.5,0.5]);

    gl.uniformMatrix4fv(cam, false, camMat);

    var lightPos = shader.getUniformLocation("lightPos");
    gl.uniform3fv(lightPos, [0.1,0.1,0.1]);

    var lightDirection = shader.getUniformLocation("lightDirection");
    gl.uniform3fv(lightDirection, [-1, -1, -1]);
    
    var materialDiffuse = shader.getUniformLocation("materialDiffuse");
    gl.uniform4fv(materialDiffuse, [0.8, 0.2, 0.2, 1.0]);

    var lightDiffuse = shader.getUniformLocation("lightDiffuse");
    gl.uniform4fv(lightDiffuse, [1,1,1,1]);

    var matProject = mat4.identity(mat4.create());//2PI = 360d -> 1d = PI/180
    mat4.perspective(matProject, Math.PI/180 * 80, 1, 0, 1);

    gl.uniformMatrix4fv(
      shader.getUniformLocation("matProject"),
      false,
      matProject
    );

    onReady(gl, cubeBuffer, shader);
  });
}

setTimeout(init, 0);
function onReady(gl, buffer, shader){
  onDraw();

  function onDraw(){
    gl.clearColor(0.0, 0.0, 0.0, 1.0);

    gl.enable(gl.DEPTH_TEST);

    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    gl.viewport(0,0,300,300);

    gl.enableVertexAttribArray(shader.aVertexPosition);
    gl.enableVertexAttribArray(shader.aVertexNormal);

    gl.bindBuffer(gl.ARRAY_BUFFER, buffer.vertex);
    gl.vertexAttribPointer(shader.aVertexPosition, 3, gl.FLOAT, false, 0, 0);

    gl.bindBuffer(gl.ARRAY_BUFFER, buffer.normal);
    gl.vertexAttribPointer(shader.aVertexNormal, 3, gl.FLOAT, false, 0, 0);

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer.index);
    gl.drawElements(gl.TRIANGLES, buffer.index.length, gl.UNSIGNED_SHORT, 0);
  }
}

function ajax(url, callback){
  var ajax = new XMLHttpRequest();
  ajax.onreadystatechange = function(){
    if(ajax.readyState === 4){
      //complete requset
      if(ajax.status === 200){
        //not error
        callback(null, ajax.responseText);
      }
    }
  }

  ajax.open("GET", url, true);//if need Sync method set false;
  ajax.send(null);
}
//Lib function
function getGLContext(){
  var canvas = document.getElementsByTagName("canvas");
    
  canvas = [].filter.call(canvas, function(element){
    if(element.getAttribute("WebGL") != null)
      return true;
    else
      return false;
  });
  canvas = canvas[0];

  return canvas.getContext("experimental-webgl");
}


//Lib Class
function GLBuffer(gl, model){
  this.model = model;

  try {
    //only binded buffer can send data
    //vertex is coord of points
    var vertexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(model.vertices), gl.STATIC_DRAW);
    gl.bindBuffer(gl.ARRAY_BUFFER, null);

    //index is triangle point index of suface
    var indexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(model.indices), gl.STATIC_DRAW);
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);

    //normals Buffer
    var normalBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(model.normals), gl.STATIC_DRAW);
    gl.bindBuffer(gl.ARRAY_BUFFER, null);

    this.vertex = vertexBuffer;
    this.index = indexBuffer;
    this.index.length = model.indices.length;
    this.normal = normalBuffer;
  } catch(e){
    throw Error("Can not create Buffer");
  }
}
function GLShader(gl, vertexSource, fragmentSource){
  var shaderProgram = gl.createProgram();

  //compile Source
  var vertexShader = gl.createShader(gl.VERTEX_SHADER);
  gl.shaderSource(vertexShader, vertexSource);
  gl.compileShader(vertexShader);

  checkCompile(vertexShader);

  var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
  gl.shaderSource(fragmentShader, fragmentSource);
  gl.compileShader(fragmentShader);

  checkCompile(fragmentShader);

  //attach shader
  gl.attachShader(shaderProgram, fragmentShader);
  
  gl.attachShader(shaderProgram, vertexShader);
  
  //link
  gl.linkProgram(shaderProgram);

  this.program = shaderProgram;

  this._private = {
    gl : gl
  }

  function checkCompile(shader){
    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
      throw Error(gl.getShaderInfoLog(shader));
    }
  }
}
GLShader.prototype.getUniformLocation = function(name){
  return this._private.gl.getUniformLocation(this.program, name);
}
GLShader.prototype.uniform4fv = function(name, arr){
  this._private.gl.uniform4fv(this.getUniformLocation(name), arr);
}
GLShader.prototype.uniform3fv = function(name, arr){
  this._private.gl.uniform3fv(this.getUniformLocation(name), arr);
}
GLShader.prototype.uniformMatrix4fv = function(name, arr){
  this._private.gl.uniformMatrix4fv(this.getUniformLocation(name), false, arr);
}

5. WebGL의 좌표계

OpenGL과 동일한 -1.0 ~ 1.0이며 이를 넘어갈시에는 표현되지 않는다. 매트릭스 연산을 직접해야만 하는 WebGL에서는 이점이 간과되기 쉬워서 이미 그린 페이지가 어디에 있는지 찾는 현상이 발생하게 된다.

계속 작성중.
Valid XHTML 1.0! Valid CSS! powered by MoniWiki
last modified 2021-02-07 05:28:23
Processing time 0.0519 sec