I'm looking for a way to mask my entire viewport using a texture. In my case I would like to use a checkered black and white pattern (or any 2 colors) and only show the parts that are black on the scene.
Would the best way to do this be with a cliping mask, a fragment shaders, or an alpha blending. I've seen on SO this post: How to create Stencil buffer with texture (Image) in OpenGL-ES 2.0 which seems similar to what I need, but I don't completely understand what to do with the discard keyword. Would it apply to my situation.
Let's assume you have a checkered texture of black and white squares. First, you'll want to setup the stencil test to draw the mask:
glEnable(GL_STENCIL_TEST);
glDisable(GL_DEPTH_TEST);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
glDepthMask(GL_FALSE);
glStencilFunc(GL_ALWAYS, 1, -1);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
Next draw the checkered texture. The key step here (where discard comes in), is that you set up your fragment shader to only draw fragments where the checkerboard is black (or flip it for white if you prefer). The discard keyword skips rendering of any pixels that don't match your criteria.
//in the fragment shader
if(sampleColor.r > 0.5) { discard; }
After this rendering step you will have a stencil buffer with a checkerboard image where half of the buffer has a stencil value of 0 and the other half has a stencil value of 1.
Then render normally with stencil test enabled to pass when the stencil buffer is == 1.
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glStencilFunc(GL_EQUAL, 1, -1);
Related
I'm implementing a paint app by using OpenGL/GLSL.
There is a feature where a user draws a "mask" by using brush with a pattern image, meantime the background changes according to the brush position. Take a look at the video to understand: video
I used CALayer's mask (iOS stuff) to achieve this effect (on the video). But this implementation is very costly, fps is pretty low. So I decided to use OpenGL for that.
For OpenGL implementation, I use the Stencil buffer for masking, i.e.:
glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_ALWAYS, 1, 0);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
// Draw mask (brush pattern)
glStencilFunc(GL_EQUAL, 1, 255);
// Draw gradient background
// Display the buffer
glBindRenderbuffer(GL_RENDERBUFFER, viewRenderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER];
The problem: Stencil buffer doesn't work with alpha, that's why I can't use semi-transparent patterns for the brushes.
The question: How can I achieve that effect from video by using OpenGL/GLSL but without Stencil buffer?
Since your background is already generated (from comments) then you can simply use 2 textures in the shader to draw a each of the segments. You will need to redraw all of them until user lifts up his finger though.
So assume you have a texture that has a white footprint on it with alpha channel footprintTextureID and a background texture "backgroundTextureID". You need to bind both of a textures using activeTexture 1 and 2 and pass the 2 as uniforms in the shader.
Now in your vertex shader you will need to generate the relative texture coordinates from the position. There should be a line similar to gl_Position = computedPosition; so you need to add another varying value:
backgroundTextureCoordinates = vec2((computedPosition.x+1.0)*0.5, (computedPosition.y+1.0)*0.5);
or if you need to flip vertically
backgroundTextureCoordinates = vec2((computedPosition.x+1.0)*0.5, (-computedPosition.y+1.0)*0.5):
(The reason for this equation is that the output vertices are in interval [-1,1] but the textures use [0,1]: [-1,1]+1 = [0,2] then [0,2]*0.5 = [0,1]).
Ok so assuming you bound all of these correctly you now only need to multiply the colors in fragment shader to get the blended color:
uniform sampler2D footprintTexture;
varying lowp vec2 footprintTextureCoordinate;
uniform sampler2D backgroundTexture;
varying lowp vec2 backgroundTextureCoordinates;
void main() {
lowp vec4 footprintColor = texture2D(footprintTexture, footprintTextureCoordinate);
lowp vec4 backgroundColor = texture2D(backgroundTexture, backgroundTextureCoordinates);
gl_FragColor = footprintColor*backgroundColor;
}
If you wanted you could multiply with alpha value from the footprint but that only loses the flexibility. Until the footprint texture is white it makes no difference so it is your choice.
Stencil is a boolean on/off test, so as you say it can't cope with alpha.
The only GL technique which works with alpha is the blending, but due to the color change between frames you can't simply flatten this into a single layer in a single pass.
To my mind it sounds like you need to maintain multiple independent layers in off-screen buffers, and then blend them together per frame to form what is shown on screen. This gives you complete independence for how you update each layer per frame.
I am trying to draw a texture in OpenGL ES 2.0 using GL_POINTS by applying a stencil buffer. The stencil buffer should come from a texture. I am rendering the results to another texture and then presenting the texture to screen. Here is my code for rendering to texture:
//Initialize buffers, initialize texture, bind frameBuffer
.....
glClearStencil(0);
glClear (GL_STENCIL_BUFFER_BIT);
glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_ALWAYS, 1, 1);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
glBindTexture(GL_TEXTURE_2D, stencil);
glUseProgram(program[PROGRAM_POINT].id);
glDrawArrays(GL_POINTS, 0, (int)vertexCount);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glStencilFunc(GL_NEVER, 0, 1);
glStencilOp(GL_REPLACE, GL_KEEP, GL_KEEP);
glBindTexture(GL_TEXTURE_2D, texture);
glUseProgram(program[PROGRAM_POINT].id);
glDrawArrays(GL_POINTS, 0, (int)vertexCount);
glDisable(GL_STENCIL_TEST);
....
//Render texture to screen
The result I am getting is just my texture being drawn without any masking applied from the stencil. I had a few questions regarding this issue:
Is is possible to use a stencil buffer with GL_POINTS?
Is is possible to use a stencil buffer when rendering to a texture?
Does the stencil texture have to have any special properties (solid colour, internal format...etc)?
Are there any apparent mistakes with my code?
This is the result I am looking for:
UPDATE:
My problem, as pointed out by the selected answer, was primarily that I did not attach the stencil to the stencil attachment of the FBO:
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
GL_RENDERBUFFER, stencilBufferId);
I did not know that it was required when rendering to a texture. Secondly I was not using the proper stencil test.
glStencilFunc(GL_EQUAL, 1, 1);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
Did the job.
Addressing your questions in order:
Is is possible to use a stencil buffer with GL_POINTS?
Yes. The stencil test is applied to all fragments, no matter of the primitive type rendered. The only case where you write to the framebuffer without applying the stencil test is with glClear().
Is is possible to use a stencil buffer when rendering to a texture?
Yes. However, when you render to a texture using an FBO, the stencil buffer of your default framebuffer will not be used. You have to create a stencil renderbuffer, and attach it to the stencil attachment of the FBO:
GLuint stencilBufferId = 0;
glGenRenderbuffers(1, &stencilBufferId);
glBindRenderbuffer(GL_RENDERBUFFER, stencilBufferId);
glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, width, height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
GL_RENDERBUFFER, stencilBufferId);
Does the stencil texture have to have any special properties (solid colour, internal format...etc)?
OpenGL ES 2.0 does not have stencil textures. You have to use a renderbuffer as the stencil attachment, as shown in the code fragment above. GL_STENCIL_INDEX8 is the only format supported for renderbuffers that can be used as stencil attachment. ES 3.0 supports depth/stencil textures.
Are there any apparent mistakes with my code?
Maybe. One thing that looks slightly odd is that you never really apply a stencil test in the code that is shown. While you do enable the stencil test, you only use GL_ALWAYS and GL_NEVER for the stencil function. As the names suggest, these functions either always or never pass the stencil test. So you don't let fragments pass/fail depending on the stencil value. I would have expected something like this before the second draw call:
glStencilFunc(GL_EQUAL, 1, 1);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
This would only render the fragments where the current stencil buffer value is 1, which corresponds to the fragments you rendered with the previous draw call.
If you want to achieve a blending of textures with transparency (like PNG) that is similar to UIKit, how do you configure OpenGL ES 1.1 appropriately?
I found:
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_ALPHA_TEST);
But there is also:
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE);
You should use glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);. The other one has no respect for the alpha channel at all in this case, it will simply sum up the source and destination colours for you.
There is one thing you should note though. This will work great on colours but your destination alpha channel will not be correct. In most cases you do not use it but if you wish to extract the image from buffer with the alpha channel as well you will need it, same goes for using FBO and reuse texture with transparency. In this case you should draw the alpha channel separately using glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_ONE). This means doubling the draw calls (at least I can't think of a better solution without shaders). To draw to colour only you have to set glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE) and to draw alpha only use glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE)
To sum up:
glEnable(GL_BLEND);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
//draw the scene
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE);
glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_ONE);
//draw the scene
glDisable(GL_ALPHA_TEST);
glDisable(GL_BLEND);
In a paint app I am developing, I want my user to be able to draw with a transparent brush, for example black paint over white background should result in grey colour. When more paint is applied, the resulting colour will be closer to black.
However no matter how many times I draw over the place, the resulting colour never turnts black; in fact it stops changing after a few lines. Photoshop says that the alpha of the blob drawn on the left in OpenGL is max 0.8, where I expect it to be 1.
My app works by drawing series of stamps as in Apple's GLPaint sample to form a line. The stamps are blended with the following function:
glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_ONE);
glBlendEquation(GL_FUNC_ADD);
My fragment shader:
uniform lowp sampler2D u_texture;
varying highp vec4 f_color;
void main(){
gl_FragColor = texture2D(u_texture, gl_PointCoord).aaaa*f_color*vec4(f_color.aaa, 1);
}
How should I configure the blending in order to get full colour when drawing repeatedly?
Update 07/11/2013
Perhaps I should also note that I first draw to a texture, and then draw the texture onscreen. The texture is generated using the following code:
glGenFramebuffers(1, &textureFramebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, textureFramebuffer);
glGenTextures(1, &drawingTexture);
glBindTexture(GL_TEXTURE_2D, drawingTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pixelWidth, pixelHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
Update 02/12/2013
I tried modifying Apple's GLPaint program to and it turned out that this behaviour is observable only on iOS7. As it can be seen on the screenshots bellow, the colours on iOS 7 are a bit pale and don't blend nicely. The
GL_ONE, GL_ONE_MINUS_SRC_ALPHA
blend function does well on iOS6. Can this behaviour be caused by iOS7's implementation of CALAyer or something else and how do I solve it?
Update 10/07/2014
Apple recently updated their GLPaint sample for iOS7 and the issue is observable there, too. I made a separate thread based on their code: Unmodified iOS7 Apple GLPaint example blending issue
Just because your brush does "darken" the image doesn't mean, that this was subtractive blending. This is in face regular additive blending, where the black brush merely overdraws the picture. You want a (GL_ONE, GL_ONE_MINUS_SRC_ALPHA) blending function (nonseparated). The brush is contained only within the alpha channel of the texture, there's no color channels in the texture, the brush color is determined by glColor or an equivalent uniform.
Using the destination alpha value is not required in most cases.
I'm doing a color lookup using a texture to apply an effect to a picture. My lookup is a gradient map using the luminance of the fragment of the first texture, then looking that up on a second texture. The 2nd texture is 256x256 with gradients going horizontally and several different gradients top to bottom. So 32 horizontal stripes each 8 pixels tall. My lookup on the x is the luminance, on the y it's a gradient and I target the center of the stripe to avoid crossover.
My fragment shader looks like this:
lowp vec4 source = texture2D(u_textureSampler, v_fragmentTexCoord0);
float luminance = 1.0 - dot(source.rgb, W);
lowp vec2 texPos;
texPos.x = clamp(luminance, 0.0, 1.0);
// the y value selects which gradient to use by supplying a T value
// this would be more efficient in the vertex shader
texPos.y = clamp(u_value4, 0.0, 1.0);
lowp vec4 newColor1 = texture2D(u_textureSampler2, texPos);
It works good but I was getting distortion in the whitest parts of the whites and the blackest part of the blacks. Basically it looked like it grabbed that newColor from a completely different place on texture2, or possibly was just getting nothing for those fragments. I added the clamps in the shader to try to keep it from getting outside the edge of the lookup texture but that didn't help. Am I not using clamp correctly?
Finally I considered that it might have something to do with my source texture or the way it's loaded. I ended up fixing it by adding:
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
So.. WHY?
It's a little annoying to have to clamp the textures because it means I have to write an exception in my code when I'm loading lookup tables..
If my textPos.x and .y are clamped to 0-1.. how is it pulling a sample beyond the edge?
Also.. do I have to use the above clamp call when creating the texture or can I call it when I'm about to use the texture?
This is correct behavior of texture sampler.
Let me explain this. When you use textures with GL_LINEAR sampling GPU will take an average color of pixel blended with nearby pixels (that's why you don't see pixelation as with GL_NEAREST mode - pixels are blurred instead).
And with GL_REPEAT mode texture coordinates will wrap from 0 to 1 and vice versa, blending with nearby pixels (i.e. in extreme coordinates it will blend with opposite side of texture). GL_CLAMP_TO_EDGE prevents this wrapping behavior, and pixels won't blend with pixels from opposite side of texture.
Hope my explanation is clear.