I'm doing some racing game on three.js and I stuck with the following problem...
I'm having 2 cars, so we need to render 4 spotlights (minimum) for rear car lights and front car lights for each car...
Also we need some lights on the road...
So i'm having this code:
//front car1 light
var SpotLight = new THREE.SpotLight( 0xffffff, 5, 300, Math.PI/2, 1 );
SpotLight.position.set( 50, 10, 700 );
SpotLight.target.position.set(50, 0, 800);
SpotLight.castShadow = true;
SpotLight.shadowCameraVisible = false;
SpotLight.shadowDarkness = 0.5;
scene.add(SpotLight);
//front car2 light
var SpotLight = new THREE.SpotLight( 0xffffff, 5, 300, -Math.PI/2, 1 );
SpotLight.position.set( -50, 10, 40 );
SpotLight.target.position.set(-50, 0, 100);
SpotLight.castShadow = true;
SpotLight.shadowCameraVisible = false;
SpotLight.shadowDarkness = 0.5;
scene.add(SpotLight);
//rear car1 light
var SpotLight = new THREE.SpotLight( 0xff0000, 2, 200, Math.PI/2, 2 );
SpotLight.position.set( 50, 20, 660 );
SpotLight.target.position.set(50, 0, 600);
SpotLight.castShadow = true;
SpotLight.shadowCameraVisible = false;
SpotLight.shadowDarkness = 0.5;
scene.add(SpotLight);
//rear car2 light
var SpotLight = new THREE.SpotLight( 0xff0000, 2, 100, Math.PI/2, 1 );
SpotLight.position.set( -50, 20, -35 );
SpotLight.target.position.set(-50, 0, -100);
SpotLight.castShadow = true;
SpotLight.shadowCameraVisible = false;
SpotLight.shadowDarkness = 0.5;
scene.add(SpotLight);
//some road light
var SpotLight = new THREE.SpotLight( 0x404040, 3, 500, Math.PI/2, 2 );
SpotLight.position.set( 0, 300, 0 );
SpotLight.target.position.set(0, 0, 0);
SpotLight.castShadow = true;
SpotLight.shadowCameraVisible = false;
SpotLight.shadowDarkness = 0.5;
scene.add(SpotLight);
Nothing special.. but performance dropped to 20-30 FPS and it's a little bit laggy :-1:
And if I add some lights in the future, the performance will be lifted even further ...
Has anyone encountered similar problems already? How to deal with this? Or maybe I'm doing something wrong?
Lights are a very consuming when doing realtime rendering. You'll need to find the cheapest approach that mimics the result you're after.
For instance, you could have a textured plane in front of your car with a texture that looks like the if there were spotlights aiming to the floor. It won't be right, but it will give the impression that is right and you will be saving 4 spotlights and your game will run at 60fps.
Shadows are most likely the culprit in this case - under the hood, the scene needs to be rendered from the point of view of each shadow-casting light. If possible, save them for the most important ones, disable shadows on other lights.
For many lights, you could try to use WebGLDeferredRenderer, it can handle multiple lights much better than the default renderer. It's experimental work in progress though, so you are likely to run into other problems. Also I'm not sure if it helps shadow mapping performance.
I had exactly same issue, beside mrdoob's & yaku's suggestions, which were really helpful, another approach is reducing number of segments & polygons in your geometries.
i.e If you have a simple cylinder in your scene, you can reduce number of segments by assigning heightSegments & radialSegments in initialization time:
As an very simple example, avoid doing something like this if you need to create a simple cylinder:
sampleCylinderGeo = new THREE.CylinderGeometry(2, 2, 5, 16, 32);
instead try:
sampleCylinderGeo = new THREE.CylinderGeometry(2, 2, 5, 8, 1);
Of course if you want smoother cylinder you can increase radial segments from 8 to something like 16 or more according to your needs, but for heightSegments its simply useless to have more than 1 segments in a simple cylinder.
So just adjust number of segments according to your needs so you will save lots of unnecessary segments and achieve a lot more FPS when working with lights, specially when you have lots of geometries in you scene.
Related
I am trying to write some sample code of three.js where i have a plane and i want that plane to rotate around.
This is my camera:
var camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(50, 50, 60);
camera.lookAt(scene.position);
and this is my plane:
var planeGeometry = new THREE.PlaneGeometry(70, 30, 1, 1);
var planeMaterial = new THREE.MeshBasicMaterial({ color: green });
var plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -0.5 * Math.PI;
scene.add(plane);
i have other code too but this is my render and animationframe code:
renderScene();
function cameraUpdate() {
camera.position.x = cameraRadius * Math.cos(step);
camera.position.z = cameraRadius * Math.sin(step);
camera.lookAt(scene.position);
}
function renderScene() {
//make update to position, rotation of objects in the scene
step += 0.05;
cameraUpdate();
requestAnimationFrame(renderScene);
renderer.render(scene, camera);
}
My question is that inside cameraUpdate function if dont put
camera.lookAt(scene.position);
the rotation become very wierd and when i put this inside cameraUpdate, i get rotation of the plane in own axis which is desired !!!
My question is
what does scene.position mean
Why do i need to make camera lookat at every animation frame? i dont understand how its value get changed when camera is rotated
Thank you in advance !!!
If you want the plane to rotate on its own, you should just use plane.rotation.y += 0.1 on each frame, or something simple like that. What you’re doing instead is that you’re making the camera move around in circles around the plane. Think of it as walking in a circle around a coffee table. If you keep your head looking forward, you won’t see the table as you walk past it. That’s what camera.lookAt() fixes. It makes the camera look at a point in space. You can read about it in the docs.
scene.position is by default at (0, 0, 0) so it’s just another way of telling the camera to look at the center of the scene.
Background of Question
I am working on a game that is a mix between Europa Universalis 4 and Age of Empires 3. The game is made in JavaScript and utilizes Three.js (r109) library. As of right now I have made randomly generated low-poly terrain with trees and reflective water. In the beginning I want the game to spawn a Navy, represented by a galleon (in screenshot below). I want to make it so when its called to spawn, it will pick a random location within the bounds of the water. The water mesh is represented by a semi-opaque plane spanning the size of the map- with a THREE.Reflector object underneath it. The terrain is also a plane but has been altered using a SimplexNoise heightmap.
The Question
How do I detect if an x and z position intersects with the water mesh and not the terrain mesh? THREE.Raycaster seems to be useful for what I am trying to do, but I wan't to know if there is a better solution. If using THREE.Raycaster is the best option, how would I go about implementing it for this purpose? Should I make an individual THREE.Raycaster for every object I am doing this with? Keep in mind I'm not placing this object with the mouse, I want to place it with a method that checks the position as stated above.
It's difficult to give specific advice without knowing anything at all about your code, but it sounds like all you need to do is create a collision list for your valid water surfaces and then check that when you want to spawn something.
A very simple jsfiddle is here. It creates a "land" mesh (green) and a "water" mesh (blue), adds the "water" mesh to a variable called collisionList. It then calls a spawn function for coordinates diagonally across both surfaces. The function uses a raycaster to check if the coordinates are over the "water" mesh and spawns a red cube if it is.
Here's the code:
window.onload = function() {
var camera = null, land = null, water = null, renderer = null, lights;
var collisionList;
var d, n, scene = null, animID;
n = document.getElementById('canvas');
function load() {
var height = 600, width = 800;
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(60, width/height, 1, 1000);
camera.position.set(0, 0, -10);
camera.lookAt(new THREE.Vector3(0, 0, 0));
scene.add(camera);
lights = [];
lights[0] = new THREE.PointLight(0xffffff, 1, 0);
lights[1] = new THREE.PointLight(0xffffff, 1, 0);
lights[2] = new THREE.PointLight(0xffffff, 1, 0);
lights[0].position.set(0, 200, 0);
lights[1].position.set(100, 200, 100);
lights[2].position.set(-100, -200, -100);
scene.add(lights[0]);
scene.add(lights[1]);
scene.add(lights[2]);
water = new THREE.Mesh(new THREE.PlaneGeometry(7, 7, 10),
new THREE.MeshStandardMaterial({
color: 0x0000ff,
side: THREE.DoubleSide,
}));
water.position.set(0, 0, 0);
scene.add(water);
land = new THREE.Mesh(new THREE.PlaneGeometry(12, 12, 10),
new THREE.MeshStandardMaterial({
color: 0x00ff00,
side: THREE.DoubleSide,
}));
land.position.set(0, 0, 1);
scene.add(land);
renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height);
n.appendChild(renderer.domElement);
collisionList = [ water ];
for(var i = -6; i < 6; i++)
spawn(i);
animate();
}
function spawn(x) {
var dir, intersect, mesh, ray, v;
v = new THREE.Vector3(x, x, -1);
dir = new THREE.Vector3(0, 0, 1);
ray = new THREE.Raycaster(v, dir.normalize(), 0, 100);
intersect = ray.intersectObjects(collisionList);
if(intersect.length <= 0)
return;
mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1, 1, 1, 1),
new THREE.MeshStandardMaterial({ color: 0xff0000 }));
mesh.position.set(x, x, 0);
scene.add(mesh);
}
function animate() {
if(!scene) return;
animID = requestAnimationFrame(animate);
render();
update();
}
function render() {
if(!scene || !camera || !renderer) return;
renderer.render(scene, camera);
}
function update() {
if(!scene || !camera) return;
}
load();
As for whether this is a smart way to do it, that really depends on the design of the rest of your game.
If your world is procgen then it may be more efficient/less error prone to generate the spawn points (and any other "functional" parts of the world) first and use that to generate the geography instead of the other way around.
Gutentag, guys!
I am trying to make a hole in my ExtrudeGeometry but it does not work.
My code looks like this:
var shape = new THREE.Mesh(new THREE.ExtrudeGeometry(drawShape(), options), new THREE.MeshLambertMaterial({color: 0x593788}))
function drawShape() {
// create a basic shape
var shape = new THREE.Shape();
shape.moveTo(10, 10);
shape.lineTo(10, 40);
shape.lineTo(30, 40);
shape.lineTo(30, 10);
shape.lineTo(10, 10);
var hole1 = new THREE.Path();
hole1.moveTo(12,15);
hole1.lineTo(15,16);
hole1.lineTo(16,16);
hole1.lineTo(16,15);
hole1.lineTo(12,15);
shape.holes.push(hole1);
return shape;
}
What I am getting is a plain shape figure with no hole
(drew a white hole with Clipping Tool to show where it should be) --->
May I ask why is this happening?
Because when I add absellipse or absarc - the hole works fine.
var hole2 = new THREE.Path();
hole2.absellipse(23, 24, 2, 3, 0, Math.PI * 2, true);
shape.holes.push(hole2);
Is it impossible to push holes made from line points instead of ellipse or arc?
If you have any idea why this does not work or how to fix it, please help.
I'm working on a problem with three.js and ExtrudeGeometry.
I do have a wave-like structure which is made from several individual frames. Each of them is extruded using ExtrudeGeometry.
I'd like to apply a texture to each frame of the structure which is "wrapped around" the structure. For some reason (possibly wrong UV-mapping?) the texture does not display correctly on the extruded edges where the wave-like surface is out of level. (There are some tiny sections in the picture where the texture wraps correctly). I'm using the following script to apply the textures:
// create some simple Geometry
var shape = new THREE.Shape();
shape.moveTo( 0,0 );
shape.lineTo( 0,10 );
shape.lineTo( 100,7 );
shape.lineTo( 100,0 );
var extrudeSettings = {
steps: 2,
amount: 10,
bevelEnabled: false,
bevelThickness: 0,
bevelSize: 0,
bevelSegments: 0
};
var geometry = new THREE.ExtrudeGeometry( shape, extrudeSettings );
var texture = new THREE.Texture( image );
texture.wrapS = THREE.ClampToEdgeWrapping;
texture.wrapT = THREE.ClampToEdgeWrapping;
texture.repeat.set( 0.1, 0.1 );
var material = new THREE.MeshBasicMaterial( {map: texture} );
var mesh = new THREE.Mesh( geometry, material ) ;
scene.add( mesh );
Every help is much appreciated! Cheers!
Edit:
I've created this image to better illustrate the problem. White Arrows show, how the texture is supposed to wrap around the object. At some very rare spots it actually does!
Have a look at this example
https://threejs.org/examples/?q=geome#webgl_geometry_shapes
at row 70 there is a comment that states that texture.repeat must be set to (0.008, 0.008)
I am trying to replicate the functionality of Google's Cardboard Demo "Exhibit" with three.js. I took the starting example straight from the Chrome Experiments web page and just dropped in code to draw a simple triangular pyramid in the init method:
function init() {
renderer = new THREE.WebGLRenderer();
element = renderer.domElement;
container = document.getElementById('example');
container.appendChild(element);
effect = new THREE.StereoEffect(renderer);
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(90, 1, 0.001, 700);
camera.position.set(0, 0, 50);
scene.add(camera);
controls = new THREE.OrbitControls(camera, element);
controls.rotateUp(Math.PI / 4);
controls.noZoom = true;
controls.noPan = true;
var geometry = new THREE.CylinderGeometry( 0, 10, 30, 4, 1 );
var material = new THREE.MeshPhongMaterial( { color:0xffffff, shading: THREE.FlatShading } );
var mesh = new THREE.Mesh( geometry, material );
mesh.updateMatrix();
mesh.matrixAutoUpdate = false;
scene.add( mesh );
var light = new THREE.DirectionalLight( 0xffffff );
light.position.set( 1, 1, 1 );
scene.add( light );
light = new THREE.DirectionalLight( 0x002288 );
light.position.set( -1, -1, -1 );
scene.add( light );
light = new THREE.AmbientLight( 0x222222 );
scene.add( light );
function setOrientationControls(e) {
if (!e.alpha) {
return;
}
controls = new THREE.DeviceOrientationControls(camera);
controls.connect();
controls.update();
element.addEventListener('click', fullscreen, false);
window.removeEventListener('deviceorientation', setOrientationControls, true);
}
window.addEventListener('deviceorientation', setOrientationControls, true);
window.addEventListener('resize', resize, false);
setTimeout(resize, 1);
}
The OrbitControls method on desktop works perfectly: by dragging with the mouse, the screen orbits around the pyramid. On mobile using DeviceOrientationControls however, this effect is entirely lost and instead the camera moves freely at (0, 0, 0). I tried doing as a previous question suggested and replacing the camera with scene such that:
controls = new THREE.DeviceOrientationControls(scene);
however this does not work at all and nothing moves when the device is rotated. What do I need to change to replicate OrbitControls behavior with the motion captured by DeviceOrientationControls?
To create a deviceorientation orbit controler, like you see on this demo, http://novak.us/labs/UmDemo/; It involves modifying the existing OrbitControls.js.
The file changes can be seen in this commit on github: https://github.com/snovak/three.js/commit/f6542ab3d95b1c746ab4d39ab5d3253720830dd3
I've been meaning to do a pull request for months. Just haven't gotten around to it. Needs a bunch of clean up.
You can download my modified OrbitControls.js here (I haven't merged in months either, results may vary): https://raw.githubusercontent.com/snovak/three.js/master/examples/js/controls/OrbitControls.js
Below is how you would implement the modified OrbitControls in your own scripts:
this.controls = new THREE.OrbitControls( camera, document.getElementById('screen') ) ;
controls.tiltEnabled = true ; // default is false. You need to turn this on to control with the gyro sensor.
controls.minPolarAngle = Math.PI * 0.4; // radians
controls.maxPolarAngle = Math.PI * 0.6; // radians
controls.noZoom = true ;
// How far you can rotate on the horizontal axis, upper and lower limits.
// If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].
controls.minAzimuthAngle = - Math.PI * 0.1; // radians
controls.maxAzimuthAngle = Math.PI * 0.1; // radians
this.UMLogo = scene.children[1];
controls.target = UMLogo.position;
I hope that gets you where you want to be! :-)
To use the DeviceOrientationControls You must call controls.update() during your animation loop or the control will not update it's position based on device info.