1 let gl, canvas, glProgram, style; 2 let teapotMesh, positionBuffer, indexBuffer, wireframeBuffer, normalBuffer; 3 4 const TARGET_ASPECT = 1.0; 5 const WIREFRAME = true; 6 7 function makeShader(source, type) { 8 let shader = gl.createShader(type); 9 gl.shaderSource(shader, source); 10 gl.compileShader(shader); 11 return shader; 12 } 13 14 async function setupShaders() { 15 glProgram = gl.createProgram(); 16 17 const [vertexShaderSrc, fragmentShaderSrc] = await Promise.all([ 18 fetch('main.vert').then(r => r.text()), 19 fetch('main.frag').then(r => r.text()) 20 ]); 21 22 const vertexShader = makeShader(vertexShaderSrc, gl.VERTEX_SHADER); 23 gl.attachShader(glProgram, vertexShader); 24 25 const fragmentShader = makeShader(fragmentShaderSrc, gl.FRAGMENT_SHADER); 26 gl.attachShader(glProgram, fragmentShader); 27 28 gl.linkProgram(glProgram); 29 gl.useProgram(glProgram); 30 } 31 32 async function setupBuffers() { 33 const teapotText = await fetch('teapot.obj').then(r => r.text()); 34 teapotMesh = parseOBJ(teapotText); 35 36 positionBuffer = gl.createBuffer(); 37 gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); 38 gl.bufferData(gl.ARRAY_BUFFER, 39 new Float32Array(teapotMesh.positions), gl.STATIC_DRAW); 40 41 normalBuffer = gl.createBuffer(); 42 gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer); 43 gl.bufferData(gl.ARRAY_BUFFER, 44 new Float32Array(teapotMesh.normals), gl.STATIC_DRAW); 45 46 if(WIREFRAME == true) { 47 wireframeBuffer = gl.createBuffer(); 48 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, wireframeBuffer); 49 gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, 50 new Uint16Array(teapotMesh.wireframeIndices), gl.STATIC_DRAW); 51 } else { 52 indexBuffer = gl.createBuffer(); 53 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); 54 gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, 55 new Uint16Array(teapotMesh.indices), gl.STATIC_DRAW); 56 } 57 } 58 59 function generateWireframeIndices(triangleIndices) { 60 const edges = new Set(); 61 const wireframeIndices = []; 62 63 for (let i = 0; i < triangleIndices.length; i += 3) { 64 const a = triangleIndices[i]; 65 const b = triangleIndices[i + 1]; 66 const c = triangleIndices[i + 2]; 67 68 function addEdge(i1, i2) { 69 const edge = i1 < i2 ? `${i1}-${i2}` : `${i2}-${i1}`; 70 if (!edges.has(edge)) { 71 edges.add(edge); 72 wireframeIndices.push(i1, i2); 73 } 74 } 75 76 addEdge(a, b); 77 addEdge(b, c); 78 addEdge(c, a); 79 } 80 81 return wireframeIndices; 82 } 83 84 function parseOBJ(text) { 85 const positions = []; 86 const texcoords = []; 87 const normals = []; 88 const indices = []; 89 90 const finalPositions = []; 91 const finalTexcoords = []; 92 const finalNormals = []; 93 const indexMap = new Map(); 94 95 const lines = text.split('\n'); 96 97 for (const line of lines) { 98 const parts = line.trim().split(/\s+/); 99 if (parts.length === 0) continue; 100 101 switch (parts[0]) { 102 case 'v': 103 positions.push(parts.slice(1).map(Number)); 104 break; 105 case 'vt': 106 texcoords.push(parts.slice(1).map(Number)); 107 break; 108 case 'vn': 109 normals.push(parts.slice(1).map(Number)); 110 break; 111 case 'f': 112 for (let i = 1; i <= 3; i++) { 113 const [v, vt, vn] = parts[i].split('/').map(str => parseInt(str, 10) - 1); 114 const key = `${v}/${vt}/${vn}`; 115 if (!indexMap.has(key)) { 116 finalPositions.push(...positions[v]); 117 if (texcoords[vt]) finalTexcoords.push(...texcoords[vt]); 118 if (normals[vn]) finalNormals.push(...normals[vn]); 119 indexMap.set(key, finalPositions.length / 3 - 1); 120 } 121 indices.push(indexMap.get(key)); 122 } 123 break; 124 } 125 } 126 127 const wireframeIndices = generateWireframeIndices(indices); 128 return { 129 positions: finalPositions, 130 texcoords: finalTexcoords, 131 normals: finalNormals, 132 indices: indices, 133 wireframeIndices: wireframeIndices, 134 }; 135 } 136 137 function drawScene() { 138 const aPos = gl.getAttribLocation(glProgram, "a_pos"); 139 const aNormal = gl.getAttribLocation(glProgram, "a_normal"); 140 141 gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); 142 gl.enableVertexAttribArray(aPos); 143 gl.vertexAttribPointer(aPos, 3, gl.FLOAT, false, 0, 0); 144 145 gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer); 146 gl.enableVertexAttribArray(aNormal); 147 gl.vertexAttribPointer(aNormal, 3, gl.FLOAT, false, 0, 0); 148 149 if(WIREFRAME == true) { 150 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, wireframeBuffer); 151 } else { 152 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); 153 } 154 } 155 156 function onResize() { 157 canvas.width = window.innerWidth; 158 canvas.height = window.innerHeight; 159 const aspect_ratio = canvas.width / canvas.height; 160 161 let width, height; 162 if (aspect_ratio > TARGET_ASPECT) { 163 height = window.innerHeight; 164 width = height * TARGET_ASPECT; 165 } else { 166 width = window.innerWidth; 167 height = width / TARGET_ASPECT; 168 } 169 170 canvas.style.width = width + 'px'; 171 canvas.style.height = height + 'px'; 172 173 gl.viewport(0, 0, canvas.width, canvas.height); 174 } 175 176 function parseCSSColor(cssColor) { 177 const match = cssColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/); 178 179 if (!match) { 180 return [0, 0, 0, 1]; 181 } 182 183 const r = parseInt(match[1], 10) / 255; 184 const g = parseInt(match[2], 10) / 255; 185 const b = parseInt(match[3], 10) / 255; 186 const a = match[4] !== undefined ? parseFloat(match[4]) : 1; 187 188 return [r, g, b, a]; 189 } 190 191 let timeLast, frames; 192 function render() { 193 const time = performance.now()/1000; // in ms 194 195 if((time - timeLast) > 1) { 196 const fps_div = document.getElementById("fps"); 197 fps_div.innerHTML = frames; 198 timeLast = performance.now()/1000; 199 frames = 0; 200 } 201 202 frames++; 203 204 const uTransform = gl.getUniformLocation(glProgram, "u_transform"); 205 const uNormalMatrix = gl.getUniformLocation(glProgram, "u_normal_matrix"); 206 207 const uLightDirection = gl.getUniformLocation(glProgram, "u_light_dir"); 208 const uLightColor = gl.getUniformLocation(glProgram, "u_light_color"); 209 const uObjectColor = gl.getUniformLocation(glProgram, "u_object_color"); 210 const uAmbientLightColor = gl.getUniformLocation(glProgram, "u_ambient_color"); 211 212 gl.uniform3fv(uLightDirection, glMatrix.vec3.normalize([], [0.0, 1.0, 0.0])); 213 gl.uniform3fv(uLightColor, [1.0, 1.0, 1.0]); 214 gl.uniform3fv(uObjectColor, [1.0, 0.0, 0.0]); 215 gl.uniform3fv(uAmbientLightColor, [0.10, 0.10, 0.10]); 216 217 const modelMatrix = glMatrix.mat4.create(); 218 219 glMatrix.mat4.translate(modelMatrix, modelMatrix, [-.01, 0, 0]); 220 glMatrix.mat4.rotate(modelMatrix, modelMatrix, -time*0.250, [0, 1, 0]); 221 glMatrix.mat4.translate(modelMatrix, modelMatrix, [0.08, 0, 0]); 222 glMatrix.mat4.scale(modelMatrix, modelMatrix, [.075, .075, .075]); 223 224 const viewMatrix = glMatrix.mat4.create(); 225 glMatrix.mat4.lookAt(viewMatrix, 226 [0, 4, 4], // position 227 [0, 0, 0], // target 228 [0, 1, 0] // up vector 229 ); 230 231 const projectionMatrix = glMatrix.mat4.create(); 232 glMatrix.mat4.perspective(projectionMatrix, 233 Math.PI/5, TARGET_ASPECT, 0.1, 100.0); 234 235 const modelViewMatrix = glMatrix.mat4.create(); 236 const modelViewProjectionMatrix = glMatrix.mat4.create(); 237 glMatrix.mat4.multiply(modelViewMatrix, viewMatrix, modelMatrix); 238 glMatrix.mat4.multiply(modelViewProjectionMatrix, projectionMatrix, modelViewMatrix); 239 240 const normalMatrix = glMatrix.mat3.create(); 241 glMatrix.mat3.normalFromMat4(normalMatrix, modelMatrix); 242 243 gl.uniformMatrix3fv(uNormalMatrix, false, normalMatrix); 244 gl.uniformMatrix4fv(uTransform, false, modelViewProjectionMatrix); 245 246 const [r, g, b, a] = parseCSSColor(style.backgroundColor); 247 gl.clearColor(r, g, b, a); 248 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 249 250 if(WIREFRAME == true) { 251 gl.drawElements(gl.LINES, teapotMesh.wireframeIndices.length, gl.UNSIGNED_SHORT, 0); 252 } else { 253 gl.drawElements(gl.TRIANGLES, teapotMesh.indices.length, gl.UNSIGNED_SHORT, 0); 254 } 255 256 requestAnimationFrame(render); 257 } 258 259 function main() { 260 canvas = document.getElementById("canvas"); 261 style = getComputedStyle(document.querySelector('body')); 262 263 gl = canvas.getContext("webgl"); 264 gl.disable(gl.CULL_FACE); 265 gl.enable(gl.DEPTH_TEST); 266 gl.depthMask(true); 267 268 timeLast = performance.now()/1000; 269 frames = 0; 270 271 onResize(); 272 window.addEventListener('resize', onResize); 273 274 (async () => { 275 await setupShaders(); 276 console.log("[LOG]: Shaders done"); 277 278 await setupBuffers(); 279 console.log("[LOG]: Buffers done"); 280 281 drawScene(); 282 requestAnimationFrame(render); 283 })(); 284 } 285 286 main();
