WebGL 은 기본적으로 3D를 위해서 존재합니다.
가상의 공간에 물체를 놓고, 어느 한 시점에서 바라볼 때 눈에 보이는 장면을 2D 로 그려내는 것입니다.
3D Graphic 분야에서 사용되는, 익숙하지 않은 많은 용어들이 있습니다. 한꺼번에 다 설명할 수도 없고, 진행해 나가면서 필요할 때 하나씩 설명을 하겠습니다. 사실 저도 3D 전문가가 아니라, 잘 모릅니다. 단지 제가 이해하는 만큼 설명이 가능할 것입니다.
1. HTML 기본 태그를 작성합시다.
-----
<!DOCTYPE html>
<html>
<head>
<title>WebGL</title>
<meta charset="UTF-8">
<script type="text/javascript">
function webgl_start() {
} // webgl_start()
</script>
</head>
<body onload="webgl_start()">
<canvas id="webgl-screen" width='500' height='500'></canvas>
</body>
</html>
-----
가로/세로 500짜리 canvas 를 준비해두고, webgl_start() 함수내에서 작업해봅시다.
먼저 WebGLRenderingContext 를 가져옵시다.
전역변수로 gl 을 만들어 두고, canvas 의 id 를 인자로 받아서 해당 context 를 리턴하는 함수를 작성해서 사용하도록 합시다.
아래의 getGL() 함수를 script 태그 내에 작성을 합시다.
-----
function getGL(name) {
var canvas = document.getElementById(name);
var ctx = null;
if (canvas == null){
alert('there is no canvas on this page');
return null;
}
else {
c_width = canvas.width;
c_height = canvas.height;
}
var names = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"];
for (var i = 0; i < names.length; ++i) {
try {
ctx = canvas.getContext(names[i]);
}
catch(e) {}
if (ctx) {
break;
}
}
if (ctx == null) {
alert("Could not initialise WebGL");
return null;
}
else {
return ctx;
}
}
-----
<script type="text/javascript"> 태그 안에 아래와 같이 변수를 선언해 둡시다.
var gl;
var c_width; // 캔버스 가로
var c_height; // 캔버스 세로
webgl_start() 안에 아래 줄을 작성합시다.
gl = getGL('webgl-screen');
webgl-screen canvas에서 context 를 가져옵니다. 이때, 캔버스의 가로/세로 크기도 저장을 해 놓습니다.
webGL 을 사용할 준비는 됐습니다.
Vertex Buffer / Index Buffer 준비
그릴 대상을 설정해주어야겠지요?
(-1, -1, 0) , (1,-1,0), (0,1,0) 를 세꼭지점으로 하는 삼각형을 그려봅시다.
vertex 좌표를 모아놓는 vertex buffer 와 삼각형을 지정할 index 를 모아놓는 index buffer 을 준비해줘야합니다.
-----
var vertices = [ -1.0, -1.0, 0,
1.0, -1.0, 0,
0, 1.0, 0
];
var indices = [ 0, 1, 2 ];
var vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW );
var indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW );
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
-----
배열변수 vertice 에 알아보기 쉽게 3개의 꼭지점 좌표를 선언해줍니다.
순서대로 0, 1, 2 라는 번호를 가지게 됩니다.
그럼, indices 는 삼각형을 이루게 될 세 꼭지점을 꼭지점 번호로 지정을 해 줍니다.
0,1,2 3개의 vertex 가 삼각형을 이루게 되겠죠.
이렇게 선언한 자바스크립트 배열을 webGL 이 써먹을 수 있게 손을 좀 봐 줍시다.
Vertex Buffer 와 Index Buffer
vertex 를 모아놓을 버퍼를 생성하는 방법입니다.
var vertexBuffer = gl.createBuffer();
먼저 버퍼를 준비하고,
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
그 버퍼를 ARRAY_BUFFER 라고 gl에 연결시킵니다.
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW );
그 리고나서 그 버퍼에 데이터를 저장합니다. 이때 저장될 데이터는 float32 형태의 배열로 되어있으니, vertices 변수에서 가져와라...라고 알려줍니다. STATIC_DRAW 는 이 버퍼의 내용은 변하지 않는 STATIC 한 형태라고 알려줍니다.
index buffer 의 경우도 매우 유사합니다. 단지 ELEMENT_ARRAY_BUFFER 라는 인자가 사용되고, 저장될 데이터가 Uint16 형태라는 차이가 있을 뿐입니다.
마 지막 두 줄은 데이터를 채워넣으려고 붙잡아뒀던 buffer를 놓아주는 것입니다. gl 은 뭔가 작업을 할때, 연결된 버퍼를 찾아서 작업을 하게 됩니다. 필요할 때 붙여서 작업을 하고, 다 했으면 다시 떼어놓고.. 붙였다, 뗏다 하면서 진행됩니다.
Shader program
3D 를 하다보면 render 라고하는, 이미지화하는 작업을 의미하는 동사를 많이 만나게 됩니다. renderder 라고 하면, 데이터로부터 이미지를 만들어내는 녀석을 의미하죠. renderer 는 어떻게 이미지로 만들까요? 그래픽 카드에서 뭔가 사삭하고 돌아가면서 색색이 화면을 만들어 낼 텐데, 이때, 그 작업을 하는 프로그램이 있습니다. 그녀석을 shader 라고 부릅니다. 실제 컴파일 링크되서 실행코드가 되어서 webgl 옆에서 열심히 실행되는 녀석입니다. 색깔을 어찌어찌하고 모양은 어찌어찌하도록 처리를 해라..라는 것들을 프로그래밍 해 줄 수 있습니다.
렌더링을 시키기 전에 제대로된 지시사항을 내려주는 거죠.
우리도 여기서 삼각형을 어떤 색상으로 그려라...를 shader 를 통해 지정을 해 줍시다.
shader 는 vertex shader, fragment shader 두가지가 있습니다. 점을 어떻게 처리해라..라는 것을 지정하는 것이 vertex shader, 점들로 이루어진 면을 어떻게 처리해라...라는 것을 지정하는 것이 fragment shader 입니다.
------
<!-- Fragment Shader //-->
<script id='shader-fs' type='x-shader/x-fragment'>
#ifdef GL_ES
precision highp float;
#endif
void main(void) {
gl_FragColor = vec4(0.5, 0.9, 0.2, 1.0); //Green
}
</script>
<!-- Vertex Shader //-->
<script id='shader-vs' type='x-shader/x-vertex'>
attribute vec3 aVertexPosition;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
void main(void) {
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
gl_PointSize = 3.0;
}
</script>
-----
위와 같이 script 태그를 이용해서 두 가지 shader를 프로그래밍할 수 있습니다. C언어와 매우 유사합니다. main() 함수가 있는 각각이 하나의 프로그램이되죠.
쉐이더 프로그래밍 분야도 심오합니다. 위의 내용은 단지, 색상을 녹색으로 설정한다는 정도로 알고 우선 넘어갑니다. ㅜㅜ
저 코드들을 컴파일하고 링크해서 WebGL에게 전해줘야합니다.
우선 프로그램 코드부분을 추출해 내야 컴파일/링크를 할 수 있겠죠?
그래서 아래의 코드가 추가로 필요합니다. script 태그 내에 포함된 프로그램 코드를 가져와서 컴파일까지 하는 자바스크립트 입니다.
-----
function getShader(gl, id) {
var script = document.getElementById(id);
if (!script) {
return null;
}
var str = "";
var k = script.firstChild;
while (k) {
if (k.nodeType == 3) {
str += k.textContent;
}
k = k.nextSibling;
}
var shader;
if (script.type == "x-shader/x-fragment") {
shader = gl.createShader(gl.FRAGMENT_SHADER);
} else if (script.type == "x-shader/x-vertex") {
shader = gl.createShader(gl.VERTEX_SHADER);
} else {
return null;
}
gl.shaderSource(shader, str);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert(gl.getShaderInfoLog(shader));
return null;
}
return shader;
}
-----
이 함수는 getGL() 함수 선언 뒤에 추가해 줍시다.
이제 shader 코드를 가져와서 컴파일까지 할 수 있으니, 이제 WebGL에 전해줘야겠네요.
webgl-start() 함수의 아까 마지막 작업했던
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); 다음 줄에 아래 코드를 계속 이어갑시다.
-----
// shader program
var fragmentShader = getShader(gl, "shader-fs");
var vertexShader = getShader(gl, "shader-vs");
var shaders = gl.createProgram();
gl.attachShader(shaders, vertexShader);
gl.attachShader(shaders, fragmentShader);
gl.linkProgram(shaders);
if (!gl.getProgramParameter(shaders, gl.LINK_STATUS)) {
alert("Could not initialise shaders");
}
gl.useProgram(shaders);
shaders.vertexPositionAttribute = gl.getAttribLocation(shaders, 'aVertexPosition');
shaders.pMatrixUniform = gl.getUniformLocation(shaders, 'uPMatrix');
shaders.mvMatrixUniform = gl.getUniformLocation(shaders, 'uMVMatrix');
-----
var fragmentShader = getShader(gl, "shader-fs");
script 중에 shader-fs 의 코드를 가져와서 컴파일 한 후, fragmentShader에 저장합니다.
var vertexShader = getShader(gl, "shader-vs");
마찬가지로, script 중에 shader-vs 의 코드를 가져와서 컴파일 한 후, vertexShader에 저장합니다.
var shaders = gl.createProgram();
쉐이더 프로그램을 담을 객체를 생성합니다.
gl.attachShader(shaders, vertexShader);
gl.attachShader(shaders, fragmentShader);
두개의 컴파일한 shader 코드를 gl에 붙이고,
gl.linkProgram(shaders);
링크해서 최종 프로그램을 만들어둡니다.
그 아래줄은 링크결과를 살펴서 오류가 있는지 확인하는 코드입니다.
gl.useProgram(shaderProgram);
오류가 없으면, gl에게 이거 쓰라고 알려줍니다.ㅎㅎ
마지막 석 줄은 설명이 다음에 설명할 수 있기를 바랍니다. ㅋ
여기까지... WebGL Context 준비하고, 그릴 대상 준비하고, 어떻게 그려라고 shader program 준비까지 했습니다.
이제 그려야겠군요.
shader 설정부분 아래에 다음 코드를 추가합시다.
-----
// draw
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.enable(gl.DEPTH_TEST);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.viewport(0,0,c_width, c_height);
mat4.perspective(45, c_width / c_height, 0.1, 10000.0, pMatrix);
mat4.identity(mvMatrix);
mat4.translate(mvMatrix, [0.0, 0.0, -5.0]);
gl.uniformMatrix4fv(shaders.pMatrixUniform, false, pMatrix);
gl.uniformMatrix4fv(shaders.mvMatrixUniform, false, mvMatrix);
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.vertexAttribPointer(shaders.aVertexPosition, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(shaders.vertexPositionAttribute);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.drawElements(gl.LINE_LOOP, indices.length, gl.UNSIGNED_SHORT,0);
-----
그리고 gl, c_width 선언했던 곳에 아래 매트릭스도 선언해 줍시다.
var mvMatrix = mat4.create(); // The Model-View matrix
var pMatrix = mat4.create(); // The projection matrix
매트릭스가 등장했습니다.
매트릭스 관련은 따로 포스팅을 해야겠습니다. 지금은 그냥..그냥...
매트릭스 연산을 위한 라이브러리를 아래와 같이 추가해줍시다.
<script type='text/javascript' src='js/gl-matrix-min.js'></script>
대략 과정을 보면...
화면을 지우고, viewport 를 canvas 크기만큼 설정을 해 주고, 그려줄 객체를 바라보는 방법을 matrix 를 통해서 지정을 해 줍니다.
vertex buffer 를 통해 쉐이더에게 vertex 정보를 넘깁니다. ( 이 과정도 좀 더 깊게 살펴보고 정리해야겠습니다. )
index buffer 를 바인드하고, 그것을 이용해서 드디어 화면에 그립니다. drawElements()
아래와 같이 나오나요?
전체 코드는 아래와 같습니다.
-----
<!DOCTYPE html>
<html>
<head>
<title>WebGL</title>
<meta charset="UTF-8">
<script type='text/javascript' src='js/gl-matrix-min.js'></script>
<!-- Fragment Shader //-->
<script id='shader-fs' type='x-shader/x-fragment'>
#ifdef GL_ES
precision highp float;
#endif
void main(void) {
gl_FragColor = vec4(0.5, 0.9, 0.2, 1.0); //Green
}
</script>
<!-- Vertex Shader //-->
<script id='shader-vs' type='x-shader/x-vertex'>
attribute vec3 aVertexPosition;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
void main(void) {
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
gl_PointSize = 3.0;
}
</script>
<script type="text/javascript">
var gl;
var c_width;
var c_height;
var mvMatrix = mat4.create(); // The Model-View matrix
var pMatrix = mat4.create(); // The projection matrix
function getGL(name) {
var canvas = document.getElementById(name);
var ctx = null;
if (canvas == null){
alert('there is no canvas on this page');
return null;
}
else {
c_width = canvas.width;
c_height = canvas.height;
}
var names = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"];
for (var i = 0; i < names.length; ++i) {
try {
ctx = canvas.getContext(names[i]);
}
catch(e) {}
if (ctx) {
break;
}
}
if (ctx == null) {
alert("Could not initialise WebGL");
return null;
}
else {
return ctx;
}
}
function getShader(gl, id) {
var script = document.getElementById(id);
if (!script) {
return null;
}
var str = "";
var k = script.firstChild;
while (k) {
if (k.nodeType == 3) {
str += k.textContent;
}
k = k.nextSibling;
}
var shader;
if (script.type == "x-shader/x-fragment") {
shader = gl.createShader(gl.FRAGMENT_SHADER);
} else if (script.type == "x-shader/x-vertex") {
shader = gl.createShader(gl.VERTEX_SHADER);
} else {
return null;
}
gl.shaderSource(shader, str);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert(gl.getShaderInfoLog(shader));
return null;
}
return shader;
}
function webgl_start() {
gl = getGL('webgl-screen');
var vertices = [ -1.0, -1.0, 0,
1.0, -1.0, 0,
0, 1.0, 0
];
var indices = [ 0, 1, 2 ];
var vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW );
var indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW );
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
// shader program
var fragmentShader = getShader(gl, "shader-fs");
var vertexShader = getShader(gl, "shader-vs");
var shaders = gl.createProgram();
gl.attachShader(shaders, vertexShader);
gl.attachShader(shaders, fragmentShader);
gl.linkProgram(shaders);
if (!gl.getProgramParameter(shaders, gl.LINK_STATUS)) {
alert("Could not initialise shaders");
}
gl.useProgram(shaders);
shaders.vertexPositionAttribute = gl.getAttribLocation(shaders, 'aVertexPosition');
shaders.pMatrixUniform = gl.getUniformLocation(shaders, 'uPMatrix');
shaders.mvMatrixUniform = gl.getUniformLocation(shaders, 'uMVMatrix');
// draw
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.enable(gl.DEPTH_TEST);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.viewport(0,0,c_width, c_height);
mat4.perspective(45, c_width / c_height, 0.1, 10000.0, pMatrix);
mat4.identity(mvMatrix);
mat4.translate(mvMatrix, [0.0, 0.0, -5.0]);
gl.uniformMatrix4fv(shaders.pMatrixUniform, false, pMatrix);
gl.uniformMatrix4fv(shaders.mvMatrixUniform, false, mvMatrix);
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.vertexAttribPointer(shaders.aVertexPosition, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(shaders.vertexPositionAttribute);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.drawElements(gl.LINE_LOOP, indices.length, gl.UNSIGNED_SHORT,0);
} // webgl_start()
</script>
</head>
<body onload="webgl_start()">
<canvas id="webgl-screen" width='500' height='500'></canvas>
</body>
</html>
-----
더 찾아봐야 할 내용 많네요. ㅠㅠ
'WebGL' 카테고리의 다른 글
WebGL - (6) 여러가지 Matrix 에 대한 설명 (0) | 2013.05.25 |
---|---|
WebGL - (5) 사각형 그리기 (0) | 2013.05.23 |
WebGL - (3) Context 가져오는 좀 더 나은 방법 (1) | 2013.05.22 |
WebGL - (2) 작업영역 지우기 (0) | 2013.05.21 |
WebGL - (1) (0) | 2013.05.21 |
댓글