bevyengine / bevyengine/bevy

`bevy_math`: `ViewFrustum` implementation is incorrect

Open
#23,875 0 comments 0 reactions 0 assignees View on GitHub
A-Camera A-Math A-Rendering C-Bug S-Needs-Review
Dominant language
Rust
Stars
48.2k
Forks
4.8k
Avg merge
3d 16h
Merged PRs (30d)
171

Description

## Bevy 0.19

bevy_math: The implementation of `ViewFrustum` seems to be incorrect.

But I'm puzzled as to why the rendering system can still work normally if there's a design error here. Hope it's just my misunderstanding.

In the right-handed coordinate system, a camera with a near plane distance of 1 and a far plane distance of 100 is set, looking towards the -Z direction. The calculated results are as follows:

```text
=== from_clip_from_world ===
0: Left: Vec4(0.8660254, -0.0, -0.5, 0.0)
1: Right: Vec4(-0.8660254, 0.0, -0.5, 0.0)
2: Top: Vec4(0.0, -0.8660254, -0.5, 0.0)
3: Bottom: Vec4(0.0, 0.8660254, -0.5, 0.0)
4: Near: Vec4(0.0, -0.0, -1.0, -0.5025126)
5: Far: Vec4(0.0, -0.0, -1.0, -1.0)

=== from_clip_from_world_custom_far ===
0: Left: Vec4(0.8660254, -0.0, -0.5, 0.0)
1: Right: Vec4(-0.8660254, 0.0, -0.5, 0.0)
2: Top: Vec4(0.0, -0.8660254, -0.5, 0.0)
3: Bottom: Vec4(0.0, 0.8660254, -0.5, 0.0)
4: Near: Vec4(0.0, -0.0, -1.0, -0.5025126)
5: Far: Vec4(0.0, 0.0, 1.0, 100.0)
```

1. The result of "Near" is incorrect.
2. In `from_clip_from_world`, the result of "Far" is incorrect. And even more exaggerated is that the normal vectors of the "Near" and "Far" planes are the same.

In the `from_clip_from_world_no_far` function, the `Near` term should be `row2` instead of `row3+row2`.

In the `from_clip_from_world` function, the `Far` value should be `row3 - row2`, instead of `row2`.

test code:

```rust
let mut clip_from_world = Mat4::perspective_rh(60.0_f32.to_radians(), 1.0, 1.0, 100.0);
clip_from_world.y_axis = -clip_from_world.y_axis; // Flip the Y axis downwards.

let frustum = ViewFrustum::from_clip_from_world(&clip_from_world);

std::eprintln!("\n=== from_clip_from_world ===");
for (i, name) in ["0: Left", "1: Right", "2: Top", "3: Bottom", "4: Near", "5: Far"].iter().enumerate() {
std::eprintln!("{}: {:?}", name, frustum.half_spaces[i].normal_d());
}

let frustum = ViewFrustum::from_clip_from_world_custom_far(
&clip_from_world,
&Vec3::ZERO,
&Vec3 { x: 0.0, y: 0.0, z: 1.0 },
100.0
);

std::eprintln!("\n=== from_clip_from_world_custom_far ===");
for (i, name) in ["0: Left", "1: Right", "2: Top", "3: Bottom", "4: Near", "5: Far"].iter().enumerate() {
std::eprintln!("{}: {:?}", name, frustum.half_spaces[i].normal_d());
}
```

Contributor guide

Open the contributing guide

Research direction

Start by reading ViewFrustum::from_clip_from_world_no_far and ViewFrustum::from_clip_from_world, then run the Rust reproduction in the issue to inspect the extracted planes. Done means the near and far plane calculations match the stated row formulas and the reported normals and distances are correct.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
computer-graphics, game-dev
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.