问题
I am working on a visualization project based through a web server and I have it working and looking how I want it but it is much slower than I was hoping. Basically, it has a huge grid for modeling a space and then individual cubes are displayed in the grid with different colors (or just removed currently if there is nothing there, but could easily just use transparent materials instead) to represent how likely there is an object in that area of the grid, and the colors need to change dynamically as it receives more data (not currently implemented but easily done at this point). Below is the code as I currently have it working (named VFF.js):
//dimensions in feet
var xFeet = 20;
var yFeet = 10;
var zFeet = 15;
var certaintyGrid = [];
var gridSize = 6; //6 inch squares (higher number = lower resolution)
var objectThreshhold = 5;
//change the dimesnions to blocks/grid spaces
var xDim = Math.ceil(xFeet * 12 / gridSize);
var yDim = Math.ceil(yFeet * 12 / gridSize);
var zDim = Math.ceil(zFeet * 12 / gridSize);
//parrot ar.drone is 22.4 x 22.4 x 5.4 inches
var droneWidth = 22.4 / gridSize;
var droneLength = 22.4 / gridSize;
var droneHeight = 5.4 / gridSize;
//get the canvas and set its background
var container = document.getElementById("VFFCanvas");
container.style.background = '#cccccc';
//create the scene, renderer, and camera and then put it in the VFFCanvas
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45, container.width/container.height, 0.1, 1000);
var renderer = new THREE.WebGLRenderer( { antialias: false, canvas: container, alpha: true} );
renderer.setClearColor(new THREE.Color().setRGB( 0.95, 0.95, 0.95 ));
renderer.setSize(container.width, container.height);
renderer.enableScissorTest ( true );
document.body.appendChild(renderer.domElement);
//create the light source
var directionalLight = new THREE.DirectionalLight(0xffffff);
scene.add(directionalLight);
//create the drone object
var droneGeo = new THREE.CubeGeometry(droneWidth, droneHeight, droneLength);
var droneMat = new THREE.MeshLambertMaterial({color: 0x888888});
var drone = new THREE.Mesh(droneGeo, droneMat);
//drone.position.set(15,4,10);
drone.position.set(xDim / 2 - 1, 2, zDim / 2 - 1); //start the drone in the center of the grid close to the ground
scene.add(drone);
//maybe do a small far clipping for the top down so its easier to see things around you
//set up the camera and views
var chaseDistance = droneWidth * 8; //bigger = farther away
var chaseClipping = 1.2; //bigger = more not displayed
var topDownDroneScaledWidth = droneWidth * 15; //bigger # = farther away
var topDownHeightMargin = 4; //how many drones above or below it will cut off before it clips the squares away and not have them block the
//view of where we are
var views = [{ //top down cam
left: 0.505,
bottom: 0.01,
width: 0.485,
height: 0.485,
fov: 45,
closeClip: topDownDroneScaledWidth - droneHeight * topDownHeightMargin,
farClip: topDownDroneScaledWidth + droneHeight * topDownHeightMargin,
//background: new THREE.Color().setRGB( 0.6, 0.2, 0.2 ),
setup: function (camera) {
camera.rotation.x = -Math.PI/2;
},
updateCamera: function (camera, scene) {
//position it above the drone (need to be carefull if we are taking ceiling measurments or else our view will be blocked)
camera.position.x = drone.position.x;
camera.position.z = drone.position.z;
camera.position.y = drone.position.y + topDownDroneScaledWidth; //this height shows a decent view based on the drones size
camera.rotation.z = drone.rotation.y; //use the z because we are looking straight down
}
},{ //chase cam
left: 0.01,
bottom: 0.01,
width: 0.485,
height: 0.98,
fov: 45,
closeClip: chaseDistance * chaseClipping, //chaseDistance * sqrt(2) is our distance to the center of the drone
farClip: 10000,
//background: new THREE.Color().setRGB( 0.5, 0.5, 0.7 ),
setup: function (camera) {},
updateCamera: function (camera, scene) {
//find out wheres behind the drone
camera.position.x = drone.position.x + chaseDistance * Math.sin(drone.rotation.y);
camera.position.z = drone.position.z + chaseDistance * Math.cos(drone.rotation.y);
camera.position.y = drone.position.y + chaseDistance;
//focus on the drone
camera.lookAt(drone.position);
}
},{ //cockpit cam
left: 0.505,
bottom: 0.505,
width: 0.485,
height: 0.485,
fov: 45,
closeClip: 0.1,
farClip: 10000,
//background: new THREE.Color().setRGB( 0.3, 0.7, 0.3 ),
setup: function (camera) {
drone.add(camera);
camera.position.z = -droneLength / 2; //position it where the camera is on the ar drone
},
updateCamera: function (camera, scene) {}
}];
//initialize the views' cameras
for (var ii = 0; ii < views.length; ++ii ) {
var view = views[ii];
camera = new THREE.PerspectiveCamera( view.fov, container.width / container.height, view.closeClip, view.farClip );
view.camera = camera;
view.setup(camera);
view.left = Math.floor( container.width * view.left );
view.bottom = Math.floor( container.height * view.bottom );
view.width = Math.floor( container.width * view.width );
view.height = Math.floor( container.height * view.height );
}
//create the grid objects
var geometry = new THREE.CubeGeometry(0.9, 0.9, 0.9);
//var material = new THREE.MeshLambertMaterial({color: 0x0000ff, transparent: false, opacity: 0 });
for(i = 0; i < xDim; i++) {
certaintyGrid[i] = [];
for(j = 0; j < zDim; j++) {
certaintyGrid[i][j] = [];
for(k = 0; k < yDim; k++) {
//start them as non existent (no certainty) or else it could case errors
var material = new THREE.MeshLambertMaterial({color: 0x0000ff, transparent: false, opacity: 0 });
var cube = new THREE.Mesh(geometry, material);
cube.position.set(i,k,j);
material.certaintyValue = 0;
//this is just for testing - creates a wall of squares along the edges of the grid
if(j == 0 || i == 0 || k == 0 || j == zDim - 1 || k == yDim - 1 || i == xDim -1) {
material.certaintyValue = Math.floor(Math.random() * 220);
}
//keep our pointer to our object so we can add it later if it gets any certainty
certaintyGrid[i][j][k] = cube;
}
}
}
/* Attempt to merge the meshes
var geo = new THREE.Geometry();
var meshTest = new THREE.Mesh(geo, material);
for (i = 0; i < xDim; i++) {
for(j = 0; j < zDim; j++) {
for(k = 0; k < yDim; k++) {
THREE.GeometryUtils.merge(geo, certaintyGrid[i][j][k]);
}
}
}
scene.add(meshTest);
*/
//this is where it loops and updates the camera and scene
var render = function () {
requestAnimationFrame(render);
//testin stuff
drone.rotation.y += 0.01;
//makes it so the light is always comming from behind where the drone is facing
directionalLight.position.x = Math.sin(drone.rotation.y);
directionalLight.position.z = Math.cos(drone.rotation.y);
//update the cubes based on their certainty values (maybe make this "smarter" later so it only updates the ones changed)
for(i = 0; i < xDim; i++) {
for(j = 0; j < zDim; j++) {
for(k = 0; k < yDim; k++) {
var currMater = certaintyGrid[i][j][k].material;
if(currMater.certaintyValue > objectThreshhold) {
if(currMater.opacity != 1) {
if (currMater.transparent == false)
scene.add(certaintyGrid[i][j][k]);
currMater.transparent = false;
currMater.opacity = 1;
}
var red = (currMater.certaintyValue - objectThreshhold)/255;
var blue = (255 - (currMater.certaintyValue - objectThreshhold))/255;
currMater.color.setRGB(red, .2, blue);
} else if (currMater.certaintyValue < 1) {
if(currMater.opacity != 0) {
currMater.transparent = false;
currMater.opacity = 0;
scene.remove(certaintyGrid[i][j][k]);
}
} else {
if(currMater.opacity == 0 || currMater.opacity == 1) {
currMater.color.setHex(0x0000ff);
currMater.transparent = true;
if(currMater.opacity == 0) //only add it if we are going from no certainty
scene.add(certaintyGrid[i][j][k]);
}
currMater.opacity = 0.05 * (currMater.certaintyValue + 1);
}
}
}
}
//update the views and cameras
for ( var ii = 0; ii < views.length; ++ii ) {
view = views[ii];
camera = view.camera;
view.updateCamera(camera, scene);
renderer.setScissor( view.left, view.bottom, view.width, view.height );
renderer.setViewport( view.left, view.bottom, view.width, view.height );
//renderer.setClearColor( view.background );
camera.aspect = view.width / view.height;
camera.updateProjectionMatrix();
renderer.render( scene, camera );
}
};
//now actually get us started (puts us in the infinite run loop)
render();
Also, I have it running in this simple HTML file just for testing :
<html>
<head>
<title>VFF Vizualization Test</title>
</head>
<body>
<canvas id="VFFCanvas" width="640" height="480"></canvas>
<script src="three.min.js"></script>
<script src="VFF.js"></script>
</body>
</html>
I did some searching and tried a few different methods to speed it up, including using fewer materials, but it did not help running speed much if at all (can easily get by with a few different colors if it helps performance and don't need large level of detail as far as colors go). Also, I tried merging the meshes of the cubes and it speed it up fantastically, but then there was only one material for all the cubes which won't work for this. I saw some things on MeshFaceMaterial and thought it might work, but only saw it implemented on single, unmerged meshes (usually cubes) and wasn't sure if it could be used for this application or how it works assigning the materials.
I'm open to any ideas that could work (not just MeshFaceMaterial) and appreciate the help!
回答1:
I spot one particular performance killer: you're modifying the scene by adding and removing objects in the render() function. Instead you should create all the objects you need and turn the objects you don't need invisible or visible. Use visibility rather than transparency to make things invisible.
Another problem (not performance related) is that you're moving a directional light ... a directional light doesn't have a position, but it does have an orientation.
Lastly, as you say in your code, you should really optimise that loop and only change what you need to. As any geometry changes you make have to be delivered to the GPU on each frame which is expensive, you really don't want to make changes you don't need to make, really keep your render() loop as quick as possible.
One particular change I'd probably also make for this is to add a method which builds a material by colour. If you already have that colour it returns that, otherwise it creates that colour. It might not help much, but every little bit can help.
回答2:
In case if anyone is looking how to actually merge meshes with multiple materials here's my function. This will merge meshes and re-index materials so all should work fine(tested on r71).
function mergeMeshes (meshArr) {
var geometry = new THREE.Geometry(),
materials = [],
m,
materialPointer = 0,
reindex = 0;
for (var i = 0; i < meshArr.length; i++) {
m = meshArr[i];
if (m.material.materials) {
for (var j = 0; j < m.material.materials.length; j++) {
materials[materialPointer++] = m.material.materials[j];
}
} else if (m.material) {
materials[materialPointer++] = m.material;
}
geometry.merge(m.geometry, m.matrix, reindex);
reindex = materialPointer;
}
return new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(materials));
};
This will definitely speed up scenes with thousands of static objects. Once objects are merged they can only be transformed as one massive mesh.
来源:https://stackoverflow.com/questions/24662674/three-js-merging-meshes-but-keeping-separate-materials