Why is the Geometry faster than BufferGeometry?

Quote from docs / Geometry:

Geometries are easier to work with than BufferGeometries as they store attributes such as vertices, faces, colors and so on directly (rather than in buffers), however they are generally slower.

Because it’s easier, I’ve started the addon
Addon. Produces almost infinite many time-varying geometries with functions
with Geometry. I was told I should always take BufferGeometry. Now I have expanded the addon.

The surprise: Geometry is the fastest!
See http://sandbox.threejs.hofk.de/Geometry_BufferGeometry/speedtest.html
How can that be explained? I don’t no why.

May be my calcultion of vertices, positions, faces, normals are not efficient enought and the three.js internal calculations of Geometry are often much better.

Hello hofk,

Buffergeometry saves its data into buffers: fast memory pools (for lack of better words) to reduce memory cost and cpu cycles. The performance improvement could be shown only when a certain number of vertices are used and it is GPU specific. It is recommended to use buffergeo, because in most cases (imported models) the end result performs better and it makes sense to have a standard/unique way to deal with geometries. Plus, three.js webGL 2.0 renderer makes usage of buffergeometries only.

I’ve not looked into your code or performance test but Geometry allocates much memory than BufferGeometry. Especially for complex geometries, this is a real problem. Besides, entities that rely on Geometry are automatically converted to BufferGeometry by three.js. To avoid this overhead and all related problems, Geometry will sooner or later be deprecated.

Check out this video for more information: https://www.youtube.com/watch?v=jLNtjujPhzY&feature=youtu.be&t=1712

1 Like

Because I was not sure if my extensive code (THREEf.js) has influence on the test, I have made a very simple example. Except for the differences between Geometry and BufferGeometry, the code is the same.

Try out
http://sandbox.threejs.hofk.de/Geometry_BufferGeometry/speedtestSimple.html

But again, Geometry is faster.

Memory consumption and speed are usually in the reverse ratio.

So if Geometry is omitted, then you have to use even more complicated things than BufferGeometry instead of simpler Geometry (to get speed)?

If Geometry is eliminated, it will be much harder for beginners to enter three.js. Many simple examples currently use Geometry. I understand, however, that the focus is on the professional programmers.

I can not fully understand the internals of three.js, maybe there is a way to solve the speed problem?

The code:

segments 
<input type="text" size="5" id="hs" value="100" >  x  
<input type="text" size="5" id="rs" value="100" > :  
	
<input type="radio" name="geom" id="Geometry" checked="checked" > Geometry
<input type="radio" name="geom" id="BufferGeometry" > indexed BufferGeometry 

<input type="radio" name="color" id="multicolor" checked="checked" > multicolor	
<input type="radio" name="color" id="monochrome" > monochrome

<button type="button" id="show">  -> show new mesh </button> 

script src=“three.min.86.js”
script src=“OrbitControls.js”
script src=“THREEx.WindowResize.js”

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 200000 );
camera.position.set( 400, 600, 1000 );
var renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor( 0xeeeeee, 1 );
var container = document.createElement( 'div' );
document.body.appendChild( container );
container.appendChild( renderer.domElement ); 
THREEx.WindowResize( renderer, camera );
var controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.enableZoom = true;
var clock  = new THREE.Clock( true );
var t; // time
var	g; // Geometry or BufferGeometry
var mesh;
var hs; // height segment count ( y direction)
var rs; // radial segment count (here in test  x direction)
var hss; // hs + 1
var rss; // rs + 1
var vertexCount;
var vIdx;	// vertex Index
var faceCount;
var fIdx;	// face index
var j0, j1; // j index 
var a, b1, c1, c2; // vertex indices,  b2 equals c1
var ni, nj; // relative counter variable
var posIdx; // position Index
var x, y, z; // position coordinates
var materialSegment;
var multicolor = null;;

var showGeo = false;

// materials
var uvTex			= new THREE.TextureLoader().load( "uvgrid01.png" );
var waterlilyTex	= new THREE.TextureLoader().load( "waterlily.png" );
// var earthTex		= new THREE.TextureLoader().load( "earth_nasa_map_900.png" );
var side = THREE.DoubleSide;

var materials = [
																						// material index:
    new THREE.MeshBasicMaterial( { color: 0x000000,transparent: true, opacity: 0.6,	side: side } ),	//  0 transparent
	new THREE.MeshBasicMaterial( { map: uvTex, 							side: side } ),	//  1 uv grid
	new THREE.MeshPhongMaterial( { color: 0xff0000, emissive: 0xff0000, side: side } ),	//  2 red
	new THREE.MeshPhongMaterial( { color: 0x00ff00, emissive: 0x00ff00, side: side } ),	//  3 green
	new THREE.MeshPhongMaterial( { color: 0x0000ff, emissive: 0x0000ff, side: side } ),	//  4 blue
	new THREE.MeshPhongMaterial( { color: 0xffff00, emissive: 0xffff00, side: side } ),	//  5 yellow
	new THREE.MeshPhongMaterial( { color: 0xff00ff, emissive: 0xff00ff, side: side } ),	//  6 mgenta
	new THREE.MeshPhongMaterial( { color: 0x00ffff, emissive: 0x00ffff, side: side } ),	//  7 cyan	
	new THREE.MeshBasicMaterial( { map: waterlilyTex,					side: side } ),	//  8 photo waterlily (free)
	new THREE.MeshPhongMaterial( { color: 0x7755ff, emissive: 0x4433dd, side: side } ),	//  9 color
	new THREE.MeshPhongMaterial( { color: 0x444444, emissive: 0x333333, side: side } )		// 10 grey
	
];

// var material = new THREE.MeshBasicMaterial( { color: 0x880088, side: side, wireframe: true } );
// var material = new THREE.MeshBasicMaterial( { map: earthTex,	side: side } );

document.getElementById( "show" ).onclick = showNewMesh;

animate();

function showNewMesh() {

	if ( mesh ) {
	
		scene.remove( mesh );
		g.dispose();
		showGeo = false;
		
		multicolor = null;
				
	}
	
	hs = Math.floor( document.getElementById( "hs" ).value );
	rs = Math.floor( document.getElementById( "rs" ).value );
	
	hss = hs + 1;
	rss = rs + 1;
	
	vertexCount = hss * rss;
	faceCount =  hs * rs * 2;
	
	multicolor = document.getElementById( "multicolor" ).checked;
	
	// Geometry or BufferGeometry
	
	if ( document.getElementById( "Geometry" ).checked ) g = new THREE.Geometry();
	if ( document.getElementById( "BufferGeometry" ).checked ) g = new THREE.BufferGeometry();
		
	// mesh
	
	mesh = new THREE.Mesh( g, materials );
	scene.add( mesh );
	
	//configure
	
	if ( g.isGeometry ) {
		
		for ( var i = 0; i < vertexCount; i ++ ) { 
			
			g.vertices.push( new THREE.Vector3( 0, 0, 0 ) ); 
		
		}
		
	}	
		
	if ( g.isBufferGeometry ) {
	
		var idxCount = 0;
		
		g.faceIndices = new Uint32Array( faceCount * 3 );
		g.vertices = new Float32Array( vertexCount * 3 );  
				
		g.setIndex( new THREE.BufferAttribute( g.faceIndices, 1 ) );	
		g.addAttribute( 'position', new THREE.BufferAttribute( g.vertices, 3 ).setDynamic( true ) );
		
		for ( var f = 0, p = 0; f < faceCount; f ++, p += 3 ) {
			
			g.addGroup( p, 3, 0 );  // write group for multi material
			
		}
		
	}
	
	for ( var j = 0; j < rs; j ++ ) {
	
		j0 = hss * j; 
		j1 = hss * ( j + 1 );
		
		for ( var i = 0; i < hs; i ++ ) {
			
			// 2 faces / segment,  3 vertex indices
			a =  j0 + i;
			b1 = j1 + i;			// right-bottom
			c1 = j1 + 1 + i;
			// b2 = j1 + 1 + i; =c1	// left-top 
			c2 = j0 + 1 + i;
			
			if ( g.isGeometry ) {
				
				g.faces.push( new THREE.Face3( a, b1, c1 ) ); // right-bottom
				g.faces.push( new THREE.Face3( a, c1, c2 ) ); // left-top
				
			}
			
			if ( g.isBufferGeometry ) {
			
				g.faceIndices[ idxCount     ] = a; // right-bottom
				g.faceIndices[ idxCount + 1 ] = b1;
				g.faceIndices[ idxCount + 2 ] = c1; 
				
				g.faceIndices[ idxCount + 3 ] = a; // left-top
				g.faceIndices[ idxCount + 4 ] = c1,
				g.faceIndices[ idxCount + 5 ] = c2; 
				
				idxCount += 6;
				
			}
						
		}
		
	}
	
	showGeo = true;	 // start animation
	
}

function move( t ) {
	
	if ( g.isGeometry ) g.verticesNeedUpdate  = true;
	if ( g.isBufferGeometry ) g.attributes.position.needsUpdate = true;
	
	for ( var j = 0; j < rss; j ++ ) {
		
		nj = j / rs;
		
		y = 400 * nj;							// calculate y
		
		for ( var i = 0; i < hss; i ++ ) {
			
			ni   = i / hs;
			
			x = 400 * ni;						// calculate x
			
			z =  400 * Math.sin( t + ni + nj );	// calculate z
			
			vIdx = hss * j + i; 
			
			if ( g.isGeometry ) g.vertices[ vIdx ].set( x, y, z );
			
			if ( g.isBufferGeometry ) {
			
				posIdx = vIdx * 3;
				
				g.vertices[ posIdx ]  = x;		
				g.vertices[ posIdx + 1 ]  = y;
				g.vertices[ posIdx + 2 ]  = z;
				
			}	
			
		}
		
	}
	
	if ( multicolor ) {
	
		g.groupsNeedUpdate = true;	// to change materialIndex for multi material
	
		for ( var j = 0; j < rs ; j ++ ) {
			
			for ( var i = 0; i < hs; i ++ ) {
				
				materialSegment = Math.floor( 5 * ( 1 + Math.cos( 0.2 * t + i * i + 2 * j ) ) ); // calculate material
				
				fIdx = 2 * hs * j + 2 * i;
				
				if ( g.isGeometry ) {
					
					g.faces[ fIdx ].materialIndex = materialSegment;
					g.faces[ fIdx + 1 ].materialIndex = materialSegment;
					
				}
				
				if ( g.isBufferGeometry ) {
					
					g.groups[ fIdx ].materialIndex = materialSegment;
					g.groups[ fIdx + 1 ].materialIndex = materialSegment;
					
				}
				
			}
			
		}
		
	}	
	
}

function animate() {
	
	requestAnimationFrame( animate );
	t = clock.getElapsedTime();
	
	if ( showGeo ) move( t );
	 
	renderer.render( scene, camera );
	controls.update();
	
}

Geometry is converted to BufferGeometry internally anyway, basically Geometry just allows to construct geometries with easy-to-use arrays and Face3, Vector3 etc objects, what is btw. an awfull waste of memory and slow, then converted to BufferGeometry.

1 Like

I must admit, i find that the level of forum/stack overflow questions, is mostly appalling :frowning:

The surprise: Geometry is the fastest!

“Fastest” is absolute, what is the context here, faster in what, faster compared to what?

But again, Geometry is faster.

HOW is it faster? What is the benchmark, what parameters were used? I couldnt run the demo on my phone and now i’m annoyed that i had to open my laptop to find no frame rate being recorded, no initialization times being recorded, no memory usage / garbage collection etc. etc .

This example is pretty gnarly and there is a ton going on. Three converts Geometry to BufferGeometry, you just are adding the overhead.

To avoid this overhead and all related problems, Geometry will sooner or later be deprecated.

This though, would surprise me. Geometry is a structure that helps you do operations that would otherwise be very hard on the raw buffers BufferGeometry is made out of. For example, you can do a cross product or a dot product on a “normal”, with buffers, you just have three numbers.

2 Likes

2 years later I’m facing the same problem, and I’m wondwring if I can find among assistance in this forum, judging from pailhead’s reply…

@pailhead, faster means faster than…
It was made clear that using Geometry is faster than using BufferGeometry…

Do you really need stats from an app?
Cant you see it in you browser?

So, the question for all of us (the newbies) in three.js remains:
Geometry or BufferGeometry?

There are several reasons to use Buffergeometry over Geometry.

  • It consumes less memory because Geometry creates a lot of Vector3 and Face objects.
  • GLTF imports output in BufferGeom.
  • The Animation system uses BufferGeom
  • InstancedBufferGeometry is a great solution for rendering several clones of the same object in a single drawcall.

So if you want to learn Three.js for the long term and plan to use the more advanced features in the future, it’d be in your best interest to get acquainted with BufferGeometry. If you just want to make a few spinning shapes on the screen for a quick demo without digging too deep, then Geometry should be fine.

Here’s another thread that also discusses the topic.

3 Likes

There is no remaining question, since there is no Geometry used, internally it is converted to BufferGeometry before rendering.

3 Likes

:heavy_plus_sign: There’s one more question about this. :heavy_plus_sign:


The number of 7.7k views (6th place on the list) of this post so far shows that it obviously interests many beginners burning. :fire: :information_source: :fire:

Some time ago I have added the desired stats to the speedtests. But the viewer is less interested in a theoretical value than in a smooth and largely jerk-free representation.
But these two examples clearly show the better performance of Geometry for this particular case.
I still can’t explain it to myself. I cannot completely penetrate the very complex system of three.js. :sob:

Since I have been programming for 50 years (not as a profession, but also larger projects, see e.g. page 29 Addon. Produces almost infinite many time-varying geometries with functions) I can think of something. I started with binary coding / machine language and assembler, later FORTRAN and other languages. But it doesn’t have to be true in this case. I am looking for an explanation for this phenomenon. :thinking: :thinking::thinking:

If I write an algorithm directly in binary/assembler, but don’t do this very effectively/optimally, it is possible that a very good compiler of a programming language will generate better binary code from an optimal version of the algorithm.

:question: Are there any differences in the definition of BufferGeometry and its conversion up to the CPU/GPU and the process when starting from Geometry and looking at the conversions :question:


My first addons THREEf and THREEg I realized after a hint quite consciously parallel with Geometry, indexed BufferGeometry and non-indexed BufferGeometry. Here you can compare the variants. https://github.com/hofk
Now I work with BufferGeometry. Yes, some things only work with BufferGeometry.

As a beginner I also started with Geometry in 2016. For BufferGeometry it would need more basic examples in the documentation. The examples show the potential of three.js, but are often too complex for beginners, the core of the matter is not always immediately tangible.

But who should provide these basic examples?

=> A lot has already been delivered! <= :+1:

I collect it all the time and sometimes I can’t keep up. Collection of examples from discourse.threejs.org


Conclusion:
At first BufferGeometry seems more difficult, but it is like anywhere a question of exercise and habituation.


PS:
My page was just updated from http to https. Maybe there are still some problems.
Wow, an extra-long contribution from me. Because of my miserable English: Translated by https://www.deepl.com/translator

Well, my phone is showing that the geometry versions run at about twice the FPS of the buffergeo versions, so I’d say there’s still a question left.

However, I don’t the think that the answer can be that Geometry is faster than BufferGeometry, so there is probably something else causing this.

This isn’t related to the geometry itself then, you can only feed buffers to WebGL, internally Geometry is converted to BufferGeometry, rendering a geometry with primitives would be extremely slow outside from a simple geometry like in this example.

It is rather likely the implementation for morphing the vertices it seems, morphVertices is the function called per frame.
https://hofk.de/main/threejs/sandbox/Geometry_BufferGeometry/THREEf.js

Recording in devtools morphVertices is recorded multiple times per frame for BufferGeometry and seems to be rendered in multiple parts.

Geometry 1 frame

BufferGeometry 1 frame (couldn’t fit in, multiply draw calls next)

2 Likes

This is some 3rd party implementation, and we’re profiling it?

Yes. BufferGeometry does not get converted, it’s using native structures that WebGL consumes. Geometry cannot possibly be fed to WebGL, it has to be converted to BufferGeometry.