const imageURLs = [
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAUVBMVEX/BgD/////9vb/6ur/29v/fnz/a2n/VlP/Qz7/MSr/HxL/y8r/urn/kpH/p6YAWFkAbG0ARUUANDQA29wAy8wAursAp6gAk5QAgIEAJCQAFRVoLfwBAAAAW0lEQVQY04XGRw6FMBTF0Pt7oQXSs/+FEp5nTDiSJet2ovuJHmitMXqi1spoMFvHaTysh9Fo7hYzG02dc5PrGb1QSmH0Rs6Z0QcpJUZfxBgZ/RBCYPSH957RpR17cQhbwmkrjgAAAABJRU5ErkJggg==",
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAUVBMVEWH/wD////7//bT/6PK/42//3e2/2Ks/0mi/y/1/+nu/9rl/8nd/7dqAN9jAM6a/wiR/wAhAEYZADURACUKABVZAL1QAKpHAJY9AII0AG4qAFrKpkVwAAAAXElEQVQY04XGNwKAMBADQZFzsMHx/w/lgmlomEJaVB+oPzCJm2hhZpeQxEKstToEKzHG6BA0r5wzH9pXSokPXRFjlEevQggaGIT3flAYmXNuLHCQveDGSbaCG78ep3wInxKnersAAAAASUVORK5CYII=",
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAS1BMVEUA/v/7//+k//9k/v/p//+P//95/v+4///a///J//9N/v80/v8Y/v/NAAC8AACpAAA1AAAlAABGAADeAACCAABtAAAVAACVAABZAABgJ6tzAAAAW0lEQVQY043IRw7AIAxE0UnvQEiB+580Y1tskk2exPgLVC/fj1bdZIVR7EoTE4UQbAiDiJGPQ6iLnLMcdOYiDfTmJA00RUpJDmby3tsQFnLO2RBWsSlNmIPw0wP2LQdICCV9sQAAAABJRU5ErkJggg==",
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAUVBMVEWDAP/////69v/06v+fOf+XIf+OAP/s2v/kyv/Spf/IkP/cuP+9e/9TqQBJlAC0Zv+pUP9AgQAjRgA2bQArWQAaNQASJABt3QBlzQBdvAALFQBmRdTVAAAAYklEQVQY04XGWRaFIAwD0LzBGQUUZdj/Qi2x/vjjPW0SfB7wfcBPlVLY6C6H4EBPe8WFqVqJE0MVglzgxCi2TV6igjHGe68hYK1dblbAOTffnMBf5ZzZaFRKiY1WxRjZeHUCx08IxkTd/o4AAAAASUVORK5CYII=",
];
const vs = `#version 300 es
void main() {
gl_Position = vec4(0, 0, 0, 1);
gl_PointSize = 150.0;
}
`
const fs = `#version 300 es
precision mediump float;
uniform mediump sampler3D tex;
out vec4 outColor;
void main() {
vec3 uvw = vec3(gl_PointCoord.xy, gl_PointCoord.x * gl_PointCoord.y);
outColor = texture(tex, uvw);
}
`;
function main() {
const gl = document.querySelector('canvas').getContext("webgl2");
if (!gl) { return console.log; ("need webgl2"); }
const program = twgl.createProgram(gl, [vs, fs]);
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_3D, tex);
gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
// we either need to preallocate the space OR we need to wait for
// one texture to download to find out the size. All the sizes have
// to be the same as all slices are required to have the same dimensions
// I happen to know all the images are 16x16 so
const levels = 1;
const internalFormat = gl.RGBA8;
const width = 16;
const height = 16;
const depth = imageURLs.length;
gl.texStorage3D(gl.TEXTURE_3D, levels, internalFormat, width, height, depth);
// load the images
imageURLs.forEach((url, ndx) => {
const img = new Image();
document.body.appendChild(img); // so we can see the images;
img.onload = () => {
gl.bindTexture(gl.TEXTURE_3D, tex);
const level = 0;
const x = 0;
const y = 0;
const z = ndx;
const depth = 1
gl.texSubImage3D(
gl.TEXTURE_3D, level,
x, y, z, width, height, depth,
gl.RGBA, gl.UNSIGNED_BYTE, img);
// render each time an image comes in. This means
// the first render will be missing 3 layers or rather
// the'll be blank (0,0,0,0). We could wait for all images
// to load instead.
render();
};
img.src = url;
});
function render() {
gl.useProgram(program);
gl.drawArrays(gl.POINTS, 0, 1); // 1 point
}
}
main();
canvas {
margin: .25em;
border: 1px solid black;
}
img {
margin: .25em;
border: 1px solid black;
width: 64px;
height: 64px;
image-rendering: pixelated;
}
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl.min.js"></script>