NOTE: This tutorial builds up on my previous tutorial on how to setup OpenGL ES 2.0 on Android.
———————————————————————————————————-
I’ll first link to the apk, source code and the repository:
apk here.
Source code here.
Google Code Repository here. NOTE: Branch is “renderToTex”
———————————————————————————————————–
Rendering to a texture is important when it comes to various graphics techniques and algorithms (Shadow Mapping, cube map generation, Deferred Shading, etc.) So this quick tutorial will demonstrate how to setup a texture for rendering (and then how to display that texture on screen).
What is rendered to the texture?
We are going to render the same objects as in the previous tutorial (where you can choose between gouraud/phong/normal mapping shaders). The texture is going to be a simple quad which will then be displayed on the full screen. I have a Samsung Captivate(Samsung Galaxy S) which has a screen resolution of 800 X 480, so I use those values as the height and width of my texture. Adjust accordingly to your phone (I was a tad lazy to read in the values from the system itself). Let’s begin:
- New Variable declarations:
The variables below are for creating the quad to render the texture onto:
// the full-screen quad buffers final float x = 10.0f; final float y = 15.0f; final float z = 2.0f; // vertex information - clockwise // x, y, z, nx, ny, nz, u, v final float _quadv[] = { -x, -y, z, 0, 0, -1, 0, 0, -x, y, z, 0, 0, -1, 0, 1, x, y, z, 0, 0, -1, 1, 1, x, -y, z, 0, 0, -1, 1, 0 }; private FloatBuffer _qvb; // index final int _quadi[] = { 0, 1, 2, 2, 3, 0 }; private IntBuffer _qib;
Variables used for rendering to texture are declared below:
// RENDER TO TEXTURE VARIABLES int[] fb, depthRb, renderTex; // the framebuffer, the renderbuffer and the texture to render int texW = 480 * 2; // the texture's width int texH = 800 * 2; // the texture's height IntBuffer texBuffer; // Buffer to store the texture
- Setup Render To Texture:
The method setupRenderToTexture() (Hopefully the comments give enough detail):
// create the ints for the framebuffer, depth render buffer and texture fb = new int[1]; depthRb = new int[1]; renderTex = new int[1]; // generate GLES20.glGenFramebuffers(1, fb, 0); GLES20.glGenRenderbuffers(1, depthRb, 0); // the depth buffer GLES20.glGenTextures(1, renderTex, 0); // generate texture GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, renderTex[0]); // parameters - we have to make sure we clamp the textures to the edges GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); // create it // create an empty intbuffer first int[] buf = new int[texW * texH]; texBuffer = ByteBuffer.allocateDirect(buf.length * FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asIntBuffer();; // generate the textures GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGB, texW, texH, 0, GLES20.GL_RGB, GLES20.GL_UNSIGNED_SHORT_5_6_5, texBuffer); // create render buffer and bind 16-bit depth buffer GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, depthRb[0]); GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES20.GL_DEPTH_COMPONENT16, texW, texH);
- Rendering:
The rendering begins in the onDrawFrame() method. First we render to texture:
// viewport should match texture size Matrix.frustumM(mProjMatrix, 0, -ratio, ratio, -1, 1, 0.5f, 10); GLES20.glViewport(0, 0, this.texW, this.texH); // Bind the framebuffer GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fb[0]); // specify texture as color attachment GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, renderTex[0], 0); // attach render buffer as depth buffer GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT, GLES20.GL_RENDERBUFFER, depthRb[0]); // check status int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER); if (status != GLES20.GL_FRAMEBUFFER_COMPLETE) return false; // Clear the texture (buffer) and then render as usual... GLES20.glClearColor(.0f, .0f, .0f, 1.0f); GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT); ....usual rendering continues....
The final step is to render the quad itself to cover the screen fully (this is done using the gouraud shader):
// Bind the default framebuffer (to render to the screen) - indicated by '0' GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); GLES20.glClearColor(.0f, .0f, .0f, 1.0f); GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT); ...setup all the matrices as usual.... // send the vertex coordinates _qvb.position(TRIANGLE_VERTICES_DATA_POS_OFFSET); GLES20.glVertexAttribPointer(GLES20.glGetAttribLocation(_program, "aPosition"), 3, GLES20.GL_FLOAT, false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, _qvb); GLES20.glEnableVertexAttribArray(GLES20.glGetAttribLocation(_program, "aPosition")); // the normal info (though shouldn't be needed) _qvb.position(TRIANGLE_VERTICES_DATA_NOR_OFFSET); GLES20.glVertexAttribPointer(GLES20.glGetAttribLocation(_program, "aNormal"), 3, GLES20.GL_FLOAT, false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, _qvb); GLES20.glEnableVertexAttribArray(GLES20.glGetAttribLocation(_program, "aNormal")); // bind the framebuffer texture - this is what will be attached to the quad GLES20.glActiveTexture(GLES20.GL_TEXTURE0); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, renderTex[0]); GLES20.glUniform1i(GLES20.glGetUniformLocation(_program, "texture1"), 0); // enable texturing? [fix - sending float is waste] GLES20.glUniform1f(GLES20.glGetUniformLocation(_program, "hasTexture"), 1.0f); // texture coordinates _qvb.position(TRIANGLE_VERTICES_DATA_TEX_OFFSET); GLES20.glVertexAttribPointer(GLES20.glGetAttribLocation(_program, "textureCoord"), 2, GLES20.GL_FLOAT, false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, _qvb); GLES20.glEnableVertexAttribArray(GLES20.glGetAttribLocation(_program, "textureCoord"));//GLES20.glEnableVertexAttribArray(shader.maTextureHandle); // Draw with indices GLES20.glDrawElements(GLES20.GL_TRIANGLES, _quadi.length, GLES20.GL_UNSIGNED_INT, _qib);
And that’s it! Hopefully all the comments in the code snippets above explain everything. The quad and texture sizes are something I just fiddled around with, so adjust accordingly to the device you are testing on.
If you have any questions, suggestions or comments please let me know.
———————————————————————————————————-
Links to the apk, source code and the repository:
apk here.
Source code here.
Google Code Repository here. NOTE: Branch is “renderToTex”
———————————————————————————————————–
Comments on: "Android OpenGL ES 2.0 – Render To Texture" (31)
This is great stuff, thanks a lot. I am wondering… do you see a massive performance drop as well? I’m basically going from 80fps to 45fps when rendering to a texture.
Are you talking about my example? Yes there is a big framerate drop – mainly because the texture I’m rendering to is twice the size of the screen (screen size is 800 X 480). It was just a test case – usually when render to texture is done you use much smaller sizes (in the case of shadow mapping and other techniques for example).
Oh yeah true! I’ll play with it, thanks a lot.
it is really bad idea to give final lecture’s source code. When you expect to deal with something relatively simple you just lost yourself in the source code!
? Not sure what you mean
This comment absolutely makes no sense
Hi, Thanks for excellent tutorial, i have one question though. Why does result gets smaller if i render to texture and how can i get exact size result while rendering to texture ?
Not sure what you mean – I guess it depends on the size of the quad you are rendering to and the distance between the camera and the quad.
Hi,
I saw a post about your app on this forum:
http://forums.arm.com/index.php?/topic/15265-fbo-doesnt-work/
I did some investigation and got it working on the Galaxy SII. Please see the thread for details but basically the dot() function musn’t be overridden in the normalmap shaders, glDrawElements() must only be passed GL_UNSIGNED_SHORT and the related quad index buffers must be changed from int buffers to short buffers.
HTH, Pete
Thanks for your comment! I actually saw that forum post yesterday. It’s good to know that you fixed the code so that it now runs on Galaxy S2 also. I remember I was having issues with GL_UNSIGNED_INT/SHORT on my Galaxy S Captivate, but I will try to fix it soon.
Thanks again
Thank you so much for this example. Very helpful!
Are you seeing a huge performance hits simply by using the glBindRenderBuffer() line? I got the code working, and then I tried simply running it without any draw calls, and narrowed down a framerate hit to this line. I’m dropping from 60fps to 38fps on my HTC Incredible. And I think the 60 was just a limit from the display, so the hit might be even worse than it looks.
What factors could be making glBindRenderBuffer() really slow?
Look at the size of the texture I’m creating:
int texW = 480 * 2; // the texture’s width
int texH = 800 * 2; // the texture’s height
So the texture you are rendering to is massive! I think that is what causing the big drop – in practical applications you would render to a much smaller texture. Try decreasing the size and see if that helps.
I changed the code to render to a 256×256 square. And even if I don’t draw anything at all, there is a big framerate hit.
Hi all,
Exactly same problem here … I also tried to render to a very little square like 4×4 px.
I tried that on 2 smartphone, HTC Desire, HTC Desire HD, same thing.
Using a profiler, I found that this call takes 15ms:
glBindFramebuffer(GL_FRAMEBUFFER, uiFrameBuffer);
Someone find a solution for that ? Maybe there’s a problem with the HTC hardware (Darren has also test that with a HTC) ?
Hmm, is there an alternative to glBindRenderBuffer?
There is a post about this (FBO performance) that might be relevant on stack overflow:
http://stackoverflow.com/questions/10729352/framebuffer-fbo-render-to-texture-is-very-slow-using-opengl-es-2-0-on-android
Thanks Jason – there are certainly perf issues with rendering to texture. Always good to have more info
[…] using the Frame Buffer Object and render it on top of the current frame. So seeing some nice tutorials on how to do something like that I ended up with this code which basically **render my scene on the […]
[…] using the Frame Buffer Object and render it on top of the current frame. So seeing some nice tutorials on how to do something like that I ended up with this code which basically **render my scene on the […]
Imaging you only have one texture and u are changing the texture color when the user touches the screen. How could I switch the original texture to the changed one(render[0]) so when the user touches the screen again the changes are not lost …
I tried switching in regularRender the original tex with renderTex[0] and I got a very weird behavior ..
Anyone help would be appreciate it
Imagine that when the user touches the screen the shader changes the texture color. How could you switch the original texture to the changed tex (renderTex[0]) so the changes made in the last touch dont get lost ?
In other words how could I keep rendering the “result” texture as the original
you could just have a simple flag which changes on the first touch:
in the class:
boolean ifTouched = false;
in the onTouch() function:
if (!onTouch) {
onTouch = true;
changeTextureFunction();
}
I think that’s what you are looking for?
sjaay thanks for ur replay. Im pretty much a newbie to opengl
mTextureId being the original texture read from a bitmap
renderTex[0] being the texture attached to the fbo
u mead something like this:
if(!screenTouched)
{
GLES20.glBindTexture ( GLES20.GL_TEXTURE_2D, mTextureId );
}else
{
GLES20.glBindTexture ( GLES20.GL_TEXTURE_2D, renderTex[0]);
}
First of all when I render renderTex[0] I dont know why it appears upside down and when I touch the screen both the upside down and the normal texture are rendered and the screen is flickering
Maybe I should unbind the mTextureId?
Thanks
Sorry I posted instead of reply.
from your previous comment, if u could give some sample code for that
changeTextureFunction(); you wrote I would really appreciate it. Thanks
yes you should unbind the previous texture. that should be all there is to it
As a follow-up to rendering-to-texture, do you know if this can be extended to render a simple static OpenGL ES drawing to an Android bitmap format (for being displayed in an Android framework bitmap control)
Gingerbread doesn’t seem to support scrolling a SurfaceView within a ScrollView and so I’m trying to figure out if it will be possible to render OpenGL ES output into a bitmap and place the bitmap in the ScrollView
Thanks for sharing.
Do you know of a resource on the web that accomplishes render-to-texture using NDK?
Also, shadow mapping code often renders to a depth-texture, which has no R/G/B/A, just Z.
The Mali OpenGL ES SDK (http://www.malideveloper.com/developer-resources/opengl-es-sdk.php) has a set of examples written using the Android NDK. The FrameBufferObject example uses a render-to-texture technique to paint a spinning cube on another spinning cube as a texture.
Hi.
The only commands you need when rendering to texture is
// Bind the framebuffer
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fb[0]);
// check status
int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER);
if (status != GLES20.GL_FRAMEBUFFER_COMPLETE)
return false;
// Clear the texture (buffer) and then render as usual…
GLES20.glClearColor(.0f, .0f, .0f, 1.0f);
GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT
and when switching back to default
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER,0);
this helps with fps , but still drops down >10 fps
Your int[] buf seems to be totally useless!
Also if anyone is looking for a way to render android’s Views to OpenGL texture here is a open source sample project:
https://github.com/ArtemBogush/AndroidViewToGLRendering