Shadows, self and nearby craft

This commit is contained in:
connor committed 2026-10-14 20:34:12 -04:00
1 parent e77d68d0a6
commit 33f986044c
14 files changed
+1273 -37

No files matched your search

+83 -2
View File
@@ -369,7 +369,8 @@ Both write into the same depth image, so they sort against each other
correctly. Ray casting a mesh is also possible, by testing rays against
its triangles. That is what M5's ray tracer does, because following rays
gives exact shadows and light bouncing between surfaces. Rasterization can
only approximate those, for example with the shadow maps planned for M2.
only approximate those, for example with the shadow maps lighting L1 uses
(below).
### One sample per pixel
@@ -580,6 +581,87 @@ as a solar array, has one surface but two sides. Whichever side faces the
camera is the side that is lit. A closed shape never shows its inside, so
this only matters for thin parts.
### Shadows on the target
Code: `shadow.rs`, `gpu/shadow.rs`, `shaders/mesh.wgsl`. From lighting L1.
A solar array shades the bus behind it, an antenna dish shades its feed,
and a second spacecraft close by can shade the first. Rasterization draws
each triangle on its own, so by itself it cannot know that something else
stands between a point and the Sun. The renderer finds out with a shadow
map.
- **Shadow map.** Before the camera's view is drawn, the target is drawn
once more as the Sun sees it, looking straight down the Sun's rays. That
drawing keeps only depth: for each texel (a pixel of the map), how far
along the Sun's direction the first surface is. The Sun is far enough
away that its rays are parallel across a spacecraft, so the view has no
perspective, and a map 4096 texels across covers the target's bounding
sphere. For Landsat 8, whose sphere is 16.6 m across, one texel is 4 mm.
- **Lookup.** When the camera's view shades a point, it finds that point
in the map. If the map holds a surface nearer the Sun than the point, the
point is in shadow.
Each target gets its own map. Another target is drawn into that map when it
is sunward of this one and close enough across the Sun's rays to shade it.
A target off to the side is left out, since it cannot cast a shadow here.
The Sun is a disk half a degree across, so a shadow edge is not sharp. A
point near the edge sees part of the disk past the blocker, the same
penumbra as in an eclipse. The width of the penumbra is the distance from
blocker to surface times the tangent of the Sun's angular diameter, 0.93 %
at 1 AU. A solar array 2 m above the bus casts an edge 19 mm wide. One 10 m
away casts an edge 9 cm wide. The renderer reproduces this with
percentage-closer soft shadows:
1. Search the map around the point for blockers, and average their depth.
That gives the blocker-to-surface distance, and from it the penumbra
width.
2. Compare the point's depth against a 6 x 6 grid of map texels spread over
that width, and take the fraction that are not blocked as the share of
the Sun the point sees.
Both steps use fixed grids rather than random samples, so the image has no
noise and the same scene always gives the same pixels.
A surface can wrongly shadow itself, because the map only stores its depth
once per texel. Where the surface is tilted to the Sun, depth changes
across a texel, and half of each texel looks like it is behind itself. The
result is a pattern of dark stripes called shadow acne. The renderer
prevents it three ways. The stored depth is pushed away from the Sun in
proportion to the surface's tilt. The lookup is made from a point 1.5
texels off the surface, toward the Sun. And a blocker has to be more than 2
texels nearer the Sun to count.
Tests (`tests/shadows.rs`) check, on the GPU and lavapipe:
- A plate under a 2 m square: the shadow reads zero direct light, and its
edge falls where the square's edge projects, to within one texel.
- The measured penumbra width is within 2.2 % of 0.93 % of the occluder's
height, at heights of 1, 2 and 3 m.
- No pixel darkens compared with L0 on a plate tilted 80 and 87 degrees
from the Sun, or on a faceted sphere.
- A target sunward of another shades it; one to the side does not.
On the RTX 3060, shadows cost 0.14 ms of GPU time per frame for Landsat 8
(85,000 triangles) and 0.34 ms for ICESat-2 (752,000), plus about 0.15 ms
in the camera view's lookups. Maps 2048 texels across save about 0.15 ms.
Limitations:
- **Penumbra shape.** The grid averages a square of the map, so brightness
rises in a straight line across the penumbra. A real disk-shaped Sun
rises along an S-shaped curve: the full width is the same, but the middle
80 % of the change is 0.69 of the width rather than 0.8.
- **Resolution.** A detail smaller than a texel casts no reliable shadow. A
gap narrower than about 2 texels between a blocker and the surface it
shades (a few mm on Landsat 8) lets light through, because of the acne
margins above.
- **One sample per pixel.** Shadows are sampled at the pixel center, like
everything else, until M2's pixel-area sampling.
- **Bodies.** Earth and the Moon do not use shadow maps. Their shadows on
the target are the eclipse factor below, applied to the whole target.
### Eclipses
Code: `eclipse.rs`.
@@ -641,7 +723,6 @@ velocity as an input, which the scene does not carry yet.
| Light from Earth and the Moon illuminating the target (Earthshine, Moonshine) | M2 |
| Earth's shadow on the Moon (lunar eclipses) | M2 |
| Earthshine on the Moon's night side | M4 |
| Shadows the target casts on itself | M2 |
| The Sun as a disk that is darker toward its edge (limb darkening) | M2 |
| Pixel-area sampling (anti-aliasing): graded edges, thin parts | M2 |
| Lens distortion | M3 |