Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TerrainExtension (2/3) #7605

Merged
merged 14 commits into from
Feb 13, 2023
Merged

TerrainExtension (2/3) #7605

merged 14 commits into from
Feb 13, 2023

Conversation

Pessimistress
Copy link
Collaborator

@Pessimistress Pessimistress commented Jan 28, 2023

For #7195

See docs for API review.
Tests will be in the next PR.

Change List

  • Add TerrainExtension
  • Override the getBounds method in SimpleMeshLayer to account for the non-instanced use case (used by the TerrainLayer)
  • Documentation


const MAX_SIZE = 2048;

// TODO - import from loaders when Tileset2D is split out
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI @ibgreen - I'm not importing the types from geo-layers because geo-layers currently depend on extensions. TypeScript really does not like circular dependencies. Also a good argument to strip Tileset2D out of that increasingly complex module.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As long as you use import type you might be OK, as TypeScript does support for circular import type declarations.

But I agree we should try very hard to avoid this circularity as it is really confusing and will lead to other things creeping in down the line.

BTW, I got a good way on the PR separating Tileset2D from Viewport but then ran into issues:

  • both node and browser tests are badly broken on Apple M1, so I really couldn't iterate in an efficient way, replying on CI takes like 10-15 minutes per cycle.
  • Also there is one part where the Tileset2D interacts back and forth with the viewport which makes it hard to make the required values static input parameters, would be great to get your thoughts on that.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 on moving Tileset2D/TileHeader into core

if (!terrainFittingMode) {
// props.extruded is used as an indication that the layer is 2.5D
// @ts-ignore `extruded` may not exist in props
const is3d = this.props.extruded as boolean;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to note here that MVTLayer renders a single SolidPolygonLayer for both extruded and non-extruded polygons (by returning 0 from getElevation). This makes it impossible to render the park layer as draped and the building layer as offset. We have discussed separating features by MVT layer, and this is an example where it is necessary.

@felixpalmer @alasarr

@coveralls
Copy link

coveralls commented Jan 31, 2023

Coverage Status

Coverage: 88.855% (-1.3%) from 90.146% when pulling 8ddedf1 on x/terrain-2 into bd5bcf1 on master.

Copy link
Collaborator

@ibgreen ibgreen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some quick nits from first read through.


const MAX_SIZE = 2048;

// TODO - import from loaders when Tileset2D is split out
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As long as you use import type you might be OK, as TypeScript does support for circular import type declarations.

But I agree we should try very hard to avoid this circularity as it is really confusing and will lead to other things creeping in down the line.

BTW, I got a good way on the PR separating Tileset2D from Viewport but then ran into issues:

  • both node and browser tests are badly broken on Apple M1, so I really couldn't iterate in an efficient way, replying on CI takes like 10-15 minutes per cycle.
  • Also there is one part where the Tileset2D interacts back and forth with the viewport which makes it hard to make the required values static input parameters, would be great to get your thoughts on that.

modules/extensions/src/terrain/terrain-cover.ts Outdated Show resolved Hide resolved
modules/extensions/src/terrain/terrain-cover.ts Outdated Show resolved Hide resolved
modules/extensions/src/terrain/terrain-cover.ts Outdated Show resolved Hide resolved
modules/extensions/src/terrain/terrain-cover.ts Outdated Show resolved Hide resolved
modules/extensions/src/terrain/terrain-cover.ts Outdated Show resolved Hide resolved
return layers;
}

/** If layer is the descendent of a TileLayer, return the corresponding tile. */
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty deep knowledge about layer types... Do we may want to elevate some coreTileLayer into the canonical set of "building block" layers (Layer, CompositeLayer, ...)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That may not be a bad idea...

modules/extensions/src/terrain/terrain-picking-pass.ts Outdated Show resolved Hide resolved
docs/api-reference/extensions/terrain-extension.md Outdated Show resolved Hide resolved
docs/api-reference/extensions/terrain-extension.md Outdated Show resolved Hide resolved
docs/api-reference/extensions/terrain-extension.md Outdated Show resolved Hide resolved

const MAX_SIZE = 2048;

// TODO - import from loaders when Tileset2D is split out
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 on moving Tileset2D/TileHeader into core

modules/extensions/src/terrain/terrain-cover.ts Outdated Show resolved Hide resolved
useInPicking = true;

/** true if inside a picking pass */
private isPicking: boolean = false;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that useInPicking is set on Effect, would it not be better to move isPicking there also and then have it set externally rather than inferring from the Pass?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically this flag is not a property of the effect, but a property of the current pass. What we need is an isPicking in PreRenderOptions, just like in LayerFilterContext. I'm not making that change here because it's going to be a much more extensive API change. All these bits of information about the current render pipeline are scattered and slightly different when passed to each effect method, layer method, pass method, and user callbacks. We need to do a thorough review of the Effect interface in v9.0, now that we have a lot more use cases to consider than when it was first designed.

Given that we are very close to the 8.9 release, I propose that we don't rush to another big API overhaul prematurely. At least this is a self-contained module and will be easy to refactor when we're ready.

modules/extensions/src/terrain/terrain-effect.ts Outdated Show resolved Hide resolved
...opts,
pass: 'terrain-height-map',
viewports: [viewport],
layers: terrainLayers,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another worry about updating the viewport is that slight changes in the viewport will result in a different rasterized heightmap. Thus offset features will wobble as the camera is moved. An extreme example is a thin column terrain layer offseting points. Sometimes the sample will hit and other times miss

Screen.Recording.2023-02-02.at.13.14.22.mov

Perhaps the heightmap could be rendered into a viewport defined in the coordinate space of the terrain layer?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was a hard decision to make. There is only one height map for all layers. If we index the height information by common space coordinates instead of clipspace coordinates, then for high-pitch scenarios, the resolution closer to the camera will be very bad. I can try construct a viewport from the pitch, bearing and the common bounds, which could be more stable. The shadow module suffers from the similar problem, and honestly it's never completely solved.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great if at least when panning we could avoid the glitches. I know it is a hard problem to solve, one approach I've used in the past was to quantize the viewport calculations (e.g. round to the nearest degree/zoom) so that small changes don't cause a heightmap re-render. While this doesn't completely solve the issue it at least means there are fewer jumps.

I also have the same issue in the CollideExtension where fading isn't completely smooth due to sampling precision. I've considered writing the visibilities into a intermediate buffer/texture and averaging over frames to smooth out the visibility value, but it is too much to fit into the current work, but perhaps something to consider as a future improvement.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we index the height information by common space coordinates instead of clipspace coordinates, then for high-pitch scenarios, the resolution closer to the camera will be very bad.

Superficially, this seems quite similar to the problems that arise for perspective shadow maps. The good news is that there are a wide range of techniques to address those.

Rendering the shadowmap in three zones (top thirds, middle third, bottom third), rendering by object distance, logarithmic shadowmaps etc. It is not hard to find good articles.

If my hunch is right, we might even be able to share such code with an improved shadow effect?

Now this implementation seems to have some nice advantages, it should have good performance characteristics for static layers, however for heavily animated layers or very dense scenes where the draw call count becomes very high with this method, it is not impossible that a more "traditional", non-tiled approach could be useful

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you both for the suggestions. I'm interested in exploring this idea further in a future PR, especially as a reusable strategy in multiple effects. As it is now the height map texture is not bound to any viewport type (common space -> clip space projection is handled by its own shader, not the standard project module) which means we could potentially use regions of this texture differently to provide multi-tier resolution.

This PR is already huge as is. I acknowledge that various functionalities could still be improved, but I prefer that we land a good skeleton first then improve it incrementally.

...opts,
layers: drapeLayers,
moduleParameters: {
dummyHeightMap: this.dummyHeightMap,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are using this dummyXXXXMap pattern in a number of effects. Would it be worth having a single global dummy map to avoid having to duplicate resources?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ibgreen Maybe this is a luma question - is it reasonable to add something like Texture.getDefaultTexture(device)? These "dummy" textures are never read in the shader, just to make sure that all samplers are supplied for the model to render.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is indeed annoying that one cannot detect in the shader if a texture or sampler was actually supplied. Additional boolean uniforms tend to be used for this.

I suspect there is a perf advantage to the boolean uniform approach as it avoids the sample/memory read but the default texture idea is not bad for cases where this is not a perf bottleneck.

Supplying a default 1x1 texture would not be a problem. However we may need to check for WebGPU, where the setup is more rigid. No point in adding an interface in 8.9 that we have to retire in 9.0

modules/extensions/src/terrain/terrain-effect.ts Outdated Show resolved Hide resolved
@alasarr alasarr added this to the 8.9 milestone Feb 2, 2023
@Pessimistress Pessimistress force-pushed the x/terrain-2 branch 2 times, most recently from e4d92cd to 1e6a3d4 Compare February 4, 2023 02:20
modules/extensions/src/common/projection-utils.ts Outdated Show resolved Hide resolved
modules/extensions/src/index.ts Show resolved Hide resolved
modules/extensions/src/terrain/height-map.ts Outdated Show resolved Hide resolved
modules/extensions/src/terrain/height-map.ts Outdated Show resolved Hide resolved
SKIP: 5
};

const TERRAIN_MODE_CONSTANTS = Object.keys(TERRAIN_MODE)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we were changing them with some frequency that could make some sense. But our projection system feels quite complicated now, The saved effort in not having to change these during an update to this system must be really small.

modules/extensions/src/terrain/terrain-cover.ts Outdated Show resolved Hide resolved
...opts,
pass: 'terrain-height-map',
viewports: [viewport],
layers: terrainLayers,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we index the height information by common space coordinates instead of clipspace coordinates, then for high-pitch scenarios, the resolution closer to the camera will be very bad.

Superficially, this seems quite similar to the problems that arise for perspective shadow maps. The good news is that there are a wide range of techniques to address those.

Rendering the shadowmap in three zones (top thirds, middle third, bottom third), rendering by object distance, logarithmic shadowmaps etc. It is not hard to find good articles.

If my hunch is right, we might even be able to share such code with an improved shadow effect?

Now this implementation seems to have some nice advantages, it should have good performance characteristics for static layers, however for heavily animated layers or very dense scenes where the draw call count becomes very high with this method, it is not impossible that a more "traditional", non-tiled approach could be useful

...opts,
layers: drapeLayers,
moduleParameters: {
dummyHeightMap: this.dummyHeightMap,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is indeed annoying that one cannot detect in the shader if a texture or sampler was actually supplied. Additional boolean uniforms tend to be used for this.

I suspect there is a perf advantage to the boolean uniform approach as it avoids the sample/memory read but the default texture idea is not bad for cases where this is not a perf bottleneck.

Supplying a default 1x1 texture would not be a problem. However we may need to check for WebGPU, where the setup is more rigid. No point in adding an interface in 8.9 that we have to retire in 9.0

@@ -224,6 +226,32 @@ export default class SimpleMeshLayer<DataT = any, ExtraPropsT = {}> extends Laye
});
}

getBounds(): [number[], number[]] | null {
if (this.props._instanced) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this prop?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was an experimental prop we introduced when working on tiled geometries. By default this layer interprets mesh.POSITION as meter offsets from the anchor (instancePosition) regardless of the anchor's coordinate system. If _instanced: false then the layer only renders 1 copy of the mesh and mesh.POSITION is interpreted as world space offset (i.e. in LNGLAT mode the vertex lnglat is calculated as instancePosition + meshPosition). It's used by the i3s and terrain use cases. I agree that the prop name does not make much sense to the user, hence the underscore.

@Pessimistress Pessimistress merged commit dbfb31c into master Feb 13, 2023
@Pessimistress Pessimistress deleted the x/terrain-2 branch February 13, 2023 20:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants