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は、現代のWeb開発において、ブラウザ上でインタラクティブかつ高品質な3Dグラフィックスを実現するための強力なツールです。WebGLを使用することで、GPUを直接操作して効率的にレンダリングを行い、ゲーム、データビジュアライゼーション、VR/ARアプリケーションなど、豊かな表現力を持つWebアプリを構築できます。

WebGLとは?

WebGL(Web Graphics Library)は、HTML5の一部として提供される低レベルのグラフィックスAPIで、ブラウザ上で直接3D描画を行うために使用されます。OpenGL ES 2.0の仕様に基づいており、JavaScriptからGPUを制御します。WebGLを利用することで、特別なプラグインを必要とせず、ほぼすべてのモダンブラウザで高度な3Dグラフィックスを表示できます。

WebGLの基礎

WebGLで描画を行うためには、いくつかの主要なコンセプトを理解する必要があります。WebGLは、他のグラフィックスAPI(例えばOpenGLやDirectX)と同様に、グラフィックスパイプラインを通じてデータを処理し、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で描画するためには、頂点シェーダーとフラグメントシェーダーという2つのシェーダーを使用します。頂点シェーダーは、オブジェクトの各頂点の位置を計算し、フラグメントシェーダーは各ピクセルの色を計算します。これらのシェーダーは、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で実行可能な描画パイプラインが構築されます。

バッファと頂点データの準備

次に、描画するジオメトリのデータを用意し、それを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には多くの要素がありますが、それらを効果的に利用することでインタラクティブで視覚的にリッチなアプリケーションを開発できます。

YouTubeチャンネルでは、Visual Studio Codeを用いて上記の記事を見ながら確認できます。 ぜひYouTubeチャンネルもご覧ください。

YouTube Video