Uvod u WebGL
Updated: 2024-12-05
Published: 2024-11-25
Zadatak 1: usporedba OpenGLa i WebGLa
WebGL | OpenGL | |
---|---|---|
Upotreba | prikaz grafičkih komponenti u okruženju s ograničenim pristupom karakteristikama hardvera | grafički prikaz uz nesiguran pristup hardveru |
Programski dizajn | web aplikacije | nativne aplikacije |
Programski jezik | JS | C |
Značajke i funkcije | WebGL2: 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
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
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
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
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
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
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
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
source
// umjesto `body onload="main()"`
main()
Zadatak 7: pretvorba koordinata točke
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
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
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
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
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
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
Boja pozadine: Veličina točke:
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;
}
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( 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: 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> </span><span>,</span><span> </span><span> </span><span>,</span><span> </span>\n`;
ps.scrollTop = ps.scrollHeight;
}
123456789101112131415161718192021222324252627282930313233
source
// Simulacija ponovog pokretanja.
setTimeout(function update() {
main();
setTimeout(update, 3000);
}, 10);