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

[Merged by Bors] - Frustum culling #2861

Closed
wants to merge 23 commits into from

Conversation

superdump
Copy link
Contributor

Objective

Implement frustum culling for much better performance on more complex scenes. With the Amazon Lumberyard Bistro scene, I was getting roughly 15fps without frustum culling and 60+fps with frustum culling on a MacBook Pro 16 with i9 9980HK 8c/16t CPU and Radeon Pro 5500M.

macOS does weird things with vsync so even though vsync was off, it really looked like sometimes other applications or the desktop window compositor were interfering, but the difference could be even more as I even saw up to 90+fps sometimes.

Solution

  • Until the Primitive Shapes rfcs#12 RFC is completed, I wanted to implement at least some of the bounding volume functionality we needed to be able to unblock a bunch of rendering features and optimisations such as frustum culling, fitting the directional light orthographic projection to the relevant meshes in the view, clustered forward rendering, etc.
  • I have added Aabb, Frustum, and Sphere types with only the necessary intersection tests for the algorithms used. I also added CubemapFrusta which contains a [Frustum; 6] and can be used by cube maps such as environment maps, and point light shadow maps.
    • I did do a bit of benchmarking and optimisation on the intersection tests. I compared the rafx parallel-comparison bitmask approach with a naïve loop that has an early-out in case of a bounding volume being outside of any one of the Frustum planes and found them to be very similar, so I chose the simpler and more readable option. I also compared using Vec3 and Vec3A and it turned out that promoting Vec3s to Vec3A improved performance of the culling significantly due to Vec3A operations using SIMD optimisations where Vec3 uses plain scalar operations.
  • When loading glTF models, the vertex attribute accessors generally store the minimum and maximum values, which allows for adding AABBs to meshes loaded from glTF for free.
  • For meshes without an AABB (PbrBundle deliberately does not have an AABB by default), a system is executed that scans over the vertex positions to find the minimum and maximum values along each axis. This is used to construct the AABB.
  • The Frustum::intersects_obb and Sphere::insersects_obb algorithm is from Foundations of Game Engine Development 2: Rendering by Eric Lengyel. There is no OBB type, yet, rather an AABB and the model matrix are passed in as arguments. This calculates a 'relative radius' of the AABB with respect to the plane normal (the plane normal in the Sphere case being something I came up with as the direction pointing from the centre of the sphere to the centre of the AABB) such that it can then do a sphere-sphere intersection test in practice.
  • RenderLayers were copied over from the current renderer.
  • VisibleEntities was copied over from the current renderer and a CubemapVisibleEntities was added to support PointLights for now. VisibleEntities are added to views (cameras and lights) and contain a Vec<Entity> that is populated by culling/visibility systems that run in PostUpdate of the app world, and are iterated over in the render world for, for example, queuing up meshes to be drawn by lights for shadow maps and the main pass for cameras.
  • Visibility and ComputedVisibility components were added. The Visibility component is user-facing so that, for example, the entity can be marked as not visible in an editor. ComputedVisibility on the other hand is the result of the culling/visibility systems and takes Visibility into account. So if an entity is marked as not being visible in its Visibility component, that will skip culling/visibility intersection tests and just mark the ComputedVisibility as false.
  • The ComputedVisibility is used to decide which meshes to extract.
  • I had to add a way to get the far plane from the CameraProjection in order to define an explicit far frustum plane for culling. This should perhaps be optional as it is not always desired and in that case, testing 5 planes instead of 6 is a performance win.

I think that's about all. I discussed some of the design with @cart on Discord already so hopefully it's not too far from being mergeable. It works well at least. 😄

@superdump
Copy link
Contributor Author

Updated on top of #2741 (normal maps PR.)

@superdump
Copy link
Contributor Author

Updated again on top of updated #2741.

@superdump
Copy link
Contributor Author

Updated on top of pipelined-rendering, which involved dropping the 'draw sprites based on VisibleEntities' commit as visibility is used to extract the sprite or not, then batching is done, and then drawing is done based on the batches.

I only quickly tested 2D (bevymark_pipelined) and 3D (3d_scene_pipelined, load_gltf_pipelined) are working on native.

The WebGL2 testing I was doing was on top of this visibility branch, FYI. I don't think there is anything in here that prevents WebGL2 compatibility.

pipelined/bevy_render2/src/view/visibility/mod.rs Outdated Show resolved Hide resolved
pipelined/bevy_render2/src/primitives/mod.rs Outdated Show resolved Hide resolved
pipelined/bevy_render2/src/primitives/mod.rs Outdated Show resolved Hide resolved
pipelined/bevy_pbr2/src/render/light.rs Outdated Show resolved Hide resolved
pipelined/bevy_pbr2/src/render/light.rs Outdated Show resolved Hide resolved
pipelined/bevy_pbr2/src/render/light.rs Outdated Show resolved Hide resolved
pipelined/bevy_pbr2/src/render/light.rs Outdated Show resolved Hide resolved
self.data.iter_mut()
}
}

/// A component bundle for "point light" entities
#[derive(Debug, Bundle, Default)]
pub struct PointLightBundle {
Copy link
Member

Choose a reason for hiding this comment

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

Not all lights need to cast shadows, and given how expensive point light shadows are, its probably worth adding a way to disable shadows for lights. This should also skip entity visibility calculations.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have another branch for that that comes when I knew it would be needed - before clustered forward rendering. Can we add it in after #3072 or do I have to move it in here?

Copy link
Member

Choose a reason for hiding this comment

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

I'm definitely down to wait!

Copy link
Member

Choose a reason for hiding this comment

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

We can do it in whatever order works best for you :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Then I'll wait on this one as it's coming just after depth prepass and alpha modes. Thanks. :)

@cart
Copy link
Member

cart commented Nov 7, 2021

bors r+

bors bot pushed a commit that referenced this pull request Nov 7, 2021
# Objective

Implement frustum culling for much better performance on more complex scenes. With the Amazon Lumberyard Bistro scene, I was getting roughly 15fps without frustum culling and 60+fps with frustum culling on a MacBook Pro 16 with i9 9980HK 8c/16t CPU and Radeon Pro 5500M.

macOS does weird things with vsync so even though vsync was off, it really looked like sometimes other applications or the desktop window compositor were interfering, but the difference could be even more as I even saw up to 90+fps sometimes.

## Solution

- Until the bevyengine/rfcs#12 RFC is completed, I wanted to implement at least some of the bounding volume functionality we needed to be able to unblock a bunch of rendering features and optimisations such as frustum culling, fitting the directional light orthographic projection to the relevant meshes in the view, clustered forward rendering, etc.
- I have added `Aabb`, `Frustum`, and `Sphere` types with only the necessary intersection tests for the algorithms used. I also added `CubemapFrusta` which contains a `[Frustum; 6]` and can be used by cube maps such as environment maps, and point light shadow maps.
  - I did do a bit of benchmarking and optimisation on the intersection tests. I compared the [rafx parallel-comparison bitmask approach](https://github.com/aclysma/rafx/blob/c91bd5fcfdfa3f4d1b43507c32d84b94ffdf1b2e/rafx-visibility/src/geometry/frustum.rs#L64-L92) with a naïve loop that has an early-out in case of a bounding volume being outside of any one of the `Frustum` planes and found them to be very similar, so I chose the simpler and more readable option. I also compared using Vec3 and Vec3A and it turned out that promoting Vec3s to Vec3A improved performance of the culling significantly due to Vec3A operations using SIMD optimisations where Vec3 uses plain scalar operations.
- When loading glTF models, the vertex attribute accessors generally store the minimum and maximum values, which allows for adding AABBs to meshes loaded from glTF for free.
- For meshes without an AABB (`PbrBundle` deliberately does not have an AABB by default), a system is executed that scans over the vertex positions to find the minimum and maximum values along each axis. This is used to construct the AABB.
- The `Frustum::intersects_obb` and `Sphere::insersects_obb` algorithm is from Foundations of Game Engine Development 2: Rendering by Eric Lengyel. There is no OBB type, yet, rather an AABB and the model matrix are passed in as arguments. This calculates a 'relative radius' of the AABB with respect to the plane normal (the plane normal in the Sphere case being something I came up with as the direction pointing from the centre of the sphere to the centre of the AABB) such that it can then do a sphere-sphere intersection test in practice.
- `RenderLayers` were copied over from the current renderer.
- `VisibleEntities` was copied over from the current renderer and a `CubemapVisibleEntities` was added to support `PointLight`s for now. `VisibleEntities` are added to views (cameras and lights) and contain a `Vec<Entity>` that is populated by culling/visibility systems that run in PostUpdate of the app world, and are iterated over in the render world for, for example, queuing up meshes to be drawn by lights for shadow maps and the main pass for cameras.
- `Visibility` and `ComputedVisibility` components were added. The `Visibility` component is user-facing so that, for example, the entity can be marked as not visible in an editor. `ComputedVisibility` on the other hand is the result of the culling/visibility systems and takes `Visibility` into account. So if an entity is marked as not being visible in its `Visibility` component, that will skip culling/visibility intersection tests and just mark the `ComputedVisibility` as false.
- The `ComputedVisibility` is used to decide which meshes to extract.
- I had to add a way to get the far plane from the `CameraProjection` in order to define an explicit far frustum plane for culling. This should perhaps be optional as it is not always desired and in that case, testing 5 planes instead of 6 is a performance win.

I think that's about all. I discussed some of the design with @cart on Discord already so hopefully it's not too far from being mergeable. It works well at least. 😄
@bors
Copy link
Contributor

bors bot commented Nov 7, 2021

@bors bors bot changed the title Frustum culling [Merged by Bors] - Frustum culling Nov 7, 2021
@bors bors bot closed this Nov 7, 2021
bors bot pushed a commit that referenced this pull request Nov 16, 2021
# Objective

Add depth prepass and support for opaque, alpha mask, and alpha blend modes for the 3D PBR target.

## Solution

NOTE: This is based on top of #2861 frustum culling. Just lining it up to keep @cart loaded with the review train. 🚂 

There are a lot of important details here. Big thanks to @cwfitzgerald of wgpu, naga, and rend3 fame for explaining how to do it properly!

* An `AlphaMode` component is added that defines whether a material should be considered opaque, an alpha mask (with a cutoff value that defaults to 0.5, the same as glTF), or transparent and should be alpha blended
* Two depth prepasses are added:
  * Opaque does a plain vertex stage
  * Alpha mask does the vertex stage but also a fragment stage that samples the colour for the fragment and discards if its alpha value is below the cutoff value
  * Both are sorted front to back, not that it matters for these passes. (Maybe there should be a way to skip sorting?)
* Three main passes are added:
  * Opaque and alpha mask passes use a depth comparison function of Equal such that only the geometry that was closest is processed further, due to early-z testing
  * The transparent pass uses the Greater depth comparison function so that only transparent objects that are closer than anything opaque are rendered
  * The opaque fragment shading is as before except that alpha is explicitly set to 1.0
  * Alpha mask fragment shading sets the alpha value to 1.0 if it is equal to or above the cutoff, as defined by glTF
  * Opaque and alpha mask are sorted front to back (again not that it matters as we will skip anything that is not equal... maybe sorting is no longer needed here?)
  * Transparent is sorted back to front. Transparent fragment shading uses the alpha blending over operator

Co-authored-by: Carter Anderson <mcanders1@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Rendering Drawing game state to the screen C-Feature A new feature, making something new possible
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants