sjkm
sjkm

Reputation: 3937

OpenGL stencil (Clip Entity)

I am trying to use a sprite as clip-entity. This means I want that all it's child entities are clipped so that only the parts of the children that overlap the clip-entity (parent) are visible.

I'm trying to do that using the OpenGL stencil function. On my emulator I got it working, but on my Android phone it doesn't. What am I doing wrong?

GLES20.glEnable(GLES20.GL_STENCIL_TEST);
GLES20.glClearStencil(0);
GLES20.glStencilMask(1);

GLES20.glClear(GLES20.GL_STENCIL_BUFFER_BIT);

// pre draw
GLES20.glStencilFunc(GLES20.GL_NEVER, 1, 1);
GLES20.glStencilOp(GLES20.GL_REPLACE, GLES20.GL_KEEP, GLES20.GL_KEEP);

// draw clip-entity (this) (sprite)
this.draw();

// post draw
GLES20.glStencilFunc(GLES20.GL_LESS, 2, 1);
GLES20.glStencilOp(GLES20.GL_KEEP, GLES20.GL_KEEP, GLES20.GL_KEEP);

// draw children of clip-entity (this) which should now be clipped
this.drawChildren();

GLES20.glDisable(GLES20.GL_STENCIL_TEST);

enter image description here

Upvotes: 1

Views: 1113

Answers (1)

Reto Koradi
Reto Koradi

Reputation: 54592

Using a stencil buffer for what you're describing sounds like a good approach.

You need to make sure that you request a framebuffer configuration with a stencil buffer. When using GLSurfaceView on Android, this is done with one of the overloaded setEGLConfigChooser() methods. The simplest form is the one that takes a bit depth for Red, Green, Blue, Alpha, Depth, and Stencil. If you also need a depth buffer, you can try combinations like these to get a config with stencil:

setEGLConfigChooser(8, 8, 8, 8, 24, 8);
setEGLConfigChooser(8, 8, 8, 8, 16, 8);
setEGLConfigChooser(8, 8, 8, 0, 24, 8);
setEGLConfigChooser(8, 8, 8, 0, 16, 8);
setEGLConfigChooser(5, 6, 5, 0, 24, 8);
setEGLConfigChooser(5, 6, 5, 0, 16, 8);

To be even more thorough, you can also use the setEGLConfigChooser() override that lets you register your own EGLConfigChooser implementation. Using that, you can enumerate configuration, and implement your own logic to choose the one that best meets your need.

To verify that you indeed got a stencil buffer, you can use:

GLint stencilBits = 0;
glGetIntegerv(GL_STENCIL_BITS, &stencilBits);

I thought that stencil support was standard, but the way I read the ES 2.0 spec, it's not required:

Further, an implementation or context may not provide depth or stencil buffers. However, an OpenGL ES implementation must support at least one config with a depth bit depth.

I believe at least relatively recent GPUs support stencil. If you have to get this working on a GPU that indeed does not provide stencil after you tried all of the above, things get more difficult. For the example you sketched, where you clip by a rectangle, that could of course simply be done by using a scissor rectangle. But I'm sure that you only used that for illustration.

Depending how much you use the depth buffer for your actual rendering, you could play some tricks with the depth buffer instead. For example, it could look like this:

glClearDepthf(0.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDisable(GL_DEPTH_TEST);
// draw clip entity
glEnable(GL_DEPTH_TEST);
// draw children

The downside of this is that you need to disable depth testing for part of your drawing, which might be completely unacceptable if that drawing needs depth testing to render properly.

Upvotes: 2

Related Questions