I have a sprite with alpha channel (first image, it has texture padding, to prevent texture bleeding, thats why it's "thicker"). The second picture is what I see when I render the sprite. The strange thing is the alpha blending works, because the light blue inside the circle is alpha blue, I see it is working. And the orange parts has also alpha colors at the edges (because of anti aliasing).
But how that white edge possible around the blue circle? The blue circle has alpha, I see it is working. I can't comprehend if the alpha blending working (for the alpha blue pixels), how that white edge around the blue circle is possible (that's the alpha orange pixels)?
How can I prevent rendering the white edges around the sprite's blue circle?
gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
If I render the sprite to a HTML canvas works as expected, has alpha blending, no white edges around the circle.
The attached images has no alpha channel, but it's there in my app.
I found the solution.. It seems if I not turning off the alpha at the getContext, alpha not working properly.
const gl = canvas.getContext("webgl2", {
alpha: false,
});
Then I realized this way only works for NEAREST texture filtering, to work for LINEAR filtering and for MIPMAPs you also need:
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
Related
I am trying to draw an opaque shape on top of a colorful background in p5.js. So I give both the fill color and the stroke color an alpha value. The problem is that the outline of the shape is partly drawn onto the shape. Because of the opaque stroke, this makes it look like there is two outlines with different colors. Here is an example:
function setup() {
createCanvas(60, 60);
}
function draw() {
background(255);
fill(127,127);
stroke(50,127);
strokeWeight(5);
rect(10,10,40,40);
}
What I get is this square which appears to have an inner dark gray and an outer light gray bounding box.
Is there an easy way to prevent this from happening? I know I could draw the shape "twice" (first the fill and then the outline a little bit further out), but
I am trying to draw a rather complicated polygon, so I would have to adjust all the vertex coordinates of the outer shape which would be pretty annoying to work out.
I have already tried the different blendModes, but they didn't seem to be able to solve my problem.
The reason this is happening is because you have a stroke weight of 5, which is drawn from the edge of the shape, in both directions. So some of the stroke is on the inside of the shape, and some of the stroke is on the outside.
I don't know of a way to change this setting. I tried playing with strokeCap and strokeJoin but nothing solved the problem.
One option that occurs to me is drawing a scaled-down version of the shape with a stroke weight of 1. That would look something like this:
let shapeGraphics;
function setup() {
createCanvas(260, 260);
// draw a scaled-down version of the shape
shapeGraphics = createGraphics(8, 8);
shapeGraphics.fill(127, 127);
shapeGraphics.stroke(50, 127);
shapeGraphics.rect(0, 0, 8, 8);
}
function draw() {
background(255);
noSmooth();
// draw a scaled-up version of the graphics
image(shapeGraphics, 10, 10, 40, 40);
}
If that doesn't work, then the only other option I can think of is to calculate the outer edges yourself and draw the shape twice, like you mentioned.
I've added on scene1 plane1 with semi transparent texture
I'm rendering this scene1 into renderTarget and than using renderTarget.texture on another plane2 that placed on another scene2.
The problem is that I see that texture's semitransparancy looks like it mixed with black background. See jsfiddle expample.
Use THREE.NoBlending for plane1 material is seems like an answer of my issue but is not an option for me. Because pane1 can overlaps other planes on scene1.
Do anyone know how to avoid such behavior?
You are using the texture of a RenderTarget as the diffuse map for your plane mesh.
Note that RenderTarget.texture was created using THREE.NormalBlending, and as a result, the texture has "premultiplied alpha". That is, the RGB channels in RenderTarget.texture are multiplied by the texture's alpha channel.
Consequently, you need to specify a custom blending function when you use the render target's texture as a map. The appropriate blending function when the source and destination both have premultiplied alpha is:
blending: THREE.CustomBlending,
blendEquation: THREE.AddEquation,
blendSrc: THREE.OneFactor,
blendDst: THREE.OneMinusSrcAlphaFactor,
blendSrcAlpha: THREE.OneFactor,
blendDstAlpha: THREE.OneMinusSrcAlphaFactor,
See "Alpha Blending" in the Wikipedia article Alpha Compositing.
updated fiddle: https://jsfiddle.net/njetLLz7/212/
three.js r.97
Can anybody explain me, preferably with illustrative pictures, how do these methods work? I've looked at different examples and tutorials but can't seem to grasp the idea. I understand, that the createRadialGradient() creates two circles, but how do those two circles relate to each other and the addColorStop() method ?
Yes, I know this is necro'd... but it seems a valid question that was never snawered, so I leave this here in case someone else needs it.
================================================================================
Well, a gradient is a smooth shift from one color to another.
In any gradient, you pick a point where the colors begin, a point where the color ends, and the colors you want, and the color smoothly transitions between them.
The color stops are there to determine what colors will be a part of the gradient, and where in the gradient those colors will appear.
In a linear gradient, the color transitions from one color to the next in a straight line so that bands of color form along the line, perpendicular to the axis.
In a radial gradient, the color wraps itself around a central circle (or point, which is simply a very small circle) and transitions from that center to the edge of the gradient.
This means that the color bands that make up the gradient form into larger and larger circles as they transition from center to edge.
HEREis an example of a simple radial gradient transitioning from white in the center to black at the outside edge.
This is the origin of the syntax for createRadialGradient.
This first circle will be where the color begins, we will arbitrarily state that it starts in the center... lets say that is x:100,y:100
The second circle will be where the color ends, since we picked the center to start it, the color finishes at the outside edge of the circle (although these could just as easily be reversed).
For simplicity's sake, the center point (in this case) will remain the same: x:100,y:100
The real difference between these circles will be the radius. Since the center should be small, we will give it a radius of 1, while the larger outside radius of the circle, we will make 100.
This gives us the required parameters:
x = 100;
y = 100;
radiusStart = 1;
radiusEnd = 100;
var grad = ctx.createRadialGradient(x,y,radiusStart,x,y,radiusEnd);
However, if we try to display this code as is, we won't see anything... this is because we need the color stops.
Color stops are declared with two parameters... the position and the color of the stop.
The position is a number between 0 and 1, and represents the percentage of the distance from start to end.
If we want the color to start at white, then we would use:
grad.addColorStop(0,'#FFFFFF');
This means that we the color stop starts at 0% of the way down the line (meaning right where the gradient begins), and gives the color to paint there as white.
Similarly, the second gradient should be black, and should be placed at the very end of the gradient:
grad.addColorStop(1,'#000000');
Notice that these do not reference context directly... we referenced context to create the gradient, but we are adding stops directly to the gradient that we created.
When we are finished creating the gradient, then we can use this gradient as a fillStyle (or even a strokeStyle) for as long as the gradient that we created remains in scope.
Full code:
x = 100;
y = 100;
radiusStart = 1;
radiusEnd = 100;
var grad = ctx.createRadialGradient(x,y,radiusStart,x,y,radiusEnd);
grad.addColorStop(0,'#FFFFFF');
grad.addColorStop(1,'#000000');
ctx.beginPath();
ctx.arc(x,y,radiusEnd,0,Math.PI*2,false);
ctx.fillStyle = grad;
ctx.fill();
While you are playing with this, don't forget to experiment a bit.
Try adding more than two color stops... this means that instead of transitioning from black to white (boring), you can transition from blue to green to yellow to orange to red to purple.
Just remember to set the positions appropriately... if you have 6 colors, for example (as above), and you want them evenly spaced, then you would set the positions at .2 intervals:
grad.addColorStop(0,'#0000FF');
grad.addColorStop(.2,'#00FF00');
grad.addColorStop(.4,'#FFFF00');
grad.addColorStop(.6,'#FF8800');
grad.addColorStop(.8,'#FF0000');
grad.addColorStop(1,'#AA00AA');
Any color stops you try to place in the same position will overwrite one another.
Another cool effect is to set two different centers for the circles when creating the gradient... this lends a different effect to the gradient, and can be a worthy addition to your arsenal.
HERE are two images from the W3C specification (which itself is HERE). Both of these are radial gradient with different center points for the first and second circles.
A better example is HERE, although the code itself is written in svg for html backgrounds, the examples still show some great ways to use radial gradients with differing centers. He covers the theory of radial gradients as well as shows some very nice examples.
Finally, a tip... while it is quite possible to write gradients by hand, its kind of a pain in the butt. It is usually far easier to grab Photoshop, Illustrator, GIMP, or Inkscape, and build the gradient in one of those... then you can adjust the gradient directly until you like it. Then simply copy the color stop information over to your canvas code.
Hope some of that helps someone.
I am writing a colouring game for small children, where I have a black and white image shown on a canvas initially, and as the user moves the drawing tool (mouse) over the canvas, the black and white surface gets over-painted with the colour information from the corresponding coloured image.
In particular, on every mouse move I need to copy a circular area from the coloured image to my canvas. The edge of the circle should be a little blurry to better immitate the qualities of a real drawing tool.
The question is how to accomplish this?
One way I see is to use a clipping region, but this approach does not let me have blurry edges. Or does it?
So I was thinking about using an alpha mask to do that and copy only pixels that correspond to the pixels in the mask that have non zero alpha. Is it feasible?
My suggestion is to have your drawable canvas in front of the coloured image you wish to reveal. (You could use your coloured image as a CSS background image for the canvas.)
Initially have the canvas containing the black and white image with 100% opacity. Then, when you draw, actually erase the contents of the canvas to show the image behind.
Like this:
var pos_x, pos_y, circle_radius; // initialise these appropriately
context.globalCompositeOperation = 'destination-out';
context.fillStyle = "rgba(0,0,0, 1.0)";
// And "draw" a circle (actually erase it to reveal the background image)
context.beginPath();
context.arc(pos_x, pos_y, circle_radius, 0, Math.PI*2);
context.fill();
I would probably use multiple clipping regions with varying alpha (one dab for each) to mimic the effect you are after. Render the low opacity one first (paste using drawImage) and render the rest after that till you reach alpha=1.0.
Have you considered using radial gradients that go from an opaque color to a fully transparent one?
Here is a demo from Mozilla. The circles are drawn the way you need. - https://developer.mozilla.org/samples/canvas-tutorial/4_10_canvas_radialgradient.html
There's a 3x3 image. The CanvasPixelArray is:
[12,24,48,255,12,24,48,255,12,24,48,255,12,24,48,255,12,24,48,255,12,24,48,255,12,24,48,255,12,24,48,255,12,24,48,255]
I change the alpha of all pixels to 0 and back by:
bobs = this.gfx.getImageData(0,0,3,3).data
for (a=3;a<bobs.data.length;a+=4)
bobs.data[a] = 0
this.gfx.putImageData(bobs,0,0)
bobs = this.gfx.getImageData(0,0,3,3).data
for (a=3;a<bobs.data.length;a+=4)
bobs.data[a] = 255
this.gfx.putImageData(bobs,0,0)
All pixels became black. The browser changed the colors to black to save memory. Is there a way to prevent this or should I save a duplicate?
I think the reason for this is that Canvas uses premultiplied alpha, meaning all rgb values are multiplied by the alpha value for those pixels. It's done to speed up alpha blending with backgrounds, etc.
There's a section on premultiplied alpha in this article: wikipedia:Alpha_compositing
You'll probably have to either, as you say keep a copy of the unmodified values, or perhaps store an alpha value for the image and set the globalAlpha property before drawing the image.
(When I say image, you can equally create a 3x3 canvas, store the pixels there and use drawImage() to draw it to the main canvas).