[WebGL] WebGL #3 – 기초 3

개요top

WebGL 기초 3번째 글입니다. 지난 글에서 WebGL을 이용해 어떻게 삼각형을 그릴 수 있는지 살펴보았습니다. 삼각형 하나를 그리기 위해 많은 기초 지식이 필요함도 알 수 있었죠. 이번 글에서는 지난 글에서 익힌 지식과 코드를 바탕으로 조금 더 응용해서 WebGL과 더욱 친해져 보도록 하겠습니다.

(참고로 이 글은 webglfundamentals.org의 글을 나름대로 해석하고 공부하면서 정리한 글입니다.)

지난 글에서 작성한 코드 – WebGL로 삼각형 그리기top

아래 코드는 지난 글에서 작성한 코드입니다. 대신 주석을 전부 지웠습니다. 우리는 주석이 지워진 이 코드만으로 한 줄씩 의미를 되새김하시면서 그 의미를 충분히 설명할 수 있어야 합니다. 그래야 앞으로 나오는 새로운 지식을 받아들이기도 훨씬 수월하지 않을까 싶습니다.

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>WebGL #3 - 기초 3</title>
</head>
<body>

<canvas id="c" width="500" height="500"></canvas>

<script id="2d-vertex-shader" type="notjs">
attribute vec4 a_position;
void main() {
	gl_Position = a_position;
}
</script>
<script id="2d-fragment-shader" type="notjs">
precision mediump float;
void main() {
	gl_FragColor = vec4(1, 0, 0.5, 1);
}
</script> 

<script>
//[렌더링 준비]
var canvas = document.getElementById("c");
var gl = canvas.getContext("webgl");
if (!gl) {
    console.log('no webgl for you!');
}

var vertexShaderSource = document.getElementById("2d-vertex-shader").text;
var fragmentShaderSource = document.getElementById("2d-fragment-shader").text;

function createShader(gl, type, source) {
	var shader = gl.createShader(type);
	gl.shaderSource(shader, source);
	gl.compileShader(shader);
	var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
	if(success){
		return shader;
	}
	console.log(gl.getShaderInfoLog(shader));
	gl.deleteShader(shader);
}
var vertexShader = createShader(
	gl, gl.VERTEX_SHADER, vertexShaderSource
);
var fragmentShader = createShader(
	gl, gl.FRAGMENT_SHADER, fragmentShaderSource
);

function createProgram(gl, vertexShader, fragmentShader) {
	var program = gl.createProgram();
	gl.attachShader(program, vertexShader);
	gl.attachShader(program, fragmentShader);
	gl.linkProgram(program);
	var success = gl.getProgramParameter(
		program, gl.LINK_STATUS
	);
	if (success) {
		return program;
	}
	console.log(gl.getProgramInfoLog(program));
	gl.deleteProgram(program);
}
var program = createProgram(
	gl, vertexShader, fragmentShader
);

var positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
var positions = [
  0, 0,
  0, 0.5,
  0.7, 0,
];
gl.bufferData(
	gl.ARRAY_BUFFER, 
	new Float32Array(positions), 
	gl.STATIC_DRAW
);

var positionAttributeLocation = 
   gl.getAttribLocation(program, "a_position");

//[렌더링 실시]
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);

gl.useProgram(program);

gl.enableVertexAttribArray(positionAttributeLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
 
var size = 2;           
var type = gl.FLOAT;  
var normalize = false;
var stride = 0; 
var offset = 0;
gl.vertexAttribPointer(
	positionAttributeLocation, 
	size, 
	type, 
	normalize, 
	stride, 
	offset
);

var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = 3;
gl.drawArrays(primitiveType, offset, count);
</script>
</body>
</html>

픽셀 단위 위치 데이타로 그림 그리기top

앞서 작성한 버텍스 셰이더(Vertex shader)는 공급한 위치 데이타를 가공없이 그대로 사용했습니다. 그래서 제공된 위치 데이터가 바로 클립 공간(clip space)의 위치가 되는 셈이지요. WebGL은 단지 래스터화(rasterization) API일 뿐이므로 만약 3D 화면을 보길 원한다면 3D 데이타를 제공하고 클립 공간으로 변환하는 셰이더를 작성하면 됩니다(이에 대해선 차차 공부하게 될겁니다).

위 그림은 위 코드의 실행 결과입니다. 삼각형이 가운데를 중심에서 오른쪽 상단(1사분면)에 위치했습니다. 이는 클립 공간의 x축 값은 -1 ~ 1의 범위를 가지기 때문에 0이 중심이고 양수값이 오른쪽에 있음을 의미합니다. 같은 이유로 삼각형이 중앙에서 상단에 위치한 이유는 y축도 -1 ~ 1의 범위를 가지고 0은 가운데 하단 쪽은 음수이고 상단은 양수이기 때문입니다.

보통의 경험상 우리는 2D 화면에 그림을 그리는 경우 클립 공간보다 픽셀(pixel) 단위의 스크린 공간(screen space)의 데이타를 다루기 편한 경우가 생깁니다. 그래서 위치 데이타를 클립 공간의 좌표값이 아닌 처음부터 스크린 공간(screen space)의 좌표를 제공하고 이것을 클립 공간으로 변환하도록 할 수 있습니다. 이 목적을 달성하기 위한 버텍스 셰이더는 다음처럼 작성할 수 있습니다.

<script id="2d-vertex-shader" type="notjs">
//픽셀 단위 x, y좌표 애트리뷰트 
attribute vec2 a_position;

//x축, y축 해상도 유니폼 
uniform vec2 u_resolution;

void main() {
	// 픽셀을 0.0 ~ 1.0으로 위치 변환
	vec2 zeroToOne = a_position / u_resolution;

	// 0->1에서 0->2로 변환 
	vec2 zeroToTwo = zeroToOne * 2.0;

	// 0->2에서 -1->+1로 변환(클립공간 좌표)
	vec2 clipSpace = zeroToTwo - 1.0;

	gl_Position = vec4(clipSpace, 0, 1);
}
</script>

위 버텍스 셰이더 코드는 지난 번과 다르게 a_position 애트리뷰트가 vec4가 아닌 vec2입니다. 즉, 2개의 픽셀단위 좌표인 x, y값만 가지는 데이타를 다루려고 합니다. vec4는 x,y,z,w의 4개 값을 가지지만 vec2는 x, y 두 개의 데이타만 가지지요. 지난 번에 vec4이였던 이유는 gl_Position는 vec4만 받을 수 있고 a_position이 직접 gl_Position으로 전달되기 때문이였습니다. 하지만 이번에 위치 데이타는 어짜피 가공할 데이타이므로 vec4일 필요는 없습니다. 그래서 vec2가 된 것이라고 이해할 수 있겠습니다. 암튼, 이젠 a_position은 위치 데이타를 픽셀 단위로 공급해 주기로 했으므로 아래처럼 버퍼 데이타를 만들면 되겠습니다.

var positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// 위치 데이타를 클립공간 좌표가 아닌 픽셀 단위값으로 줍니다. 
// x, y쌍으로 6개인 건 삼각형 2개를 그리겠다는 의미겠지요.
var positions = [
  10, 20,
  200, 20,
  10, 150,
  10, 150,
  200, 20,
  200, 150,
];
gl.bufferData(
	gl.ARRAY_BUFFER, 
	new Float32Array(positions), 
	gl.STATIC_DRAW
);

그리고 유니폼(uniform)이 처음 등장했습니다. 이는 u_resolution 이라는 이름을 가진 vec2 형으로 정의했습니다. x, y축의 해상도를 담는 전역변수임을 알려주는 셈이겠지요. 유니폼도 애트리뷰트처럼 데이타를 WebGL API로 공급해줘야 합니다. 다만 애트리뷰트처럼 버퍼를 통해 데이터를 공급받지 않고 직접 WebGL API로 받게 됩니다.

GLSL 프로그램에 정의된 해상도를 담당하는 유니폼인 u_resolution을 참조하기 위해 WebGL API로 아래 처럼 작성하면 됩니다.

var resolutionUniformLocation = 
   gl.getUniformLocation(program, "u_resolution");   

결국 버텍스 쉐이더의 main()함수 내부의 주석에서 잘 설명되어 있듯이, 유니폼 u_resolution를 이용하면 positionBuffer에 넣은 스크린 공간 좌표인 픽셀 값을 클립 공간 좌표값으로 변환시킬 수 있게 됩니다.

버텍스 쉐이더의 마지막에 gl_Position = vec4(clipSpace, 0, 1);를 이해해 봅시다. 이렇게 변환된 클립 공간 좌표값을 담는 변수인 clipSpace는 vec2입니다. 즉, x, y값만 가집니다. 하지만 GPU에 클립 공간 좌표를 gl_Position로 전달함에 있어서 이건 vec4만 받을 수 있으므로 직접 gl_Position = clipSpace로 할 수 없고 vec4로 변환해야 합니다. 이를 위해 vec4()함수를 사용했습니다. vec4()함수에서 첫번째 인자가 vec2이기 때문에 나머지 2개 z, w의 기본값인 0, 1를 인자값으로 주면 vec4 값으로 변환해줍니다.

이 유니폼에 해상도를 설정하는 코드는 다음과 같습니다.

gl.useProgram(program);

// 해상도 설정
gl.uniform2f(resolutionUniformLocation, gl.canvas.width, gl.canvas.height);

기존 코드에서 gl.useProgram(program) 뒤에 해상도 설정을 했습니다. gl.uniformXXX 함수를 사용해 현재 설정된 프로그램에 유니폼의 값을 셋팅할 수 있습니다.

마지막으로 삼각형 2개를 그리므로 버텍스 셰이더는 총 6번 호출되어야 합니다. 그러므로 count를 6으로 수정해 주세요.

var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = 6; //삼각형 2개를 그릴 것으므로 6으로 바꿈 
gl.drawArrays(primitiveType, offset, count);

자, 실행해보면 다음과 같은 결과 화면을 볼 수 있을 겁니다.

사각형이 그려졌습니다. 사실 삼각형 2개를 빗변을 마주보고 그려지도록 좌표값을 주엇기 때문에 사각형으로 보이는 겁니다. WebGL은 하단 좌측 코너를 0,0으로 생각하기 때문에 일반적인 2d 그래픽 API에서 다루는 전통적인 상단좌측 코너로 변환할 필요가 있을 수 있습니다. 이는 단지 버텍스 셰이더에서 아래 코드처럼 클립 공간의 y축 좌표를 플립(flip) 변환하면 됩니다.

gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);

결과는 다음과 같습니다.

전체 코드는 다음과 같습니다.

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>WebGL #3 - 기초 3-1</title>
</head>
<body>

<canvas id="c" width="500" height="500"></canvas>

<script id="2d-vertex-shader" type="notjs">
//픽셀 단위 x, y좌표 애트리뷰트 
attribute vec2 a_position;

//x축, y축 해상도 유니폼 
uniform vec2 u_resolution;

void main() {
	// 픽셀을 0.0 ~ 1.0으로 위치 변환
	vec2 zeroToOne = a_position / u_resolution;

	// 0->1에서 0->2로 변환 
	vec2 zeroToTwo = zeroToOne * 2.0;

	// 0->2에서 -1->+1로 변환(클립공간 좌표)
	vec2 clipSpace = zeroToTwo - 1.0;

	gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
}
</script>
<script id="2d-fragment-shader" type="notjs">
precision mediump float;
void main() {
	gl_FragColor = vec4(1, 0, 0.5, 1);
}
</script> 

<script>
//[렌더링 준비]
var canvas = document.getElementById("c");
var gl = canvas.getContext("webgl");
if (!gl) {
    console.log('no webgl for you!');
}

var vertexShaderSource = document.getElementById("2d-vertex-shader").text;
var fragmentShaderSource = document.getElementById("2d-fragment-shader").text;

function createShader(gl, type, source) {
	var shader = gl.createShader(type);
	gl.shaderSource(shader, source);
	gl.compileShader(shader);
	var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
	if(success){
		return shader;
	}
	console.log(gl.getShaderInfoLog(shader));
	gl.deleteShader(shader);
}
var vertexShader = createShader(
	gl, gl.VERTEX_SHADER, vertexShaderSource
);
var fragmentShader = createShader(
	gl, gl.FRAGMENT_SHADER, fragmentShaderSource
);

function createProgram(gl, vertexShader, fragmentShader) {
	var program = gl.createProgram();
	gl.attachShader(program, vertexShader);
	gl.attachShader(program, fragmentShader);
	gl.linkProgram(program);
	var success = gl.getProgramParameter(
		program, gl.LINK_STATUS
	);
	if (success) {
		return program;
	}
	console.log(gl.getProgramInfoLog(program));
	gl.deleteProgram(program);
}
var program = createProgram(
	gl, vertexShader, fragmentShader
);

var positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// 위치 데이타를 클립공간 좌표가 아닌 픽셀 단위값으로 줍니다. 
// x, y쌍으로 6개인 건 삼각형 2개를 그리겠다는 의미겠지요.
var positions = [
  10, 20,
  200, 20,
  10, 150,
  10, 150,
  200, 20,
  200, 150,
];
gl.bufferData(
	gl.ARRAY_BUFFER, 
	new Float32Array(positions), 
	gl.STATIC_DRAW
);

var positionAttributeLocation = 
   gl.getAttribLocation(program, "a_position");

//해상도를 제공할 유니폼의 위치 참조 
var resolutionUniformLocation = 
   gl.getUniformLocation(program, "u_resolution");   

//[렌더링 실시]
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);

gl.useProgram(program);

// 해상도 설정
gl.uniform2f(resolutionUniformLocation, gl.canvas.width, gl.canvas.height);

gl.enableVertexAttribArray(positionAttributeLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
 
var size = 2;           
var type = gl.FLOAT;  
var normalize = false;
var stride = 0; 
var offset = 0;
gl.vertexAttribPointer(
	positionAttributeLocation, 
	size, 
	type, 
	normalize, 
	stride, 
	offset
);

var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = 6; //삼각형 2개를 그릴 것으므로 6으로 바꿈 
gl.drawArrays(primitiveType, offset, count);
</script>
</body>
</html>

다른 색, 크기, 위치를 가진 사각형 여러 개 그리기top

다른 응용을 해볼까 합니다. 직사각형을 정의하는 함수를 만들고 이 함수를 여러번 호출해 다른 크기와 위치를 가진 사각형을 여러개 출력할 수 있도록 해보겠습니다. 더불어 프래그먼트 셰이더(fragment shader)를 수정해서 고정된 색이 아닌 WebGL API로 공급해 주는 색으로 그리도록 할 것입니다.

먼저 색을 공급을 받을 수 있도록 프래그먼트 셰이더 코드를 다음처럼 수정해주세요.

<script id="2d-fragment-shader" type="notjs">
precision mediump float;

//R, G, B, A값을 가지는 vec4 형태의 유니폼입니다.
uniform vec4 u_color;
 
void main() {
	//유니폼으로 지정된 색을 
	//gl_FragColor로 전달합니다. 
	gl_FragColor = u_color;
}
</script> 

유니폼을 새롭게 지정했으므로 그 위치를 참조해야 합니다.

//색을 제공할 유니폼의 위치 참조    
var colorUniformLocation = 
   gl.getUniformLocation(program, "u_color");

그리고 사각형 데이타를 버퍼에 공급해줄 함수를 아래처럼 만듭니다.

// 사각형을 정의하는 값으로 버퍼에 제공함
function setRectangle(gl, x, y, width, height){
  var x1 = x;
  var x2 = x + width;
  var y1 = y;
  var y2 = y + height;
 
  //참고 : gl.bufferData(gl.ARRAY_BUFFER, ...) 는 현재
  //`ARRAY_BUFFER` 바인드 포인트가 가리키는 버퍼에만 영향을 줍니다.
  //만약 여러개의 버퍼를 가지고 있다면 
  //그 버퍼를 먼저 바인딩 해야 합니다.
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
     x1, y1,
     x2, y1,
     x1, y2,
     x1, y2,
     x2, y1,
     x2, y2]), gl.STATIC_DRAW);
}
</script>

이 함수는 위치와 크기를 인자로 받아 현재 가리키는 ARRAY_BUFFER 바인드 포인트의 버퍼에 사각형 데이타를 공급해줍니다(이제 이렇게 써도 이해가 되셔야 합니다).

기존에 작성된 위치 데이터 공급 부분과 drawArray()함수를 호출하는 부분은 주석처리 해주시고요.

var positionBuffer = gl.createBuffer();
/*
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// 위치 데이타를 클립공간 좌표가 아닌 픽셀 단위값으로 줍니다. 
// x, y쌍으로 6개인 건 삼각형 2개를 그리겠다는 의미겠지요.
var positions = [
  10, 20,
  200, 20,
  10, 150,
  10, 150,
  200, 20,
  200, 150,
];
gl.bufferData(
	gl.ARRAY_BUFFER, 
	new Float32Array(positions), 
	gl.STATIC_DRAW
);
*/

........

/*
var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = 6; //삼각형 2개를 그릴 것으므로 6으로 바꿈 
gl.drawArrays(primitiveType, offset, count);
*/

그 아래에 다음 코드를 넣어봅시다.

// 0 ~ range까지의 정수값을 무작위로 반환함 
function randomInt(range){
	return Math.floor(Math.random() * range);
}

// 50개의 무작위 크기, 위치, 색을 가진 사각형을 그린다.
for(var i = 0; i < 50; ++i) {
	// 무작위 사각형을 설정합니다.
	// 이것은 positionBuffer에 설정할 겁니다.
	// 왜냐하면 위 코드에서 마지막 
	// ARRAY_BUFFER 바인드 포인트는
	// positionBuffer이기 때문입니다.
	setRectangle(
		gl, 
		randomInt(300), //x
		randomInt(300), //y
		randomInt(300), //width
		randomInt(300)  //height
	);

	// 무작위 색을 셋팅한다.
	gl.uniform4f(
		colorUniformLocation, 
		Math.random(), //red
		Math.random(), //green
		Math.random(), //blue
		1 //opacity
	);

	// 사각형을 그린다.
	gl.drawArrays(gl.TRIANGLES, 0, 6);
}

결과는 다음과 같습니다.

작성된 전체 코드입니다.

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>WebGL #3 - 기초 3-2</title>
</head>
<body>

<canvas id="c" width="500" height="500"></canvas>

<script id="2d-vertex-shader" type="notjs">
//픽셀 단위 x, y좌표 애트리뷰트 
attribute vec2 a_position;

//x축, y축 해상도 유니폼 
uniform vec2 u_resolution;

void main() {
	// 픽셀을 0.0 ~ 1.0으로 위치 변환
	vec2 zeroToOne = a_position / u_resolution;

	// 0->1에서 0->2로 변환 
	vec2 zeroToTwo = zeroToOne * 2.0;

	// 0->2에서 -1->+1로 변환(클립공간 좌표)
	vec2 clipSpace = zeroToTwo - 1.0;

	gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
}
</script>
<script id="2d-fragment-shader" type="notjs">
precision mediump float;

//R, G, B, A값을 가지는 vec4 형태의 유니폼입니다.
uniform vec4 u_color;
 
void main() {
	//유니폼으로 지정된 색을 
	//gl_FragColor로 전달합니다. 
	gl_FragColor = u_color;
}
</script> 

<script>
//[렌더링 준비]
var canvas = document.getElementById("c");
var gl = canvas.getContext("webgl");
if (!gl) {
    console.log('no webgl for you!');
}

var vertexShaderSource = document.getElementById("2d-vertex-shader").text;
var fragmentShaderSource = document.getElementById("2d-fragment-shader").text;

function createShader(gl, type, source) {
	var shader = gl.createShader(type);
	gl.shaderSource(shader, source);
	gl.compileShader(shader);
	var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
	if(success){
		return shader;
	}
	console.log(gl.getShaderInfoLog(shader));
	gl.deleteShader(shader);
}
var vertexShader = createShader(
	gl, gl.VERTEX_SHADER, vertexShaderSource
);
var fragmentShader = createShader(
	gl, gl.FRAGMENT_SHADER, fragmentShaderSource
);

function createProgram(gl, vertexShader, fragmentShader) {
	var program = gl.createProgram();
	gl.attachShader(program, vertexShader);
	gl.attachShader(program, fragmentShader);
	gl.linkProgram(program);
	var success = gl.getProgramParameter(
		program, gl.LINK_STATUS
	);
	if (success) {
		return program;
	}
	console.log(gl.getProgramInfoLog(program));
	gl.deleteProgram(program);
}
var program = createProgram(
	gl, vertexShader, fragmentShader
);

var positionBuffer = gl.createBuffer();
/*
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// 위치 데이타를 클립공간 좌표가 아닌 픽셀 단위값으로 줍니다. 
// x, y쌍으로 6개인 건 삼각형 2개를 그리겠다는 의미겠지요.
var positions = [
  10, 20,
  200, 20,
  10, 150,
  10, 150,
  200, 20,
  200, 150,
];
gl.bufferData(
	gl.ARRAY_BUFFER, 
	new Float32Array(positions), 
	gl.STATIC_DRAW
);
*/

var positionAttributeLocation = 
   gl.getAttribLocation(program, "a_position");

//해상도를 제공할 유니폼의 위치 참조 
var resolutionUniformLocation = 
   gl.getUniformLocation(program, "u_resolution");   
   
//색을 제공할 유니폼의 위치 참조    
var colorUniformLocation = 
   gl.getUniformLocation(program, "u_color");

//[렌더링 실시]
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);

gl.useProgram(program);

// 해상도 설정
gl.uniform2f(resolutionUniformLocation, gl.canvas.width, gl.canvas.height);

gl.enableVertexAttribArray(positionAttributeLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
 
var size = 2;           
var type = gl.FLOAT;  
var normalize = false;
var stride = 0; 
var offset = 0;
gl.vertexAttribPointer(
	positionAttributeLocation, 
	size, 
	type, 
	normalize, 
	stride, 
	offset
);

/*
var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = 6; //삼각형 2개를 그릴 것으므로 6으로 바꿈 
gl.drawArrays(primitiveType, offset, count);
*/

// 50개의 무작위 크기, 위치, 색을 가진 사각형을 그린다.
for(var i = 0; i < 50; ++i) {
	// 무작위 사각형을 설정합니다.
	// 이것은 positionBuffer에 설정할 겁니다.
	// 왜냐하면 위 코드에서 마지막 
	// ARRAY_BUFFER 바인드 포인트는
	// positionBuffer이기 때문입니다.
	setRectangle(
		gl, 
		randomInt(300), //x
		randomInt(300), //y
		randomInt(300), //width
		randomInt(300)  //height
	);

	// 무작위 색을 셋팅한다.
	gl.uniform4f(
		colorUniformLocation, 
		Math.random(), //red
		Math.random(), //green
		Math.random(), //blue
		1 //opacity
	);

	// 사각형을 그린다.
	gl.drawArrays(gl.TRIANGLES, 0, 6);
}


// 0 ~ range까지의 정수값을 무작위로 반환함 
function randomInt(range){
	return Math.floor(Math.random() * range);
}
 
// 사각형을 정의하는 값으로 버퍼에 제공함
function setRectangle(gl, x, y, width, height){
  var x1 = x;
  var x2 = x + width;
  var y1 = y;
  var y2 = y + height;
 
  //참고 : gl.bufferData(gl.ARRAY_BUFFER, ...) 는 현재
  //`ARRAY_BUFFER` 바인드 포인트가 가리키는 버퍼에만 영향을 줍니다.
  //만약 여러개의 버퍼를 가지고 있다면 
  //그 버퍼를 먼저 바인딩 해야 합니다.
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
     x1, y1,
     x2, y1,
     x1, y2,
     x1, y2,
     x2, y1,
     x2, y2]), gl.STATIC_DRAW);
}
</script>
</body>
</html>

이 코드는 기초 수준에서 작성된 코드이기 때문에 여러가지 개선사항이 있습니다. 즉, 위치, 크기, 색을 제공하는 방법이 이것만은 있는 것이 아닙니다. 더욱 효과적인 방법으로 제공하는 방법이 다양하게 있기 때문에 이 글에서 익힌 건 기초 수준에서 WebGL을 익히기 위한 예시일 뿐이라는 점을 명심하는게 좋을 것 같아요.

결론top

지금까지의 기초과정을 천천히 따라오고 실습했다면 WegGL이 결코 어렵고 힘든 API가 아님을 알 수 있습니다. 원리만 잘 이해하면 되는겁니다. 나머지는 그저 풍부하게 제공되는 WebGL API 사용법을 익히고 GLSL과 조금씩 친해지는게 전부일지 모릅니다. 하지만 3D를 그린다는 것은 생각보다 만만치 않을겁니다. 그럼에도 불구하고 이 때문에 WebGL API과 GLSL 자체를 익히는게 어렵다는 뜻은 아니라는 것을 아는게 중요할 것 같습니다. 3D를 그리기 위한 그저 여러가지 수학적 알고리즘 및 표현 방법들을 익히고 그것을 이용해 복잡한 셰이더를 작성해야 하는 문제이지, 사실 WegGL API와 GLSL 자체를 학습하는 것과는 별개일지 모릅니다. 이제 알아야 하는 것은 WebGL API는 단지 래스터화(Rasterizer)만 관심이 있을 뿐입니다.

기초과정에서는 GPU에 데이타를 공급하는 방법중에 애트리뷰트와 유니폼만 다뤘습니다. 아직 텍스쳐(Texture)와 배어링(Varing)은 다루지 않았지요. 이건 차차 학습해 갈겁니다. 실무에서는 여러분이 알고 있는 멋진 3D 화면을 그리기 위해 이 4가지를 조합해 다양한 형태의 셰이더 코드를 만들게 됩니다.

아무튼 어려운데 여기까지 읽어주셔서 감사합니다.
항상 이 자료를 준비하고 공부할 수 있게 한 webglfundamentals.org 사이트 관리자분께 진심으로 감사드립니다.

글은 계속 연재될 겁니다.

이전글 : WebGL #2 – 기초 2
다음글 : [WebGL] WebGL #4 – WebGL 작동 방법의 이해 1