I’m not one to argue much on semantics, it’s the idea that’s important. Simple example in the fiddle here (disregard the other unrelated stuff there): the rotations perform zero increments on every frame (basically, doing nothing), if you press the P key to animate. If you press the P key again, you can pause it via cancelAnimationFrame(), with the same visual effect as not doing it at all … but with a noticeable effect on the CPU / GPU usage, if you’re curious to monitor them in real time like I do.
Note: You might need to focus the fiddle’s output window first via a click on it, in order for it to receive keystroke events.
That’s fine, but for the sake of debugging this specific example (which got me really curious) i think the simplest the loop the better. Thousands of planes should be trivial to render even on low end gpus if it’s properly set up.
Agree on simplicity, it’s desirable, but not always possible or available when it comes to projects tied up to all kinds of dependencies - I’m talking in general. This got me curious as well, for roughly the same reason as you got curious: serious slowdown shouldn’t happen in such cases, at least from what we know about the project. I’m sure you know it already, most of the times it’s the little details that make a difference in these cases, the kind of details one wouldn’t think they can cause such an effect.
As for rendering lots of stuff easily on low end GPUs, I guess it depends on the definition someone uses for such cards. I know Three.js and the like can render a lot in general and for sure how it’s implemented matters significantly, but for some cards it’s a bit harder to do that without some more serious resource consumption, especially if their time has passed, so to speak. People like to talk about “modern” hardware and such, but not everybody has it or can afford it, especially at the rate technologies change nowadays.
My reasoning is that for example walls, if they made 1000 of those 2000 draw calls, could be merged into a mesh that is 2000 triangles, which should be trivial to render. Unless depth testing is disabled, and we are rendering in the worst possible order filling the screen with each wall (somehow).
this example can help for different textures for instanced mesh, though I am loading 5 different imgs as a texture, you can use texture atlas and pass uv offsets as a attributes instead of index of texture in shader.
Hi,
In your example you have walls as a mesh already, but they are only meshes after they have been turned into one by passing the planegeometry into the constructor, which in turn makes a new mesh thus a new draw call. So would the positioning still work if I merged geometries before turning it into a mesh? Not sure how it does the wall rotation as at the moment I am using a quaternion on the mesh.
Does the draw call happen once the mesh has been created or on the renderer.render function?
Indeed, but how about the mesh / geometry size? Doesn’t that also affect the time needed to render? Sure, it’s a single draw call, but it’s a draw call for something 2000 bigger - just saying…
Yes. If you look at the code, a new mesh is being created and a different mesh not called “singlePlane” but “manyPlanes” is passed to the constructor. Well, “merged” in my snippet, but anyway…
If you don’t add a mesh to the scene the renderer will never consume it. So not only will you not have draw calls, you won’t even upload the single plane to the gpu. You need to set your quarter ions and positions on the meshes before you merge (before the update matrix world call).
Yes and no. The draw call is usually much more expensive than drawing many triangles. Of course there are many circumstances, but a simple shader, with a simple transformation, yes.
If you look at threes examples I’m sure that there are some rendering millions of triangles on phones. Drawing 2000 should be the same as drawing two. Again depending on other things, but generally faster.
If you set 2000 triangles to render full screen one on top of another (back to front) you won’t be able to render a single frame and you will lose your webgl context or crash the entire page.
Actually, it’s on the first render. When you render your 2000 meshes for the first time, the plane geometry gets uploaded to the gpu. I’m my snippet a single geometry containing the combined result of 2000 geometries will be uploaded once you render the scene.
I think it’s safe to assume that occlusion culling is way over your head?
I see, so merging geometries generally pays off, though it also depends on circumstances like complexity or particularities of the setup - thanks for the details.
P.S. Ideally, one would probably like to merge all geometries in one … but with the ability to treat parts of it separately, for say, rotations or other localized transformations.
I am more versed with OpenGL on desktop than web, but there is no THREE.xxxx in OpenGL it is all drawing buffers and vertex arrays. So yes for THREE it is over my head.
If I upload the combined result will that remove any colouring/texturing already applied? Not that it matters for testing but I want to know what it is supposed to do so I know I haven’t made a mistake.
If you apply my snippet to the T, you’re going to probably get a random colored flat shaded geometry, or just flat white, because i’m not assigning any material to the mesh i just let it create the default.
So yes, it should render without coloring and texturing. Think of it this way - should the far property of the camera have anything to do with objects colors? If you think it should, why do you think so? There should be absolutely no difference between rendering some geometry as red and yellow for example.
So, why have textures and colors if you’re looking at frustrum culling, camera clipping and such? Focus just on the transformations and the vertex shaders.
OpenGL should actually help you understand better what three.js does, not make things even more confusing. In my approach, this problem is narrowed down to one vertex array and a couple of uniforms (attribute vec3 position and uniform mat4 projectionMatrix, modelViewMatrix;) You couldn’t do this much differently with openGL if you wanted to look at some “mesh” through some “camera” or “perspective” ,
Would introducing textures in OpenGL be more complex than just using flat colors? Or better yet just using one single color for everything? If it would, then why do you think it would be a good idea to test all these complex things at once?
If anyone wants to play with this stuff, i don’t have the time atm, but i think a few useful snippets could be written and tested.
For example:
const pg = new PlaneBufferGeometry(1,1,1,1)
for (let i = 0; i < 1000 ; i ++ ) {
const mesh = new Mesh(pg)
scene.add(mesh)
}
Should crash the tab if you have orbit controls and zoom in so the plane fills the entire screen. But if you start far, and the plane(s) only take a few pixels on screen it should render (but also probably slow because of the 1000 draw calls).
Then something like this:
const pg = new PlaneBufferGeometry(1,1,1,1)
for (let i = 0; i < 1000 ; i ++ ) {
const mesh = new Mesh(pg)
mesh.position.z = i * 0.001
mesh.renderOrder = 1000 - i
scene.add(mesh)
}
Should not crash if you zoom, but should still render slow.
It starts of very fast, probably 144hz on my 2080ti, but if i zoom, even in this non-full screen window it will drop and struggle.
Yet, with the slight tweak from the other snippet, it runs at max even when you zoom in:
My machine is dealing fine with the 1000 draw calls, but it’s also struggling with the fill rate. I could reduce this to one draw call, and still set it up so that i have the fill rate (i think its the fill rate) problem.
Something like this, based on your mention of zooms and fars, is my guess what is going on in your app. It would have been nice to even have just had a screenshot.
snapshot of the frame showing 1000 draw calls (actually +1 because of the Axes)
the number of meshes stay the same
there is no frustrum culling going on (you can verify and set frustrumCull to false)
seeing as this is the first bit of code you’ve provided, this give a lot of insight. Math.abs, which you are calling 4 times a frame in one line is a huge signifier of detrimental performance, as you can see in the following image, Math.abs can take up to 308ms to evaluate on safari, the quickest evaluation being chrome at 111ms, you’re calling this every frame. there will be much quicker ways to achieve what you’re trying to do here… such as rounding up the values of your vector3 camera positions with .ceil three.js docs
I tried this, I had to correct a few mistakes like tempGeom rather than tempGeometry etc, and I changed the assignment to said variable as they are a three.group (just the one mesh added to the group) for better positioning to: