I am loading several models into scene using the same geometry like so (pseudo code):
var geoCache = [];
function parseJSONGeometry(json_geo){
// this code is the three.js model parser from the jsonloader
return geometry;
}
function loadCachedGeo(data){
if( !geoCache[data.id] ){
geoCache[json.id] = parseJSONGeometry(data);
}
return geoCache[json.id];
}
function loadObjects(json){
var mats = [];
combined = new THREE.Geometry();
for(i=0<i<json.geometries.length;i++){
data = json.geometries[i];
geo = loadCachedGeo(data.id);
mats.push(new THREE.MeshBasicMaterial(map:THREE.imageUtils.loadTexture(data.src)));
mesh = new THREE.Mesh(geo);
mesh.position.set(data.x,data.y,data.z);
combined = THREE.GeometryUtils.mergeGeometry(combined,mesh);
}
mesh = new THREE.Mesh(combined,new THREE.MeshFaceMaterial(mats));
scene.add(mesh);
}
I also cache the textures, however I omitted that for the sake of simplicity.
When I call:
renderer.info.render.faces
renderer.info.memory.textures
renderer.info.memory.programs
renderer.info.memory.geometries;
renderer.info.render.calls
I notice when one object is on the screen the poly count is say 1000, textures: 1, calls: 1, shaders: 1 and geometries: 1. When two objects are on the screen 2000 faces are reported, 1 texture, 1 shader, 2 calls, and 2 geometries.
I thought that reusing geometry in this fashion only loads the geometry once into the gpu. Am I missing something, can someone PLEASE explain this behavior?
Three.js r59
You need to inspect
renderer.info.memory.geometries
There is also
renderer.info.memory.textures
renderer.info.memory.programs
three.js r.59
Related
I'm wondering how I would go about calling the individual children of cube (mesh0, mesh1) so I'm able to set different styles / animations to them both.
AFRAME.registerComponent('multi_box', {
schema: {},
update: function() {
for (var i = 0; i < 2; i++) {
var material = new THREE.MeshBasicMaterial({color: "blue"});
var geometry = new THREE.BoxGeometry(1, 1, 1);
var cube = new THREE.Mesh(geometry, material);
cube.position.x = i == 0 ? -1 : 1;
cube.position.y = 0.5;
cube.position.z = -5;
this.el.setObject3D("mesh"+i, cube); //unique name for each object
}
console.log(this.el.object3DMap)
}
});
Codepen link: https://codepen.io/ubermario/pen/wrwjVG
I can console.log them both and see that they are unique objects to each other but I'm having trouble calling them:
var meshtest = this.el.getObject3D('mesh0')
console.log(meshtest.position)
I'v tried this method but with no luck: aframe get object3d children
Any help is appreciated :)
Instancing
In your for cycle, you create
a new geometry instance
a new material instance
a new mesh that connect the previous two
Each new keyword creates a unique instance that is independent of the others. In your case mesh0 and mesh1 are fully independent of each other. If you change any property of an instance, like for example material color or position, the other(s) will not be affected by that. In fact you do that by assigning a different x position to each cube.
Storage
Your component holds a map of 3D objects. Each is identified by a unique name. You generate this name by concatenating the prefix mesh with the iteration number (value of i).
You can later access the cubes just the same way you created them. Either by name
this.el.getObject3D('mesh0')
this.el.getObject3D('mesh1')
//etc.
or by index
this.el.object3D.children[0]
this.el.object3D.children[1]
and you can manipulate them further. For example you can put his on Ln19 in your Codepen:
this.el.getObject3D('mesh0').position.y = 2;
this.el.object3D.children[1].position.z = -3;
Just for completeness: If you would omit the +i at the end, you would overwrite the same key again and again, so mesh would reference just the last cube and others would get lost.
Changing properties
Three.js has a nice API, but in javascript you always need to think what happens behind the scenes. You will see that while learning new stuff. For example:
this.el.object3D.children[1].position.z = -3;
this.el.object3D.children[1].material.color.set('#ffff00');
this.el.object3D.children[1].material.color = [1, 1, 0];
As you can see the position can be changed directly, but color needs a setter sometimes. Things get more complicated with vectors where you need to watch which methods change the current instance and which produce a new one. You could easily forget to clone it. Say you had:
var pos = new THREE.Vector3(-1, 0.5, -5)
for (var i = 0; i < 2; i++) {
var material = new THREE.MeshBasicMaterial({color: "blue"});
var geometry = new THREE.BoxGeometry(1, 1, 1);
var cube = new THREE.Mesh(geometry, material);
//wrong, since it will assign the object reference,
//i.e. always the same object.
//Thus, the cubes will be at one position in the end
cube.position = pos;
//right
cube.pos = pos.clone();
pos.x += 1;
this.el.setObject3D("mesh"+i, cube); //unique name for each object
}
The difference is a bit more moderate topic on reference and value types. You will find many tutorials for that, for example this small gist I just found.
I hope this was helpful and wish you good progress in learning!
How to make a part of object visible in reflection on another part of the same object?
Threejs is not a ray tracing engine. Therefore it is not using real ray casts to calculate reflections on surfaces. It actually renders the other objects onto your reflective object.
What your scene is missing is an infinity mirror effect between surfaces, where light rays can bounce between two reflective surfaces multiple times. This is what a ray tracing engine would do.
You can try to fake this. Just split your geometry of into multiple objects and use extra cameras for them to render the scene looking from your reflective object into the scene and using this as a texture for the object.
You can use this example as a starting point: https://threejs.org/examples/#webgl_materials_cubemap_dynamic
Here an example of a material with dynamic environment texture captured by a CubeCamera:
var near = 1
var far = 100
var cubeResolution = 128
var camera1 = new THREE.CubeCamera( near, far, cubeResolution);
camera1.renderTarget.texture.generateMipmaps = true;
camera1.renderTarget.texture.minFilter = THREE.LinearMipMapLinearFilter;
scene.add( camera1 );
var material = new THREE.MeshBasicMaterial( { envMap: camera1.renderTarget.texture } );
var cube = new THREE.Mesh( new THREE.BoxBufferGeometry( 20, 20, 20 ), material );
scene.add(cube)
I am a newbie in Three js. In my project, I need draw a ground with a lot of texture. The ground has many layers, every layer has 4 textures and textures in different layers are different size. Below picture describe the ground:
Ground is one mesh has multiple material:
this.mesh = new THREE.Mesh(geometry, new THREE.MultiMaterial(materials));
Suppose I have a car it always at center of ground, in other word, center of all layers. When it move, the ground will translate to make sure the car always at center. So everytime ground translate, I need to update texture in new position.
The picture draw 3 layers for illustration, but in my project is 6 layers. So everytime all texture change, that means need to change 6 * 4 = 24 textures, and that cause low fps in my program.
This is my function to load texture from indexed DB every time a texture change:
Ground.prototype.loadTextureFromIndexedDB = function (url, materialIndex) {
var loader = new THREE.TextureLoader();
loader.crossOrigin = '';
loader.load(url,
function (texture) {
var groundMaterial = ground.mesh.material.materials[materialIndex];
groundMaterial.map.dispose();
groundMaterial.map = texture;
groundMaterial.map.anisotropy = ground.maxAnisotropy;
groundMaterial.map.minFilter = THREE.LinearFilter;
groundMaterial.map.needsUpdate = true;
img = null;
window.URL.revokeObjectURL(url);
});
}
I have tried many solutions. One of them is make a mesh with a BufferGeometry and MultiMaterial with array of ShaderMaterial. As what I known, it is the best for performance in THREE JS, isn't it? If it is then maybe THREE JS is not powerful as I thinked. Should I change to another API for my project?
Anyone suggest me any solution to make higher performance in my program? Thanks a lot!
Is there a way to prevent a directional light from illuminating a specific object? I guess this would also apply to a spotlight. The reason for this is I would like to use two directional lights, but with one light shining on an object to give it 'self shadowing', and do not want that light it to interfere with another object.
Currently I have a single directional light declared as such :
function addpointlight()
{
var SHADOW_MAP_WIDTH = 4096, SHADOW_MAP_HEIGHT = 2048;
//Enabling this this light just creates serious artifacts on the obj I am trying to shadow.
//var newlight=new THREE.DirectionalLight(0xeeeeee,0.7);
//newlight.position.set( 0, 100, 300 );
//newlight.castShadow=false;
//scene.add(newlight);
// create a directional light
pointLight = new THREE.DirectionalLight(0xeeeeee,0.80);
//pointLight.onlyShadow=true;
pointLight.position.set( 0, 100, 300 );
pointLight.shadowCameraVisible=true;
pointLight.shadowCameraNear = 10;
pointLight.shadowCameraFar = 1500;
pointLight.shadowCameraFov = 90;
pointLight.castShadow=true;
var d = 4;
pointLight.shadowCameraLeft = -d;
pointLight.shadowCameraRight = d;
pointLight.shadowCameraTop = -d;
pointLight.shadowCameraBottom = d;
pointLight.shadowBias = 0.00;
pointLight.shadowDarkness = 0.7;
pointLight.shadowMapWidth = SHADOW_MAP_WIDTH;
pointLight.shadowMapHeight = SHADOW_MAP_HEIGHT;
// add to the scene
scene.add(pointLight);
}
This light wraps nicely around the object I want to self shadow, eliminating shadow artifacts. It moves with a moving object that it is creating a shadow on using this :-
pointLight.position.set(obj.position.x+40,obj.position.y+5,obj.position.z+300);
pointLight.target=obj;
So I'd like to create a second directional light that only affects the other objects, not this one, tnd this one's light must not affect other objects.
I'd create a fiddle, but the models I am testing with together with the textures make it a rather large fiddle in terms of bandwidth.
The three.js version in r70.
You want to limit the objects that a light affects. Until the time at which three.js supports "layers", where a light will only affect objects in its own layer(s), you may be able to achieve what you want with a work-around: two separate scenes and two render passes.
renderer.autoClear = false;
...
renderer.clear();
renderer.render( scene1, camera );
renderer.render( scene2, camera );
If you have transparent objects, they will have to be in the second scene. Also, an object can only be in one scene, so you will have to duplicate a light if you want it in both.
three.js r.70
I want to implement per-object motion-blur effect based on calculating previous pixel position inside shaders.
This technic's first step is to build velocity map of moving objects. This step requirements is to have as uniform variables projection and model view matrices of current frame and the same matrices of previous frame.
How could I include those matrices to uniforms for some special shader? I supposed to have solution in some way like:
uniforms = {
some_uniform_var : {type: "m4", value: initialMatrix, getter: function(){
// `this` points to object
return this.worldMatrix
}}
}
But now in THREE.js this is not available. We could make some sort of monkey patching, but I cannot find best way to do it.
Any suggestions?
The current solvation to this problems consist of several parts. I'm using EffectComposer to make several passes of rendered scene, one of then - VelocityPass. It takes current and previous model-view matrix and projection matrix and produces two positions. Both of them then used to calculate speed of a point.
Shader looks like this
"void main() {",
"vec2 a = (pos.xy / pos.w) * 0.5 + 0.5;",
"vec2 b = (prevPos.xy / prevPos.w) * 0.5 + 0.5;",
"vec2 oVelocity = a - b;",
"gl_FragColor = vec4(oVelocity, 0.0, 1.);",
"}"
There're several issues of this decision.
Three.js has certain point where it injects matrices to object-related shaders. The very ending of SetProgram closure, which lives in WebGLRenderer. That's why I took the whole renderer file, renamed renderer to THREE.MySuperDuperWebGLRenderer and added couple lines of code in it:
A closure to access closures, defined in userspace:
function materialPerObjectSetup(material, object){
if( material.customUniformUpdate ){
material.customUniformUpdate( object, material, _gl ); // Yes, I had to pass it...
}
}
And calling of it in renderBuffer and renderBufferDirect;
var program = setProgram( camera, lights, fog, material, object );
materialPerObjectSetup(material, object);
Now - the userspace part:
velocityMat = new THREE.ShaderMaterial( THREE.VelocityShader );
velocityMat.customUniformUpdate = function(obj, mat, _gl){
// console.log("gotcha");
var new_m = obj.matrixWorld;
var p_uniforms = mat.program.uniforms;
var mvMatrix = camera.matrixWorldInverse.clone().multiplyMatrices(camera.matrixWorldInverse, obj._oldMatrix );
_gl.uniformMatrix4fv( p_uniforms.prevModelViewMatrix, false, mvMatrix.elements );
_gl.uniformMatrix4fv( p_uniforms.prevProjectionMatrix, false, camera.projectionMatrix.elements );
obj._pass_complete = true; // Необходимо сохранять состояние старой матрицы пока не отрисуется этот пасс.
// А то матрицы обновляются каждый рендеринг сцены.
}
_pass_complete needed when we rerendering scene several times - each time matrix recalculated. This trick help us save previous matrix untill we use it.
_gl.uniformMatrix4fv is needed, because three.js serves universes one time before rendering. No matter how much objects we have - other method will pass to the shader modelViewMatrix of the last one. This happens because I want to draw this scene fully using VelocityShader. There's no other way to say to Renderer to use some alternative material for objects.
And as final point of this explaination I putting here a trick to manage previous matrix of an object:
THREE.Mesh.prototype._updateMatrixWorld = rotatedObject.updateMatrixWorld;
THREE.Mesh.prototype._pass_complete = true;
Object.defineProperty(THREE.Mesh.prototype, "updateMatrixWorld", {get: function(){
if(this._pass_complete){
this._oldMatrix = this.matrixWorld.clone();
this._pass_complete = false;
}
this._updateMatrixWorld();
return (function(){
});
}})
I believe, that there's could be a nicer solution. But sometimes I need to act in rush. And such kind of monkey things could happen.