peedee
peedee

Reputation: 3739

OpenGL ES 2.0: How to render to a frameBuffer in a lower resolution for environment mapping?

I'm trying to implement dynamic environment reflections on Android using OpenGL ES 2.0.

For this I set my camera in the place of my reflective object and render to an off-screen renderbuffer in 6 different directions (two per axis) to build a cube map, but that's very slow, so my idea is to make the cube map have a lower resolution in order to speed things up. I thought this should be simple but I don't understand my observations.

I want to see the results of those 6 renders to check if the results are as expected, so I export them to disk as png files before rendering the next one. I rendered once with framebuffer of 1024x1024 and once with 256x256. However, when I look at the exported files, I can see that the 256.png only contains a fraction of the content of the bigger one. I was expecting them to have the same content (field of view if you like) but different resolutions ("bigger pixels"), but that's not what happens.

I have static constants REFLECTION_TEX_WIDTH and REFLECTION_TEX_HEIGHT to set the width and height of the created textures and renderbuffers, and I use those constants for both creation and exporting. But the exported files never cover as much area as I expect. When I set those dimensions really large, like 2000 each, the rendered area seems to cover about 1080x1550 pixel, the rest of the file remains black. Can someone tell me what's going on here?

I'm not sure if the problem is in my understanding of how the framebuffer works or if the rendering is correct but the problem is introduced in my file export... those file export methods are copied from the internet, I don't really understand them.

I want to render the same area/FOV but in a coarser resolution. Is that too much to ask?

Some code then. Initializations:

// create 6 textures for the dynamic environment reflection
final int skyboxFaces=6;
final int[] textureId=new int[1];
GLES20.glGenTextures(1, textureId, 0);
skyboxTexture=textureId[0];
ShaderFactory.checkGLError("initRendering::createSkyboxTextures");

GLES20.glBindTexture(GLES20.GL_TEXTURE_CUBE_MAP, skyboxTexture);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_CUBE_MAP, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_CUBE_MAP, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_CUBE_MAP, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_CUBE_MAP, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
for(int i=0; i < skyboxFaces; i++)
{
  GLES20.glTexImage2D(GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GLES20.GL_RGBA,
    REFLECTION_TEX_WIDTH, REFLECTION_TEX_HEIGHT, 0, GLES20.GL_RGBA,
    GLES20.GL_UNSIGNED_BYTE, null);
  ShaderFactory.checkGLError("initRendering::setSkyboxTexture(" + i + ")");
}
// create renderbuffer and bind 16-bit depth buffer
renderBuffers=new int[1];
GLES20.glGenRenderbuffers(1, renderBuffers, 0);
GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, renderBuffers[0]);
GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES20.GL_DEPTH_COMPONENT16,
  REFLECTION_TEX_WIDTH, REFLECTION_TEX_HEIGHT);
ShaderFactory.checkGLError("initRendering::createRenderbuffer");

frameBuffers=new int[1];
GLES20.glGenFramebuffers(1, frameBuffers, 0);
// GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
// GLES20.GL_RENDERBUFFER, frameBuffers[0]);
ShaderFactory.checkGLError("initRendering::createFrameBuffer");

Then in the renderloop I do the following:

GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBuffers[0]);
GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, renderBuffers[0]);
// assign the cubemap texture to the framebuffer
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
  GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_X + bufferId, skyboxTexture, 0);
// assign the depth renderbuffer to the framebuffer
GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT,
  GLES20.GL_RENDERBUFFER, frameBuffers[0]);
// clear the current framebuffer (color and depth)
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);

And to export the results as files I do this:

public void savePNG(final int x, final int y, final int w, final int h, final String name)
{
  final Bitmap bmp=savePixels(x, y, w, h);
  try
  {
    final File file=new File(Environment.getExternalStoragePublicDirectory(
      Environment.DIRECTORY_PICTURES), name);
    final File parent=file.getParentFile();
    // create parent directories if necessary
    if(null != parent && !parent.isDirectory())
      parent.mkdirs();
    // delete existing file to avoid mixing old data with new
    if(file.exists())
      file.delete();

    final FileOutputStream fos=new FileOutputStream(file);
    bmp.compress(CompressFormat.PNG, 100, fos);
    fos.flush();
    fos.close();
    context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file)));
  }
  catch(final FileNotFoundException e)
  {
    // TODO Auto-generated catch block
    LOG.error("problem " + e);
  }
  catch(final IOException e)
  {
    // TODO Auto-generated catch block
    LOG.error("problem " + e);
  }
}

// TODO: magic imported code
public Bitmap savePixels(final int x, final int y, final int w, final int h)
{
  final int b[]=new int[w * (y + h)];
  final int bt[]=new int[w * h];
  final IntBuffer ib=IntBuffer.wrap(b);
  ib.position(0);
  GLES20.glReadPixels(x, 0, w, y + h, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, ib);

  for(int i=0, k=0; i < h; i++, k++)
  {
    // OpenGL bitmap is incompatible with Android bitmap and needs some correction.
    for(int j=0; j < w; j++)
    {
      final int pix=b[i * w + j];
      final int pb=(pix >> 16) & 0xff;
      final int pr=(pix << 16) & 0x00ff0000;
      final int pix1=(pix & 0xff00ff00) | pr | pb;
      bt[(h - k - 1) * w + j]=pix1;
    }
  }

  final Bitmap sb=Bitmap.createBitmap(bt, w, h, Bitmap.Config.ARGB_8888);
  return sb;
}

Upvotes: 3

Views: 2666

Answers (1)

Reto Koradi
Reto Koradi

Reputation: 54592

It looks like you're missing to set the viewport before rendering to the FBO. During the setup of FBO rendering, add this call:

glViewport(0, 0, REFLECTION_TEX_WIDTH, REFLECTION_TEX_HEIGHT);

You can place it around where you have the glClear(). Don't forget to set it back to the size of the default framebuffer after you're done with the FBO rendering, and before rendering to the default framebuffer.

The viewport size is global state, and defaults to the initial size of the default framebuffer. So anytime you use an FBO with a size different from the default draw surface, you need to set the viewport accordingly.

Upvotes: 4

Related Questions