I'm hoping this is possible to do without using frame buffers or shaders, just by straight up using the glBlendFunc or glBlendFuncSeparate.
I'm rendering my scene normally with my standard blend mode:
glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
Ontop of that scene, I want to draw a texture which is masked by some other texture. These are drawn at arbitrary positions (not necessarily rectangular, not necessarily the same size / position as each other).
The order is; render the masked texture, then the mask texture.
The masked texture is a regular image, with alpha.
The mask texture either black RGBA(0, 0, 0, 255), or transparent RGBA(0, 0, 0, 0)
I want anything that the black does NOT touch, to be invisible. Basically, the final result should be:
RGBA(masked.r, masked.g, masked.b, masked.a * mask.a)
Below are images of the ordering, to explain what I mean. I'm really looking for a solution to avoid having to use a different shader or stick things onto a framebuffer. If it absolutely isn't possible, please let me know.
I'll explain why this isn't possible. Masking with blending requires three passes because it has three parts: the destination, the source, and the mask. No matter what you do, you must blend the source and the mask into a framebuffer and THEN render to destination.
The stencil buffer, however, is built into the default window framebuffer, provided you tell OpenGL to provide for it (like you would a depth buffer), and appears to do exactly what you want. As a GLUT call, it would look like this to initialize the stencil buffer in your window along with the alpha-enabled color and depth buffers, in a double-buffered window:
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_ALPHA | GLUT_DEPTH | GLUT_STENCIL);
The stencil buffer is able to do exactly what you need - you can draw a shape in it, and selectively tell it to either discard or keep pixels inside that shape. Here's an example of how to use it, modified from the the OpenGL Red Book:
GLdouble dRadius = 0.1; // Initial radius of spiral
GLdouble dAngle; // Looping variable
// Use 0 for clear stencil, enable stencil test
glClearStencil(0);
glEnable(GL_STENCIL_TEST);
// Clear stencil buffer
glClear(GL_STENCIL_BUFFER_BIT);
// All drawing commands fail the stencil test, and are not
// drawn, but increment the value in the stencil buffer.
// glStencilFunc takes three arguments: the stencil function, the reference value, and the mask value. Whenever the stencil function is tested (for example GL_LESS), both the reference and the stencil value being tested from the buffer is bitwise ANDed with the mask: GL_LESS returns true if (ref & mask) < (stencil & mask).
glStencilFunc(GL_NEVER, 0x0, 0x0);
// glStencilOp takes three arguments: the stencil operation to call when the stencil test fails, the stencil operation to call when the stencil test passes but the depth test fails, and the stenciloperation to call when the stencil test passes AND the depth test passes (or depth test is disabled or no depth buffer is allocated).
glStencilOp(GL_INCR, GL_INCR, GL_INCR);
// Spiral pattern will create stencil pattern
// Draw the spiral pattern with white lines. We
// make the lines white to demonstrate that the
// stencil function prevents them from being drawn
glColor3f(1.0f, 1.0f, 1.0f);
glBegin(GL_LINE_STRIP);
for(dAngle = 0; dAngle < 400.0; dAngle += 0.1)
{
glVertex2d(dRadius * cos(dAngle), dRadius * sin(dAngle));
dRadius *= 1.002;
}
glEnd();
// Now, allow drawing, except where the stencil pattern is 0x1
// and do not make any further changes to the stencil buffer
glStencilFunc(GL_NOTEQUAL, 0x1, 0x1);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
// Now draw red square
glColor3f(0.0f, 1.0f, 0.0f);
glRectf(0, 0, 200, 200);
The output of this drawing code is a red square, with a spiral across it, starting at (1, 1). The spiral is made up of discarded pixels, and as such will be the same color as the cleared color buffer. If you were to use this code for your purposes, you would draw the square where you wanted your texture to be where the spiral code is written, and then use GL_EQUAL as the stencil function, drawing your masked texture where the red square is drawn. More information on the stencil buffer can be found here.
Related
The question has been updated thanks to the comments.
Screenshot of how textures overlap
To draw 2 points with brush texture using the stencil buffer to avoid textures transparency overlap, the following code is used:
glEnable(GL_STENCIL_TEST.gluint)
glClear(GL_STENCIL_BUFFER_BIT.gluint | GL_DEPTH_BUFFER_BIT.gluint)
glStencilOp(GL_KEEP.gluint, GL_KEEP.gluint, GL_REPLACE.gluint)
glStencilFunc(GL_ALWAYS.gluint, 1, 1)
glStencilMask(1)
glDrawArrays(GL_POINTS.gluint, 0, 1)
glStencilFunc(GL_NOTEQUAL.gluint, 1, 1)
glStencilMask(1)
glDrawArrays(GL_POINTS.gluint, 1, 1)
glDisable(GL_STENCIL_TEST.gluint)
And stencil buffer works, however, each point fill a full rectangle in the stencil buffer, but a texture image has transparency. So maybe texture used in the wrong way?
The texture is loaded like this
glGenTextures(1, &gl_id)
glBindTexture(GL_TEXTURE_2D.gluint, gl_id)
glTexParameteri(GL_TEXTURE_2D.gluint, GL_TEXTURE_MIN_FILTER.gluint, GL_LINEAR)
glTexImage2D(GL_TEXTURE_2D.gluint, 0, GL_RGBA, gl_width.int32, gl_height.int32, 0, GL_RGBA.gluint, GL_UNSIGNED_BYTE.gluint, gl_data)
Blending set as
glEnable(GL_BLEND.gluint)
glBlendFunc(GL_ONE.gluint, GL_ONE_MINUS_SRC_ALPHA.gluint)
Could you please advice where to look in order to fill 1s in stencil buffer by exactly not transparent area of brush image?
I recommend to discard the transparent parts of the texture in the fragment shader. A fragment can be completely skipped in the fragment shader by the discard keyword.
See Fragment Shader - Special operations.
Use a small threshold and discard a fragment, if the alpha channel of the texture color is below the threshold:
vec4 texture_color = .....;
float threshold = 0.01;
if ( texture_color.a < threshold )
discard;
An other possibility would be to use an Alpha test. This would be only available in OpenGL compatibility profile, but not in core profile or OpenGL ES.
See Khronos OpenGL-Refpages glAlphaFunc:
The alpha test discards fragments depending on the outcome of a comparison between an incoming fragment's alpha value and a constant reference value.
With the following alpha test, the fragments whos alpha channel is below the threshold are discarded:
float threshold = 0.01;
glAlphaFunc(GL_GEQUAL, threshold);
glEnable(GL_ALPHA_TEST)
I'm making an app which shows a terrain and some custom pits which should be inserted into the terrain. To insert the pits into the terrain I use stencil testing. It works almost great, but some strange bugs are present.
The code:
glEnable(GL_STENCIL_TEST);
glStencilFuncSeparate(GL_FRONT, GL_ALWAYS, 1, 255);
glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_REPLACE);
glStencilFuncSeparate(GL_BACK, GL_ALWAYS, 1, 255);
glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_KEEP);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glDepthMask(GL_TRUE);
for (Pit3D* pit in pitList) {
[pit render];
}
glStencilFuncSeparate(GL_FRONT, GL_EQUAL, 0, 255);
glStencilOpSeparate(GL_FRONT, GL_REPLACE, GL_KEEP, GL_KEEP);
glStencilFuncSeparate(GL_BACK, GL_EQUAL, 0, 255);
glStencilOpSeparate(GL_BACK, GL_REPLACE, GL_KEEP, GL_KEEP);
[terrain render];
Bug #1:
When I look through a hill to the pits I see some strange shadows or black spots. Although I look through an another hill to the pits I don't see that shadows or spots.
Bug #2:
Some strange pixels are present when the pits are close to the camera.
They definitely belongs to the pits but they shouldn't be visible.
Unfortunately I don't have screenshot at the moment. It will be added later today.
Bug #3
Depends on range and view angle sometimes the pits are covered by the terrain. But when the pits are close to the camera the terrain cover is disappeared from the pits.
I have no idea how to fix these bugs. Please help me. All answers will be upvoted.
UPDATES:
I've recently discovered that the pits are covered by terrain back-face.
To achieve what you want you have either to draw the scene from the back to the front. Note, the back faces of the geometry have to clear the stencil buffer, before the font faces are drawn.
Or you draw the scene twice, using Face Culling.
When you draw the scene first, then cull all the front faces and when you draw it second then cull all the back faces.
Use the separated stencil test for front and back faces in that way, that th back faces clear the stencil buffer.
(see glStencilFuncSeparate and glStencilOpSeparate)
The rendering process works as follows:
Enable the depth test
Disable the color buffer and enable the stencil test for setting the stencil mask
Draw the "holes". This causes that the stencil buffer is set to 1 at the positions of the holes.
Setup the stencil test for clearing the stencil buffer
Enable face culling for front faces
Draw the geometry. This causes that the stencil buffer is cleared at the positions where the "holes" covered from the back faces of the geometry.
Enable face culling for back faces
Enable the color buffer
Draw the geometry
To make tha algorithm work, you have to draw all your primitives int the same winding order. And you have to tell OpenGL the direction
by glFrontFace.
Either clockwise GL_CW or counterclockwise GL_CCW.
glStencilMask(0xFF);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glFrontFace(GL_CCW); // depends on your geometry "GL_CCW" or "GL_CW"
glDisable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS); // default
glColorMask(GL_FALSE,GL_FALSE,GL_FALSE,GL_FALSE);
glEnable(GL_STENCIL_TEST);
glStencilFuncSeparate(GL_FRONT, GL_ALWAYS, 1, 255);
glStencilFuncSeparate(GL_BACK, GL_ALWAYS, 1, 255);
glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_REPLACE);
glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_KEEP);
// draw the holes
// ....
glStencilFuncSeparate(GL_FRONT, GL_EQUAL, 0, 255);
glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_KEEP);
glStencilFuncSeparate(GL_BACK, GL_ALWAYS, 0, 255);
glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_REPLACE);
glEnable(GL_CULL_FACE);
glCullFace(GL_FRONT);
// draw the geometry the 1. time ("draw" back faces)
// ....
glCullFace(GL_BACK);
glColorMask(GL_TRUE,GL_TRUE,GL_TRUE,GL_TRUE);
// draw the geometry the 2. time (draw front faces)
// ....
Is it possible to add image/text on the 2d image so that it gives a real view.For example as present in : http://www.zazzle.com/make_your_own_iphone_5_case-179092402149274498.
These views are orthographic and isometric views and they can be reproduced using affine transformations in canvas, as they are also parallelograms.
First you will need to make masks for the different cases. These needs to be drawn in the same orientation as the case in the "photo". Use solid pixels (any color will do, it won't show in later step) where you want the custom graphics to show, transparent pixels anywhere else (anti-aliased pixels are fine).
Then draw in the mask in the canvas, select composite mode "source-in" to replace non-transparent pixels and finally, select blending mode "multiply" and draw the case "photo" on top to mix in shadows and highlights. The latter step is what will give the illusion of the image having depth.
For the isometric views, calculate the skew angle (or use trial and error if you're not sure if the image is accurate - this is as a rule-of-thumb usually tan(60°), ie. transform(1, 0, Math.tan(60/180*Math.PI), 1, 0, 0)), then do the same process as above. Just remember only apply transformation when drawing the custom image, mask and top layer must be drawn without transformations.
The orthographic side views can be generated using scaling for the x-axis. Depending on which angle, add a stripe for the side of the case.
Example of steps
var img = new Image(),
cust = new Image(),
count = 2,
ctx = document.querySelector("canvas").getContext("2d");
img.onload = cust.onload = comp;
img.src = "http://i.stack.imgur.com/je0Jh.png";
cust.src = "http://i.stack.imgur.com/uRPDt.png";
function comp() {
if (--count) return;
// draw in mask
ctx.drawImage(img, 0, 0);
// comp. mode source-in
ctx.globalCompositeOperation = "source-in";
// draw in custom graphics
ctx.drawImage(cust, 0, 0, ctx.canvas.width, ctx.canvas.height);
// blend mode multiply
ctx.globalCompositeOperation = "multiply";
// draw in original case multiplied (does not work in IE)
ctx.drawImage(img, 0, 0);
}
<canvas with=263 height=505></canvas>
The quality largely depends on the quality of the mask - I made a very quick-n-dirty version here as you can see (your case image can also act as the mask btw).
The steps are the same for the isometric view with the exception of the skew transform. Multiply does not work in IE, you can use alpha here instead or make a separate mask containing only shadows etc.
That being said: remember that this is not the image sent to production. This will just show a representation of the final result. What is used is the image, image position and size. These data is then used to build an unmasked flat print-template which is used to make the phone-case.
Ive been trying to understand / debug a problem to do with the stencil buffer. I probably dont understand how it works versus how I think it works. Essentially I have a scene where I render all my solid objects and assign them a stencil value of 1. I then go through the same scene again, but the second time I render only the transparent objects and give them a stencil value of 2. In the final step I have a screen quad that draws the scene to a texture. I want to use the stencil buffer to only draw the final quad where the stencil bits were set to 1 or 2. So in code:
// First enable the stencil buffer
gl.enable( gl.STENCIL_TEST );
gl.clearStencil( 0 );
gl.clear(gl.STENCIL_BUFFER_BIT); // presumably this clears the buffer to 0?
// ...for the solid meshes
gl.stencilFunc( gl.ALWAYS, 1, 0xffffffff ); // Always pass the stencil
gl.stencilOp( gl.REPLACE, gl.REPLACE, gl.REPLACE ); // Replace the stencil value with ref=1
// ...for the transparent meshes
gl.stencilFunc( gl.ALWAYS, 2, 0xffffffff ); // Always pass the stencil
gl.stencilOp( gl.REPLACE, gl.REPLACE, gl.REPLACE ); // Replace the stencil value with ref=2
// ...for the final screen quad
gl.stencilFunc( gl.GEQUAL, 1, 0xffffffff ); // Only draw the bits higher than 1 (so 1 and 2 should be included)
gl.stencilOp( gl.KEEP, gl.KEEP, gl.KEEP); // Dont touch the stencil values in the buffer
However this never seems to work. From my tests it seems as though the stencil values are set to 0xffffffff before I assign them values. This makes the GEQUAL test pointless as the buffer values are greater than 0.
I created a JS fiddle to demo what I mean. I draw a small square and assign its values to 4. Then draw a bigger square and test for gequal to 5. I would have thought that the bigger square would always fail. However it always passes. When I test using gl.stencilFunc( gl.GEQUAL, 4, 0xffffffff ); I see the correct small square only. But when I test gl.stencilFunc( gl.GEQUAL, 0xffffff, 0xffffffff ); then it also looks correct. This to me says that the stencil buffer is not set to 0?
http://jsfiddle.net/90af0rov/
Im really confused and would appreciate any pointers on this <:(
You are getting the GEQUAL operation the wrong way around in stencilFunc.
See the documentation here: https://www.opengl.org/sdk/docs/man2/xhtml/glStencilFunc.xml
GL_GEQUAL
Passes if ( ref & mask ) >= ( stencil & mask ).
So in your code example, it will only draw when ref (which is 1) is greater equal to the value currently in the stencil buffer. If you change it to:
gl.stencilFunc( gl.LESS, 0, 0xffffffff );
Then it should fix it, as it will now pass if the ref of zero is less than the value in the stencil buffer. You could also use NOTEQUAL instead of LESS in this case.
I've noticed some strange behaviour with glDrawPixels() when using a 0.375 translation. This is my GL initialization:
width = 640; height = 480;
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity( );
glOrtho(0, width, height, 0, 0, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity( );
glTranslatef(0.375, 0.375, 0.0);
Now I want to draw a 640x30 pixel buffer to the very last 30 rows of my GL window. Hence, I do the following:
glRasterPos2i(0, 480);
glDrawPixels(640, 30, GL_RGBA, GL_UNSIGNED_BYTE, pixelbuffer);
Unfortunately, nothing gets drawn using this code. glGetError() also returns 0. The interesting thing is that as soon as I remove the call to glTranslatef(0.375, 0.375, 0.0) everything works fine!
So could somebody explain to me why this 0.375 translation on both axes confuses glDrawPixels()? Is this somehow rounded to 1.0 internally making my call to glDrawPixels() suddenly want to draw beyond the context's boundaries and thus it gets clipped by OpenGL? This is the only explanation I can think of but I don't understand why OpenGL should round a 0.375 translation to 1.0... it should be rounded down to 0.0 instead, shouldn't it?
The point (0,480) actually straddles one of your clipping planes given your projection matrix. Your sub-pixel shift hack pushes the point beyond the breaking point and the raster position is clipped. In GL, glRasterPos (...) will invalidate all following raster operations as long as the initial position is clipped (which in this case, it is).
You could try glRasterPos2i (0, 479). This is altogether more meaningful given the dimensions of your window anyway. You could also drop the whole charade and use glWindowPos2i (...) instead of relying on your projection and modelview matrices to position the raster coordinate in window-space.
I can't answer your question on why glTranslatef stops glDrawPixels from working, but I can tell you that isn't the way to select where to draw. Check the man page for glDrawPixels for a bit more info. It will tell you about glRasterPos and glWindowPos