Spectraljump
Spectraljump

Reputation: 4617

Drawing multiple 2D images in WebGL

I've been following a couple of tutorials on html5rocks, and I managed to make a javascript program which displays ONE image on a canvas using webGL. I have posted the code I have for this below.

The problem is, it seems there is nobody out there who shows you how to draw more than ONE object in webGL. I've never worked directly with webGL before, so it's not very intuitive for me.

How can I modify this code to draw each object in the imageObjectArray? (notice that now I'm just drawing imageObjectArray[0].

    function render(canvas, contextGL, imageObjectArray) { 
        vertexShader = createShaderFromScriptElement(contextGL, "2d-vertex-shader");
        fragmentShader = createShaderFromScriptElement(contextGL, "2d-fragment-shader");

        program = createProgram(contextGL, [vertexShader, fragmentShader]);
        contextGL.useProgram(program);

        var positionLocation = contextGL.getAttribLocation(program, "a_position");

        var texCoordLocation = contextGL.getAttribLocation(program, "a_texCoord");

        var texCoordBuffer = contextGL.createBuffer();
        contextGL.bindBuffer(contextGL.ARRAY_BUFFER, texCoordBuffer);

        contextGL.enableVertexAttribArray(texCoordLocation);
        contextGL.vertexAttribPointer(texCoordLocation, 2, contextGL.FLOAT, false, 0, 0);

        setRectangle(contextGL, 0.0, 0.0, 1.0, 1.0);

        var texture = contextGL.createTexture();
        contextGL.bindTexture(contextGL.TEXTURE_2D, texture);

        contextGL.texParameteri(contextGL.TEXTURE_2D, contextGL.TEXTURE_WRAP_S, contextGL.CLAMP_TO_EDGE);
        contextGL.texParameteri(contextGL.TEXTURE_2D, contextGL.TEXTURE_WRAP_T, contextGL.CLAMP_TO_EDGE);
        contextGL.texParameteri(contextGL.TEXTURE_2D, contextGL.TEXTURE_MIN_FILTER, contextGL.NEAREST);
        contextGL.texParameteri(contextGL.TEXTURE_2D, contextGL.TEXTURE_MAG_FILTER, contextGL.NEAREST);

        contextGL.texImage2D(contextGL.TEXTURE_2D, 0, contextGL.RGBA, contextGL.RGBA, contextGL.UNSIGNED_BYTE, imageObjectArray[0].displayObject.data);

        var resolutionLocation = contextGL.getUniformLocation(program, "u_resolution");
        contextGL.uniform2f(resolutionLocation, canvas.width, canvas.height);

        var buffer = contextGL.createBuffer();
        contextGL.bindBuffer(contextGL.ARRAY_BUFFER, buffer);
        contextGL.enableVertexAttribArray(positionLocation);
        contextGL.vertexAttribPointer(positionLocation, 2, contextGL.FLOAT, false, 0, 0);

        setRectangle(contextGL, imageObjectArray[0].x, imageObjectArray[0].y, imageObjectArray[0].width, imageObjectArray[0].height);

        // draw
        contextGL.drawArrays(contextGL.TRIANGLES, 0, 6);
    }

function setRectangle(gl, x, y, width, height) {
    var x2 = x + width;
    var y2 = y + height;
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(
                                        [x, y,
                                         x2, y,
                                         x, y2,
                                         x, y2,
                                         x2, y,
                                         x2, y2
                                        ]), gl.STATIC_DRAW);
}

My aim here is to work on a (very basic) 2d sprite game. Without the comfort of libraries. (except for maybe glMatrix.js)

[EDIT] My render function had an error in it, fixed.

Upvotes: 4

Views: 4013

Answers (1)

Cutch
Cutch

Reputation: 3575

The following code does the following:

  1. Compiles the vertex and fragment shaders
  2. Links them together into a shader program
  3. Creates a vertex buffer to hold texture coordinates and fills it (texCoordBuffer)
  4. Creates a texture (createTexture)
  5. Configures how the texture is sampled (texParameteri)

The above 5 steps only need to be run once.

    vertexShader = createShaderFromScriptElement(contextGL, "2d-vertex-shader");
    fragmentShader = createShaderFromScriptElement(contextGL, "2d-fragment-shader");

    program = createProgram(contextGL, [vertexShader, fragmentShader]);
    contextGL.useProgram(program);

    var positionLocation = contextGL.getAttribLocation(program, "a_position");

    var texCoordLocation = contextGL.getAttribLocation(program, "a_texCoord");

    var texCoordBuffer = contextGL.createBuffer();
    contextGL.bindBuffer(contextGL.ARRAY_BUFFER, texCoordBuffer);

    contextGL.enableVertexAttribArray(texCoordLocation);
    contextGL.vertexAttribPointer(texCoordLocation, 2, contextGL.FLOAT, false, 0, 0);

    var texture = contextGL.createTexture();
    contextGL.bindTexture(contextGL.TEXTURE_2D, texture);

    contextGL.texParameteri(contextGL.TEXTURE_2D, contextGL.TEXTURE_WRAP_S, contextGL.CLAMP_TO_EDGE);
    contextGL.texParameteri(contextGL.TEXTURE_2D, contextGL.TEXTURE_WRAP_T, contextGL.CLAMP_TO_EDGE);
    contextGL.texParameteri(contextGL.TEXTURE_2D, contextGL.TEXTURE_MIN_FILTER, contextGL.NEAREST);
    contextGL.texParameteri(contextGL.TEXTURE_2D, contextGL.TEXTURE_MAG_FILTER, contextGL.NEAREST);

    setRectangle(contextGL, 0.0, 0.0, 1.0, 1.0);

The rest of the code must execute for each image you want to draw and it does the following:

  1. Uploads the image into the texture (texImage2D)
  2. Creates a vertex buffer to hold positions and fills it (buffer)
  3. Calls drawArrays
    contextGL.texImage2D(contextGL.TEXTURE_2D, 0, contextGL.RGBA, contextGL.RGBA,contextGL.UNSIGNED_BYTE, imageObjectArray[0].displayObject.data);

    var resolutionLocation = contextGL.getUniformLocation(program, "u_resolution");
    contextGL.uniform2f(resolutionLocation, canvas.width, canvas.height);

    var buffer = contextGL.createBuffer();
    contextGL.bindBuffer(contextGL.ARRAY_BUFFER, buffer);
    contextGL.enableVertexAttribArray(positionLocation);
    contextGL.vertexAttribPointer(positionLocation, 2, contextGL.FLOAT, false, 0, 0);

    setRectangle(contextGL, imageObjectArray[0].x, imageObjectArray[0].y, imageObjectArray[0].width, imageObjectArray[0].height);

    // draw
    contextGL.drawArrays(contextGL.TRIANGLES, 0, 6);

You need to split step 2 into separate steps. The first step, creating the vertex buffer for posistions, should only be executed once. The second step, filling the position of the image, needs to be executed for each image you want to draw.

I should say that my suggestions will not give the optimal implementation but it will get you drawing more than 1 image. To be optimal you should consider doing:

  • Implement texture atlasing (pack all your images onto a single texture).
  • Only upload your texture and position coordinates once.
  • Use a better vertex and fragment shader to pick which image is being drawn (texture coordinate offset), where it is being drawn (position offset) and how large it should be (width & height scaling)

Upvotes: 5

Related Questions