three.js transparent texture and shader material - javascript

I'm using a circle texture to render particles:
The circle has transparent pixels. I'm using ShaderMaterial and BufferGeometry to provide custom size, color for each node. However I'm stuck with proper z-index rendering. In the image below:
The white particle is the nearest to the camera, the cyan (0x00ffff) is the second, and hibiscus color on the top right (0xC3206F looks pinkish) is the farthest.
As you can see the order is not correct. Ideally white circle should fully override cyan, and partially cover hibiscus. If I set depthTest: true for ShaderMaterial, the transparent regions of the texture become solid:
Here is the full source code: http://jsbin.com/mikimifipi/edit?js,output
I'm probably missing something with blending or messed up with the shaders. Can you please help?

The particles are rendered in the order specified by BufferGeometry.
If depthTest = true, the material is not becoming solid, as you claim -- the particles behind it (and rendered later) are simply not being rendered at all.
You can improve some artifacts by setting if ( tColor.a < 0.5 ) discard;.
You likely should not be premultiplying your fragment shader output RGB by alpha. That is not the correct thing to do when using the default alpha-blending in three.js. It is also is what is causing the ring to appear around the white disk.
three.js r.71

Related

Three.js - Clip plane if object intersects?

I'm using a PlaneGeometry as water, and have added a ship (gltf model) on it. Problem is, when the boat slightly rests into the water, the water is shown inside the boat, even though the boat is afloat. Is there a way to clip the water when a boat (or other models/objects) intersect with it with motion?
Every material in Three.js has an AlphaMap property that you can use to change the opacity of the mesh. So you could draw a black rectangle where the boat is to "cut out" that part of the water plane with an opacity of 0.
I presume your boat is going to be moving, so your texture would also need to move this black rectangle. To solve this, you could use an HTML <canvas> element to draw and move the black rectangle. Then you could use THREE.CanvasTexture to turn the <canvas> into a texture for your plane's alpha.
const drawingCanvas = document.getElementById( 'drawing-canvas' );
const canvasTexture = new THREE.CanvasTexture( drawingCanvas );
const waterMaterial = new THREE.MeshBasicMaterial({
transparent: true,
alphaMap: squareTexture
});
See this working demo for how to use a 2D canvas as a texture in your 3D scene. When you draw on the top-left square, you'll see it being applied to the cube. You could copy this approach, but instead of assigning it to material.map, you'd use it on material.alphaMap.
You could check for the position of the hull vertices of the ship each frame and, given enough points on the PlaneGeometry, you could displace all vertices of the surface inside the ship hull to the y coordinate of the nearest hull vertex. This could probably also be done by a custom shader, which is probably a more efficient solution.
#Marquizzo's idea with the alpha channel is probably a better solution, too, since you don't really want to simulate the displacement of the water, as I assume, but simply get rid of the display of water inside the ship. In this case, I would place an orthographic camera above the ship, set the near and far clipping planes as close as possible to the plane, and use the alpha channel or rather CanvasTexture inside the alpha channel as rendertarget. This way, you'll get a real time alpha map that also reacts to rolling, pitching and heave of the ship.
Effect of floating is made with sin function, applied to y-coordinate. You can use the same principle to make gltf model(ship) move on any coordinate axis.
Example:
position.y = Math.sin(ship.userData.initFloating + t) * 0.15;

Three.js - Simple custom texture box looks different on the sides than on the up / down faces. Why it happens and how to match them

In three.js when using a single texture for a box the side faces look different than the top and bottom faces.
Why does this happen? and how can I make the textures of the 6 faces look similar?
The following is the part of the code related to texture
var corkTexture = new THREE.ImageUtils.loadTexture( 'img/cork-256.png' );
corkTexture.wrapS = corkTexture.wrapT = THREE.MirroredRepeatWrapping;
corkTexture.repeat.set( 10, 10 );
corkTexture.offset.set(0.5, 0.5);
var corkMaterial = new THREE.MeshBasicMaterial( { map: corkTexture } );
In this url you can see the complete code and the result
http://heyplay.org/animation/index.html
Thanks.
Using a default BoxGeometry, your texture will be transformed to fit across the whole surface. This means that for a square texture, a square surface will look correct, but a short surface will have a squished texture, and a tall surface will have a stretched texture.
Consider you have a box that is 10w×10h×10d. If you apply a square texture to the box, all sides will look the same. But if your box is 10w×2h×10d, then the top and bottom will look correct, but the short sides will have a squished texture, like the problem you're seeing.
You can get the sides of a short box to look similar (and it helps that you have a repeating texture) by adjusting the UV values for either the top or bottom vertices of all four side faces.
Take a look at Geometry.faceVertexUvs. This is where the UV information is stored. Identify which vertices are the ones you want to update, and then change the values of the associated UVs. (Deciding what values to use is beyond the scope of this question, but I recommend doing so based on the aspect ratio of the face vs. the texture.)

How to make a 'Flash' effect over a Sprite in Phaser.js?

I'm creating an Rpg in Phaser, and I'm trying to make a Flash effect happen over a Sprite -that means turning the Sprite all white for a moment and then returning to its original color-.
So my question is: what's the best way of achieving this effect?. I've tried two solutions so far, but i'm missing something:
Solution 1:
I tried tweening the tint parameter of the sprite, like this:
this.game.add.tween(enemy).to({
tint: 0xffffff,
}, 100, Phaser.Easing.Exponential.Out, true, 0, 0, true);
But it doesn't work since setting the tint to 0xffffff is the same as setting it to its default color.
Solution 2:
My second possible solution is adding a white square that has the same size of the sprite, and using the actual sprite as a mask for the square:
var flash = this.game.add.graphics(0, 0);
flash.beginFill(0xffffff);
flash.drawRect(enemy.x, enemy.y, enemy.width, enemy.height);
flash.endFill();
flash.mask = enemy // enemy is my Sprite
/* .. code for tweening the flash */
The problem with this solution is that the mask needs to be a PIXI.Graphics object; and I'm using a Sprite object.
So please, any guidance would be appreciated.
In the version of Pixi that Phaser 2.2.2 uses there is a 'tintCache' which basically rounds the tint value, then caches the result. This means you can't do subtle tint ramping like you're trying to do with a tween. We removed this in Phaser 2.3, so it will be available from then, but as of now it's still in dev.
Also you can tint to a 'near white' colour - only 0xffffff precisely resets the tint. But a value very close to that would still be set ok and probably have the desired result.
If you're using WebGL I would still use a tint with 'as near as white as possible' colour values and tween them. You could disable the tint cache for yourself by copying that part of the changed code from the Phaser dev branch.
In Canvas mode it's expensive though as it has to recalculate the pixels every single time you update it.
If you need to worry about Canvas performance then honestly I would create a new PNG that matches your sprite, colour it in all-white and display it over the top of your main sprite as needed and alpha it out. It's less than ideal because of the extra assets required, but it would be the fastest in canvas mode for sure. All depends on your game though is that's acceptable or not.
Edit: Also occurred to me that you could probably achieve what you need by using a blend mode too, such as lighten. You'd duplicate your main sprite, set the blend mode on it, display it over the top of your sprite and fade it out. This would work fine in Canvas at least.
You can use a ColorMatrixFilter on the Sprite. In Phaser, you may have to manually load in the PIXI script first:
game.load.script('filter', 'js/filters/ColorMatrixFilter.js');
Use this for white:
var filter = new PIXI.ColorMatrixFilter();
filter.matrix = [1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,1];
this.game.filter = [filter];
You can also tween the matrix values if you want a smooth transition.

apply gradient based on 3d vector in css

I have this javascript (and the markup and css is in this fiddle)
$('input[type=range]').change(function (e) {
$('.box').css('transform','rotateY('+($(this).val()/100)*360+'deg) rotateX(20deg)');
});
So basically I am trying to light the cube based on some directional light coming from certain direction.
Lets say directional light is coming from (x,y,z)... (100,100,100) to (0,0,0).
Then the gradient need to be darker based on its vector alignment from on side and lighter or same depending on the other side. It's all doable but I can't wrap my mind around it as to get transformation data comes in 3dmatrix which is beyond me.
/* .front { background: linear-gradient(to bottom, blue, rgba(220,30,30,0));} */
pseudo code may look like this:
get div's base color.
get cube 3d vector based on its transformation and its parent if any.
calculate what angle of gradient should be.
calculate what intensity of base color should be on each side..better yet add second div inside and apply transparent gradient over it which will enable for even pictures and stuff easily.
apply that
etc

JavaScript canvas clearRect leaves borders when using floating point coordinates

I am using clearRect on a HTML5 canvas to redraw a rectangle. When using floating point coordinates the clearRect leaves a border from my rectangle on the canvas.
The following code demonstrates the problem with the rectangle using integer coordinates being fully cleared while the one using floating point leaves a border.
<html>
<head>
</head>
<body>
<script type="text/javascript" >
var canvas = document.createElement("canvas");
canvas.width = 100;
canvas.height = 100;
canvas.style.border = "1px solid";
document.body.appendChild(canvas);
var ctx = canvas.getContext("2d");
ctx.fillRect(20.1,20.1,30,30);
ctx.clearRect(20.1,20.1,30,30);
ctx.fillRect(50,50,30,30);
ctx.clearRect(50,50,30,30);
</script>
</body>
</html>
The resulting canvas looks like this:
I can fix this by clearing a larger region, but that increases the risk of clearing and having to redraw adjacent shapes. This is for example suggested here: I can't completely clear the transformed rectangle in <canvas>
I can fix it by using integer coordinates, but that is not an option in this application.
Are there other ways to make clearRect actually clear all of the drawn rectangle without clearing a larger region or using integer coordinates?
All points in canvas are in fact centered in their middle coordinates (0.5, 0.5).
If you want to draw a black line one pixel thick, you'll have to draw it with centered coordinates.
If you draw it on an integer boundary, you'll in fact draw a two pixel thick lines both with lower opacity, leading to a thicker line drawn in dark gray instead of black :
Here's a picture showing this, zoomed 3 times :
More generally, any coordinates off the 0.5 boundary will be drawn with an opacity proportional to its distance to mid point.
Here's a set of horizontal line segments starting on an integer boundary, then shifted 1/10th of a pixel every 20 pixels :
zoomed 4 times :
We can see that we really have a 1 pixel line only when centered.
For your issue, there's no way you 'partially' clear a pixel : pixel is the ultimate unit here, so since colors have already been mixed, you can only either clear whole pixel, or just attenuate its intensity (which is the result you see).
I can think of two solutions :
rather than clearing, redraw everything except what you don't want any more. For this you have to handle some kind of scene graph, meaning : you need to have a collection of all the objects that needs drawing (held within an array for instance), and at draw time, you erase everything, redraw everything except the rectangle.
handle a bigger canvas behind the scene, that will have a higher resolution than the user canvas. This is were you draw, with better quality, and after drawing you copy it to the low-resolution user canvas.
Draw on 0.5 boundaries with integer sizes (width/height of your rect for instance). This main canvas might be 4 or 8 times bigger. The maximum size of the canvas is limited, so watch out for this if you target all browsers, they do not all allow the same max size (below 6400X6400 should be fine, but not sure about it). You can handle multiples backstage canvas to go beyond that limit, with a little bit of extra work.
(Rq for solution 2 : be sure to disable image smoothing before copying to avoid artifacts).
(( the fiddle for the drawings is here : jsbin.com/xucuxoxo/1/ ))
Edit : it is a good practice to translate the context from (0.5;0.5) right after you created it. Then you will always draw integer coordinates. This way, you ensure that all, say, 1 pixel thick line will actually be drawn one pixel thick. Test rounding with floor or ceil, and choose the one you prefer.
Html canvas always applies anti-aliasing to "cure the jaggies".
Anti-aliasing visually smooths lines by adding semi-transparent pixels along the line so the eye is fooled into seeing a less-jagged line.
When you draw your rectangles, these semi-transparent pixels are automatically being applied outside the 30,30 area of your rectangles.
This means your 30x30 rectangle is actually slightly larger than 30x30.
When you do context.clearRect the browser does not clear those extra semi-transparent pixels.
That's why the uncleared pixels appear "ghostly" -- they are semi-transparent.
Unfortunately, there is no way currently to turn off anti-aliasing for html canvas primitive drawing (lines, etc).
You have discovered the 2 fastest solutions:
round pixel drawing coordinates to integers
clear an area slightly larger than the original drawing
You can draw without anti-aliasing by drawing pixels manually using getImageData/putImageData. This manual method works but is costly to performance. The decreased performance defeats the purpose of clearing just the drawn area.
Bottom line: You've already discovered the best solutions canvas currently has to offer :-(

Categories