1
\$\begingroup\$

I am using WebGL 2. A tag for it does not exist but it should.

I have a 10x10 sprite sheet of squares that are 16x16 pixels in size (all in one PNG image).

I'd like to create a 2D texture array out of them, where each 16x16 square gets its own, unique Z/depth value.

let texture = gl.createTexture();
let image = new Image();

image.onload = function() {
    gl.bindTexture(gl.TEXTURE_2D_ARRAY, texture);
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);

    gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 5, gl.RGBA, 16, 16, NUM_IMAGES);

    // Now what?
    // gl.texSubImage3D doesn't let me copy in a section of the src image
}

image.src = "https://source_url.fake/image.png";

I know that gl.texSubImage3D exists but it only accepts an entire image as a source?

glTexSubImage3D: https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glTexSubImage3D.xml

\$\endgroup\$
3
  • \$\begingroup\$ glTexSubImage3D can and does accept a subimage as source: hence the name. Maybe if you post the code you're using that "doesn't let me copy in a section of the src image" we can help you find what you're doing wrong. \$\endgroup\$ Aug 31, 2017 at 12:47
  • \$\begingroup\$ @LeComteduMerde-fou developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext/… they accept a variety of sources like ImageBitmap and ArrayBufferView which has an offer. Not sure how to get one of these from an image though and reliably get an offset etc. \$\endgroup\$
    – Lucien
    Aug 31, 2017 at 16:10
  • \$\begingroup\$ make sure you're looking at the correct version of.the docs. for WebGL2 it's khronos.org/registry/OpenGL-Refpages/es3.0/html/… \$\endgroup\$
    – gman
    Sep 2, 2017 at 5:29

1 Answer 1

2
\$\begingroup\$

IIRC you can select a portion of the image using the various settings to gl.pixelStorei

Here's some code that makes a 128x128x8 2D Array texture from a 512x256 2D image

  const tex = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D_ARRAY, tex);
  const levels = 7;
  const width = 128;
  const height = 128;
  const depth = 8;
  gl.texStorage3D(gl.TEXTURE_2D_ARRAY, levels, gl.RGBA8, width, height, depth);
  // Tell WebGL the size of one row
  gl.pixelStorei(gl.UNPACK_ROW_LENGTH, img.width);
  for (let d = 0; d < depth; ++d) {
    const xoff = d % 4 * width;
    const yoff = (d / 4 | 0) * height;
    // Tell WebGL where to start copying from
    gl.pixelStorei(gl.UNPACK_SKIP_PIXELS, xoff);
    gl.pixelStorei(gl.UNPACK_SKIP_ROWS, yoff);
    const level = 0;
    gl.texSubImage3D(gl.TEXTURE_2D_ARRAY, level, 0, 0, d, width, height, 1, gl.RGBA, gl.UNSIGNED_BYTE, img);
  }
  gl.generateMipmap(gl.TEXTURE_2D_ARRAY);

Here's the image

And here's a working sample

"use strict";

const img = new Image();
img.onload = main;
img.crossOrigin = "";
img.src = "https://webglfundamentals.org/webgl/resources/noodles.jpg";

const vs = `#version 300 es
uniform mat4 u_worldViewProjection;

in vec4 a_position;
in vec2 a_texcoord;
in uint a_faceId;

out vec4 v_position;
out vec2 v_texCoord;
flat out uint v_faceId;

void main() {
  v_faceId = a_faceId;
  v_texCoord = a_texcoord;
  gl_Position = u_worldViewProjection * a_position;
}
`;
const fs = `#version 300 es
precision mediump float;

in vec4 v_position;
in vec2 v_texCoord;
flat in uint v_faceId;

uniform mediump sampler2DArray u_diffuse;
uniform uint u_faceIndex[6];

out vec4 outColor;

void main() {
  outColor = texture(u_diffuse, vec3(v_texCoord, u_faceIndex[v_faceId]));
}
`;

function main() {
  twgl.setDefaults({attribPrefix: "a_"});
  var m4 = twgl.m4;
  var gl = document.querySelector("canvas").getContext("webgl2");
  if (!gl) {
    alert("Sorry, this example requires WebGL 2.0");  // eslint-disable-line
    return;
  }
  var programInfo = twgl.createProgramInfo(gl, [vs, fs]);

  var arrays = {
    position: [1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1],
    normal:   [1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1],
    texcoord: [1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1],
    faceId:   { numComponents: 1, data: new Uint8Array([0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6]), },
    indices:  [0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 8, 9, 10, 8, 10, 11, 12, 13, 14, 12, 14, 15, 16, 17, 18, 16, 18, 19, 20, 21, 22, 20, 22, 23],
  };
  var bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
  
  const tex = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D_ARRAY, tex);
  const levels = 7;
  const width = 128;
  const height = 128;
  const depth = 8;
  gl.texStorage3D(gl.TEXTURE_2D_ARRAY, levels, gl.RGBA8, width, height, depth);
  // Tell WebGL the size of one row
  gl.pixelStorei(gl.UNPACK_ROW_LENGTH, img.width);
  for (let d = 0; d < depth; ++d) {
    const xoff = d % 4 * width;
    const yoff = (d / 4 | 0) * height;
    // Tell WebGL where to start copying from
    gl.pixelStorei(gl.UNPACK_SKIP_PIXELS, xoff);
    gl.pixelStorei(gl.UNPACK_SKIP_ROWS, yoff);
    const level = 0;
    gl.texSubImage3D(gl.TEXTURE_2D_ARRAY, level, 0, 0, d, width, height, 1, gl.RGBA, gl.UNSIGNED_BYTE, img);
  }
  gl.generateMipmap(gl.TEXTURE_2D_ARRAY);

  var uniforms = {
    u_diffuse: tex,
    u_faceIndex: [0, 1, 2, 3, 4, 5],
  };

  var oldTime = 0;
  function render(time) {
    time *= 0.001;
    twgl.resizeCanvasToDisplaySize(gl.canvas);
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

    oldTime = time;


    gl.enable(gl.DEPTH_TEST);
    gl.enable(gl.CULL_FACE);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    var projection = m4.perspective(30 * Math.PI / 180, gl.canvas.clientWidth / gl.canvas.clientHeight, 0.5, 10);
    var eye = [1, 4, -6];
    var target = [0, 0, 0];
    var up = [0, 1, 0];

    var camera = m4.lookAt(eye, target, up);
    var view = m4.inverse(camera);
    var viewProjection = m4.multiply(projection, view);
    var world = m4.rotationY(time);

    uniforms.u_worldViewProjection = m4.multiply(viewProjection, world);

    gl.useProgram(programInfo.program);
    twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
    twgl.setUniforms(programInfo, uniforms);
    twgl.drawBufferInfo(gl, bufferInfo);

    requestAnimationFrame(render);
  }
  requestAnimationFrame(render);
}
body {
    margin: 0;
}
canvas {
    display: block;
    width: 100vw;
    height: 100vh;
}
<canvas></canvas>
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>

Note you might want to set all those settings back to 0 after you're done as they'll affect all other calls to gl.texImageXXX and gl.texSubImageXXX

You could even just turn it into a function

function texImageBySlice(gl, target, level, 
                         sliceWidth, sliceHeight, 
                         format, type, image) {
  const slicesAcross = image.width / sliceWidth;
  const slicesDown = image.height / sliceHeight;

  // Tell WebGL the size of one row
  gl.pixelStorei(gl.UNPACK_ROW_LENGTH, img.width);

  for (let y = 0; y < slicesDown; ++y) {
    for (let x = 0; x < slicesAcross; ++x) {
      const xoff = x * sliceWidth;
      const yoff = y * sliceHeight;
      const depth = y * slicesAcross + x;

      // Tell WebGL where to start copying from
      gl.pixelStorei(gl.UNPACK_SKIP_PIXELS, xoff);
      gl.pixelStorei(gl.UNPACK_SKIP_ROWS, yoff);

      gl.texSubImage3D(target, level, 0, 0, depth, 
                       sliceWidth, sliceHeight, 1, 
                       format, type, img);
    }
  }

  gl.pixelStorei(gl.UNPACK_ROW_LENGTH, 0);
  gl.pixelStorei(gl.UNPACK_SKIP_PIXELS, 0);
  gl.pixelStorei(gl.UNPACK_SKIP_ROWS, 0);
}
\$\endgroup\$
0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .