///|
priv struct DirectionalShadowBound3D {
near_bound : Double
far_bound : Double
}
///|
priv struct DirectionalShadowCascade3D {
light_index : Int
cascade_index : Int
light_view_projection : Array[Double]
atlas_offset_x : Double
atlas_offset_y : Double
atlas_scale_x : Double
atlas_scale_y : Double
near_bound : Double
far_bound : Double
}
///|
priv struct CameraBasis3D {
forward : @smath.Vec3
right : @smath.Vec3
up : @smath.Vec3
}
///|
fn pack_directional_shadow_cascade_data(
frame : @render3d_types.RenderFrame3D,
aspect : Double,
mode? : Render3DLightingMode = Full,
) -> Array[Double] {
let values : Array[Double] = []
match frame.camera {
None => values
Some(camera) => {
let tile_size = frame.directional_shadow_map_size.to_double().max(1.0)
let count = min_int(
frame.directional_lights.length(),
max_directional_lights_for_mode(mode),
)
for light_index in 0.. Array[Double] {
let values : Array[Double] = []
let count = min_int(
frame.spot_lights.length(),
max_spot_lights_for_mode(mode),
)
for index in 0.. Array[Double] {
let values : Array[Double] = []
let count = min_int(
frame.point_lights.length(),
max_point_lights_for_mode(mode),
)
for light_index in 0.. Array[DirectionalShadowCascade3D] {
let bounds = directional_shadow_bounds3d(light, camera)
let cascades : Array[DirectionalShadowCascade3D] = []
if bounds.length() == 0 {
return cascades
}
let direction = normalize_or3d(light.direction, Vec3(0.0, -1.0, 0.0))
let light_view = mat4_look_at3d(
Vec3(0.0, 0.0, 0.0),
direction,
choose_shadow_up3d(direction),
)
let (atlas_columns, atlas_rows) = directional_shadow_atlas_layout3d(
bounds.length(),
)
let atlas_width = tile_size * atlas_columns.to_double()
let atlas_height = tile_size * atlas_rows.to_double()
for cascade_index in 0..= 8 {
let mut min_corner = transform_point3d(light_view, corners[0])
let mut max_corner = min_corner
for index in 1.. Array[Double] {
let direction = normalize_or3d(light.direction, Vec3(0.0, -1.0, 0.0))
let (near_plane, far_plane) = shadow_near_far3d(
light.shadow_map_near_z,
light.range,
)
let outer_angle = light.outer_angle.max(0.008726646259971648)
mat4_multiply3d(
mat4_perspective3d(
outer_angle * 2.0 * 180.0 / @math.PI,
1.0,
near_plane,
far_plane,
),
mat4_look_at3d(
light.position,
light.position + direction,
choose_shadow_up3d(direction),
),
)
}
///|
fn point_shadow_face_view_projection3d(
light : @render3d_types.FramePointLight3D,
face_index : Int,
) -> Array[Double] {
let (direction, up) = point_shadow_face3d(face_index)
let (near_plane, far_plane) = shadow_near_far3d(
light.shadow_map_near_z,
light.range,
)
mat4_multiply3d(
mat4_perspective3d(90.0, 1.0, near_plane, far_plane),
mat4_look_at3d(light.position, light.position + direction, up),
)
}
///|
fn directional_shadow_bounds3d(
light : @render3d_types.DirectionalLight3D,
camera : @render3d_types.FrameCamera3D,
) -> Array[DirectionalShadowBound3D] {
let bounds : Array[DirectionalShadowBound3D] = []
let config = light.cascade_shadow_config
let camera_near = if camera.near == 0.0 { 0.1 } else { camera.near }
let minimum_distance = camera_near
.max(0.0)
.max(config.minimum_distance.max(0.0))
let far = if camera.far == 0.0 {
minimum_distance + 200.0
} else {
camera.far
}
if far <= minimum_distance {
return bounds
}
let overlap = config.overlap_proportion.max(0.0).min(1.0)
let authored_bounds = config.bounds
let cascade_count = if authored_bounds.length() < 1 {
1
} else {
min_int(4, authored_bounds.length()).max(1)
}
let mut previous_far = minimum_distance
for index in 0.. near_bound {
bounds.push({ near_bound, far_bound })
if far_bound >= far {
return bounds
}
}
}
bounds
}
///|
fn camera_frustum_slice_corners3d(
camera : @render3d_types.FrameCamera3D,
aspect : Double,
near_bound : Double,
far_bound : Double,
) -> Array[@smath.Vec3] {
let basis = camera_basis3d(camera)
let corners : Array[@smath.Vec3] = []
if camera.projection == Orthographic {
let half_width = camera.orthographic_size
.map_or(20.0, fn(size) {
if size[X].abs() > 0.0001 {
size[X]
} else {
20.0
}
})
.abs()
.max(0.0001) *
0.5
let half_height = camera.orthographic_size
.map_or(20.0, fn(size) {
if size[Y].abs() > 0.0001 {
size[Y]
} else {
20.0
}
})
.abs()
.max(0.0001) *
0.5
let near_center = camera.position + basis.forward.scalar_mul(near_bound)
let far_center = camera.position + basis.forward.scalar_mul(far_bound)
push_frustum_plane_corners3d(
corners,
near_center,
basis.right,
basis.up,
half_width,
half_height,
)
push_frustum_plane_corners3d(
corners,
far_center,
basis.right,
basis.up,
half_width,
half_height,
)
} else {
let fov_y = camera.fov_y.max(0.0001) * @math.PI / 180.0
let tan_half_y = @math.tan(fov_y * 0.5)
let safe_aspect = if aspect.abs() > 0.0001 { aspect } else { 1.0 }
let near_height = near_bound * tan_half_y
let near_width = near_height * safe_aspect
let far_height = far_bound * tan_half_y
let far_width = far_height * safe_aspect
let near_center = camera.position + basis.forward.scalar_mul(near_bound)
let far_center = camera.position + basis.forward.scalar_mul(far_bound)
push_frustum_plane_corners3d(
corners,
near_center,
basis.right,
basis.up,
near_width,
near_height,
)
push_frustum_plane_corners3d(
corners,
far_center,
basis.right,
basis.up,
far_width,
far_height,
)
}
corners
}
///|
fn push_frustum_plane_corners3d(
corners : Array[@smath.Vec3],
center : @smath.Vec3,
right : @smath.Vec3,
up : @smath.Vec3,
half_width : Double,
half_height : Double,
) -> Unit {
corners.push(
center - right.scalar_mul(half_width) - up.scalar_mul(half_height),
)
corners.push(
center + right.scalar_mul(half_width) - up.scalar_mul(half_height),
)
corners.push(
center + right.scalar_mul(half_width) + up.scalar_mul(half_height),
)
corners.push(
center - right.scalar_mul(half_width) + up.scalar_mul(half_height),
)
}
///|
fn camera_basis3d(camera : @render3d_types.FrameCamera3D) -> CameraBasis3D {
let forward = normalize_or3d(
camera.target - camera.position,
Vec3(0.0, 0.0, -1.0),
)
let up = normalize_or3d(camera.up, Vec3(0.0, 1.0, 0.0))
let right = normalize_or3d(forward.cross(up), Vec3(1.0, 0.0, 0.0))
let corrected_up = normalize_or3d(right.cross(forward), Vec3(0.0, 1.0, 0.0))
{ forward, right, up: corrected_up }
}
///|
fn choose_shadow_up3d(direction : @smath.Vec3) -> @smath.Vec3 {
let normalized = normalize_or3d(direction, Vec3(0.0, -1.0, 0.0))
let world_up = @smath.Vec3(0.0, 1.0, 0.0)
if normalized.cross(world_up).length_squared() <= 0.0001 {
Vec3(0.0, 0.0, 1.0)
} else {
world_up
}
}
///|
fn directional_shadow_atlas_layout3d(cascade_count : Int) -> (Int, Int) {
if cascade_count <= 1 {
(1, 1)
} else if cascade_count == 2 {
(2, 1)
} else {
(2, 2)
}
}
///|
fn shadow_atlas_rect3d(
slot : Int,
columns : Int,
tile_size : Double,
atlas_width : Double,
atlas_height : Double,
) -> (Double, Double, Double, Double) {
let safe_columns = columns.max(1)
let column = slot % safe_columns
let row = slot / safe_columns
let tile_width = tile_size / atlas_width.max(1.0)
let tile_height = tile_size / atlas_height.max(1.0)
(
column.to_double() * tile_width,
row.to_double() * tile_height,
tile_width,
tile_height,
)
}
///|
fn point_shadow_face3d(face_index : Int) -> (@smath.Vec3, @smath.Vec3) {
match face_index {
0 => (Vec3(1.0, 0.0, 0.0), Vec3(0.0, -1.0, 0.0))
1 => (Vec3(-1.0, 0.0, 0.0), Vec3(0.0, -1.0, 0.0))
2 => (Vec3(0.0, 1.0, 0.0), Vec3(0.0, 0.0, 1.0))
3 => (Vec3(0.0, -1.0, 0.0), Vec3(0.0, 0.0, -1.0))
4 => (Vec3(0.0, 0.0, 1.0), Vec3(0.0, -1.0, 0.0))
_ => (Vec3(0.0, 0.0, -1.0), Vec3(0.0, -1.0, 0.0))
}
}
///|
fn shadow_near_far3d(near_z : Double, range : Double) -> (Double, Double) {
let near_plane = near_z.max(0.01)
let far_plane = range.max(near_plane + 1.0)
(near_plane, far_plane)
}
///|
fn mat4_translation3d(x : Double, y : Double, z : Double) -> Array[Double] {
[1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, x, y, z, 1.0]
}
///|
fn transform_point3d(
matrix : Array[Double],
point : @smath.Vec3,
) -> @smath.Vec3 {
if matrix.length() < 16 {
point
} else {
Vec3(
matrix[0] * point.x +
matrix[4] * point.y +
matrix[8] * point.z +
matrix[12],
matrix[1] * point.x +
matrix[5] * point.y +
matrix[9] * point.z +
matrix[13],
matrix[2] * point.x +
matrix[6] * point.y +
matrix[10] * point.z +
matrix[14],
)
}
}
///|
fn vec3_min3d(lhs : @smath.Vec3, rhs : @smath.Vec3) -> @smath.Vec3 {
Vec3(lhs.x.min(rhs.x), lhs.y.min(rhs.y), lhs.z.min(rhs.z))
}
///|
fn vec3_max3d(lhs : @smath.Vec3, rhs : @smath.Vec3) -> @smath.Vec3 {
Vec3(lhs.x.max(rhs.x), lhs.y.max(rhs.y), lhs.z.max(rhs.z))
}
///|
fn append_directional_shadow_cascade3d(
values : Array[Double],
cascade : DirectionalShadowCascade3D,
) -> Unit {
values.push(cascade.light_index.to_double())
values.push(cascade.cascade_index.to_double())
append_matrix3d(values, cascade.light_view_projection)
values.push(cascade.atlas_offset_x)
values.push(cascade.atlas_offset_y)
values.push(cascade.atlas_scale_x)
values.push(cascade.atlas_scale_y)
values.push(cascade.near_bound)
values.push(cascade.far_bound)
}
///|
fn append_spot_shadow_atlas_rect3d(
values : Array[Double],
light_index : Int,
) -> Unit {
let (offset_x, offset_y, scale_x, scale_y) = shadow_atlas_rect3d(
light_index, 2, 1.0, 2.0, 2.0,
)
values.push(offset_x)
values.push(offset_y)
values.push(scale_x)
values.push(scale_y)
}
///|
fn append_point_shadow_atlas_rect3d(
values : Array[Double],
light_index : Int,
face_index : Int,
) -> Unit {
let (offset_x, offset_y, scale_x, scale_y) = shadow_atlas_rect3d(
light_index * 6 + face_index,
8,
1.0,
8.0,
6.0,
)
values.push(offset_x)
values.push(offset_y)
values.push(scale_x)
values.push(scale_y)
}
///|
fn append_matrix3d(values : Array[Double], matrix : Array[Double]) -> Unit {
for value in matrix {
values.push(value)
}
}