CesiumGS / cesium

An open-source JavaScript library for world-class 3D globes and maps :earth_americas:
https://cesium.com/cesiumjs/
Apache License 2.0
12.73k stars 3.45k forks source link

i3dm renders incorrectly when model has non-identity matrix #11176

Open bbbbx opened 1 year ago

bbbbx commented 1 year ago

When the transformation matrix of the glTF node of i3dm is not the identity matrix, the calculation of the bounding sphere of the model(scene graph/primitive/draw command) is wrong, causing the far and near planes of the viewing cone to be smaller than expected, and finally the pixels outside the bounding sphere are discarded in the fragment shader(writeLogDepth.glsl).

Screenshot:

https://user-images.githubusercontent.com/22176164/226557527-dbdad565-b8fc-4ee7-8f43-d4ead14cfa95.mp4

Sandcastle example: url

Browser: Google Chrome 111.0.5563.65

Operating System: Windows 10

bbbbx commented 1 year ago

data: InstancedOrientation.zip, modify from https://github.com/CesiumGS/cesium/tree/main/Apps/SampleData/Cesium3DTiles/Instanced/InstancedOrientation

bbbbx commented 1 year ago

Model bounding sphere:

root node scale: 0.1 root node scale: 1.0
image image
ggetz commented 1 year ago

Thanks for the report @bbbbx!

LHolst commented 1 year ago

I have encountered a similar behavior with i3dm tiles, incorrectly clipping and warping vertices (looks like floatingpoint precision)

I can provide the tileset with some trees placed: Symbole_Baum_test_tileset.zip

Demo in Sandcastle 1.106

The same tileset is working just fine in 1.83.

LHolst commented 1 year ago

i retried with a simpler example, cube 10 units. The difference to the sandcastle InstancedOrientation tileset is the extra attribute SCALE_NON_UNIFORM

cube10m_instanced_tileset.zip

this leads to wobbly vertex displacements :

cube10_instanced_tileset.webm

LHolst commented 1 year ago

I've found a workaround for cases when rtc_center property is defined in i3dm files. If rtc_center is defined, the step for manual computation of a BoundingSphere and storing positions relative to the center is skipped. But if rtc_center is equal to [0,0,0] or Cartesian3.ZERO, the manual computation should be used as well. This seems to correct the rendering for the provided cases.

ggetz commented 10 months ago

Also reported in https://github.com/CesiumGS/cesium/issues/11617.

javagl commented 1 month ago

After this came up several times in the forum, most recently at https://community.cesium.com/t/disappearing-i3dms-on-map-navigation-again/34035 , I did a short debugging pass. Well. Not so short, actually. What's particularly humbling is that the statement that was made in the first comment, namely

the calculation of the bounding sphere of the model(scene graph/primitive/draw command) is wrong,

was spot on (and it took me a while to arrive at the same "insight").


Some details:

The caveat is that this node transform may, for example, involve scaling, and that scaling will therefore be indirectly applied to the instancing transforms when it comes to determining their effect on the bounding sphere.

Maybe it becomes clearer with some random debug log from some test model:

The case where it's not working, when a primitive is attached to a node with a uniform scale of 0.01:

primitive min/max is (-3558.85693359375, -2171.158447265625, -44.34097671508789) (3385.92578125, 3999.103271484375, 462.9575500488281)
instancing min/max is (-81.875, -975.484375, -156) (81.875, 975.484375, 156)
new primitive min/max is (-3640.73193359375, -3146.642822265625, -200.3409767150879) (3467.80078125, 4974.587646484375, 618.9575500488281)
Draw command bounding sphere is 
  center: Cartesian3 ...
  radius: 54.11951398537193

This cannot be right. The instancing translations alone would require a radius of ~1000.

The case where it is working, because the scale of 0.01 is "baked" into the primitive, and the node transform is the identity:

primitive min/max is (-3.376162052154541, -1.1918214559555054, -13.400678634643555) (3.376161575317383, 103.71656036376953, 4.557344913482666)
instancing min/max is (-81.875, -975.484375, -156) (81.875, 975.484375, 156)
new primitive min/max is (-85.25116205215454, -976.6761964559555, -169.40067863464355) (85.25116157531738, 1079.2009353637695, 160.55734491348267)
Draw command bounding sphere is  
  center: Cartesian3 ...
  radius:  1044.5781589720143

The bounding sphere here is properly computed from the primitive min/max plus the instancing min/max. No scaling is applied afterwards.


Now, the question is still: How to solve this?

One could be tempted to just omit some scaling here or there, to compute the proper bounding sphere.

But it's not that simple.

There could be instancing information (like from EXT_mesh_gpu_instancing) that actually is attached to a node that involves a scaling. And in this case, the instancing translations should be affected by the node transform.

In contrast to that, the I3dmLoader.createInstances function assigns the instancing information that was created from the I3DM data to all nodes of the glTF, regardless of whether they contain a scaling factor or not. This means that the instancing translations should not be affected by the node transform.

(I'm talking about "affecting" here - I should be more precise, and say whether this affects the rendering or some bounding volume computation in some draw command. But .. it's complicated. One thing that I want to try out is whether the rendering even works properly when there is an I3DM for a glTF that contains two nodes with different scaling factors. It's not yet clear whether the "error" here is really only affecting the draw command bounding volume part, or whether it also affects the rendered instances. I hope that it's only the draw command. But this has to be investigated further)

javagl commented 1 month ago

@bertt already provided very helpful data in the forum thread...s, that helped to narrow down the issue.

I created another test ("CC0", so to speak).

It is an I3DM with positions

        new float[] {
            0.0f, 0.0f, 0.0f,
            10.0f, 0.0f, 0.0f,
            0.0f, 10.0f, 0.0f,
            10.0f, 10.0f, 0.0f,
        }

This is used to create four instances of a GLB that contains some data that aims at

For the latter, there are a bazillion possible reasons. (I mean, starting with the fact that the I3DM only contains positions, but no scaling/up-vectors etc). But the glTF consists of 4 unit cubes:

(I obviously ~"tried to cover several cases" here. Doing this thoroughly and systematically, sneaking in scaling and translation (and maybe rotation etc) everywhere, and/or using transforms that "cancel out" each other, would yield a combinatorial explosion. The cases have been selected based on the insights from the debugging pass, just to have a few "hot candidates", and a first, basic test...)

The good news is: The bug does not affect the rendering of the instances themself. Regardless of the scaling and whatnot, they are all unit cubes at the proper locations:

Cesium Issue 11176 disappearing in I3DM

The ... also good... ? or bad...? ... the other news is that this already shows the problem:

Cesium 11176 disappearing in I3DM

Only the green cube seems to ever disappear. It is the only one that is attached to a node with a global scaling less than 1.0. Based on the previous debugging pass, this is not toooo surprising: For the other cubes, it will just overestimate the size of the object, roughly meaning ~"a reduced culling-efficiency" - it will still 'render' these nodes even if it wouldn't have to.

I'll have a short look at possible solutions for all this, and maybe create a draft PR.


EDIT: Nearly forgot: Here's the test data, with a tileset JSON and sandcastle:

CesiumJS issue 11176 test.zip

javagl commented 1 month ago

An old thread that very likely refers to the same issue: https://community.cesium.com/t/the-gpu-instantiated-artifacts-will-disappear-when-i-rotate-the-model-in-cesium/24688