OpenGL ES 2.0 iOS - 在模板缓冲区中绘制一个矩形,并仅在其内部限制绘图

时间:2023-02-09 23:44:17

Do a good deed and help get someone (me) out of their misery, since it's New Year's Eve soon. I'm working on an iOS app, a coloring book for kids and I haven't stumbled upon OpenGL before (more precisely OpenGLES 2.0) so there's a big chance there's stuff I don't actually get in my code.

做一件好事并帮助某人(我)摆脱他们的苦难,因为这是新年前夜。我正在开发一个iOS应用程序,一本适合孩子的着色书,我之前没有偶然发现OpenGL(更确切地说是OpenGLES 2.0),所以很有可能我的代码中没有实际存在的东西。

One of the tasks is to not let the brush spill out of the contour in which the user started drawing.

其中一项任务是不让刷子溢出用户开始绘制的轮廓。

OpenGL ES 2.0 iOS  - 在模板缓冲区中绘制一个矩形,并仅在其内部限制绘图

After reading and understanding some OpenGL basics, I found that using the stencil buffer is the right solution. This is my stencil buffer setup:

在阅读并理解了一些OpenGL基础知识之后,我发现使用模板缓冲区是正确的解决方案。这是我的模板缓冲区设置:

glClearStencil(0);

//clear the stencil
glClear(GL_STENCIL_BUFFER_BIT);

//disable writing to color buffer
glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );

//disable depth buffer
glDisable(GL_DEPTH_TEST);

//enable writing to stencil buffer
glEnable(GL_STENCIL_TEST);

glStencilFunc(GL_NEVER, 1, 0xFF);

glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);

[self drawStencil];

//re-enable color buffer
glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );

//only draw where there is a 1
glStencilFunc(GL_EQUAL, 1, 1);

//keep the pixels in the stencil buffer
glStencilOp( GL_KEEP, GL_KEEP, GL_KEEP );

Right now, I'm just trying to draw a square in the stencil buffer and see if I can limit my drawing only to that square. This is the method drawing the square:

现在,我只是想在模板缓冲区中绘制一个正方形,看看我是否可以将绘图仅限制在那个正方形上。这是绘制正方形的方法:

- (void)drawStencil
{
// Create a renderbuffer
GLuint renderbuffer;
glGenRenderbuffers(1, &renderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
[context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];

// Create a framebuffer
GLuint framebuffer;
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, renderbuffer);

// Clear
glClearColor(1, 1, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);

// Read vertex shader source
NSString *vertexShaderSource = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"VertexShader" ofType:@"vsh"] encoding:NSUTF8StringEncoding error:nil];
const char *vertexShaderSourceCString = [vertexShaderSource cStringUsingEncoding:NSUTF8StringEncoding];

// Create and compile vertex shader
GLuint _vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(_vertexShader, 1, &vertexShaderSourceCString, NULL);
glCompileShader(_vertexShader);

// Read fragment shader source
NSString *fragmentShaderSource = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"FragmentShader" ofType:@"fsh"] encoding:NSUTF8StringEncoding error:nil];
const char *fragmentShaderSourceCString = [fragmentShaderSource cStringUsingEncoding:NSUTF8StringEncoding];

// Create and compile fragment shader
GLuint _fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(_fragmentShader, 1, &fragmentShaderSourceCString, NULL);
glCompileShader(_fragmentShader);

// Create and link program
GLuint program = glCreateProgram();
glAttachShader(program, _vertexShader);
glAttachShader(program, _fragmentShader);
glLinkProgram(program);

// Use program
glUseProgram(program);

// Define geometry
GLfloat square[] = {
    -0.5, -0.5,
    0.5, -0.5,
    -0.5, 0.5,
    0.5, 0.5};

//Send geometry to vertex shader
const char *aPositionCString = [@"a_position" cStringUsingEncoding:NSUTF8StringEncoding];
GLuint aPosition = glGetAttribLocation(program, aPositionCString);
glVertexAttribPointer(aPosition, 2, GL_FLOAT, GL_FALSE, 0, square);
glEnableVertexAttribArray(aPosition);

// Draw
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

// Present renderbuffer
[context presentRenderbuffer:GL_RENDERBUFFER];
}

So much code and nothing happens... I can draw relentlessly wherever I want without a single stencil test stopping me.

如此多的代码并没有任何反应......无论我想要什么,我都可以无情地画出来,而不需要单一的模板测试来阻止我。

What can I do? How do I check if the stencil buffer has something drawn inside it? If there's a missing puzzle for any of you, I will happily share any other parts of the code.

我能做什么?如何检查模板缓冲区是否有内部绘制的东西?如果你们中的任何人都缺少一个谜题,我将很乐意分享代码的任何其他部分。

Any help is greatly appreciated! This has been torturing me for a while now. I will be forever in your debt!

任何帮助是极大的赞赏!这一直困扰着我一段时间。我会永远欠你的债!

UPDATE

UPDATE

I got the contour thing to work but I didn't use the stencil buffer. I created masks for every drawing area and textures for each mask which I loaded in the fragment shader along with the brush texture. When I tap on an area, I iterate through the array of masks and see which one was selected and bind the mask texture. I will make another post on SO with a more appropriate title and explain it there.

我得到了轮廓的东西,但我没有使用模板缓冲区。我为每个绘图区域创建了蒙版,并为每个蒙版创建了纹理,我在片段着色器中加载了画笔纹理。当我点击一个区域时,我遍历掩码数组并查看选择了哪一个并绑定了掩码纹理。我将在SO上发表另一篇更合适的标题并在那里解释。

1 个解决方案

#1


2  

The way you allocate the renderbuffer storage looks problematic:

分配renderbuffer存储的方式看起来有问题:

[context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];

The documentation says about this method:

文档说明了这种方法:

The width, height, and internal color buffer format are derived from the characteristics of the drawable object.

宽度,高度和内部颜色缓冲区格式是从可绘制对象的特征派生的。

The way I understand it, since your "drawable object" will normally be a color buffer, this will create a color renderbuffer. But you need a renderbuffer with stencil format in your case. I'm not sure if there's a way to do this with a utility method in the context class (the documentation says something about "overriding the internal color buffer format"), but the easiest way is probably to simply call the corresponding OpenGL function directly:

我理解它的方式,因为你的“可绘制对象”通常是一个颜色缓冲区,这将创建一个颜色渲染缓冲区。但是你需要一个带有模板格式的渲染缓冲区。我不确定是否有办法在上下文类中使用实用程序方法(文档说明了“覆盖内部颜色缓冲区格式”),但最简单的方法可能是直接调用相应的OpenGL函数:

glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, width, height);

If you want to use your own FBO for this rendering, you will also need to create a color buffer for it, and attach it to the FBO. Otherwise you're not really producing any rendering output.

如果要将此FBO用于此渲染,则还需要为其创建颜色缓冲区,并将其附加到FBO。否则你实际上并没有产生任何渲染输出。

Instead of creating a new FBO, it might be easier to make sure that the default framebuffer has a stencil buffer, and render to it directly. To do this, you can request a stencil buffer for your GLKView derived view by making this call during setup:

可能更容易确保默认帧缓冲区具有模板缓冲区,并直接渲染它,而不是创建新的FBO。为此,您可以通过在安装期间进行此调用来请求GLKView派生视图的模板缓冲区:

[view setDrawableStencilFormat: GLKViewDrawableStencilFormat8];

#1


2  

The way you allocate the renderbuffer storage looks problematic:

分配renderbuffer存储的方式看起来有问题:

[context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];

The documentation says about this method:

文档说明了这种方法:

The width, height, and internal color buffer format are derived from the characteristics of the drawable object.

宽度,高度和内部颜色缓冲区格式是从可绘制对象的特征派生的。

The way I understand it, since your "drawable object" will normally be a color buffer, this will create a color renderbuffer. But you need a renderbuffer with stencil format in your case. I'm not sure if there's a way to do this with a utility method in the context class (the documentation says something about "overriding the internal color buffer format"), but the easiest way is probably to simply call the corresponding OpenGL function directly:

我理解它的方式,因为你的“可绘制对象”通常是一个颜色缓冲区,这将创建一个颜色渲染缓冲区。但是你需要一个带有模板格式的渲染缓冲区。我不确定是否有办法在上下文类中使用实用程序方法(文档说明了“覆盖内部颜色缓冲区格式”),但最简单的方法可能是直接调用相应的OpenGL函数:

glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, width, height);

If you want to use your own FBO for this rendering, you will also need to create a color buffer for it, and attach it to the FBO. Otherwise you're not really producing any rendering output.

如果要将此FBO用于此渲染,则还需要为其创建颜色缓冲区,并将其附加到FBO。否则你实际上并没有产生任何渲染输出。

Instead of creating a new FBO, it might be easier to make sure that the default framebuffer has a stencil buffer, and render to it directly. To do this, you can request a stencil buffer for your GLKView derived view by making this call during setup:

可能更容易确保默认帧缓冲区具有模板缓冲区,并直接渲染它,而不是创建新的FBO。为此,您可以通过在安装期间进行此调用来请求GLKView派生视图的模板缓冲区:

[view setDrawableStencilFormat: GLKViewDrawableStencilFormat8];