Ronan Quigley
Ronan Quigley

Reputation: 841

Black screen when rendering to a texture

I'm trying to do the following:

  1. Draw the scene in a first pass to a framebuffer I've created.
  2. Take the texture that was attached to the created framebuffer and draw that onto a plane so that it can be displayed on screen.
  3. Do some post-processing.

Just using the default framebuffer the scene looks like this:

scene with the default framebuffer

Currently I'm unable to get parts 1 & 2 working. All I get is a black screen. However, the plane is placed in the scene correctly (confirmed by looking at a wireframe using gl.LINE_STRIP). I'm unsure if this is due to a mistake I've made with the code, or a lack of understanding of how framebuffers work (webgl is new to me).

Here's the relevant code excerpts:

// ======== FRAMEBUFFER PHASE ======== //
const framebuffer = gl.createFramebuffer();

gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);

const texture = gl.createTexture();

gl.bindTexture(gl.TEXTURE_2D, texture);

gl.texImage2D(
  gl.TEXTURE_2D,
  0,
  gl.RGB,
  canvas.clientWidth,
  canvas.clientHeight,
  0,
  gl.RGB,
  gl.UNSIGNED_BYTE,
  null // don't fill it with pixel data just yet
);

gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);

gl.framebufferTexture2D(
  gl.FRAMEBUFFER,
  gl.COLOR_ATTACHMENT0,
  gl.TEXTURE_2D,
  texture,
  0
);

// ======== END FRAMEBUFFER PHASE ======== //

// =========== RENDERBUFFER PHASE ============== //

const renderBuffer = gl.createRenderbuffer();

gl.bindRenderbuffer(gl.RENDERBUFFER, renderBuffer);

gl.renderbufferStorage(
  gl.RENDERBUFFER,
  gl.DEPTH_STENCIL,
  canvas.clientWidth,
  canvas.clientHeight
);

gl.framebufferRenderbuffer(
  gl.FRAMEBUFFER,
  gl.DEPTH_STENCIL_ATTACHMENT,
  gl.RENDERBUFFER,
  renderBuffer
);

// =========== END RENDERBUFFER PHASE ============== //

// =========== CHECK FRAMEBUFFER STATUS ============== //

const framebufferState = gl.checkFramebufferStatus(gl.FRAMEBUFFER);

if (framebufferState !== gl.FRAMEBUFFER_COMPLETE) {
  throw new Error(
    `Framebuffer status is not complete: ${framebufferState}`
  );
}

// =========== END CHECK FRAMEBUFFER STATUS ============== //

// =========== FIRST PASS RENDERING ============ //

gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
gl.clearColor(0.1, 0.1, 0.1, 1.0);
gl.clear(gl.DEPTH_BUFFER_BIT | gl.COLOR_BUFFER_BIT);
gl.enable(gl.DEPTH_TEST);

// this sets up the green quad and draws it to the screen
const objectModel = setupObjectModel({
  position: [100.0, -10.0, 0.0],
  colour: [0.734345265462, 0.89624528765, 0.9868589658, 1.0],
  gl,
  canvas,
});

objectModel.draw({
  shaderProgram: mainShaderProgram,
  camera: updatedCamera,
  currentTime,
  deltaTime,
});

// =========== END FIRST PASS RENDERING ============ //

// =========== SECOND PASS RENDERING ============ //

// back to rendering with the default framebuffer
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.clearColor(1.0, 1.0, 1.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.disable(gl.DEPTH_TEST);

gl.useProgram(frameBufferShaderProgram);

// prettier-ignore
const verts = [
  // positions  // texCoords
  -1.0,  1.0,  0.0, 1.0,
  -1.0, -1.0,  0.0, 0.0,
  1.0, -1.0,  1.0, 0.0,

  -1.0,  1.0,  0.0, 1.0,
  1.0, -1.0,  1.0, 0.0,
  1.0,  1.0,  1.0, 1.0
];

// prettier-ignore-end
const screenQuad = gl.createBuffer();

gl.bindBuffer(gl.ARRAY_BUFFER, screenQuad);

gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verts), gl.STATIC_DRAW);

const aPosAttributeLocation = gl.getAttribLocation(
  frameBufferShaderProgram,
  "aPos"
);

gl.enableVertexAttribArray(aPosAttributeLocation);
gl.vertexAttribPointer(
  aPosAttributeLocation,
  2,
  gl.FLOAT,
  false,
  Float32Array.BYTES_PER_ELEMENT * 4,
  0
);

const aTexCoordsAttributeLocation = gl.getAttribLocation(
  frameBufferShaderProgram,
  "aTexCoords"
);

gl.enableVertexAttribArray(aTexCoordsAttributeLocation);
gl.vertexAttribPointer(
  aTexCoordsAttributeLocation,
  2,
  gl.FLOAT,
  false,
  Float32Array.BYTES_PER_ELEMENT * 4,
  Float32Array.BYTES_PER_ELEMENT * 2
);

const screenTexture = gl.getUniformLocation(
  frameBufferShaderProgram,
  "screenTexture"
);

gl.uniform1i(screenTexture, 0);

gl.drawArrays(gl.TRIANGLES, 0, 6);

And here is the framebuffer shader program:

// vertex shader 
precision mediump float;

attribute vec2 aPos; 
attribute vec2 aTexCoords; 

varying vec2 TexCoords; 

void main() {
    gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0);
    TexCoords = aTexCoords;
}
// fragment shader
precision mediump float;

uniform sampler2D screenTexture; 
varying vec2 TexCoords;

void main() {
    // the texture coordinates are fine here, it's the screen texture that's the issue
    gl_FragColor = texture2D(screenTexture, TexCoords.xy);
}

Upvotes: 1

Views: 694

Answers (1)

Rabbid76
Rabbid76

Reputation: 210890

This is a common mistake. WebGL 1.0 is base on OpenGL ES 2.0. The same rules apply to texture framebuffer attachments as to mipmaps. The size of a framebuffer texture must be a power of 2. See Texture Completeness and Non-Power-Of-Two Textures.

Create a framebuffer with a size equal to a power of 2 (e.g. 1024x1024):

gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);

// [...]

gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1024, 1024, 0, gl.RGBA, gl.UNSIGNED_BYTE,   null);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);

// [...]

gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, 1024, 1024);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, renderBuffer);

// [...]

gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
gl.viewport(0, 0, 1024, 1024);

// [...]

gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.viewport(0, 0, canvas.clientWidth, canvas.clientHeight,);

// [...]

Upvotes: 1

Related Questions