// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
priv struct ShadowShaderState {
shader : @raylib.Shader
light_view_projection_loc : Int
material_has_texture_loc : Int
material_alpha_mode_loc : Int
material_alpha_cutoff_loc : Int
base_uv_offset_scale_loc : Int
base_uv_rotation_loc : Int
base_uv_set_loc : Int
}
///|
priv struct Bounds3D {
min : @smath.Vec3
max : @smath.Vec3
}
///|
priv struct BoundingSphere3D {
center : @smath.Vec3
radius : Double
}
///|
priv struct DirectionalShadowSetup {
light_view_projection : @raylib.Matrix
half_extent : Double
near_plane : Double
far_plane : Double
near_bound : Double
far_bound : Double
}
///|
priv struct DirectionalShadowCascadeState {
light_view_projection : @raylib.Matrix
atlas_rect : ShadowAtlasRect
near_bound : Double
far_bound : Double
}
///|
priv struct FrameDirectionalShadowState {
depth_texture_id : UInt
cascades : Array[DirectionalShadowCascadeState]
depth_bias : Double
normal_bias : Double
}
///|
priv struct FrameShadowState {
directional_shadows : Map[Int, FrameDirectionalShadowState]
spot_shadows : Map[Int, FrameSpotShadowState]
point_shadows : Map[Int, FramePointShadowState]
spot_shadow_atlas_id : UInt?
point_shadow_atlas_id : UInt?
directional_texel_size : @smath.Vec2
spot_texel_size : @smath.Vec2
point_texel_size : @smath.Vec2
}
///|
const SHADOW_MAP_TEXTURE_SLOT_BASE : Int = 6
///|
const SHADOW_CAMERA_MARGIN_SCALE : Double = 0.15
///|
const SHADOW_CAMERA_MARGIN_MIN : Double = 2.0
///|
const SHADOW_CAMERA_DISTANCE_SCALE : Double = 2.0
///|
const SHADOW_CAMERA_DISTANCE_MIN : Double = 10.0
///|
const SHADOW_CAMERA_NEAR_MIN : Double = 0.01
///|
fn shadow_texture_slot(index : Int) -> Int {
SHADOW_MAP_TEXTURE_SLOT_BASE + index
}
///|
let raylib_shadow_vertex_shader : String =
#|#version 330
#|in vec3 vertexPosition;
#|in vec2 vertexTexCoord;
#|in vec2 vertexTexCoord2;
#|uniform mat4 matModel;
#|uniform mat4 lightViewProjection;
#|out vec2 fragTexCoord;
#|out vec2 fragTexCoord2;
#|void main() {
#| vec4 worldPosition = matModel * vec4(vertexPosition, 1.0);
#| fragTexCoord = vertexTexCoord;
#| fragTexCoord2 = vertexTexCoord2;
#| gl_Position = lightViewProjection * worldPosition;
#|}
#|
///|
let raylib_shadow_fragment_shader : String =
#|#version 330
#|in vec2 fragTexCoord;
#|in vec2 fragTexCoord2;
#|uniform sampler2D texture0;
#|uniform int materialHasTexture;
#|uniform int materialAlphaMode;
#|uniform float materialAlphaCutoff;
#|uniform vec4 baseUvOffsetScale;
#|uniform vec2 baseUvRotation;
#|uniform int baseUvSet;
#|vec2 selectUv(int setIndex) {
#| if (setIndex == 1) return fragTexCoord2;
#| return fragTexCoord;
#|}
#|vec2 applyTextureTransform(vec2 uv, vec4 offsetScale, vec2 rotationSinCos) {
#| vec2 scaled = uv * offsetScale.zw;
#| return vec2(
#| scaled.x * rotationSinCos.y - scaled.y * rotationSinCos.x + offsetScale.x,
#| scaled.x * rotationSinCos.x + scaled.y * rotationSinCos.y + offsetScale.y
#| );
#|}
#|void main() {
#| if (materialAlphaMode == 2) discard;
#| if (materialAlphaMode == 1 && materialHasTexture != 0) {
#| vec2 uv = applyTextureTransform(selectUv(baseUvSet), baseUvOffsetScale, baseUvRotation);
#| if (texture(texture0, uv).a < materialAlphaCutoff) discard;
#| }
#|}
#|
///|
fn normalize_shadow_map_size(size : Int) -> Int {
@cmp.maximum(1, size)
}
///|
fn shadow_texel_size(size : Int) -> @smath.Vec2 {
let inv = 1.0 / normalize_shadow_map_size(size).to_double()
Vec2(inv, inv)
}
///|
fn vec3_min(lhs : @smath.Vec3, rhs : @smath.Vec3) -> @smath.Vec3 {
Vec3(
@cmp.minimum(lhs.x, rhs.x),
@cmp.minimum(lhs.y, rhs.y),
@cmp.minimum(lhs.z, rhs.z),
)
}
///|
fn vec3_max(lhs : @smath.Vec3, rhs : @smath.Vec3) -> @smath.Vec3 {
Vec3(
@cmp.maximum(lhs.x, rhs.x),
@cmp.maximum(lhs.y, rhs.y),
@cmp.maximum(lhs.z, rhs.z),
)
}
///|
fn vec3_midpoint(lhs : @smath.Vec3, rhs : @smath.Vec3) -> @smath.Vec3 {
Vec3((lhs.x + rhs.x) * 0.5, (lhs.y + rhs.y) * 0.5, (lhs.z + rhs.z) * 0.5)
}
///|
fn choose_shadow_up(light_direction : @smath.Vec3) -> @smath.Vec3 {
let normalized = normalize_or(light_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_cascade_count(
light : @render3d_types.DirectionalLight3D,
) -> Int {
let count = light.cascade_shadow_config.bounds.length()
if count < 1 {
1
} else if count > MAX_DIRECTIONAL_CASCADES {
MAX_DIRECTIONAL_CASCADES
} else {
count
}
}
///|
fn directional_shadow_atlas_layout(cascade_count : Int) -> (Int, Int) {
if cascade_count <= 1 {
(1, 1)
} else if cascade_count == 2 {
(2, 1)
} else {
(2, 2)
}
}
///|
fn camera_basis(
camera : @render3d_types.FrameCamera3D,
) -> (@smath.Vec3, @smath.Vec3, @smath.Vec3) {
let forward = normalize_or(
camera.target - camera.position,
Vec3(0.0, 0.0, -1.0),
)
let up = normalize_or(camera.up, Vec3(0.0, 1.0, 0.0))
let right = normalize_or(forward.cross(up), Vec3(1.0, 0.0, 0.0))
let corrected_up = normalize_or(right.cross(forward), Vec3(0.0, 1.0, 0.0))
(forward, right, corrected_up)
}
///|
fn camera_aspect_ratio() -> Double {
if !@raylib.is_window_ready() {
return 1.0
}
let height = @raylib.get_render_height()
if height <= 0 {
1.0
} else {
@raylib.get_render_width().to_double() / height.to_double()
}
}
///|
fn camera_frustum_slice_corners(
camera : @render3d_types.FrameCamera3D,
near_bound : Double,
far_bound : Double,
) -> Array[@smath.Vec3] {
let (forward, right, up) = camera_basis(camera)
let corners : Array[@smath.Vec3] = []
match camera.projection {
Perspective => {
let aspect = camera_aspect_ratio()
let tan_half_y = @cmath.tan(camera.fov_y * 0.5 * @cmath.PI / 180.0)
let near_height = near_bound * tan_half_y
let near_width = near_height * aspect
let far_height = far_bound * tan_half_y
let far_width = far_height * aspect
let near_center = camera.position + forward.scalar_mul(near_bound)
let far_center = camera.position + forward.scalar_mul(far_bound)
corners.push(
near_center - right.scalar_mul(near_width) - up.scalar_mul(near_height),
)
corners.push(
near_center + right.scalar_mul(near_width) - up.scalar_mul(near_height),
)
corners.push(
near_center + right.scalar_mul(near_width) + up.scalar_mul(near_height),
)
corners.push(
near_center - right.scalar_mul(near_width) + up.scalar_mul(near_height),
)
corners.push(
far_center - right.scalar_mul(far_width) - up.scalar_mul(far_height),
)
corners.push(
far_center + right.scalar_mul(far_width) - up.scalar_mul(far_height),
)
corners.push(
far_center + right.scalar_mul(far_width) + up.scalar_mul(far_height),
)
corners.push(
far_center - right.scalar_mul(far_width) + up.scalar_mul(far_height),
)
}
Orthographic => {
let ortho_size = camera.orthographic_size.unwrap_or(Vec2(20.0, 20.0))
let half_width = ortho_size[X].abs() * 0.5
let half_height = ortho_size[Y].abs() * 0.5
let near_center = camera.position + forward.scalar_mul(near_bound)
let far_center = camera.position + forward.scalar_mul(far_bound)
corners.push(
near_center - right.scalar_mul(half_width) - up.scalar_mul(half_height),
)
corners.push(
near_center + right.scalar_mul(half_width) - up.scalar_mul(half_height),
)
corners.push(
near_center + right.scalar_mul(half_width) + up.scalar_mul(half_height),
)
corners.push(
near_center - right.scalar_mul(half_width) + up.scalar_mul(half_height),
)
corners.push(
far_center - right.scalar_mul(half_width) - up.scalar_mul(half_height),
)
corners.push(
far_center + right.scalar_mul(half_width) - up.scalar_mul(half_height),
)
corners.push(
far_center + right.scalar_mul(half_width) + up.scalar_mul(half_height),
)
corners.push(
far_center - right.scalar_mul(half_width) + up.scalar_mul(half_height),
)
}
}
corners
}
///|
fn directional_shadow_bounds(
light : @render3d_types.DirectionalLight3D,
camera : @render3d_types.FrameCamera3D,
) -> Array[(Double, Double)] {
let config = light.cascade_shadow_config
let minimum_distance = @cmp.maximum(
camera.near,
@cmp.maximum(0.0, config.minimum_distance),
)
if camera.far <= minimum_distance {
return []
}
let overlap_proportion = @cmp.maximum(
0.0,
@cmp.minimum(1.0, config.overlap_proportion),
)
let bounds : Array[(Double, Double)] = []
let cascade_count = directional_shadow_cascade_count(light)
let mut previous_far = minimum_distance
for index in 0..= camera.far {
break
}
}
bounds
}
///|
fn build_directional_shadow_cascades_for_light(
frame : @render3d_types.RenderFrame3D,
light_index : Int,
camera : @render3d_types.FrameCamera3D,
shadow_map_size : Int,
) -> Array[DirectionalShadowCascadeState] {
if light_index < 0 || light_index >= frame.directional_lights.length() {
return []
}
let light = frame.directional_lights[light_index]
let bounds = directional_shadow_bounds(light, camera)
if bounds.length() == 0 {
return []
}
let light_direction = normalize_or(light.direction, Vec3(0.0, -1.0, 0.0))
let light_view = @raylib.Matrix::look_at(
@raylib.Vector3::zero(),
to_ray_vector3_smath(light_direction),
to_ray_vector3_smath(choose_shadow_up(light_direction)),
)
let tile_size = normalize_shadow_map_size(shadow_map_size)
let (atlas_columns, atlas_rows) = directional_shadow_atlas_layout(
bounds.length(),
)
let atlas_width = tile_size * atlas_columns
let atlas_height = tile_size * atlas_rows
let cascades : Array[DirectionalShadowCascadeState] = []
for cascade_index in 0..= 8 else { continue }
let mut min_corner = transform_shadow_point(light_view, corners[0])
let mut max_corner = min_corner
for index in 1.. Array[Int] {
let indices : Array[Int] = []
let directional_count = @cmp.minimum(
frame.directional_lights.length(),
MAX_DIRECTIONAL_LIGHTS,
)
for index in 0.. Double {
@cmp.maximum(0.0, light.shadow_depth_bias)
}
///|
fn shadow_normal_bias(light : @render3d_types.DirectionalLight3D) -> Double {
@cmp.maximum(0.0, light.shadow_normal_bias)
}
///|
fn bounds_corners(bounds : Bounds3D) -> Array[@smath.Vec3] {
let min = bounds.min
let max = bounds.max
[
Vec3(min.x, min.y, min.z),
Vec3(min.x, min.y, max.z),
Vec3(min.x, max.y, min.z),
Vec3(min.x, max.y, max.z),
Vec3(max.x, min.y, min.z),
Vec3(max.x, min.y, max.z),
Vec3(max.x, max.y, min.z),
Vec3(max.x, max.y, max.z),
]
}
///|
fn transform_shadow_point(
matrix : @raylib.Matrix,
point : @smath.Vec3,
) -> @smath.Vec3 {
let transformed = @raylib.Vector3::transform(
@raylib.Vector3::new(
to_float(point.x),
to_float(point.y),
to_float(point.z),
),
matrix,
)
Vec3(
transformed.x.to_double(),
transformed.y.to_double(),
transformed.z.to_double(),
)
}
///|
fn triangle_mesh_local_bounds(
mesh : @render3d_types.TriangleMesh3D,
) -> Bounds3D? {
guard mesh.positions.length() > 0 else { return None }
let mut min = mesh.positions[0]
let mut max = mesh.positions[0]
for index in 1.. BoundingSphere3D? {
match primitive {
Cube(size) => {
let half_extents = @smath.Vec3(
size.x.abs() * 0.5,
size.y.abs() * 0.5,
size.z.abs() * 0.5,
)
Some({ center: @smath.Vec3::zero(), radius: half_extents.length() })
}
Sphere(radius) =>
Some({ center: @smath.Vec3::zero(), radius: radius.abs() })
Cylinder(radius_top, radius_bottom, height, _slices) => {
let max_radius = @cmp.maximum(radius_top.abs(), radius_bottom.abs())
let half_extents = @smath.Vec3(max_radius, height.abs() * 0.5, max_radius)
Some({ center: @smath.Vec3::zero(), radius: half_extents.length() })
}
Plane(size) => {
let thickness = if size.y.abs() < 0.0001 { 0.02 } else { size.y.abs() }
let half_extents = @smath.Vec3(
size.x.abs() * 0.5,
thickness * 0.5,
size.z.abs() * 0.5,
)
Some({ center: @smath.Vec3::zero(), radius: half_extents.length() })
}
Triangles(mesh) =>
triangle_mesh_local_bounds(mesh).map(fn(bounds) {
let center = vec3_midpoint(bounds.min, bounds.max)
{ center, radius: (bounds.max - center).length() }
})
}
}
///|
fn world_bounding_sphere(
mesh_asset : @render3d_types.MeshAsset,
transform : @render3d_types.FrameTransform3D,
) -> BoundingSphere3D? {
primitive_local_bounding_sphere(mesh_asset.primitive).map(fn(local_sphere) {
let scaled_center = @smath.Vec3(
local_sphere.center.x * transform.scale.x,
local_sphere.center.y * transform.scale.y,
local_sphere.center.z * transform.scale.z,
)
let max_scale = @cmp.maximum(
transform.scale.x.abs(),
@cmp.maximum(transform.scale.y.abs(), transform.scale.z.abs()),
)
{
center: transform.translation +
transform.rotation.rotate_vec3(scaled_center),
radius: local_sphere.radius * max_scale,
}
})
}
///|
fn scene_shadow_bounds(frame : @render3d_types.RenderFrame3D) -> Bounds3D? {
let mut bounds : Bounds3D? = None
for item in frame.items {
guard backend.meshes3d.get(item.mesh) is Some(mesh_asset) else { continue }
guard world_bounding_sphere(mesh_asset, item.transform) is Some(sphere) else {
continue
}
let extent = @smath.Vec3(sphere.radius, sphere.radius, sphere.radius)
let item_bounds : Bounds3D = {
min: sphere.center - extent,
max: sphere.center + extent,
}
bounds = Some(
match bounds {
Some(existing) =>
{
min: vec3_min(existing.min, item_bounds.min),
max: vec3_max(existing.max, item_bounds.max),
}
None => item_bounds
},
)
}
bounds
}
///|
fn build_directional_shadow_setup_for_light(
frame : @render3d_types.RenderFrame3D,
light_index : Int,
) -> DirectionalShadowSetup? {
guard scene_shadow_bounds(frame) is Some(bounds) else { return None }
guard light_index >= 0 && light_index < frame.directional_lights.length() else {
return None
}
let light = frame.directional_lights[light_index]
let light_direction = normalize_or(light.direction, Vec3(0.0, -1.0, 0.0))
let center = vec3_midpoint(bounds.min, bounds.max)
let extent = bounds.max - center
let radius = @cmp.maximum(extent.length(), 1.0)
let margin = @cmp.maximum(
radius * SHADOW_CAMERA_MARGIN_SCALE,
SHADOW_CAMERA_MARGIN_MIN,
)
let distance = @cmp.maximum(
radius * SHADOW_CAMERA_DISTANCE_SCALE + margin,
SHADOW_CAMERA_DISTANCE_MIN,
)
let light_position = center -
Vec3(
light_direction.x * distance,
light_direction.y * distance,
light_direction.z * distance,
)
let light_view = @raylib.Matrix::look_at(
to_ray_vector3_smath(light_position),
to_ray_vector3_smath(center),
to_ray_vector3_smath(choose_shadow_up(light_direction)),
)
let mut half_extent = 1.0
let mut min_z = 0.0
let mut max_z = 0.0
let corners = bounds_corners(bounds)
if corners.length() > 0 {
let first = transform_shadow_point(light_view, corners[0])
half_extent = @cmp.maximum(first.x.abs(), first.y.abs())
min_z = first.z
max_z = first.z
for index in 1.. Unit {
shader.set_locs(
@raylib.ShaderLocVertexPosition,
@raylib.get_shader_location_attrib(shader, "vertexPosition"),
)
shader.set_locs(
@raylib.ShaderLocVertexTexcoord01,
@raylib.get_shader_location_attrib(shader, "vertexTexCoord"),
)
shader.set_locs(
@raylib.ShaderLocVertexTexcoord02,
@raylib.get_shader_location_attrib(shader, "vertexTexCoord2"),
)
shader.set_locs(
@raylib.ShaderLocMatrixModel,
@raylib.get_shader_location(shader, "matModel"),
)
shader.set_locs(
@raylib.ShaderLocMapAlbedo,
@raylib.get_shader_location(shader, "texture0"),
)
}
///|
fn create_shadow_shader_state() -> ShadowShaderState {
let shader = @raylib.load_shader_from_memory(
raylib_shadow_vertex_shader, raylib_shadow_fragment_shader,
)
configure_shadow_shader_locations(shader)
{
shader,
light_view_projection_loc: @raylib.get_shader_location(
shader, "lightViewProjection",
),
material_has_texture_loc: @raylib.get_shader_location(
shader, "materialHasTexture",
),
material_alpha_mode_loc: @raylib.get_shader_location(
shader, "materialAlphaMode",
),
material_alpha_cutoff_loc: @raylib.get_shader_location(
shader, "materialAlphaCutoff",
),
base_uv_offset_scale_loc: @raylib.get_shader_location(
shader, "baseUvOffsetScale",
),
base_uv_rotation_loc: @raylib.get_shader_location(shader, "baseUvRotation"),
base_uv_set_loc: @raylib.get_shader_location(shader, "baseUvSet"),
}
}
///|
fn get_shadow_shader_state() -> ShadowShaderState {
if backend.shadow_shader is Some(state) {
return state
}
let state = create_shadow_shader_state()
backend.shadow_shader = Some(state)
state
}
///|
fn get_shadow_material() -> @raylib.Material {
if backend.shadow_material is Some(material) {
return material
}
let material = @raylib.Material::default()
@raylib.set_material_shader(material, get_shadow_shader_state().shader)
@raylib.set_material_map_color(
material,
@raylib.MaterialMapAlbedo,
to_ray_color(white_render_color()),
)
backend.shadow_material = Some(material)
material
}
///|
fn create_directional_shadow_render_texture(
tile_size : Int,
atlas_columns : Int,
atlas_rows : Int,
warn_key : String,
) -> @raylib.RenderTexture? {
let width = tile_size * atlas_columns
let height = tile_size * atlas_rows
let fbo_id = @rl.load_framebuffer()
let depth_tex_id = @rl.load_texture_depth(width, height, false)
@rl.framebuffer_attach(fbo_id, depth_tex_id, 100, 100, 0)
ignore(@rl.framebuffer_complete(fbo_id))
let render_texture = @raylib.RenderTexture::new(
fbo_id,
@raylib.Texture::new(0U, width, height, 1, 7),
@raylib.Texture::new(depth_tex_id, width, height, 1, 19),
)
let depth_id = @raylib.get_render_texture_depth_id(render_texture)
if render_texture.id() == 0U || depth_id == 0U {
warn_texture_issue(
warn_key, "raylib shadow-map framebuffer could not be created; directional cascaded shadows are disabled for one light",
)
render_texture.unload()
return None
}
let depth_texture = @raylib.Texture::new(
depth_id,
width,
height,
1,
@raylib.PixelformatUncompressedR8g8b8a8,
)
@raylib.set_texture_filter(depth_texture, @raylib.TextureFilterPoint)
@raylib.set_texture_wrap(depth_texture, @raylib.TextureWrapClamp)
Some(render_texture)
}
///|
fn get_shadow_render_texture(
index : Int,
shadow_map_size : Int,
cascade_count : Int,
) -> @raylib.RenderTexture? {
guard index >= 0 && index < MAX_DIRECTIONAL_LIGHTS else { return None }
let tile_size = normalize_shadow_map_size(shadow_map_size)
let cascade_count = @cmp.maximum(
1,
@cmp.minimum(MAX_DIRECTIONAL_CASCADES, cascade_count),
)
let (atlas_columns, atlas_rows) = directional_shadow_atlas_layout(
cascade_count,
)
while backend.shadow_render_textures.length() <= index {
let slot = backend.shadow_render_textures.length()
guard create_directional_shadow_render_texture(
tile_size,
atlas_columns,
atlas_rows,
"shadowmap_unavailable:\{slot}",
)
is Some(render_texture) else {
return None
}
backend.shadow_render_textures.push(render_texture)
backend.directional_shadow_tile_sizes.push(tile_size)
backend.directional_shadow_atlas_columns.push(atlas_columns)
backend.directional_shadow_atlas_rows.push(atlas_rows)
}
guard backend.shadow_render_textures.get(index) is Some(existing) else {
return None
}
let existing_tile_size = backend.directional_shadow_tile_sizes
.get(index)
.unwrap_or(0)
let existing_columns = backend.directional_shadow_atlas_columns
.get(index)
.unwrap_or(0)
let existing_rows = backend.directional_shadow_atlas_rows
.get(index)
.unwrap_or(0)
let needs_recreate = existing_tile_size != tile_size ||
existing_columns != atlas_columns ||
existing_rows != atlas_rows ||
existing.id() == 0U ||
@raylib.get_render_texture_depth_id(existing) == 0U
if needs_recreate {
existing.unload()
guard create_directional_shadow_render_texture(
tile_size,
atlas_columns,
atlas_rows,
"shadowmap_unavailable:\{index}",
)
is Some(render_texture) else {
return None
}
backend.shadow_render_textures[index] = render_texture
backend.directional_shadow_tile_sizes[index] = tile_size
backend.directional_shadow_atlas_columns[index] = atlas_columns
backend.directional_shadow_atlas_rows[index] = atlas_rows
return Some(render_texture)
}
Some(existing)
}
///|
fn upload_shadow_uniforms(
material : @render3d_types.StandardMaterial3D,
base_source : MaterialTextureSource3D?,
base_uv_set : Int,
setup : DirectionalShadowSetup,
) -> ShadowShaderState {
ignore(setup.half_extent)
ignore(setup.near_plane)
ignore(setup.far_plane)
ignore(setup.near_bound)
ignore(setup.far_bound)
let state = get_shadow_shader_state()
let shader = state.shader
@raylib.set_shader_value_matrix(
shader,
state.light_view_projection_loc,
setup.light_view_projection,
)
set_shader_uniform(
shader,
state.material_has_texture_loc,
uniform_int(if base_source is Some(_) { 1 } else { 0 }),
)
set_shader_uniform(
shader,
state.material_alpha_mode_loc,
uniform_int(alpha_mode_to_int(material.alpha_mode)),
)
set_shader_uniform(
shader,
state.material_alpha_cutoff_loc,
uniform_float(material.alpha_cutoff),
)
let transform = base_source.map_or(
@render3d_types.default_texture_transform3d(),
fn(source) { source.transform },
)
upload_texture_transform_uniforms(
shader,
state.base_uv_offset_scale_loc,
state.base_uv_rotation_loc,
transform,
)
set_shader_uniform(shader, state.base_uv_set_loc, uniform_int(base_uv_set))
state
}
///|
fn draw_shadow_mesh_with_material(
mesh : @raylib.Mesh,
material : @render3d_types.StandardMaterial3D,
base_source : MaterialTextureSource3D?,
base_uv_set : Int,
setup : DirectionalShadowSetup,
) -> Unit {
if material.alpha_mode == Blend {
return
}
ignore(upload_shadow_uniforms(material, base_source, base_uv_set, setup))
let shadow_material = get_shadow_material()
@raylib.set_material_texture(
shadow_material,
@raylib.MaterialMapAlbedo,
base_source.map_or(get_white_texture(), fn(source) { source.texture }),
)
if material.double_sided {
@rl.disable_backface_culling()
} else {
@rl.enable_backface_culling()
}
@raylib.draw_mesh(mesh, shadow_material, @raylib.Matrix::identity())
@rl.disable_backface_culling()
}
///|
fn shadow_base_uv_set(base_source : MaterialTextureSource3D?) -> Int {
match base_source {
Some(source) => if source.texcoord_set == 1 { 1 } else { 0 }
None => 0
}
}
///|
fn draw_shadow_cuboid(
size : @smath.Vec3,
material : @render3d_types.StandardMaterial3D,
base_source : MaterialTextureSource3D?,
setup : DirectionalShadowSetup,
) -> Unit {
let key = "cube:\{size.x}:\{size.y}:\{size.z}"
let mesh = get_or_create_primitive_mesh(key, fn() {
@raylib.gen_mesh_cube(to_float(size.x), to_float(size.y), to_float(size.z))
})
draw_shadow_mesh_with_material(
mesh,
material,
base_source,
shadow_base_uv_set(base_source),
setup,
)
}
///|
fn draw_shadow_sphere(
radius : Double,
material : @render3d_types.StandardMaterial3D,
base_source : MaterialTextureSource3D?,
setup : DirectionalShadowSetup,
) -> Unit {
let key = "sphere:\{radius}:\{TEXTURED_SPHERE_SLICES}:\{TEXTURED_SPHERE_STACKS}"
let mesh = get_or_create_primitive_mesh(key, fn() {
@raylib.gen_mesh_sphere(
to_float(radius),
TEXTURED_SPHERE_SLICES,
TEXTURED_SPHERE_STACKS,
)
})
draw_shadow_mesh_with_material(
mesh,
material,
base_source,
shadow_base_uv_set(base_source),
setup,
)
}
///|
fn draw_shadow_cylinder(
mesh_handle : @render3d_types.MeshHandle,
radius_top : Double,
radius_bottom : Double,
height : Double,
slices : Int,
material : @render3d_types.StandardMaterial3D,
base_source : MaterialTextureSource3D?,
setup : DirectionalShadowSetup,
) -> Unit {
let (vertices, uvs, _normals) = build_cylinder_mesh(
radius_top, radius_bottom, height, slices,
)
draw_shadow_triangle_mesh(
mesh_handle,
{
positions: vertices,
indices: None,
topology: TriangleList,
uv_sets: [uvs],
normals: None,
tangents: None,
colors: None,
},
material,
base_source,
setup,
)
}
///|
fn draw_shadow_triangle_mesh(
mesh_handle : @render3d_types.MeshHandle,
mesh : @render3d_types.TriangleMesh3D,
material : @render3d_types.StandardMaterial3D,
base_source : MaterialTextureSource3D?,
setup : DirectionalShadowSetup,
) -> Unit {
if material.alpha_mode == Blend || mesh.positions.length() < 3 {
return
}
guard resolve_triangle_indices(mesh_handle, mesh) is Some(triangle_indices) else {
return
}
guard expand_vec3_with_indices(mesh.positions, triangle_indices)
is Some(expanded_vertices) else {
return
}
let normals = resolve_triangle_normals(mesh, triangle_indices)
guard expand_vec3_with_indices(normals, triangle_indices)
is Some(expanded_normals) else {
return
}
let uv0 = mesh.uv_sets
.get(0)
.filter(fn(values) { values.length() == mesh.positions.length() })
let uv1 = mesh.uv_sets
.get(1)
.filter(fn(values) { values.length() == mesh.positions.length() })
let expanded_uv0 = uv0.bind(fn(values) {
expand_vec2_with_indices(values, triangle_indices)
})
let expanded_uv1 = uv1.bind(fn(values) {
expand_vec2_with_indices(values, triangle_indices)
})
let expanded_colors = mesh.colors.bind(fn(values) {
if values.length() != mesh.positions.length() {
None
} else {
expand_color_with_indices(values, triangle_indices)
}
})
let expanded_tangents = mesh.tangents.bind(fn(values) {
if values.length() != mesh.positions.length() {
None
} else {
expand_tangent_with_indices(values, triangle_indices)
}
})
let cache_key = triangle_mesh_cache_key(mesh_handle)
let ray_mesh = if backend.triangle_mesh_cache.get(cache_key) is Some(cached) {
cached
} else {
guard create_uploaded_triangle_mesh(
expanded_vertices, expanded_uv0, expanded_uv1, expanded_normals, expanded_tangents,
expanded_colors,
)
is Some(created) else {
return
}
backend.triangle_mesh_cache.set(cache_key, created)
created
}
let base_uv_set = match base_source {
Some(source) => if source.texcoord_set == 1 { 1 } else { 0 }
None => 0
}
let effective_base_source = match base_source {
Some(source) =>
if base_uv_set == 1 && expanded_uv1 is None {
None
} else if base_uv_set == 0 && expanded_uv0 is None {
None
} else {
Some(source)
}
None => None
}
draw_shadow_mesh_with_material(
ray_mesh, material, effective_base_source, base_uv_set, setup,
)
}
///|
fn draw_shadow_mesh3d_instance(
mesh_handle : @render3d_types.MeshHandle,
mesh_asset : @render3d_types.MeshAsset,
material : @render3d_types.StandardMaterial3D,
transform : @render3d_types.FrameTransform3D,
setup : DirectionalShadowSetup,
) -> Unit {
@rl.push_matrix()
@rl.translatef(
to_float(transform.translation.x),
to_float(transform.translation.y),
to_float(transform.translation.z),
)
apply_quat_rotation(transform.rotation)
@rl.scalef(
to_float(transform.scale.x),
to_float(transform.scale.y),
to_float(transform.scale.z),
)
let base_source = material.base_color_texture.bind(
resolve_material_texture_source,
)
match mesh_asset.primitive {
Cube(size) => draw_shadow_cuboid(size, material, base_source, setup)
Sphere(radius) => draw_shadow_sphere(radius, material, base_source, setup)
Cylinder(radius_top, radius_bottom, height, slices) =>
draw_shadow_cylinder(
mesh_handle, radius_top, radius_bottom, height, slices, material, base_source,
setup,
)
Plane(size) => {
let thickness = if size.y.abs() < 0.0001 { 0.02 } else { size.y }
draw_shadow_cuboid(
Vec3(size.x, thickness, size.z),
material,
base_source,
setup,
)
}
Triangles(mesh) =>
draw_shadow_triangle_mesh(mesh_handle, mesh, material, base_source, setup)
}
@rl.pop_matrix()
}
///|
fn render_directional_shadow_map(
frame : @render3d_types.RenderFrame3D,
camera : @render3d_types.FrameCamera3D,
) -> FrameShadowState? {
let shadow_map_size = normalize_shadow_map_size(
frame.directional_shadow_map_size,
)
let directional_shadows : Map[Int, FrameDirectionalShadowState] = Map([])
for light_index in shadowed_directional_light_indices(frame) {
let mut cascades = build_directional_shadow_cascades_for_light(
frame, light_index, camera, shadow_map_size,
)
if cascades.length() == 0 {
if build_directional_shadow_setup_for_light(frame, light_index)
is Some(setup) {
cascades = [
{
light_view_projection: setup.light_view_projection,
atlas_rect: make_shadow_atlas_rect(
0, 1, shadow_map_size, shadow_map_size, shadow_map_size,
),
near_bound: setup.near_bound,
far_bound: setup.far_bound,
},
]
} else {
continue
}
}
guard get_shadow_render_texture(
light_index,
shadow_map_size,
cascades.length(),
)
is Some(render_texture) else {
continue
}
let (atlas_columns, atlas_rows) = directional_shadow_atlas_layout(
cascades.length(),
)
let atlas_width = shadow_map_size * atlas_columns
let atlas_height = shadow_map_size * atlas_rows
@raylib.begin_texture_mode(render_texture)
@raylib.clear_background(to_ray_color(white_render_color()))
for cascade in cascades {
@rl.viewport(
(cascade.atlas_rect.offset[X] * atlas_width.to_double()).to_int(),
(cascade.atlas_rect.offset[Y] * atlas_height.to_double()).to_int(),
shadow_map_size,
shadow_map_size,
)
render_shadow_casters_for_setup(frame, camera, {
light_view_projection: cascade.light_view_projection,
half_extent: 0.0,
near_plane: SHADOW_CAMERA_NEAR_MIN,
far_plane: SHADOW_CAMERA_NEAR_MIN + 1.0,
near_bound: cascade.near_bound,
far_bound: cascade.far_bound,
})
}
@raylib.end_texture_mode()
let light = frame.directional_lights[light_index]
directional_shadows.set(light_index, {
depth_texture_id: @raylib.get_render_texture_depth_id(render_texture),
cascades,
depth_bias: shadow_depth_bias(light),
normal_bias: shadow_normal_bias(light),
})
}
let (spot_shadows, spot_shadow_atlas_id, spot_texel_size) = render_spot_shadow_map(
frame, camera,
)
let (point_shadows, point_shadow_atlas_id, point_texel_size) = render_point_shadow_map(
frame, camera,
)
if directional_shadows.length() == 0 &&
spot_shadows.length() == 0 &&
point_shadows.length() == 0 {
None
} else {
Some({
directional_shadows,
spot_shadows,
point_shadows,
spot_shadow_atlas_id,
point_shadow_atlas_id,
directional_texel_size: shadow_texel_size(shadow_map_size),
spot_texel_size,
point_texel_size,
})
}
}