Created
April 16, 2025 21:56
-
-
Save greggman/722b22d2adeb7bfba987cffe20efb899 to your computer and use it in GitHub Desktop.
WebGL2 - Textures - (speed check, update 100 textures, no mips)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@import url("https://webgl2fundamentals.org/webgl/resources/webgl-tutorials.css"); | |
body { | |
margin: 0; | |
} | |
canvas { | |
width: 100vw; | |
height: 100vh; | |
display: block; | |
} | |
#ui { | |
width: 100px; | |
background-color: red; | |
padding: 0.25em; | |
} | |
body, document { | |
user-select: none; | |
-webkit-user-select: none; | |
-moz-user-select: none; | |
-o-user-select: none; | |
-ms-user-select: none; | |
} | |
#uiContainer { | |
left: 10px; | |
top: 130px; | |
} | |
#info { | |
position: absolute; | |
left: 0; | |
top: 0; | |
background-color: black; | |
color: white; | |
margin: 0; | |
padding: 0.5em; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<canvas id="canvas"></canvas> | |
<pre id="info"></pre> | |
<!-- | |
for most samples webgl-utils only provides shader compiling/linking and | |
canvas resizing because why clutter the examples with code that's the same in every sample. | |
See https://webgl2fundamentals.org/webgl/lessons/webgl-boilerplate.html | |
and https://webgl2fundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html | |
for webgl-utils, m3, m4, and webgl-lessons-ui. | |
--> | |
<script src="https://webgl2fundamentals.org/webgl/resources/webgl-utils.js"></script> | |
<script src="https://webgl2fundamentals.org/webgl/resources/m4.js"></script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// WebGL2 - Textures - Mips - Depth | |
// from https://webgl2fundamentals.org/webgl/webgl-3d-textures-mips-tri-linear.html | |
import GUI from 'https://webgpufundamentals.org/3rdparty/muigui-0.x.module.js'; | |
"use strict"; | |
var vertexShaderSource = `#version 300 es | |
// an attribute is an input (in) to a vertex shader. | |
// It will receive data from a buffer | |
in vec4 a_position; | |
in vec2 a_texcoord; | |
// A matrix to transform the positions by | |
uniform mat4 u_matrix; | |
// a varying to pass the texture coordinates to the fragment shader | |
out vec2 v_texcoord; | |
// all shaders have a main function | |
void main() { | |
// Multiply the position by the matrix. | |
gl_Position = u_matrix * a_position; | |
// Pass the texcoord to the fragment shader. | |
v_texcoord = a_texcoord; | |
} | |
`; | |
var fragmentShaderSource = `#version 300 es | |
precision highp float; | |
// Passed in from the vertex shader. | |
in vec2 v_texcoord; | |
// The texture. | |
uniform sampler2D u_texture; | |
// we need to declare an output for the fragment shader | |
out vec4 outColor; | |
void main() { | |
outColor = texture(u_texture, v_texcoord); | |
} | |
`; | |
const maxTextures = 100; | |
var zDepth = 50; | |
function main() { | |
// Get A WebGL context | |
/** @type {HTMLCanvasElement} */ | |
var canvas = document.querySelector("#canvas"); | |
var gl = canvas.getContext("webgl2", {antialias: false}); | |
if (!gl) { | |
return; | |
} | |
// Use our boilerplate utils to compile the shaders and link into a program | |
var program = webglUtils.createProgramFromSources(gl, | |
[vertexShaderSource, fragmentShaderSource]); | |
// look up where the vertex data needs to go. | |
var positionAttributeLocation = gl.getAttribLocation(program, "a_position"); | |
var texcoordAttributeLocation = gl.getAttribLocation(program, "a_texcoord"); | |
// look up uniform locations | |
var matrixLocation = gl.getUniformLocation(program, "u_matrix"); | |
// Create a buffer | |
var positionBuffer = gl.createBuffer(); | |
// Create a vertex array object (attribute state) | |
var vao = gl.createVertexArray(); | |
// and make it the one we're currently working with | |
gl.bindVertexArray(vao); | |
// Turn on the attribute | |
gl.enableVertexAttribArray(positionAttributeLocation); | |
// Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer) | |
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); | |
// Set Geometry. | |
setGeometry(gl); | |
// Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER) | |
{ | |
const size = 3; // 3 components per iteration | |
const type = gl.FLOAT; // the data is 32bit floats | |
const normalize = false; // don't normalize the data | |
const stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position | |
const offset = 0; // start at the beginning of the buffer | |
gl.vertexAttribPointer( | |
positionAttributeLocation, size, type, normalize, stride, offset); | |
} | |
// create the texcoord buffer, make it the current ARRAY_BUFFER | |
// and copy in the texcoord values | |
var texcoordBuffer = gl.createBuffer(); | |
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer); | |
setTexcoords(gl); | |
// Turn on the attribute | |
gl.enableVertexAttribArray(texcoordAttributeLocation); | |
// Tell the attribute how to get data out of texcoordBuffer (ARRAY_BUFFER) | |
{ | |
const size = 2; // 2 components per iteration | |
const type = gl.FLOAT; // the data is 32bit floating point values | |
const normalize = true; // convert from 0-255 to 0.0-1.0 | |
const stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next color | |
const offset = 0; // start at the beginning of the buffer | |
gl.vertexAttribPointer( | |
texcoordAttributeLocation, size, type, normalize, stride, offset); | |
} | |
const size = 2048; | |
const half = size / 2; | |
const ctx = document.createElement('canvas').getContext('2d'); | |
ctx.canvas.width = size; | |
ctx.canvas.height = size; | |
const hsl = (h, s, l) => `hsl(${h * 360 | 0}, ${s * 100}%, ${l * 100 | 0}%)`; | |
function update2DCanvas(time) { | |
time *= 0.0001; | |
ctx.fillStyle = hsl(time * 0.1 + 0.5, 1, 0.125) | |
ctx.fillRect(0, 0, size, size); | |
ctx.font = `${size * 0.5 | 0}px monospace`; | |
ctx.textAlign = 'center'; | |
ctx.textBaseline = 'middle'; | |
ctx.save(); | |
ctx.translate(half, half); | |
const num = 20; | |
const t = (time * 1000).toFixed(0); | |
ctx.scale(0.1, 0.1); | |
for (let i = 0; i < num; ++i) { | |
ctx.fillStyle = hsl(i / num * 0.4 + time * 0.1, 1, i % 2 * 0.5); | |
ctx.fillText(t, 0, 0); | |
ctx.translate(0, size / num * 0.5); | |
ctx.scale(1.1, 1.1); | |
} | |
ctx.restore(); | |
} | |
update2DCanvas(0); | |
const textures = []; | |
for (let i = 0; i < maxTextures; ++i) { | |
// Create a texture. | |
const texture = gl.createTexture(); | |
// use texture unit 0 | |
gl.activeTexture(gl.TEXTURE0 + 0); | |
// bind to the TEXTURE_2D bind point of texture unit 0 | |
gl.bindTexture(gl.TEXTURE_2D, texture); | |
// Now that the image has loaded make copy it to the texture. | |
gl.bindTexture(gl.TEXTURE_2D, texture); | |
gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA8, size, size); | |
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, ctx.canvas); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); | |
//gl.generateMipmap(gl.TEXTURE_2D); | |
textures.push(texture); | |
} | |
function degToRad(d) { | |
return d * Math.PI / 180; | |
} | |
// First let's make some variables | |
// to hold the translation, | |
var fieldOfViewRadians = degToRad(60); | |
let ticks = [{time: 0, deltaTime: 1/60}]; | |
let deltaAvg = 0; | |
const info = document.querySelector('#info'); | |
const params = { | |
copyCount: 40, | |
}; | |
const gui = new GUI(); | |
gui.add(params, 'copyCount', 1, maxTextures, 1); | |
function render(time) { | |
let last = ticks[ticks.length - 1] | |
let deltaCur = time - last.time; | |
deltaAvg = ((deltaAvg * ticks.length) + deltaCur) / (ticks.length + 1) | |
ticks.push({time: time, deltaTime: deltaCur}) | |
if (ticks.length > 60) { | |
deltaAvg = ((deltaAvg * ticks.length) - ticks[0].deltaTime) / (ticks.length - 1) | |
ticks.shift() | |
} | |
info.textContent = `fps: ${(1000 / deltaAvg).toFixed(0)}`; | |
webglUtils.resizeCanvasToDisplaySize(gl.canvas, window.devicePixelRatio); | |
update2DCanvas(time); | |
for (let i = 0; i < params.copyCount; ++i) { | |
gl.bindTexture(gl.TEXTURE_2D, textures[i]); | |
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, ctx.canvas); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); | |
//gl.generateMipmap(gl.TEXTURE_2D); | |
} | |
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); | |
gl.clearColor(0, 0, 0, 1); | |
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); | |
// turn on depth testing | |
gl.enable(gl.DEPTH_TEST); | |
// tell webgl to cull faces | |
gl.enable(gl.CULL_FACE); | |
// Tell it to use our program (pair of shaders) | |
gl.useProgram(program); | |
// Bind the attribute/buffer set we want. | |
gl.bindVertexArray(vao); | |
// Compute the matrix | |
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight; | |
const zNear = 1; | |
const zFar = 2000; | |
const projectionMatrix = m4.perspective(fieldOfViewRadians, aspect, zNear, zFar); | |
const cameraPosition = [0, 0, 2]; | |
const up = [0, 1, 0]; | |
const target = [0, 0, 0]; | |
// Compute the camera's matrix using look at. | |
const cameraMatrix = m4.lookAt(cameraPosition, target, up); | |
// Make a view matrix from the camera matrix. | |
const viewMatrix = m4.inverse(cameraMatrix); | |
const viewProjectionMatrix = m4.multiply(projectionMatrix, viewMatrix); | |
const settings = [ | |
{ x: -1, y: 1, zRot: 0, magFilter: gl.NEAREST, minFilter: gl.NEAREST, }, | |
{ x: 0, y: 1, zRot: 0, magFilter: gl.LINEAR, minFilter: gl.LINEAR, }, | |
{ x: 1, y: 1, zRot: 0, magFilter: gl.LINEAR, minFilter: gl.NEAREST_MIPMAP_NEAREST, }, | |
{ x: -1, y: -1, zRot: 1, magFilter: gl.LINEAR, minFilter: gl.LINEAR_MIPMAP_NEAREST, }, | |
{ x: 0, y: -1, zRot: 1, magFilter: gl.LINEAR, minFilter: gl.NEAREST_MIPMAP_LINEAR, }, | |
{ x: 1, y: -1, zRot: 1, magFilter: gl.LINEAR, minFilter: gl.LINEAR_MIPMAP_LINEAR, }, | |
]; | |
const xSpacing = 1.2; | |
const ySpacing = 0.7; | |
for (let j = 0; j < maxTextures; j += 6) { | |
settings.forEach(function(s, i) { | |
if (j + i >= maxTextures) { | |
return; | |
} | |
gl.bindTexture(gl.TEXTURE_2D, textures[j + i]); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, s.minFilter); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, s.magFilter); | |
let matrix = m4.translate(viewProjectionMatrix, s.x * xSpacing + j * 0.1, s.y * ySpacing, -zDepth * 0.5); | |
matrix = m4.zRotate(matrix, s.zRot * Math.PI); | |
matrix = m4.scale(matrix, 1, 1, zDepth); | |
// Set the matrix. | |
gl.uniformMatrix4fv(matrixLocation, false, matrix); | |
// Draw the geometry. | |
gl.drawArrays(gl.TRIANGLES, 0, 1 * 6); | |
}); | |
} | |
requestAnimationFrame(render); | |
} | |
requestAnimationFrame(render); | |
} | |
// Fill the current ARRAY_BUFFER buffer | |
// with the values that define a plane. | |
function setGeometry(gl) { | |
var positions = new Float32Array([ | |
-0.5, 0.5, -0.5, | |
0.5, 0.5, -0.5, | |
-0.5, 0.5, 0.5, | |
-0.5, 0.5, 0.5, | |
0.5, 0.5, -0.5, | |
0.5, 0.5, 0.5, | |
]); | |
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW); | |
} | |
// Fill the current ARRAY_BUFFER buffer | |
// with texture coordinates for a plane | |
function setTexcoords(gl) { | |
gl.bufferData( | |
gl.ARRAY_BUFFER, | |
new Float32Array([ | |
0, 0, | |
1, 0, | |
0, zDepth, | |
0, zDepth, | |
1, 0, | |
1, zDepth, | |
]), | |
gl.STATIC_DRAW); | |
} | |
main(); | |
// This is needed if the images are not on the same domain | |
// NOTE: The server providing the images must give CORS permissions | |
// in order to be able to use the image with WebGL. Most sites | |
// do NOT give permission. | |
// See: http://webgl2fundamentals.org/webgl/lessons/webgl-cors-permission.html | |
function requestCORSIfNotSameOrigin(img, url) { | |
if ((new URL(url, window.location.href)).origin !== window.location.origin) { | |
img.crossOrigin = ""; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{"name":"WebGL2 - Textures - (speed check, update 100 textures, no mips) ","settings":{},"filenames":["index.html","index.css","index.js"]} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment