mrdoob / three.js

JavaScript 3D Library.
https://threejs.org/
MIT License
102.77k stars 35.38k forks source link

BatchedMesh and gl.LINES, gl.POINTS #29018

Open nkallen opened 3 months ago

nkallen commented 3 months ago

Description

I am currently using BatchedMesh extensively -- not just with meshes but also with gl.LINES and gl.POINTS

Note that multidraw does accept a mode: extension.multiDrawElementsWEBGL( mode, ... )

It may be unintended, but the isBatchedMesh check in WebGLRenderer does not set the mode; rather it is set a few lines before with checks on isPoint and isLine. This is very convenient and everything almost works out of the box. Only the points shader isn't fully compatible.

Solution

The only snag is that points.glsl.js needs to add #include <batching_vertex>

Alternatives

Well, the shader could be manipulated with a string replace but I think that is a gnarly hack

Additional context

I do want to note that while I don't actually use the THREE.BatchedMesh class very much, I have my own class with isBatchedMesh = true to benefit from access to multidraw

gkjohnson commented 3 months ago

This sounds like a fairly minimal change and I'd support adding the couple lines required to the points and line dashed materials.

Out of curiosity - what's your use case for BatchedMesh lines and points?

nkallen commented 3 months ago
Screen Shot 2024-07-30 at 14 17 13

I'm working on a CAD/3d modeling application. The user edits things like solids, curves, and surfaces. Curves and surfaces have control points that can be edited. Solids have faces and edges that can be selected and manipulated.

(We now use multidraw for everything possible, rather than one draw call per object. And we manage the buffer on the gpu explicitly using THREE.GLBufferAttribute)

In the attached picture you can see faces, edges, vertices, cvs, regions, hulls, curve segments. The points (cvs + vertices) are rendered using gl.POINTS. Curves and edges are rendered using LineSegments2. However, the in the image the editor is also in "xray mode" which renders edges and curves a second time using gl.LINES without depth testing, so you can see "through" objects.

You might wonder: why have both LineSegments2 and gl.LINES? It's just for performance reasons: gl.LINES is much faster if you only need 1px lines (which is what we do in xray mode, and for hulls). LineSegments2 is expensive both because of the massive instancing operation and (more importantly) it is extremely stressful for MSAA on the GPU. (We currently use MSAA but we will move away from it if I can find a high quality replacement)

gkjohnson commented 3 months ago

LineSegments2 is expensive both because of the massive instancing operation and (more importantly) it is extremely stressful for MSAA on the GPU. (We currently use MSAA but we will move away from it if I can find a high quality replacement)

A bit off topic but I do wonder what kind of performance improvements can be made to Line2. I haven't done any specific benchmarks but does just using instancing specifically cause such a significant performance loss? My expectation was that by using discard and / or alpha-to-coverage there would be a lot of performance loss due to overdraw (no early z rejection). I do wonder if using something like 4 or 5 triangles per cap to round the edges rather than using pixel discard could help things.

nkallen commented 3 months ago

It's on my todo to investigate more. In a test scene I use, a fully detailed ww2 tank with about 5k non-instanced objects, about 50mb worth of edges (LineSegments2):

Screen Shot 2024-07-30 at 16 11 40

The edges are by far the most expensive thing. I tried getting rid of the "mitres" (a few triangles) and didn't notice a significant difference. Almost by accident I decided to turn off MSAA and this is what I saw:

Screen Shot 2024-07-30 at 16 13 32

I read somewhere recently that very long skinny triangles are bad for gpus, and indeed that is what is happening here (basically 2px fat lines).

I read in a stackoverflow comment somewhere that someone also saw a performance benefit from reading from a texture rather than instancing. I find that hard to believe, but it's worth investigating also.

nkallen commented 3 months ago

Well, interesting tidbit re edges. On my AMD gpu, the MSAA is the major bottleneck. On my Macbook M3 it is not, edges are still the most expensive thing and disabling msaa has a very small effect.

CodyJasonBennett commented 3 months ago

I read somewhere recently that very long skinny triangles are bad for gpus, and indeed that is what is happening here (basically 2px fat lines).

Small triangles have very poor quad utilization in rasterization, so you could see performance issues similar to overdraw where extra tiles are spawned purely as helper invocations, and this adversely affects mobile/TBDR GPUs. Desktop IMR GPUs are not as affected since triangles are rasterized immediately, which would explain the discrepancy you're seeing with those small triangles and increasing their coverage with MSAA.