Create very soft shadows in three.js? - javascript

is it possible to create a very soft / very subtle shadow in three.js?
like on this pic?
everything I managed to do so far is this:
My Lights:
hemisphereLight = new THREE.HemisphereLight(0xaaaaaa,0x000000, 0.9);
ambientLight = new THREE.AmbientLight(0xdc8874, 0.5);
shadowLight = new THREE.DirectionalLight(0xffffff, 1);
shadowLight.position.set(5, 20, -5);
shadowLight.castShadow = true;
shadowLight.shadowCameraVisible = true;
shadowLight.shadowDarkness = 0.5;
shadowLight.shadow.camera.left = -500;
shadowLight.shadow.camera.right = 500;
shadowLight.shadow.camera.top = 500;
shadowLight.shadow.camera.bottom = -500;
shadowLight.shadow.camera.near = 1;
shadowLight.shadow.camera.far = 1000;
shadowLight.shadowCameraVisible = true;
shadowLight.shadow.mapSize.width = 4096; // default is 512
shadowLight.shadow.mapSize.height = 4096; // default is 512
and render:
renderer.shadowMapEnabled = true;
renderer.shadowMapSoft = true;
renderer.shadowMapType = THREE.PCFSoftShadowMap;
thanks you

You can soften shadows by setting radius like this:
var light = new THREE.PointLight(0xffffff, 0.2);
light.castShadow = true;
light.shadow.radius = 8;

I was curious about this too, so i played around with all possible vars i found. The first real change made this one at init:
shadowLight.shadow.mapSize.width = 2048; // You have there 4K no need to go over 2K
shadowLight.shadow.mapSize.height = 2048; // - || -
Then i tested something other and when i've set:
shadowLight = new THREE.DirectionalLight(0xffffff(COLOR), 1.75(NEAR should in this case under 2), 1000 (FAR should just be the range between light/floor)); the Shadows smothing more out when i set also my directional lights position the 250 with:
shadowLight.position.set( 100(X just for some side effects), 250(Y over the scene), 0(Z) );
!!!!!!!!!!!!!!!!!Delete the (VAR) parts before using!!!!!!!!!!
Then i change this value to the value of my floor width.
d = 1000;
shadowLight.shadow.camera.left = -d;
shadowLight.shadow.camera.right = d;
shadowLight.shadow.camera.top = d;
shadowLight.shadow.camera.bottom = -d;
because if you use this:
var helper = new THREE.CameraHelper( shadowLight.shadow.camera );
and put it also in render:
scenes.add( shadowLight, helper );
...you see the box of your light and it should be maxed to your scene width itself i think. Hope it helps someone out.

What you're looking at is called Ambient Occlusion. There are a few things already available to look at, and you can probably find more now that you know what to search for. For example: Ambient occlusion in threejs

Actually that is not Ambient Occlusion. AO is only the contact shadow between 2 meshes which are very close one to each other.
What you are looking for, those soft shadows, you can get them in 2 ways:
First one, the easier: creating lightmaps in the 3D software that you use to create your models. That is: baking the shadows (and ambient occlusion too, if you want, and even textures and materials) into 1 texture that you can use later in ThreeJS. BUT: you will not be able to move those objects later... or better said, you will be able to move them, but their shadows will remain in the objects where they were being projected when you baked the lightmap.
The other way is doing something as it is done in this example, but unfortunately I haven't been able to go through it yet and I don't know much about it:
http://helloracer.com/webgl/
Good luck with it! Regards.
EDIT: Sorry... the 2nd option, the one with the F1 car, is still a lightmap :( But that shadow is made to follow the car, so the effect is quite nice at the end. Here you have the shadow being used, it is all baked, not real-time calculated:
http://helloracer.com/webgl/obj/textures/Shadow.jpg

I think drei is a good solution of soft shadow
https://github.com/pmndrs/drei#softshadows

Related

Bloom and Antialiasing causing banding in Three.js render

I am trying to add a glowing effect to my scene. As far as I know the best way to do is with the a bloom filter using the EffectComposer. Unfortunately using the EffectComposer negates the beautiful anti-aliasing that comes with the renderer. I added a SSAARenderPass but it causes banding even with unbiased set to true, and sampleLevel to 32. See attached pictures below.
I ran across this discussion dealing with a similiar issue: https://discourse.threejs.org/t/effect-composer-gamma-output-difference/12039/23 and I believe I integrated the discussed solution, by explicitly creating a RenderTarget for the EffectComposer that has type set to THREE.FloatType. This definitely helped, but I still have some pretty noticeable banding.
How can I have a glowing effect and preserve a clean render without aliasing or banding?
const pixelRatio = renderer.getPixelRatio();
const renderScene = new RenderPass( scene, camera );
const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85);
bloomPass.threshold = 0.9;
bloomPass.strength = 1.5;
bloomPass.radius = 0.15;
var ssaaRenderPass = new SSAARenderPass( scene, camera );
ssaaRenderPass.sampleLevel = 32;
ssaaRenderPass.unbiased = true;
var adaptToneMappingPass = new AdaptiveToneMappingPass(true, 256);
var gammaCorrectionPass = new ShaderPass( GammaCorrectionShader );
var renderTarget = new THREE.WebGLRenderTarget( window.innerWidth * pixelRatio, window.innerHeight * pixelRatio,
{
minFilter: THREE.LinearFilter,
magFilter: THREE.LinearFilter,
format: THREE.RGBAFormat,
stencilBuffer: false,
type: THREE.FloatType
});
renderTarget.texture.name = 'EffectComposer.rt1';
composer = new EffectComposer(renderer, renderTarget);
composer.addPass(renderScene);
composer.addPass(ssaaRenderPass); //Seems to be better than fxaa but has terrible banding
composer.addPass(adaptToneMappingPass);
composer.addPass(bloomPass);
composer.addPass(gammaCorrectionPass);
This is with no SSAA or Bloom. No banding but terrible aliasing
This is with Bloom but no SSAA. No banding in the background, though there is in the bloom effect
This is with both Bloom and SSAA. The aliasing is better but the banding in the background and bloom is bad
I believe you're running into the exact issue that this demo solves. When unbiased = false you see color banding:
but when unbiased = true, the banding is fixed:
Should be a simple fix by setting ssaaRenderPass.unbiased = true;

Three.js - Place a dynamic 2D Canvas on a PlaneGeometry

so I have a school project where I have to remake a GameBoy. For this I wanted to create a GameBoy model with ThreeJS (i'm a beginner) and use a public repo of a GameBoy emulator in JavaScript. So I somewhat finished the GameBoy model (still need some some stuff to be added but i'll make it better later) and I decided to use this repo for the GameBoy emulator https://github.com/alexaladren/jsgameboy. This repo worked perfectly fine when it was given a canvas with an ID "display". But when I tried to change the canvas to the canvas I made in ThreeJS it doesn't display anything, here is the code of when I make the Canvas:
geometry = new THREE.PlaneGeometry( 0.55, 0.45, 0.1 );
for (let index = 0; index < 6; index++) {
let x2 = document.createElement("canvas");
let xc2 = x2.getContext("2d");
x2.width = 320;
x2.height = 288;
xc2.fillStyle = "rgba(0, 0, 200, 0.5)";
x2.style.id = 'display';
screenCanvas = x2;
xc2.fillRect(0, 0, x2.width, x2.height);
let tex2 = new THREE.CanvasTexture(x2);
screen.push(new THREE.MeshBasicMaterial({
map: tex2,
transparent:true,
opacity:0.3
}))
number++;
}
material = new THREE.MeshBasicMaterial({color: 0xA1A935});
mesh = new THREE.Mesh( geometry, screen );
mesh.position.y = 0.25;
mesh.position.z = 0.3;
group.add(mesh);
Here is the code I edited in the emulator where the default "display" canvas was mentioned:
if(window.gb != undefined){
clearInterval(gb.interval);
screenCanvas.getContext("2d").setTransform(1,0,0,1,0,0);
}
gb = new GameBoy(arraybuffer);
gb.displaycanvas = screenCanvas.getContext("2d");
screenCanvas.getContext("2d").scale(2,2);
The PlaneGeometry is correctly displayed (I also tried BoxGeometry but same results) but the game won't display on the Canvas I created.
My thoughts on why it doesn't work:
- Because the Canvas is created in ThreeJS it doesn't seem to be added to the DOM elements and probably to the already existing ThreeJS canvas?
- Maybe the canvas I created isn't updating? But I set it to a CanvasTexture so it should update?
Thank you for your help.
Update: Still looking into it but haven’t found a solution, been trying to find help in 3 different Discord servers that provide threejs Discord but no luck. I might have to make the gameboy static while the game is playing and put the game display ontop of the screen if I don’t find another solution.

Repair normals on possibly bad .stl files

I am new to Three.js and have been assigned the task of trying to repair the normals on files that have been coming in occasionally that appear to be bad. We do not know if they are bad scans or possibly bad uploads. We are looking into the upload function, but also would like to try and repair them if possible. Can anyone provide any ideas or tips to repair the file or find the correct normals?
Below is the code where we grab the normals and how we grab them. NOTE: this code works fine generally, it is only a problem when the normals are bad. I am also attaching one of the files so you can see the types of normals and "bad file" I am dealing with. Get File here
We are also using VTK on the backend with C++, so a solution or idea using either of these is helpful.
my.geometry = geometry;
var front = new THREE.MeshPhongMaterial(
{color: 0xe2e4dc, shininess: 50, side: THREE.DoubleSide});
var mesh = [new THREE.Mesh(geometry, front)];
my.scene.add(mesh[0]);
my.objects.push(mesh[0]);
var rc = new THREE.Raycaster();
var modelData = {'objects': [mesh[0].id], 'id': mesh[0].id};
var normalFound = false;
for (var dy = 80; dy >= -80; dy = dy - 10) {
console.log('finding a normal on', 0, dy, -200);
rc.set(new THREE.Vector3(0, dy, -200), new THREE.Vector3(0, 0, 1));
var hit = rc.intersectObjects([mesh[0]]);
if (hit.length) {
my.normal = hit[0].face.normal.normalize();
console.log('normal', my.normal.z);
modelData['normal'] = my.normal;
if ((my.normal.z > 0.9 && my.normal.z < 1.1)) {
my.requireOrienteering = true;
modelData['arch'] = 'lower';
normalFound = true;
console.log('we have a lower arch');
} else if ((my.normal.z < -0.9 && my.normal.z > -1.1)) {
modelData['arch'] = 'upper';
normalFound = true;
console.log('we have an upper arch');
}
break;
}
}
Calculating the normals is an easy step. If you calculate the cross product of two vectors (geometrical one), you will get a vector, that is orthogonal to the two, you input. All you have to do now is normalize it, since normals should be normalised to not mess up lightning calculations.
For smooth surfaces, you have to calculate all normals on the point and average them. For flat surfaces each vertex has multiple normales (one for each surface).
In pseudo code it will look like this for quads:
foreach quad : mesh
foreach vertex : quad
vector1 = neighborVertex.pos - vertex.pos;
vector2 = otherNeighborVertex.pos - vertex.pos;
vertex.normal = normalize(cross(vector1, vector2));
end foreach;
end foreach;
VTK has a filter named vtkPolyDataNormals that you can run on your file to compute normals. You probably want to call ConsistencyOn(), NonManifoldTraversalOn(), and AutoOrientNormalsOn() before running it.
If you want point-normals (instead of per-cell normals) and your shape has sharp corners, you probably want to provide a feature angle with SetFeatureAngle() and call SplittingOn().

Physijs simple collision between meshes without gravity

i am using Physijs to determine static collision between my meshes. As i need to know what surfaces are intersecting.
i hacked a simple demo that seems to work.
currently i have to configure my scene to use gravity, which prevents me from position my meshes in any y position, as they start to fall or float.
is there is simple way to remove the gravity from the simulation, and just use the mesh collision detection?
--update---
i had to explicitly set the mass to each mesh to 0 rather than blank. With mass=0 gravity has no affect. great!
however meshes are not reporting a collision.
any ideas where i am going wrong?
thanks
-lp
You cannot use Physijs for collision detection alone. It just comes fully equipped with real-time physics simulation, based on the ammo.js library. When you set the mass of the meshes to 0, it made them static. They were then unresponsive to external forces, such as collision responses (i.e. the change of velocity applied on the mesh after the collision was detected) or gravity. Also, two static meshes that overlap each other do not fire a collision event.
Solution A: Use ammo.js directly
Ported from Bullet Physics, the library provides the necessary tools for generating physics simulations, or just detect collisions between defined shapes (which Physijs doesn't want us to see). Here's a snippet for detecting collision between 2 rigid spheres:
var bt_collision_configuration;
var bt_dispatcher;
var bt_broadphase;
var bt_collision_world;
var scene_size = 500;
var max_objects = 10; // Tweak this as needed
bt_collision_configuration = new Ammo.btDefaultCollisionConfiguration();
bt_dispatcher = new Ammo.btCollisionDispatcher(bt_collision_configuration);
var wmin = new Ammo.btVector3(-scene_size, -scene_size, -scene_size);
var wmax = new Ammo.btVector3(scene_size, scene_size, scene_size);
// This is one type of broadphase, Ammo.js has others that might be faster
bt_broadphase = new Ammo.bt32BitAxisSweep3(
wmin, wmax, max_objects, 0, true /* disable raycast accelerator */);
bt_collision_world = new Ammo.btCollisionWorld(bt_dispatcher, bt_broadphase, bt_collision_configuration);
// Create two collision objects
var sphere_A = new Ammo.btCollisionObject();
var sphere_B = new Ammo.btCollisionObject();
// Move each to a specific location
sphere_A.getWorldTransform().setOrigin(new Ammo.btVector3(2, 1.5, 0));
sphere_B.getWorldTransform().setOrigin(new Ammo.btVector3(2, 0, 0));
// Create the sphere shape with a radius of 1
var sphere_shape = new Ammo.btSphereShape(1);
// Set the shape of each collision object
sphere_A.setCollisionShape(sphere_shape);
sphere_B.setCollisionShape(sphere_shape);
// Add the collision objects to our collision world
bt_collision_world.addCollisionObject(sphere_A);
bt_collision_world.addCollisionObject(sphere_B);
// Perform collision detection
bt_collision_world.performDiscreteCollisionDetection();
var numManifolds = bt_collision_world.getDispatcher().getNumManifolds();
// For each contact manifold
for(var i = 0; i < numManifolds; i++){
var contactManifold = bt_collision_world.getDispatcher().getManifoldByIndexInternal(i);
var obA = contactManifold.getBody0();
var obB = contactManifold.getBody1();
contactManifold.refreshContactPoints(obA.getWorldTransform(), obB.getWorldTransform());
var numContacts = contactManifold.getNumContacts();
// For each contact point in that manifold
for(var j = 0; j < numContacts; j++){
// Get the contact information
var pt = contactManifold.getContactPoint(j);
var ptA = pt.getPositionWorldOnA();
var ptB = pt.getPositionWorldOnB();
var ptdist = pt.getDistance();
// Do whatever else you need with the information...
}
}
// Oh yeah! Ammo.js wants us to deallocate
// the objects with 'Ammo.destroy(obj)'
I transformed this C++ code into its JS equivalent. There might have been some missing syntax, so you can check the Ammo.js API binding changes for anything that doesn't work.
Solution B: Use THREE's ray caster
The ray caster is less accurate, but can be more precise with the addition of extra vertex count in your shapes. Here's some code to detect collision between 2 boxes:
// General box mesh data
var boxGeometry = new THREE.CubeGeometry(100, 100, 20, 1, 1, 1);
var boxMaterial = new THREE.MeshBasicMaterial({color: 0x8888ff, wireframe: true});
// Create box that detects collision
var dcube = new THREE.Mesh(boxGeometry, boxMaterial);
// Create box to check collision with
var ocube = new THREE.Mesh(boxGeometry, boxMaterial);
// Create ray caster
var rcaster = new THREE.Raycaster(new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 1, 0));
// Cast a ray through every vertex or extremity
for(var vi = 0, l = dcube.geometry.vertices.length; vi < l; vi++){
var glovert = dcube.geometry.vertices[vi].clone().applyMatrix4(dcube.matrix);
var dirv = glovert.sub(dcube.position);
// Setup ray caster
rcaster.set(dcubeOrigin, dirv.clone().normalize());
// Get collision result
var hitResult = rcaster.intersectObject(ocube);
// Check if collision is within range of other cube
if(hitResult.length && hitResult[0].distance < dirv.length()){
// There was a hit detected between dcube and ocube
}
}
Check out these links for more information (and maybe their source code):
Three.js-Collision-Detection
Basic Collision Detection, Raycasting with Three.js
THREE's ray caster docs

Shadow map appearing on wrong place

I'm trying to make use of the built-in shadow map plugin in three.js. After initial difficulties I have more or less acceptable image with one last glitch. That one being shadow appearing on top some (all?) surfaces, with normal 0,0,1. Below are pictures of the same model.
Three.js
Preview.app (Mac)
And the code used to setup shadows:
var shadowLight = new THREE.DirectionalLight(0xFFFFFF);
shadowLight.position.x = cx + dmax/2;
shadowLight.position.y = cy - dmax/2;
shadowLight.position.z = dmax*1.5;
shadowLight.lookAt(new THREE.Vector3(cx, cy, 0));
shadowLight.target.position.set(cx, cy, 0);
shadowLight.castShadow = true;
shadowLight.onlyShadow = true;
shadowLight.shadowCameraNear = dmax;
shadowLight.shadowCameraFar = dmax*2;
shadowLight.shadowCameraLeft = -dmax/2;
shadowLight.shadowCameraRight = dmax/2;
shadowLight.shadowCameraBottom = -dmax/2;
shadowLight.shadowCameraTop = dmax/2;
shadowLight.shadowBias = 0.005;
shadowLight.shadowDarkness = 0.3;
shadowLight.shadowMapWidth = 2048;
shadowLight.shadowMapHeight = 2048;
// shadowLight.shadowCameraVisible = true;
scene.add(shadowLight);
UPDATE: And a live example over here: http://jsbin.com/okobum/1/edit
Your code looks fine. You just need to play with the shadowLight.shadowBias parameter. This is always a bit tricky. (Note that the bias parameter can be negative.)
EDIT: Tighten up your shadow-camera near and far planes. This will help reduce both shadow acne and peter-panning. For example, your live link, set shadowLight.shadowCameraNear = 3*dmax;. This worked for me.
You can also try adding depth to your table tops, if it's not already there.
You can try setting renderer.shadowMapCullFrontFaces = false. This will cull back faces instead of front ones.

Categories

Resources