1
votes

I'm working on a fairly simple 2d sprite based game that uses webGL. Individual sprites can be translated, scaled, and rotated. I'm using sprite sheets as textures and then modifying texture coordinates on the fly to create animation effects. Just to make things interesting, new sprites are instanciated on the fly. All of this works fine and everything renders properly when I'm only using two different textures, but it breaks down when I try to add a third. I can have multiple instances of sprites using the two textures, but as soon as I try to create an instance of a sprite with the third texture, it all goes wrong. I'm new to WebGL and I can't seem to find a tutorial that covers multiple textures inside an event loop. I figure I was doing it wrong even with two sprites, but managed to get away with until I added more complexity.

Here's my shaders:

void main() {

// Multiply the position by the matrix.
  vec2 position = (u_matrix * vec3(a_position, 1)).xy;

  // convert the position from pixels to 0.0 to 1.0
  vec2 zeroToOne = position / u_resolution;

  // convert from 0->1 to 0->2
  vec2 zeroToTwo = zeroToOne * 2.0;

  // convert from 0->2 to -1->+1 (clipspace)
  vec2 clipSpace = zeroToTwo - 1.0;

  gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
  v_texCoord = a_texCoord;
}
</script>

<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;

// our texture
uniform sampler2D u_image0;
uniform sampler2D u_image1;
uniform sampler2D u_image2;

// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;

void main() {
// Look up a color from the texture.
vec4 textureColor = texture2D(u_image0, v_texCoord);
  if (textureColor.a < 0.5) 
    discard;
  else
    gl_FragColor = vec4(textureColor.rgb, textureColor.a);

vec4 textureColor1 = texture2D(u_image1, v_texCoord);
  if (textureColor1.a < 0.5) 
    discard;
  else
    gl_FragColor = vec4(textureColor1.rgb, textureColor1.a);

vec4 textureColor2 = texture2D(u_image2, v_texCoord);
//  if (textureColor2.a < 0.5) 
//    discard;
//  else
//    gl_FragColor = vec4(textureColor2.rgb, textureColor2.a); 
}
</script>

Note how the third conditional block in the fragment shader is commented out. If I include this, it breaks. Well, the code runs, but the textures are all over the place.

This is the code that I run when the the sprite is instanciated, after the texture image loads.

image.onload = function() {
    that.buffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, that.buffer);

    var xMin = 0;
    var xMax = that.width;
    var yMin = 0;
    var yMax = that.height;


    // setup a rectangle from 0, that.width to 0, that.height in pixels
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
        xMin, yMax,
        xMax, yMax,
        xMin, yMin,
        xMin, yMin,
        xMax, yMax,
        xMax, yMin]), gl.STATIC_DRAW);
    gl.enableVertexAttribArray(globalGL.positionLocation);
    gl.vertexAttribPointer(globalGL.positionLocation, 2, gl.FLOAT, false, 0, 0);


    // look up where the texture coordinates need to go.
    that.texCoordLocation = gl.getAttribLocation(globalGL.program, "a_texCoord");

    //create a texture map object and attach to that
    that.texMap = new TextureMap({horizontalNum: that.texHorizontalNum, verticalNum: that.texVerticalNum});
    var tex = that.texMap.getTile([0, 0]);

    // provide texture coordinates for the rectangle.
    that.texCoordBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, that.texCoordBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
        tex.minX,  tex.maxY,
        tex.maxX,  tex.maxY,
        tex.minX,  tex.minY,
        tex.minX,  tex.minY,
        tex.maxX,  tex.maxY,
        tex.maxX,  tex.minY]), gl.STATIC_DRAW);
    gl.enableVertexAttribArray(that.texCoordLocation);
    gl.vertexAttribPointer(that.texCoordLocation, 2, gl.FLOAT, false, 0, 0);

    // Create a texture.
    that.texture = gl.createTexture();
    that.u_imageLocation = gl.getUniformLocation(globalGL.program, "u_image" + that.textureIndex);
    gl.uniform1i(that.u_imageLocation, that.textureIndex); 

    gl.activeTexture(gl.TEXTURE0 + that.textureIndex);
    gl.bindTexture(gl.TEXTURE_2D, that.texture);

    // Set the parameters so we can render any size image.
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);

    // Upload the image into the texture.
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);

    globalObj.agents[that.id] = that;
};

"that" is reference to the sprite object that I'm using. that.texMap is an object that tracks the texture coordinate data for the sprite. that.textureIndex is an integer unique to each type of sprite. I also save reference to the GL texture itself as that.texture.

This is what I run in the event loop for each instance of a sprite:

this.draw = function() {

    var tex, texCoordLocation, texCoordBuffer, i;

    //This pulls up the correct texture coordinates depending on what the sprite is doing.
    if (this.shooting) {
        tex = this.texMap.getTile([0, 1]);
    } else if ( this.moving) {

        if (this.moving < 15 / this.speed) {
            this.moving++;
            tex = this.texMap.getTile();
        } else {
            this.moving = 1;
            tex = this.texMap.getTile('next');
        }
    } else {
        tex = this.texMap.getTile([0, 0]);
    }

    //binds the texture associated with the sprite.
    gl.bindTexture(gl.TEXTURE_2D, this.texture);

    //gets a reference to the textCoord attribute
    texCoordLocation = gl.getAttribLocation(globalGL.program, 'a_texCoord');


    //create a buffer for texture coodinates
    texCoordBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
        tex.minX,  tex.maxY,
        tex.maxX,  tex.maxY,
        tex.minX,  tex.minY,
        tex.minX,  tex.minY,
        tex.maxX,  tex.maxY,
        tex.maxX,  tex.minY]), gl.STATIC_DRAW);
    gl.enableVertexAttribArray(texCoordLocation);
    gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);


    var matrixLocation = gl.getUniformLocation(globalGL.program,'u_matrix');

    //sets up arrays needed to rotate and translate the sprite
    var centerTranslation = [-this.width / 2, -this.height / 2];
    var decenterTranslation = [this.width / 2 , this.height / 2];
    var translation = [this.x, this.y];
    var angleInRadians = this.rotation;
    var scale = [1, 1];

    // Compute the matrices
    var centerTranslationMatrix = makeTranslation(centerTranslation[0], centerTranslation[1]);
    var decenterTranslationMatrix = makeTranslation(decenterTranslation[0], decenterTranslation[1]);
    var translationMatrix = makeTranslation(translation[0], translation[1]);
    var rotationMatrix = makeRotation(angleInRadians);
    var scaleMatrix = makeScale(scale[0], scale[1]);

    // Multiply the matrices.
    var matrix = matrixMultiply(scaleMatrix, centerTranslationMatrix);
    matrix = matrixMultiply(matrix, rotationMatrix);
    matrix = matrixMultiply(matrix, decenterTranslationMatrix);
    matrix = matrixMultiply(matrix, translationMatrix);

    // Set the matrix.
    gl.uniformMatrix3fv(matrixLocation, false, matrix);

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

Hopefully this hasn't been to verbose. I've been all over the web looking for tutorials or other scenarios that hanfle this sort of situation and I just can't seem to find anything.

Thanks!

EDIT: So, I know this problem isn't too hard for the community, yet quite a few people have viewed my my question and no one has responded. This prompted me to do a little self-reflection and take a good, long, hard look at my sample code.

I've restructured it significantly. I realized I don't need to be making a new texture every time a sprite is instanciated. Instead, I load up all the textures that I'll need once at the beginning. So the second code block has been completely reworked. It still does a lot of the same stuff, but only runs once for each texture in a for loop at the beginning. I'd be happy to upload the new code if anyone wants to have a look at it, or if someone could point me in the direction of a tutorial that uses multiple textures on multiple 2d quads (more than one quad per texture) in an event loop, I'd be happy to do the research myself.

1
To be clear, the issue is that everything will be the same texture, then a new sprite is instanciated and everything turns into the new texture. - Brian A Blackwell

1 Answers

1
votes

the issue is that everything will be the same texture, then a new sprite is instanciated and everything turns into the new texture.

From this, I'd suspect you aren't selecting the correct texture before binding (i.e. using gl.activeTexture() with correct parameters). Looks fine in the onload function, but you don't use it in your this.draw() function before binding the sprite texture, which may be the problem.