Uvod u WebGL

Updated: 2024-12-05

Published: 2024-11-25


Zadatak 1: usporedba OpenGLa i WebGLa

WebGLOpenGL
Upotrebaprikaz grafičkih komponenti u okruženju s ograničenim pristupom karakteristikama hardveragrafički prikaz uz nesiguran pristup hardveru
Programski dizajnweb aplikacijenativne aplikacije
Programski jezikJSC
Značajke i funkcijeWebGL2: OpenGL ES 3.0 + izmjene
WebGL: OpenGL ES 2.0 + izmjene
OpenGL 4.5

Grafički cjevovod je identičan, samo ima ograničenja s WebGL strane - nema double tip realnih brojeva i 3D teksture. WebGL je hardenana varijanta GLESa koji je OpenGL prilagođen za mobilne uređaje.

I naravno, veselimo se WebGPU standardu, kojim će biti izglađene implementacijske razlike!

Zadatak 2: lokalno razvojno okruženje

Doradio sam infrastrukturu stranice jer je dinamičko izvođenje JSa i tako bilo u planu za blog (iako ne u ovom razmjeru).

Svi zadaci su riješeni u ovom Markdown dokumentu, koji funkcionira poput Jupyter bilježnica, samo s HTMLom i JSom umjesto Pythona, te se pokreće u pregledniku.

Za pisanje koda je korišten OSSCode.

Zadatak 3: canvas element i crtanje kvadrata

index.htmlhtml
<html>
<head>
  <title>WebGL intro</title>
</head>
<body onload="main()">
  <canvas id="glcanvas" width="500" height="500">
    Canvas HTML komponenta nije podržana
  </canvas>

<style>
  canvas {
    border: 3px solid black;
    background-color: lightgray;
  }
</style>
</body>
</html>
1234567891011121314151617
embedded JS
index.jsjs
function main() {
  const canvas = document.querySelector("canvas#glcanvas");
  if (!window.HTMLCanvasElement) {
    console.log("non-standard browser HTMLCanvasElement support");
    return;
  }
  const ctx = canvas.getContext("2d");
  if (!ctx) {
    canvas.style.display = "block";
    canvas.innerText = "2D kontekst crtanja nije podržan";
    console.log("Context2D not supported by browser");
    return;
  }

  ctx.fillStyle = "blue";
  ctx.fillRect(200, 300, 100, 100);
  
  ctx.strokeStyle = "yellow";
  ctx.beginPath();
  ctx.moveTo(200, 300);
  ctx.lineTo(300, 400);
  ctx.stroke();

  ctx
}
12345678910111213141516171819202122232425
Canvas HTML komponenta nije podržana embedded JS
source
// umjesto `body onload="main()"`
main()

Zadatak 4: renderiranje praznog platna za crtanje

WebGL_template.htmlhtml
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Draw Multiple Points</title>
  </head>

  <body onload="main()">
    <canvas id="clean" width="400" height="400">
    Please use a browser that supports "canvas"
    </canvas>

    <script src="./lib/webgl-utils.js"></script>
    <script src="./lib/webgl-debug.js"></script>
    <script src="./lib/cuon-utils.js"></script>
    <script src="zadatak1.js"></script>
  </body>
</html>
123456789101112131415161718
remote JShttps://caellian.github.io/blog/2024/webgl_intro/lib/webgl-utils.js remote JShttps://caellian.github.io/blog/2024/webgl_intro/lib/webgl-debug.js remote JShttps://caellian.github.io/blog/2024/webgl_intro/lib/cuon-utils.js embedded JS
WebGL_template.jsjs
// Vertex shader program
function main() {
  // Dohvacanje <canvas> elementa
  var canvas = document.getElementById('webgl-z4');

  // Postavljanje konteksta renderiranja za WebGL
  var gl = getWebGLContext(canvas);
  if (!gl) {
    console.log('Failed to get the rendering context for WebGL');
    return;
  }

  // Specificiranje boje za brisanje <canvas>
  gl.clearColor(0, 0, 0, 1);

  // Brisanje <canvas>
  gl.clear(gl.COLOR_BUFFER_BIT);
}
123456789101112131415161718
Please use a browser that supports "canvas" embedded JS
source
// umjesto `body onload="main()"`
main()

Zadatak 5: crtanje točke (korištenje shadera)

VSHADER_SOURCEvert
void main() {
  gl_Position = vec4(0.2, 0.3, 0.0, 1.0);
  gl_PointSize = 10.0;
}
1234
FSHADER_SOURCEfrag
void main() {
  gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);
}
123
embedded JS
zadatak1.jsjs
function main() {
  // Dohvacanje <canvas> elementa
  var canvas = document.getElementById('webgl-z5');

  // Postavljanje konteksta renderiranja za WebGL
  var gl = getWebGLContext(canvas);
  if (!gl) {
    console.error('Failed to get the rendering context for WebGL');
    return;
  }

  // Inicijalizacija shadera
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
    console.error('Failed to intialize shaders.');
    return;
  }

  gl.clearColor(0, 0, 0, 1);
  gl.clear(gl.COLOR_BUFFER_BIT);

  // Crtanje tocaka
  gl.drawArrays(gl.POINTS, 0, 1);
}
1234567891011121314151617181920212223
Please use a browser that supports "canvas" embedded JS
source
// umjesto `body onload="main()"`
main()

Zadatak 6: korištenje varijable atributa i uniformne varijable

VSHADER_SOURCEvert
attribute vec2 a_Position;
void main() {
  gl_Position = vec4(a_Position, 0.0, 1.0);
  gl_PointSize = 10.0;
}
12345
FSHADER_SOURCEfrag
void main() {
  gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);
}
123
embedded JS
js
function main() {
  // Dohvacanje <canvas> elementa
  var canvas = document.getElementById('webgl-z6');

  // Postavljanje konteksta renderiranja za WebGL
  var gl = getWebGLContext(canvas);
  if (!gl) {
    console.log('Failed to get the rendering context for WebGL');
    return;
  }

  // Inicijalizacija shadera
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
    console.log('Failed to intialize shaders.');
    return;
  }

  var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  if (a_Position < 0) {
    console.log('Failed to get the storage location of a_Position');
    return;
  }
  gl.vertexAttrib2f(a_Position, 0.5, 0.2);

  gl.clearColor(0, 0, 0, 1);
  gl.clear(gl.COLOR_BUFFER_BIT);

  // Crtanje tocaka
  gl.drawArrays(gl.POINTS, 0, 1);
}
123456789101112131415161718192021222324252627282930
Please use a browser that supports "canvas" embedded JS
source
// umjesto `body onload="main()"`
main()

Zadatak 7: pretvorba koordinata točke

embedded JS
js
function DeviceToNormalised(canvas, pos) {
  const xPx = 2.0 / canvas.width;
  const yPx = -2.0 / canvas.height;
  return {
    x: Math.round((pos.x - canvas.width/2) * xPx * 100) / 100,
    y: Math.round((pos.y - canvas.height/2) * yPx * 100) / 100,
  };
}
12345678
embedded JS
js
function init(canvas, vertex = ArticleScope.VSHADER_SOURCE, fragment = ArticleScope.FSHADER_SOURCE) {
  if (canvas == null) {
    return
  }

  // Postavljanje konteksta renderiranja za WebGL
  var gl = getWebGLContext(canvas);
  if (!gl) {
    console.log('Failed to get the rendering context for WebGL');
    return;
  }

  // Inicijalizacija shadera
  if (!initShaders(gl, vertex, fragment)) {
    console.log('Failed to intialize shaders.');
    return;
  }
  
  gl.clearColor(0, 0, 0, 1);
  gl.clear(gl.COLOR_BUFFER_BIT);

  return gl;
}
1234567891011121314151617181920212223
embedded JS
js
function draw_point(gl, position) {
  var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  if (a_Position < 0) {
    console.log('Failed to get the storage location of a_Position');
    return;
  }
  gl.vertexAttrib2f(a_Position, position.x, position.y);

  gl.clear(gl.COLOR_BUFFER_BIT);

  // Crtanje tocaka
  gl.drawArrays(gl.POINTS, 0, 1);
}
12345678910111213
Please use a browser that supports "canvas" embedded JS
js
let canvas = document.getElementById("webgl-z7");
let ctx = init(canvas);
draw_point(ctx, DeviceToNormalised(canvas, {
  x: 200,
  y: 300,
}));
123456

Zadatak 8: korištenje event handlera

Please use a browser that supports "canvas" embedded JS
js
let canvas = document.getElementById("webgl-z8");
let ctx = init(canvas);
canvas.onclick = (ev) => {
  let rect = canvas.getBoundingClientRect();
  let pos = {
    x: ev.clientX - rect.x,
    y: ev.clientY - rect.y,
  };
  draw_point(ctx, DeviceToNormalised(canvas, pos));
}
12345678910

Zadatak 9: ePortfolio

VSHADER_SOURCEvert
attribute vec2 a_Position;
attribute float a_boxSize;
void main() {
  gl_Position = vec4(a_Position, 0.0, 1.0);
  gl_PointSize = a_boxSize;
}
123456
embedded JS
js
function draw_point(gl, position, size) {
  var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  if (a_Position < 0) {
    console.log('Failed to get the storage location of a_Position');
    return;
  }
  gl.vertexAttrib2f(a_Position, position.x, position.y);
  var a_boxSize = gl.getAttribLocation(gl.program, 'a_boxSize');
  if (a_boxSize < 0) {
    console.log('Failed to get the storage location of a_boxSize');
    return;
  }
  gl.vertexAttrib1f(a_boxSize, size);

  gl.clear(gl.COLOR_BUFFER_BIT);

  // Crtanje tocaka
  gl.drawArrays(gl.POINTS, 0, 1);
}
12345678910111213141516171819
Please use a browser that supports "canvas"
Boja pozadine: Veličina točke:
embedded JS
source
// IZVOR: https://stackoverflow.com/questions/2353211/hsl-to-rgb-color-conversion
const { abs, min, max, round } = Math;
function hslToRgb(h, s, l) {
  let r, g, b;

  if (s === 0) {
    r = g = b = l; // achromatic
  } else {
    const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
    const p = 2 * l - q;
    r = hueToRgb(p, q, h + 1/3);
    g = hueToRgb(p, q, h);
    b = hueToRgb(p, q, h - 1/3);
  }

  return [r, g, b];
}

function hueToRgb(p, q, t) {
  if (t < 0) t += 1;
  if (t > 1) t -= 1;
  if (t < 1/6) return p + (q - p) * 6 * t;
  if (t < 1/2) return q;
  if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
  return p;
}
embedded JS
js
let canvas = document.getElementById("webgl-z9");
let ctx = init(canvas);
let lastPos = { x: Math.random() * canvas.width, y: Math.random() * canvas.height };
let lastSize = 20 + 40 * Math.random();

let bgSlider = document.getElementById("bgColorPicker");
bgSlider.oninput = () => {
  bgSlider.style = `--current-color:hsl(${bgSlider.value}deg 100% 50%)`;
  let [r, g, b] = hslToRgb(bgSlider.value / 360, 0.5, 0.5);
  ctx.clearColor(r, g, b, 1);
  draw_point(ctx, DeviceToNormalised(canvas, lastPos), lastSize);
}
bgSlider.value = Math.random() * 360;
bgSlider.oninput();

let sizeSlider = document.getElementById("pointSize");
sizeSlider.oninput = () => {
  sizeSlider.style = `--box-size:${sizeSlider.value}px`;
  lastSize = sizeSlider.value;
  draw_point(ctx, DeviceToNormalised(canvas, lastPos), lastSize);
}
sizeSlider.value = lastSize;
sizeSlider.oninput();

let ps = document.getElementById("points");
function main() {
  lastPos = { x: Math.round(Math.random() * canvas.width), y: Math.round(Math.random() * canvas.height) };
  let norm = DeviceToNormalised(canvas, lastPos);
  draw_point(ctx, norm, lastSize);
  ps.innerHTML = ps.innerHTML + `<span>${lastPos.x}</span><span>,</span><span>${lastPos.y}</span><span>${norm.x}</span><span>,</span><span>${norm.y}</span>\n`;
  ps.scrollTop = ps.scrollHeight;
  
}
123456789101112131415161718192021222324252627282930313233
embedded JS
source
// Simulacija ponovog pokretanja.
setTimeout(function update() {
  main();
  setTimeout(update, 3000);
}, 10);

Comments