JavaScript와 WebGL

JavaScript와 WebGL

이 문서는 JavaScript와 WebGL에 대해 설명합니다.

YouTube Video

javascript-web-gl-texture.js
  1// Get WebGL context
  2const canvas = document.getElementById("glCanvas");
  3const gl = canvas.getContext("webgl");
  4if (!gl) {
  5  alert("WebGL is not supported by your browser.");
  6}
  7
  8// Vertex shader program
  9const vsSource = `
 10  attribute vec4 aVertexPosition;
 11  attribute vec2 aTextureCoord;
 12  varying highp vec2 vTextureCoord;
 13  void main(void) {
 14    gl_Position = aVertexPosition;
 15    vTextureCoord = aTextureCoord;
 16  }
 17`;
 18
 19// Fragment shader program
 20const fsSource = `
 21  varying highp vec2 vTextureCoord;
 22  uniform sampler2D uSampler;
 23  void main(void) {
 24    gl_FragColor = texture2D(uSampler, vTextureCoord);
 25  }
 26`;
 27
 28// Compile shader
 29function loadShader(type, source) {
 30  const shader = gl.createShader(type);
 31  gl.shaderSource(shader, source);
 32  gl.compileShader(shader);
 33  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
 34    console.error("Shader compile failed: ", gl.getShaderInfoLog(shader));
 35    gl.deleteShader(shader);
 36    return null;
 37  }
 38  return shader;
 39}
 40
 41// Initialize shader program
 42function initShaderProgram(vsSource, fsSource) {
 43  const vertexShader = loadShader(gl.VERTEX_SHADER, vsSource);
 44  const fragmentShader = loadShader(gl.FRAGMENT_SHADER, fsSource);
 45  const shaderProgram = gl.createProgram();
 46  gl.attachShader(shaderProgram, vertexShader);
 47  gl.attachShader(shaderProgram, fragmentShader);
 48  gl.linkProgram(shaderProgram);
 49  if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
 50    console.error("Unable to initialize the shader program: " + gl.getProgramInfoLog(shaderProgram));
 51    return null;
 52  }
 53  return shaderProgram;
 54}
 55
 56const shaderProgram = initShaderProgram(vsSource, fsSource);
 57const programInfo = {
 58  program: shaderProgram,
 59  attribLocations: {
 60    vertexPosition: gl.getAttribLocation(shaderProgram, "aVertexPosition"),
 61    textureCoord: gl.getAttribLocation(shaderProgram, "aTextureCoord"),
 62  },
 63  uniformLocations: {
 64    uSampler: gl.getUniformLocation(shaderProgram, "uSampler"),
 65  },
 66};
 67
 68// Define positions and texture coordinates
 69const positions = new Float32Array([
 70  -1.0,  1.0,
 71   1.0,  1.0,
 72  -1.0, -1.0,
 73   1.0, -1.0,
 74]);
 75
 76const textureCoordinates = new Float32Array([
 77  0.0, 0.0,
 78  1.0, 0.0,
 79  0.0, 1.0,
 80  1.0, 1.0,
 81]);
 82
 83// Create position buffer
 84const positionBuffer = gl.createBuffer();
 85gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
 86gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
 87
 88// Create texture coordinate buffer
 89const texCoordBuffer = gl.createBuffer();
 90gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
 91gl.bufferData(gl.ARRAY_BUFFER, textureCoordinates, gl.STATIC_DRAW);
 92
 93// Create and load texture
 94const texture = gl.createTexture();
 95gl.bindTexture(gl.TEXTURE_2D, texture);
 96
 97const image = new Image();
 98image.src = "texture.png";
 99image.onload = () => {
100  gl.bindTexture(gl.TEXTURE_2D, texture);
101  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
102  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
103  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
104  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
105  drawScene();
106};
107
108// Draw function
109function drawScene() {
110  gl.clearColor(0.0, 0.0, 0.0, 1.0); // Clear to black
111  gl.clear(gl.COLOR_BUFFER_BIT);
112
113  gl.useProgram(programInfo.program);
114
115  // Bind vertex position buffer
116  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
117  gl.vertexAttribPointer(programInfo.attribLocations.vertexPosition, 2, gl.FLOAT, false, 0, 0);
118  gl.enableVertexAttribArray(programInfo.attribLocations.vertexPosition);
119
120  // Bind texture coordinate buffer
121  gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
122  gl.vertexAttribPointer(programInfo.attribLocations.textureCoord, 2, gl.FLOAT, false, 0, 0);
123  gl.enableVertexAttribArray(programInfo.attribLocations.textureCoord);
124
125  // Bind texture
126  gl.activeTexture(gl.TEXTURE0);
127  gl.bindTexture(gl.TEXTURE_2D, texture);
128  gl.uniform1i(programInfo.uniformLocations.uSampler, 0);
129
130  // Draw rectangle
131  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
132}
javascript-web-gl-lighting.js
  1// Get the WebGL context
  2const canvas = document.getElementById("glCanvas");
  3const gl = canvas.getContext("webgl");
  4if (!gl) {
  5  alert("WebGL not supported");
  6  throw new Error("WebGL not supported");
  7}
  8
  9// Vertex shader
 10const vertexShaderSource = `
 11attribute vec3 aPosition;
 12attribute vec3 aNormal;
 13uniform mat4 uModelViewMatrix;
 14uniform mat4 uProjectionMatrix;
 15varying vec3 vNormal;
 16
 17void main(void) {
 18    gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aPosition, 1.0);
 19    vNormal = aNormal;
 20}
 21`;
 22
 23// Fragment shader (with lighting)
 24const fragmentShaderSourceWithLighting = `
 25precision mediump float;
 26uniform vec3 uLightingDirection;
 27uniform vec4 uLightColor;
 28varying vec3 vNormal;
 29
 30void main(void) {
 31    vec3 lightDirection = normalize(uLightingDirection);
 32    float directionalLightWeighting = max(dot(normalize(vNormal), lightDirection), 0.0);
 33    gl_FragColor = uLightColor * directionalLightWeighting;
 34}
 35`;
 36
 37// Shader creation helper
 38function createShader(gl, type, source) {
 39  const shader = gl.createShader(type);
 40  gl.shaderSource(shader, source);
 41  gl.compileShader(shader);
 42  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
 43    console.error("Shader compile error:", gl.getShaderInfoLog(shader));
 44    gl.deleteShader(shader);
 45    return null;
 46  }
 47  return shader;
 48}
 49
 50// Create program
 51function createProgram(gl, vs, fs) {
 52  const program = gl.createProgram();
 53  gl.attachShader(program, vs);
 54  gl.attachShader(program, fs);
 55  gl.linkProgram(program);
 56  if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
 57    console.error("Program link error:", gl.getProgramInfoLog(program));
 58    return null;
 59  }
 60  return program;
 61}
 62
 63const vs = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
 64const fs = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSourceWithLighting);
 65const program = createProgram(gl, vs, fs);
 66gl.useProgram(program);
 67
 68// Cube data
 69const vertices = new Float32Array([
 70  -1,-1,1,  1,-1,1,  1,1,1,  -1,1,1,     // Front
 71  -1,-1,-1, -1,1,-1, 1,1,-1, 1,-1,-1,    // Back
 72  -1,1,-1,  -1,1,1,  1,1,1,  1,1,-1,     // Top
 73  -1,-1,-1, 1,-1,-1, 1,-1,1, -1,-1,1,    // Bottom
 74  1,-1,-1, 1,1,-1, 1,1,1, 1,-1,1,        // Right
 75  -1,-1,-1, -1,-1,1, -1,1,1, -1,1,-1     // Left
 76]);
 77
 78const normals = new Float32Array([
 79  0,0,1, 0,0,1, 0,0,1, 0,0,1,
 80  0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1,
 81  0,1,0, 0,1,0, 0,1,0, 0,1,0,
 82  0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0,
 83  1,0,0, 1,0,0, 1,0,0, 1,0,0,
 84  -1,0,0, -1,0,0, -1,0,0, -1,0,0
 85]);
 86
 87const indices = new Uint16Array([
 88  0,1,2, 0,2,3,
 89  4,5,6, 4,6,7,
 90  8,9,10, 8,10,11,
 91  12,13,14, 12,14,15,
 92  16,17,18, 16,18,19,
 93  20,21,22, 20,22,23
 94]);
 95
 96// Buffers
 97const vertexBuffer = gl.createBuffer();
 98gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
 99gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
100
101const normalBuffer = gl.createBuffer();
102gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
103gl.bufferData(gl.ARRAY_BUFFER, normals, gl.STATIC_DRAW);
104
105const indexBuffer = gl.createBuffer();
106gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
107gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
108
109// Attribute locations
110const aPosition = gl.getAttribLocation(program, "aPosition");
111const aNormal = gl.getAttribLocation(program, "aNormal");
112
113// Uniforms
114const uProjectionMatrix = gl.getUniformLocation(program, "uProjectionMatrix");
115const uModelViewMatrix = gl.getUniformLocation(program, "uModelViewMatrix");
116const uLightingDirection = gl.getUniformLocation(program, "uLightingDirection");
117const uLightColor = gl.getUniformLocation(program, "uLightColor");
118
119// Projection
120function perspectiveMatrix(fov, aspect, near, far) {
121  const f = 1.0 / Math.tan((fov / 2) * Math.PI / 180);
122  return new Float32Array([
123    f/aspect,0,0,0,
124    0,f,0,0,
125    0,0,(far+near)/(near-far),-1,
126    0,0,(2*far*near)/(near-far),0
127  ]);
128}
129
130function identityMatrix() {
131  return new Float32Array([1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]);
132}
133
134gl.uniformMatrix4fv(uProjectionMatrix, false, perspectiveMatrix(45, canvas.width / canvas.height, 0.1, 100.0));
135gl.uniform3fv(uLightingDirection, [0.5, 0.7, 1.0]);
136gl.uniform4fv(uLightColor, [1.0, 1.0, 1.0, 1.0]);
137
138// Clear settings
139gl.clearColor(0.1, 0.1, 0.1, 1.0);
140gl.enable(gl.DEPTH_TEST);
141
142// Draw loop
143let angle = 0;
144function draw() {
145  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
146
147  // Bind position buffer
148  gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
149  gl.vertexAttribPointer(aPosition, 3, gl.FLOAT, false, 0, 0);
150  gl.enableVertexAttribArray(aPosition);
151
152  // Bind normal buffer
153  gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
154  gl.vertexAttribPointer(aNormal, 3, gl.FLOAT, false, 0, 0);
155  gl.enableVertexAttribArray(aNormal);
156
157  // Compute rotation
158  const mv = identityMatrix();
159  mv[0] = Math.cos(angle);
160  mv[2] = Math.sin(angle);
161  mv[8] = -Math.sin(angle);
162  mv[10] = Math.cos(angle);
163  mv[14] = -6.0;
164  gl.uniformMatrix4fv(uModelViewMatrix, false, mv);
165
166  // Draw
167  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
168  gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);
169
170  angle += 0.01;
171  requestAnimationFrame(draw);
172}
173
174draw();
javascript-web-gl.html
  1<!DOCTYPE html>
  2<html lang="en">
  3<head>
  4  <meta charset="UTF-8">
  5  <title>JavaScript &amp; HTML</title>
  6  <style>
  7    * {
  8        box-sizing: border-box;
  9    }
 10
 11    body {
 12        margin: 0;
 13        padding: 1em;
 14        padding-bottom: 10em;
 15        font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
 16        background-color: #f7f9fc;
 17        color: #333;
 18        line-height: 1.6;
 19    }
 20
 21    .container {
 22        max-width: 800px;
 23        margin: 0 auto;
 24        padding: 1em;
 25        background-color: #ffffff;
 26        border: 1px solid #ccc;
 27        border-radius: 10px;
 28        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
 29    }
 30
 31    .container-flex {
 32        display: flex;
 33        flex-wrap: wrap;
 34        gap: 2em;
 35        max-width: 1000px;
 36        margin: 0 auto;
 37        padding: 1em;
 38        background-color: #ffffff;
 39        border: 1px solid #ccc;
 40        border-radius: 10px;
 41        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
 42    }
 43
 44    .left-column, .right-column {
 45        flex: 1 1 200px;
 46        min-width: 200px;
 47    }
 48
 49    h1, h2 {
 50        font-size: 1.2rem;
 51        color: #007bff;
 52        margin-top: 0.5em;
 53        margin-bottom: 0.5em;
 54        border-left: 5px solid #007bff;
 55        padding-left: 0.6em;
 56        background-color: #e9f2ff;
 57    }
 58
 59    button {
 60        display: inline;
 61        margin: 0.25em;
 62        padding: 0.75em 1.5em;
 63        font-size: 1rem;
 64        background-color: #007bff;
 65        color: white;
 66        border: none;
 67        border-radius: 6px;
 68        cursor: pointer;
 69        transition: background-color 0.3s ease;
 70    }
 71
 72    button:hover {
 73        background-color: #0056b3;
 74    }
 75
 76    #output {
 77        margin-top: 1em;
 78        background-color: #1e1e1e;
 79        color: #0f0;
 80        padding: 1em;
 81        border-radius: 8px;
 82        min-height: 200px;
 83        font-family: Consolas, monospace;
 84        font-size: 0.95rem;
 85        overflow-y: auto;
 86        white-space: pre-wrap;
 87    }
 88
 89    .highlight {
 90        outline: 3px solid #ffc107; /* yellow border */
 91        background-color: #fff8e1;  /* soft yellow background */
 92        transition: background-color 0.3s ease, outline 0.3s ease;
 93    }
 94
 95    .active {
 96        background-color: #28a745; /* green background */
 97        color: #fff;
 98        box-shadow: 0 0 10px rgba(40, 167, 69, 0.5);
 99        transition: background-color 0.3s ease, box-shadow 0.3s ease;
100    }
101  </style>
102</head>
103<body>
104    <div class="container">
105        <h1>JavaScript Console</h1>
106        <button id="executeBtn">Execute</button>
107        <button id="executeTextureBtn">Execute Texture</button>
108        <button id="executeLightingBtn">Execute Lighting</button>
109        <div id="output"></div>
110    </div>
111    <div class="container">
112        <h1>WebGL Canvas</h1>
113        <canvas id="glCanvas"></canvas>
114    </div>
115
116    <script>
117        // Override console.log to display messages in the #output element
118        (function () {
119            // Override console.log
120            const originalLog = console.log;
121            console.log = function (...args) {
122                originalLog.apply(console, args);
123                const message = document.createElement('div');
124                message.textContent = args.map(String).join(' ');
125                output.appendChild(message);
126            };
127
128            // Override console.error
129            const originalError = console.error;
130            console.error = function (...args) {
131                originalError.apply(console, args);
132                const message = document.createElement('div');
133                message.textContent = args.map(String).join(' ');
134                message.style.color = 'red'; // Color error messages red
135                output.appendChild(message);
136            };
137        })();
138
139        document.getElementById('executeBtn').addEventListener('click', () => {
140            // Prevent multiple loads
141            if (document.getElementById('externalScript')) return;
142
143            const script = document.createElement('script');
144            script.src = 'javascript-web-gl.js';
145            script.id = 'externalScript';
146            document.body.appendChild(script);
147        });
148
149        document.getElementById('executeTextureBtn').addEventListener('click', () => {
150            // Prevent multiple loads
151            if (document.getElementById('externalScript')) return;
152
153            const script = document.createElement('script');
154            script.src = 'javascript-web-gl-texture.js';
155            script.id = 'externalScript';
156            document.body.appendChild(script);
157        });
158
159        document.getElementById('executeLightingBtn').addEventListener('click', () => {
160            // Prevent multiple loads
161            if (document.getElementById('externalScript')) return;
162
163            const script = document.createElement('script');
164            script.src = 'javascript-web-gl-lighting.js';
165            script.id = 'externalScript';
166            document.body.appendChild(script);
167        });
168    </script>
169</body>
170</html>

JavaScript와 WebGL

JavaScript와 WebGL은 브라우저에서 상호작용적이고 고품질의 3D 그래픽을 제공하기 위한 현대 웹 개발의 강력한 도구입니다. WebGL을 사용하면 GPU를 직접 활용하여 효율적으로 렌더링하고, 게임, 데이터 시각화, VR/AR 애플리케이션 등 표현력 있는 웹 애플리케이션을 구축할 수 있습니다.

WebGL이란 무엇인가요?

WebGL(Web Graphics Library)은 HTML5의 일부로 제공되는 저수준 그래픽 API로, 브라우저에서 직접 3D 렌더링을 수행하는 데 사용됩니다. 이는 OpenGL ES 2.0 사양을 기반으로 하며 JavaScript를 통해 GPU를 제어합니다. WebGL을 사용하면 별도의 플러그인 없이도 거의 모든 최신 브라우저에서 고급 3D 그래픽을 표시할 수 있습니다.

WebGL의 기본 사항

WebGL로 렌더링을 수행하려면 몇 가지 핵심 개념을 이해해야 합니다. 다른 그래픽 API(OpenGL, DirectX 등)와 마찬가지로, WebGL은 그래픽 파이프라인을 통해 데이터를 처리하고 GPU에서 이미지를 생성합니다.

컨텍스트 가져오기

WebGL은 HTML5 <canvas> 요소를 사용하여 렌더링합니다. JavaScript에서 먼저 WebGL 컨텍스트를 가져와야 합니다.

1const canvas = document.getElementById("glCanvas");
2const gl = canvas.getContext("webgl");
3if (!gl) {
4    console.error("WebGL not supported, falling back on experimental-webgl");
5    gl = canvas.getContext("experimental-webgl");
6}
7if (!gl) {
8    alert("Your browser does not support WebGL");
9}
  • <canvas> 요소에서 WebGL 컨텍스트를 가져옵니다. 이를 통해 JavaScript로 GPU 렌더링을 제어할 수 있습니다.

셰이더 정의하기

WebGL로 렌더링하려면 정점 셰이더와 프래그먼트 셰이더 두 가지 유형의 셰이더를 사용합니다. 버텍스 셰이더는 객체의 각 꼭짓점의 위치를 계산하고, 프래그먼트 셰이더는 각 픽셀의 색상을 계산합니다. 이러한 셰이더는 GPU에서 실행되는 프로그램입니다. 셰이더는 GLSL이라는 언어로 작성됩니다.

1const vertexShaderSource = `
2attribute vec4 aVertexPosition;
3void main(void) {
4    gl_Position = aVertexPosition;
5}`;
6const fragmentShaderSource = `
7void main(void) {
8    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // Red color
9}`;
  • 정점 셰이더는 정점 위치를 처리하고, 프래그먼트 셰이더는 픽셀별 색상을 설정합니다. 여기서는 빨간색을 출력합니다.

셰이더 컴파일 및 링크

셰이더 소스 코드를 WebGL 프로그램에 포함하려면 셰이더를 컴파일하고 링크하여 프로그램을 생성합니다.

 1function createShader(gl, type, source) {
 2    const shader = gl.createShader(type);
 3    gl.shaderSource(shader, source);
 4    gl.compileShader(shader);
 5    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
 6        console.error("An error occurred compiling the shaders:", gl.getShaderInfoLog(shader));
 7        gl.deleteShader(shader);
 8        return null;
 9    }
10    return shader;
11}
12
13const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
14const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
15
16const shaderProgram = gl.createProgram();
17gl.attachShader(shaderProgram, vertexShader);
18gl.attachShader(shaderProgram, fragmentShader);
19gl.linkProgram(shaderProgram);
20if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
21    console.error("Unable to initialize the shader program:", gl.getProgramInfoLog(shaderProgram));
22}
  • 각 셰이더를 컴파일한 뒤 프로그램으로 링크합니다. 이렇게 하면 GPU에서 실행 가능한 렌더링 파이프라인이 구성됩니다.

버퍼와 정점 데이터 준비

다음으로, 그릴 기하 데이터(geometry)를 준비하여 WebGL 버퍼로 전송합니다. 이 데이터에는 버텍스 위치, 색상, 텍스처 좌표 등이 포함됩니다.

 1const vertices = new Float32Array([
 2    -1.0, -1.0,
 3     1.0, -1.0,
 4     1.0,  1.0,
 5    -1.0,  1.0,
 6]);
 7
 8const vertexBuffer = gl.createBuffer();
 9gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
10gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
  • 사각형의 정점 좌표를 정의하고 GPU 버퍼에 업로드합니다. STATIC_DRAW는 데이터가 자주 변경되지 않음을 나타냅니다.

렌더링 과정

마지막으로 셰이더와 데이터를 사용해 WebGL이 실제 렌더링을 수행하도록 합니다. WebGL 렌더링 파이프라인을 올바르게 구성하고 GPU에 드로우 명령을 전송합니다. 여기서는 배경을 검은색으로 지우고, 정의한 정점 데이터와 셰이더 프로그램을 사용해 빨간 사각형을 그립니다.

 1gl.clearColor(0.0, 0.0, 0.0, 1.0);  // Clear to black
 2gl.clear(gl.COLOR_BUFFER_BIT);
 3
 4// Use the shader program
 5gl.useProgram(shaderProgram);
 6
 7// Bind vertex buffer
 8const positionAttributeLocation = gl.getAttribLocation(shaderProgram, "aVertexPosition");
 9gl.enableVertexAttribArray(positionAttributeLocation);
10gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
11gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
12
13// Draw
14gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
  • 이 코드에서는 WebGL 렌더링 파이프라인을 설정하고 사각형을 렌더링합니다. gl.clear()로 화면을 초기화하고 gl.drawArrays()로 GPU에 드로우 명령을 보내면, 셰이더에서 처리한 데이터가 실제로 화면에 표시됩니다.

WebGL의 고급 기능

WebGL을 사용하면 기본 렌더링을 넘어 다양한 고급 기능을 활용할 수 있습니다. 여기에서는 그 중 일부 기능을 소개합니다.

텍스처 매핑

WebGL은 텍스처 매핑을 지원하여 이미지를 3D 객체에 적용하거나 패턴을 부여할 수 있습니다. 이는 단순한 색상 대신 보다 복잡하고 현실감 있는 외형을 객체에 부여할 수 있게 합니다.

 1/* Fragment of source code */
 2const texture = gl.createTexture();
 3gl.bindTexture(gl.TEXTURE_2D, texture);
 4
 5// Load an image as the texture
 6const image = new Image();
 7image.src = "texture.png";
 8image.onload = () => {
 9    gl.bindTexture(gl.TEXTURE_2D, texture);
10    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
11
12    // Set texture parameters
13    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
14    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
15    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
16
17    // Draw the scene again with the texture applied
18    drawScene();
19};
  • 이미지를 텍스처로 로드해 객체 표면에 적용하면 더욱 현실적인 표현을 얻을 수 있습니다.

조명 및 음영

음영 모델을 사용하여 3D 객체에 현실감 있는 조명 효과를 적용할 수 있습니다. Phong 음영, Gouraud 음영과 같은 다양한 음영 모델을 기반으로 객체 표면이 빛을 반사하는 방식을 계산합니다.

 1/* Fragment of source code */
 2const fragmentShaderSourceWithLighting = `
 3precision mediump float;
 4uniform vec3 uLightingDirection;
 5uniform vec4 uLightColor;
 6void main(void) {
 7    vec3 lightDirection = normalize(uLightingDirection);
 8    float directionalLightWeighting = max(dot(vNormal, lightDirection), 0.0);
 9    gl_FragColor = uLightColor * directionalLightWeighting;
10}`;
  • 광원 방향과 노말 사이의 각도에 따라 밝기를 계산합니다. 이는 퐁(Phong) 및 구로(Gouraud) 모델과 같은 셰이딩 기법의 기반이 됩니다.

최적화 및 성능

WebGL은 GPU에서 직접 작동하므로 성능 최적화가 매우 중요합니다. 많은 정점 데이터나 복잡한 셰이더를 사용하면 성능이 저하될 수 있습니다. 다음은 성능 최적화를 위한 일반적인 접근 방법들입니다.

  • 정점 버퍼의 효율적 사용 정점 버퍼를 자주 업데이트하기보다는 한 번 생성한 버퍼를 가능한 한 재사용하세요.
  • 프레임 속도 조절 requestAnimationFrame을 사용해 애니메이션 렌더링을 제어하고 효율적인 프레임 속도를 유지하세요.
  • 오클루전 컬링 시야에 포함되지 않은 객체의 렌더링을 생략하는 기법입니다. 이 방법은 렌더링 부하를 줄일 수 있습니다.
 1function render() {
 2    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
 3
 4    // Logic to update objects in the scene
 5
 6    // Use requestAnimationFrame for smooth rendering
 7    requestAnimationFrame(render);
 8}
 9
10render();
  • requestAnimationFrame으로 렌더링 루프를 제어하여 브라우저의 렌더링 타이밍에 맞춰 효율적으로 프레임을 업데이트합니다.

요약

JavaScript와 WebGL을 결합하여 브라우저에서 실시간으로 작동하는 고급 3D 그래픽을 구현할 수 있습니다. WebGL은 셰이더와 버퍼 관리의 기본 개념부터 텍스처 적용, 성능 최적화에 이르기까지 많은 요소를 포함하며, 이를 효과적으로 활용하면 인터랙티브하고 시각적으로 풍부한 애플리케이션을 개발할 수 있습니다.

위의 기사를 보면서 Visual Studio Code를 사용해 우리 유튜브 채널에서 함께 따라할 수 있습니다. 유튜브 채널도 확인해 주세요.

YouTube Video