Trailing fade to complete black with PIXI.js and WebGLRenderer - javascript

I have application made using PIXI.js and it uses a WebGLRenderer.
I'm preserving the drawing buffer and not clearing before render:
{ preserveDrawingBuffer: true, clearBeforeRender: false }
This allows me to create trails as objects move around.
I want the trails to fade out over time, so I apply a transparent black rectangle on top over every rendering. This works, but the fade out eventually rounds off to gray. I want a complete fade to black.
I've tried using a ColorMatrixFilter filter with a lowered brightness on my root container, hoping it would cause a fade effect. It didn't cause any fade effect, instead just causing everything to be slightly darker. If that had worked, then a custom filter could help to do the job.
How can I get a slow gradual fade to complete black for the trails of my rendered objects?
EDIT: Here are a few examples of what I've tried:
// `this` being my app object.
this.fadeGraphics = new PIXI.Graphics()
this.root.addChild(this.fadeGraphics)
// Blend Mode
this.fadeGraphics.blendMode = PIXI.BLEND_MODES.MULTIPLY
this.fadeGraphics.beginFill(0xf0f0f0)
this.fadeGraphics.drawRect(0, 0, this.screenWidth, this.screenHeight)
this.fadeGraphics.endFill()
// Transparent black rectangle.
this.fadeGraphics.beginFill(0x000000, .05)
this.fadeGraphics.drawRect(0, 0, this.screenWidth, this.screenHeight)
this.fadeGraphics.endFill()
Both these methods leave me with a gray trail, the trail goes away if my values are strong enough. Though, I want a very long-term trail so I have to use small values, and possibly also apply them every nth frame.
I think a SUBTRACT blend mode might be able to do what I need.
Unfortunately it doesn't seem available in Pixi.js.

I eventually figured out that fading to white works wonderfully.
Thus, one solution is to fade to white, then invert color, and rotate hue 180 degrees.
You can do this with a CSS filter on your canvas, though it doesn't work in all browsers, has a performance hit, and all your color intensities get inverted.

Related

Three.js: Panorama Cube to Zoom In and Transition Into a Different Panorama Cube

I am new to Three.js. I am using this example with 6 image cube for panorama effect, where one can pan, zoom in and out around cubes.
https://threejs.org/examples/?q=panorama#webgl_panorama_equirectangular
I want to figure out how, at maximum zoom-in level, I can transition user into a different panorama cube (with different image source), mapped to this particular cube part. So I would, sort of, open the next scene to take user further to the next level in his journey.
This is nearly what Google Street View does when you click on arrows to move forward down the road.
I do not see many examples out there. I researched and saw this may be possible with creating 2 scenes? Any ideas how to make it functional I would appreciate.
Detecting WHEN to transition:
In the example given, the mouse events are all given. The zoom is handled in onDocumentMouseWheel by adjusting the camera's fov property. "Zoom In" reduces the fov, and "Zoom Out" increases it. It would be trivial to detect when the fov has reached a minimum/maximum value, which would trigger your transition to a new scene.
Detecting WHERE to transition:
The next step is determining into which new scene you will transition. You could do something hotspot-like, where you shoot a ray from the camera to see if it hit a particular place (for example a THREE.Sphere which you have strategically positioned). But for simplicity, let's assume you only have the 6 directions you mentioned, and that you're still using the example's mouse control.
Camera movement is handled in onDocumentMouseMove by updating the lat and lon variables (which appear to be in degrees). (Note: It seems lon increases without bounds, so for clarity it might be good to give it a reset value so it can only ever be between 0.0-359.99 or something.) You can get all math-y to check the corners better, or you could simply check your 45's:
if(lat > 45){
// you're looking up
}
else if(lat < -45){
// you're looking down
}
else{
// you're looking at a side, check "lon" instead
}
Your look direction determines to which scene you will transition, should you encounter your maximum zoom.
Transitioning
There are lots of ways you can do this. You could simply replace the texture on the cube that makes up the panorama. You could swap in a totally different THREE.Scene. You could reset the camera--or not. You could play with the lights dimming out/in while the transition happens. You could apply some post-processing to obscure the transition effect. This part is all style, and it's all up to you.
Addressing #Marquizzo's concern:
The lighting is simply a suggestion for a transition. The example doesn't use a light source because the material is a MeshBasicMaterial (doesn't require lighting). The example also doesn't use scene.background, but applies the texture to an inverted sphere. There are other methods one can use if you simply can't affect the "brightness" of the texture (such as CSS transitions).
I added the following code the the example to make it fade in and out, just as an example.
// These are in the global scope, defined just before the call to init();
// I moved "mesh" to the global scope to access its material during the animation loop.
var mesh = null,
colorChange = -0.01;
// This code is inside the "update" function, just before the call to renderer.render(...);
// It causes the color of the material to vary between white/black, giving the fading effect.
mesh.material.color.addScalar(colorChange);
if(mesh.material.color.r + colorChange < 0 || mesh.material.color.r + colorChange > 1){ // not going full epsilon checking for an example...
colorChange = -colorChange;
}
One could even affect the opacity value of the material to make one sphere fade away, and another sphere fade into place.
My main point is that the transition can be accomplished in a variety of ways, and that it's up to #Vad to decide what kind of effect to use.

Javascript & Canvas: Draw and delete lines to create a "breathing" circle

I would like to create an element, that shows a red circle. Once the user clicks on it, she can record her voice. In order to show the LIVE mode, I'd like to make the circle "breath" according to the incoming frequencies.
I'm experimenting with a <canvas> element. That means it creates a circle that gets bigger and smaller, depending on the variable arcrad. However, the lines are being drawn correctly, but they do not disappear afterwards. I tried to apply .clip() but can't get it to work...
if (arcrad <= 10) arcrad = 10;
analyserContext.beginPath();
analyserContext.arc(100,120,arcrad,0,2*Math.PI);
analyserContext.closePath();
analyserContext.lineWidth = 2;
analyserContext.strokeStyle = 'red';
analyserContext.stroke();
Any ideas - or completely different strategies for this use case?
Canvas will overdraw by default. For your animation you’ll need to clean the canvas at the start of each frame. Use something the following at the start of your drawing function:
analyserContext.clearRect(0,0,200,200);
assuming your canvas is 200 pixels wide and high. It’s worth pointing out that sometimes you don’t want to completely clear the animation field every frame. For example, if you were to draw a semi transparent rectangle over the frame at the beginning (instead of clearing it) then you’d end up with a basic ‘bullet time’ style effect.
It's a normal behavior. Once something it's drawn on the canvas, it's there forever. You have to think like if you were painting something: what has been done cannot be undone.
Luckily, you still have solutions:
1) redraw another circle on top of the first one with the background color. It's really not the recommend way, but it still can be useful
2) use clearRect method (see How to clear the canvas for redrawing)
There are numerous ways to clear a canvas pre drawing to create animation:
How to clear the canvas for redrawing
simplest in my mind:
canvas.width=canvas.width;
though can equally use clearRect (which is actually quicker and won't reset the entire canvas if that is an issue regarding transforms etc!) over the region or whole canvas.
Get the likes of:
http://jsfiddle.net/dw17jxee/

canvas clearrect, with alpha

So I know that context.clearRect makes pixels transparent, but I'm wondering, is there a function to make pixels translucent?
For example, say I have a canvas with these colors (fourth one in each color is alpha):
#ffff #feef #abff
#5f6f #000f #ffff
Running clearRect would resolve into this (or something, just make them all transparent):
#fff0 #fee0 #abf0
#5f60 #0000 #fff0
I want to remove opacity, but not make it transparent (kind of like globalAlpha for clearRect), so that it can end up like this (lets say I set the globalAlpha equivalent to 0.5):
#fff8 #fee8 #abf8
#5f68 #0008 #fff8
Is this possible? Or would it be simpler just to draw everything on an off-screen canvas, then draw that canvas (with globalAlpha set) on an on-screen one?
Let me know if this isn't clear in any way.
The answer above gets the job done, however getImageData is super slow and if you have a lot of other stuff going on it will slow down your animation immensely. If you create a second off screen canvas element you can set its global alpha to .9 and shuffle them back and forth and get the same effect with much greater speed.
context2.clearRect(0,0,width,height);
context2.globalAlpha = .9;
context2.drawImage(canvas1,0,0);
context1.clearRect(0,0,width,height);
context1.drawImage(canvas2,0,0);
context1.the rest of the drawing that you are doing goes here.
I just tried to figure this out too, and I've decided to count through the pixels, setting the alpha channel of each one manually. This is a bit more work, because I can't just cover the entire canvas with a rect, but it's working so far.
I'm doing this so that I can set a background image for the webpage and put my canvas animation over it, without having to draw the background in the canvas element.
var oldArray = context.getImageData(0,0,canvas.width,canvas.height);
//count through only the alpha pixels
for(var d=3;d<oldArray.data.length;d+=4){
//dim it with some feedback, I'm using .9
oldArray.data[d] = Math.floor(oldArray.data[d]*.9);
}
sw.context.putImageData(oldArray,0,0);

How do the createRadialGradient() and addColorStop() methods work?

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.

html canvas motion blur with transparent background

I just created a fancy canvas effect using cheap motion blur
ctx.fillStyle = "rgba(255,255,255,0.2)";
ctx.fillRect(0,0,canvas.width,canvas.height);
Now i want to do the same, but with transparent background. Is there any way to do something like that? I'm playing with globalAlpha, but this is probably a wrong way.
PS: Google really don't like me today
Here's a more performance friendly way of doing it, it requires an invisible buffer and a visible canvas.
buffer.save();
buffer.globalCompositeOperation = 'copy';
buffer.globalAlpha = 0.2;
buffer.drawImage(screen.canvas, 0, 0, screen.canvas.width, screen.canvas.height);
buffer.restore();
Basically you draw your objs to the buffer, which being invisible is very fast, then draw it to the screen. Then you replace clearing the buffer with copying the last frame onto the buffer using the global alpha, and globalCompositeOperation 'copy' to make the buffer into a semi-transparent version of the previous frame.
You can create an effect like this by using globalAlpha and two different canvas objects: one for the foreground, and one for the background. For example, with the following canvas elements:
<canvas id="bg" width="256" height="256"></canvas>
<canvas id="fg" width="256" height="256"></canvas>
You could copy draw both a background texture and a motion blurred copied of foreground like so:
bg.globalAlpha = 0.1;
bg.fillStyle = bgPattern;
bg.fillRect(0, 0, bgCanvas.width, bgCanvas.height);
bg.globalAlpha = 0.3;
bg.drawImage(fgCanvas, 0, 0);
Here is a jsFiddle example of this.
OP asked how to do this with an HTML background. Since you can't keep a copy of the background, you have to hold onto copies of previous frames, and draw all of them at various alphas each frame. Nostalgia: the old 3dfx Voodoo 5 video card had a hardware feature called a "t-buffer", which basically let you do this technique with hardware acceleration.
Here is a jsFiddle example of that style. This is nowhere near as performant as the previous method, though.
What you are doing in the example is partially clear the screen with a semi transparent color, but as it is, you will always gonna to "add" to the alpha channel up to 1 (no transparency).
To have this working with transparent canvas (so you can see what lies below) you should subtract the alpha value instead of adding, but I don't know a way to do this with the available tools, except running all the pixels one by one and decrease the alpha value, but this will be really, really slow.
If you are keeping track of the entities on screen you can do this by spawning new entities as the mouse moves and then setting their alpha level in a tween down to zero. Once they reach zero alpha, remove the entity from memory.
This requires multiple drawing and will slow down rendering if you crank it up too much. Obviously the two-canvas approach is the simplest and cheapest from a render performance perspective but it doesn't allow you to control other features like making the "particles" move erratically or apply physics to them!

Categories

Resources