mirror of
https://github.com/FyroxEngine/Fyrox.git
synced 2026-10-12 02:02:20 +00:00
use lightmap crate for the actual lightmap generation
- the interface to lightmaps in the engine remains the same - only the "math" is used from the `lightmap` crate
This commit is contained in:
@@ -67,6 +67,7 @@ half = "2.2.1"
|
||||
fast_image_resize = "3.0.4"
|
||||
base64 = "0.21.0"
|
||||
uvgen = "0.1.0"
|
||||
lightmap = { git = "https://github.com/mrDIMAS/lightmap" }
|
||||
|
||||
[features]
|
||||
enable_profiler = ["fyrox-core/enable_profiler"]
|
||||
|
||||
+73
-476
@@ -9,9 +9,8 @@
|
||||
use crate::{
|
||||
asset::manager::{ResourceManager, ResourceRegistrationError},
|
||||
core::{
|
||||
algebra::{Matrix3, Matrix4, Point3, Vector2, Vector3, Vector4},
|
||||
arrayvec::ArrayVec,
|
||||
math::{self, ray::Ray, Matrix4Ext, Rect, TriangleDefinition, Vector2Ext},
|
||||
algebra::{Matrix3, Matrix4, Point3, Vector2, Vector3},
|
||||
math::{Matrix4Ext, TriangleDefinition},
|
||||
pool::Handle,
|
||||
reflect::prelude::*,
|
||||
sstorage::ImmutableString,
|
||||
@@ -36,7 +35,9 @@ use crate::{
|
||||
utils::{uvgen, uvgen::SurfaceDataPatch},
|
||||
};
|
||||
use fxhash::FxHashMap;
|
||||
use fyrox_core::math::octree::{Octree, OctreeNode};
|
||||
use lightmap::light::{
|
||||
DirectionalLightDefinition, LightDefinition, PointLightDefinition, SpotLightDefinition,
|
||||
};
|
||||
use rayon::prelude::*;
|
||||
use std::{
|
||||
fmt::{Display, Formatter},
|
||||
@@ -151,32 +152,13 @@ pub struct Lightmap {
|
||||
pub patches: FxHashMap<u64, SurfaceDataPatchWrapper>,
|
||||
}
|
||||
|
||||
struct WorldVertex {
|
||||
world_normal: Vector3<f32>,
|
||||
world_position: Vector3<f32>,
|
||||
second_tex_coord: Vector2<f32>,
|
||||
}
|
||||
|
||||
struct InstanceData {
|
||||
/// World-space vertices.
|
||||
vertices: Vec<WorldVertex>,
|
||||
triangles: Vec<TriangleDefinition>,
|
||||
octree: Octree,
|
||||
}
|
||||
|
||||
struct Instance {
|
||||
owner: Handle<Node>,
|
||||
source_data: SurfaceSharedData,
|
||||
data: Option<InstanceData>,
|
||||
data: Option<lightmap::input::Mesh>,
|
||||
transform: Matrix4<f32>,
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
pub fn data(&self) -> &InstanceData {
|
||||
self.data.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Small helper that allows you stop lightmap generation in any time.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct CancellationToken(pub Arc<AtomicBool>);
|
||||
@@ -334,7 +316,7 @@ impl From<VertexFetchError> for LightmapGenerationError {
|
||||
pub struct LightmapInputData {
|
||||
data_set: FxHashMap<u64, SurfaceSharedData>,
|
||||
instances: Vec<Instance>,
|
||||
lights: Vec<LightDefinition>,
|
||||
lights: FxHashMap<Handle<Node>, LightDefinition>,
|
||||
}
|
||||
|
||||
impl LightmapInputData {
|
||||
@@ -364,7 +346,7 @@ impl LightmapInputData {
|
||||
|
||||
progress_indicator.set_stage(ProgressStage::LightsCaching, light_count);
|
||||
|
||||
let mut lights = Vec::with_capacity(light_count as usize);
|
||||
let mut lights = FxHashMap::default();
|
||||
|
||||
for (handle, node) in scene.graph.pair_iter() {
|
||||
if !filter(handle, node) {
|
||||
@@ -380,43 +362,50 @@ impl LightmapInputData {
|
||||
}
|
||||
|
||||
if let Some(point) = node.cast::<PointLight>() {
|
||||
lights.push(LightDefinition::Point(PointLightDefinition {
|
||||
lights.insert(
|
||||
handle,
|
||||
intensity: point.base_light_ref().intensity(),
|
||||
position: node.global_position(),
|
||||
color: point.base_light_ref().color().srgb_to_linear().as_frgb(),
|
||||
radius: point.radius(),
|
||||
sqr_radius: point.radius() * point.radius(),
|
||||
}))
|
||||
LightDefinition::Point(PointLightDefinition {
|
||||
intensity: point.base_light_ref().intensity(),
|
||||
position: node.global_position(),
|
||||
color: point.base_light_ref().color().srgb_to_linear().as_frgb(),
|
||||
radius: point.radius(),
|
||||
sqr_radius: point.radius() * point.radius(),
|
||||
}),
|
||||
)
|
||||
} else if let Some(spot) = node.cast::<SpotLight>() {
|
||||
lights.push(LightDefinition::Spot(SpotLightDefinition {
|
||||
lights.insert(
|
||||
handle,
|
||||
intensity: spot.base_light_ref().intensity(),
|
||||
edge0: ((spot.hotspot_cone_angle() + spot.falloff_angle_delta()) * 0.5).cos(),
|
||||
edge1: (spot.hotspot_cone_angle() * 0.5).cos(),
|
||||
color: spot.base_light_ref().color().srgb_to_linear().as_frgb(),
|
||||
direction: node
|
||||
.up_vector()
|
||||
.try_normalize(std::f32::EPSILON)
|
||||
.unwrap_or_else(Vector3::y),
|
||||
position: node.global_position(),
|
||||
distance: spot.distance(),
|
||||
sqr_distance: spot.distance() * spot.distance(),
|
||||
}))
|
||||
LightDefinition::Spot(SpotLightDefinition {
|
||||
intensity: spot.base_light_ref().intensity(),
|
||||
edge0: ((spot.hotspot_cone_angle() + spot.falloff_angle_delta()) * 0.5)
|
||||
.cos(),
|
||||
edge1: (spot.hotspot_cone_angle() * 0.5).cos(),
|
||||
color: spot.base_light_ref().color().srgb_to_linear().as_frgb(),
|
||||
direction: node
|
||||
.up_vector()
|
||||
.try_normalize(f32::EPSILON)
|
||||
.unwrap_or_else(Vector3::y),
|
||||
position: node.global_position(),
|
||||
distance: spot.distance(),
|
||||
sqr_distance: spot.distance() * spot.distance(),
|
||||
}),
|
||||
)
|
||||
} else if let Some(directional) = node.cast::<DirectionalLight>() {
|
||||
lights.push(LightDefinition::Directional(DirectionalLightDefinition {
|
||||
lights.insert(
|
||||
handle,
|
||||
intensity: directional.base_light_ref().intensity(),
|
||||
direction: node
|
||||
.up_vector()
|
||||
.try_normalize(std::f32::EPSILON)
|
||||
.unwrap_or_else(Vector3::y),
|
||||
color: directional
|
||||
.base_light_ref()
|
||||
.color()
|
||||
.srgb_to_linear()
|
||||
.as_frgb(),
|
||||
}))
|
||||
LightDefinition::Directional(DirectionalLightDefinition {
|
||||
intensity: directional.base_light_ref().intensity(),
|
||||
direction: node
|
||||
.up_vector()
|
||||
.try_normalize(f32::EPSILON)
|
||||
.unwrap_or_else(Vector3::y),
|
||||
color: directional
|
||||
.base_light_ref()
|
||||
.color()
|
||||
.srgb_to_linear()
|
||||
.as_frgb(),
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
@@ -579,7 +568,7 @@ impl Lightmap {
|
||||
* view.read_3_f32(VertexAttributeUsage::Normal).unwrap())
|
||||
.try_normalize(f32::EPSILON)
|
||||
.unwrap_or_default();
|
||||
WorldVertex {
|
||||
lightmap::input::WorldVertex {
|
||||
world_normal,
|
||||
world_position,
|
||||
second_tex_coord: view
|
||||
@@ -589,23 +578,17 @@ impl Lightmap {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let world_triangles = data
|
||||
.geometry_buffer
|
||||
.iter()
|
||||
.map(|tri| {
|
||||
[
|
||||
world_vertices[tri[0] as usize].world_position,
|
||||
world_vertices[tri[1] as usize].world_position,
|
||||
world_vertices[tri[2] as usize].world_position,
|
||||
]
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
instance.data = Some(InstanceData {
|
||||
vertices: world_vertices,
|
||||
triangles: data.geometry_buffer.triangles_ref().to_vec(),
|
||||
octree: Octree::new(&world_triangles, 64),
|
||||
});
|
||||
instance.data = Some(
|
||||
lightmap::input::Mesh::new(
|
||||
world_vertices,
|
||||
data.geometry_buffer
|
||||
.triangles_ref()
|
||||
.iter()
|
||||
.map(|t| t.0)
|
||||
.collect(),
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
progress_indicator.advance_progress();
|
||||
|
||||
@@ -617,15 +600,20 @@ impl Lightmap {
|
||||
progress_indicator.set_stage(ProgressStage::CalculatingLight, instances.len() as u32);
|
||||
|
||||
let mut map: FxHashMap<Handle<Node>, Vec<LightmapEntry>> = FxHashMap::default();
|
||||
for instance in instances.iter() {
|
||||
let meshes = instances
|
||||
.iter_mut()
|
||||
.filter_map(|i| i.data.take())
|
||||
.collect::<Vec<_>>();
|
||||
let light_definitions = lights.values().map(|d| d).cloned().collect::<Vec<_>>();
|
||||
for (mesh, instance) in meshes.iter().zip(instances.iter()) {
|
||||
if cancellation_token.is_cancelled() {
|
||||
return Err(LightmapGenerationError::Cancelled);
|
||||
}
|
||||
|
||||
let lightmap = generate_lightmap(instance, &instances, &lights, texels_per_unit);
|
||||
let lightmap = generate_lightmap(mesh, &meshes, &light_definitions, texels_per_unit);
|
||||
map.entry(instance.owner).or_default().push(LightmapEntry {
|
||||
texture: Some(TextureResource::new_ok(Default::default(), lightmap)),
|
||||
lights: lights.iter().map(|light| light.handle()).collect(),
|
||||
lights: lights.keys().cloned().collect(),
|
||||
});
|
||||
|
||||
progress_indicator.advance_progress();
|
||||
@@ -661,212 +649,6 @@ impl Lightmap {
|
||||
}
|
||||
}
|
||||
|
||||
/// Directional light is a light source with parallel rays. Example: Sun.
|
||||
pub struct DirectionalLightDefinition {
|
||||
/// A handle of light in the scene.
|
||||
pub handle: Handle<Node>,
|
||||
/// Intensity is how bright light is. Default is 1.0.
|
||||
pub intensity: f32,
|
||||
/// Direction of light rays.
|
||||
pub direction: Vector3<f32>,
|
||||
/// Color of light.
|
||||
pub color: Vector3<f32>,
|
||||
}
|
||||
|
||||
/// Spot light is a cone light source. Example: flashlight.
|
||||
pub struct SpotLightDefinition {
|
||||
/// A handle of light in the scene.
|
||||
pub handle: Handle<Node>,
|
||||
/// Intensity is how bright light is. Default is 1.0.
|
||||
pub intensity: f32,
|
||||
/// Color of light.
|
||||
pub color: Vector3<f32>,
|
||||
/// Direction vector of light.
|
||||
pub direction: Vector3<f32>,
|
||||
/// Position of light in world coordinates.
|
||||
pub position: Vector3<f32>,
|
||||
/// Distance at which light intensity decays to zero.
|
||||
pub distance: f32,
|
||||
/// Square of distance.
|
||||
pub sqr_distance: f32,
|
||||
/// Smoothstep left bound. It is ((hotspot_cone_angle + falloff_angle_delta) * 0.5).cos()
|
||||
pub edge0: f32,
|
||||
/// Smoothstep right bound. It is (hotspot_cone_angle * 0.5).cos()
|
||||
pub edge1: f32,
|
||||
}
|
||||
|
||||
/// Point light is a spherical light source. Example: light bulb.
|
||||
pub struct PointLightDefinition {
|
||||
/// A handle of light in the scene.
|
||||
pub handle: Handle<Node>,
|
||||
/// Intensity is how bright light is. Default is 1.0.
|
||||
pub intensity: f32,
|
||||
/// Position of light in world coordinates.
|
||||
pub position: Vector3<f32>,
|
||||
/// Color of light.
|
||||
pub color: Vector3<f32>,
|
||||
/// Radius of sphere at which light intensity decays to zero.
|
||||
pub radius: f32,
|
||||
/// Square of radius.
|
||||
pub sqr_radius: f32,
|
||||
}
|
||||
|
||||
/// Light definition for lightmap rendering.
|
||||
pub enum LightDefinition {
|
||||
/// See docs of [DirectionalLightDefinition](struct.PointLightDefinition.html)
|
||||
Directional(DirectionalLightDefinition),
|
||||
/// See docs of [SpotLightDefinition](struct.SpotLightDefinition.html)
|
||||
Spot(SpotLightDefinition),
|
||||
/// See docs of [PointLightDefinition](struct.PointLightDefinition.html)
|
||||
Point(PointLightDefinition),
|
||||
}
|
||||
|
||||
impl LightDefinition {
|
||||
fn handle(&self) -> Handle<Node> {
|
||||
match self {
|
||||
LightDefinition::Directional(v) => v.handle,
|
||||
LightDefinition::Spot(v) => v.handle,
|
||||
LightDefinition::Point(v) => v.handle,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes total area of triangles in surface data and returns size of square
|
||||
/// in which triangles can fit.
|
||||
fn estimate_size(data: &InstanceData, texels_per_unit: u32) -> u32 {
|
||||
let mut area = 0.0;
|
||||
for triangle in data.triangles.iter() {
|
||||
let a = data.vertices[triangle[0] as usize].world_position;
|
||||
let b = data.vertices[triangle[1] as usize].world_position;
|
||||
let c = data.vertices[triangle[2] as usize].world_position;
|
||||
area += math::triangle_area(a, b, c);
|
||||
}
|
||||
area.sqrt().ceil() as u32 * texels_per_unit
|
||||
}
|
||||
|
||||
/// Calculates distance attenuation for a point using given distance to the point and
|
||||
/// radius of a light.
|
||||
fn distance_attenuation(distance: f32, sqr_radius: f32) -> f32 {
|
||||
let attenuation = (1.0 - distance * distance / sqr_radius).clamp(0.0, 1.0);
|
||||
attenuation * attenuation
|
||||
}
|
||||
|
||||
/// Calculates properties of pixel (world position, normal) at given position.
|
||||
fn pick(
|
||||
uv: Vector2<f32>,
|
||||
grid: &Grid,
|
||||
data: &InstanceData,
|
||||
scale: f32,
|
||||
) -> Option<(Vector3<f32>, Vector3<f32>)> {
|
||||
if let Some(cell) = grid.pick(uv) {
|
||||
for triangle in cell.triangles.iter().map(|&ti| &data.triangles[ti]) {
|
||||
let ia = triangle[0] as usize;
|
||||
let ib = triangle[1] as usize;
|
||||
let ic = triangle[2] as usize;
|
||||
|
||||
let uv_a = data.vertices[ia].second_tex_coord;
|
||||
let uv_b = data.vertices[ib].second_tex_coord;
|
||||
let uv_c = data.vertices[ic].second_tex_coord;
|
||||
|
||||
let center = (uv_a + uv_b + uv_c).scale(1.0 / 3.0);
|
||||
let to_center = (center - uv)
|
||||
.try_normalize(std::f32::EPSILON)
|
||||
.unwrap_or_default()
|
||||
.scale(scale * 0.3333333);
|
||||
|
||||
let mut current_uv = uv;
|
||||
for _ in 0..3 {
|
||||
let barycentric = math::get_barycentric_coords_2d(current_uv, uv_a, uv_b, uv_c);
|
||||
|
||||
if math::barycentric_is_inside(barycentric) {
|
||||
let a = data.vertices[ia].world_position;
|
||||
let b = data.vertices[ib].world_position;
|
||||
let c = data.vertices[ic].world_position;
|
||||
|
||||
let na = data.vertices[ia].world_normal;
|
||||
let nb = data.vertices[ib].world_normal;
|
||||
let nc = data.vertices[ic].world_normal;
|
||||
|
||||
return Some((
|
||||
math::barycentric_to_world(barycentric, a, b, c),
|
||||
math::barycentric_to_world(barycentric, na, nb, nc),
|
||||
));
|
||||
}
|
||||
|
||||
// Offset uv to center for conservative rasterization.
|
||||
current_uv += to_center;
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
struct GridCell {
|
||||
// List of triangle indices.
|
||||
triangles: Vec<usize>,
|
||||
}
|
||||
|
||||
struct Grid {
|
||||
cells: Vec<GridCell>,
|
||||
size: usize,
|
||||
fsize: f32,
|
||||
}
|
||||
|
||||
impl Grid {
|
||||
/// Creates uniform grid where each cell contains list of triangles
|
||||
/// whose second texture coordinates intersects with it.
|
||||
fn new(data: &InstanceData, size: usize) -> Self {
|
||||
let mut cells = Vec::with_capacity(size);
|
||||
let fsize = size as f32;
|
||||
for y in 0..size {
|
||||
for x in 0..size {
|
||||
let bounds =
|
||||
Rect::new(x as f32 / fsize, y as f32 / fsize, 1.0 / fsize, 1.0 / fsize);
|
||||
|
||||
let mut triangles = Vec::new();
|
||||
|
||||
for (triangle_index, triangle) in data.triangles.iter().enumerate() {
|
||||
let uv_a = data.vertices[triangle[0] as usize].second_tex_coord;
|
||||
let uv_b = data.vertices[triangle[1] as usize].second_tex_coord;
|
||||
let uv_c = data.vertices[triangle[2] as usize].second_tex_coord;
|
||||
let uv_min = uv_a.per_component_min(&uv_b).per_component_min(&uv_c);
|
||||
let uv_max = uv_a.per_component_max(&uv_b).per_component_max(&uv_c);
|
||||
let triangle_bounds =
|
||||
Rect::new(uv_min.x, uv_min.y, uv_max.x - uv_min.x, uv_max.y - uv_min.y);
|
||||
if triangle_bounds.intersects(bounds) {
|
||||
triangles.push(triangle_index);
|
||||
}
|
||||
}
|
||||
|
||||
cells.push(GridCell { triangles })
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
cells,
|
||||
size,
|
||||
fsize: size as f32,
|
||||
}
|
||||
}
|
||||
|
||||
fn pick(&self, v: Vector2<f32>) -> Option<&GridCell> {
|
||||
let ix = (v.x * self.fsize) as usize;
|
||||
let iy = (v.y * self.fsize) as usize;
|
||||
self.cells.get(iy * self.size + ix)
|
||||
}
|
||||
}
|
||||
|
||||
/// https://en.wikipedia.org/wiki/Lambert%27s_cosine_law
|
||||
fn lambertian(light_vec: Vector3<f32>, normal: Vector3<f32>) -> f32 {
|
||||
normal.dot(&light_vec).max(0.0)
|
||||
}
|
||||
|
||||
/// https://en.wikipedia.org/wiki/Smoothstep
|
||||
fn smoothstep(edge0: f32, edge1: f32, x: f32) -> f32 {
|
||||
let k = ((x - edge0) / (edge1 - edge0)).clamp(0.0, 1.0);
|
||||
k * k * (3.0 - 2.0 * k)
|
||||
}
|
||||
|
||||
/// Generates lightmap for given surface data with specified transform.
|
||||
///
|
||||
/// # Performance
|
||||
@@ -875,205 +657,20 @@ fn smoothstep(edge0: f32, edge1: f32, x: f32) -> f32 {
|
||||
/// time it will take. Required time increases drastically if you enable shadows and
|
||||
/// global illumination (TODO), because in this case your data will be raytraced.
|
||||
fn generate_lightmap(
|
||||
instance: &Instance,
|
||||
other_instances: &[Instance],
|
||||
mesh: &lightmap::input::Mesh,
|
||||
other_meshes: &[lightmap::input::Mesh],
|
||||
lights: &[LightDefinition],
|
||||
texels_per_unit: u32,
|
||||
) -> Texture {
|
||||
// We have to re-generate new set of world-space vertices because UV generator
|
||||
// may add new vertices on seams.
|
||||
let atlas_size = estimate_size(instance.data(), texels_per_unit);
|
||||
let scale = 1.0 / atlas_size as f32;
|
||||
let grid = Grid::new(instance.data(), (atlas_size / 32).max(4) as usize);
|
||||
|
||||
let mut pixels: Vec<Vector4<u8>> =
|
||||
vec![Vector4::new(0, 0, 0, 0); (atlas_size * atlas_size) as usize];
|
||||
|
||||
let half_pixel = scale * 0.5;
|
||||
pixels
|
||||
.par_iter_mut()
|
||||
.enumerate()
|
||||
.for_each(|(i, pixel): (usize, &mut Vector4<u8>)| {
|
||||
let x = i as u32 % atlas_size;
|
||||
let y = i as u32 / atlas_size;
|
||||
|
||||
let uv = Vector2::new(x as f32 * scale + half_pixel, y as f32 * scale + half_pixel);
|
||||
|
||||
if let Some((world_position, world_normal)) = pick(uv, &grid, instance.data(), scale) {
|
||||
let mut pixel_color = Vector3::default();
|
||||
for light in lights {
|
||||
let (light_color, mut attenuation, light_position) = match light {
|
||||
LightDefinition::Directional(directional) => {
|
||||
let attenuation = directional.intensity
|
||||
* lambertian(directional.direction, world_normal);
|
||||
(directional.color, attenuation, Vector3::default())
|
||||
}
|
||||
LightDefinition::Spot(spot) => {
|
||||
let d = spot.position - world_position;
|
||||
let distance = d.norm();
|
||||
let light_vec = d.scale(1.0 / distance);
|
||||
let spot_angle_cos = light_vec.dot(&spot.direction);
|
||||
let cone_factor = smoothstep(spot.edge0, spot.edge1, spot_angle_cos);
|
||||
let attenuation = cone_factor
|
||||
* spot.intensity
|
||||
* lambertian(light_vec, world_normal)
|
||||
* distance_attenuation(distance, spot.sqr_distance);
|
||||
(spot.color, attenuation, spot.position)
|
||||
}
|
||||
LightDefinition::Point(point) => {
|
||||
let d = point.position - world_position;
|
||||
let distance = d.norm();
|
||||
let light_vec = d.scale(1.0 / distance);
|
||||
let attenuation = point.intensity
|
||||
* lambertian(light_vec, world_normal)
|
||||
* distance_attenuation(distance, point.sqr_radius);
|
||||
(point.color, attenuation, point.position)
|
||||
}
|
||||
};
|
||||
// Shadows
|
||||
if attenuation >= 0.01 {
|
||||
let mut query_buffer = ArrayVec::<usize, 64>::new();
|
||||
let shadow_bias = 0.01;
|
||||
let ray = Ray::from_two_points(light_position, world_position);
|
||||
'outer_loop: for other_instance in other_instances {
|
||||
other_instance
|
||||
.data()
|
||||
.octree
|
||||
.ray_query_static(&ray, &mut query_buffer);
|
||||
for &node in query_buffer.iter() {
|
||||
match other_instance.data().octree.node(node) {
|
||||
OctreeNode::Leaf { indices, .. } => {
|
||||
let other_data = other_instance.data();
|
||||
for &triangle_index in indices {
|
||||
let triangle =
|
||||
&other_data.triangles[triangle_index as usize];
|
||||
let va = other_data.vertices[triangle[0] as usize]
|
||||
.world_position;
|
||||
let vb = other_data.vertices[triangle[1] as usize]
|
||||
.world_position;
|
||||
let vc = other_data.vertices[triangle[2] as usize]
|
||||
.world_position;
|
||||
if let Some(pt) =
|
||||
ray.triangle_intersection_point(&[va, vb, vc])
|
||||
{
|
||||
if ray.origin.metric_distance(&pt) + shadow_bias
|
||||
< ray.dir.norm()
|
||||
{
|
||||
attenuation = 0.0;
|
||||
break 'outer_loop;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
OctreeNode::Branch { .. } => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pixel_color += light_color.scale(attenuation);
|
||||
}
|
||||
|
||||
*pixel = Vector4::new(
|
||||
(pixel_color.x.clamp(0.0, 1.0) * 255.0) as u8,
|
||||
(pixel_color.y.clamp(0.0, 1.0) * 255.0) as u8,
|
||||
(pixel_color.z.clamp(0.0, 1.0) * 255.0) as u8,
|
||||
255, // Indicates that this pixel was "filled"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Prepare light map for bilinear filtration. This step is mandatory to prevent bleeding.
|
||||
let mut rgb_pixels: Vec<Vector3<u8>> = Vec::with_capacity((atlas_size * atlas_size) as usize);
|
||||
for y in 0..(atlas_size as i32) {
|
||||
for x in 0..(atlas_size as i32) {
|
||||
let fetch = |dx: i32, dy: i32| -> Option<Vector3<u8>> {
|
||||
pixels
|
||||
.get(((y + dy) * (atlas_size as i32) + x + dx) as usize)
|
||||
.and_then(|p| {
|
||||
if p.w != 0 {
|
||||
Some(Vector3::new(p.x, p.y, p.z))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let src_pixel = pixels[(y * (atlas_size as i32) + x) as usize];
|
||||
if src_pixel.w == 0 {
|
||||
// Check neighbour pixels marked as "filled" and use it as value.
|
||||
if let Some(west) = fetch(-1, 0) {
|
||||
rgb_pixels.push(west);
|
||||
} else if let Some(east) = fetch(1, 0) {
|
||||
rgb_pixels.push(east);
|
||||
} else if let Some(north) = fetch(0, -1) {
|
||||
rgb_pixels.push(north);
|
||||
} else if let Some(south) = fetch(0, 1) {
|
||||
rgb_pixels.push(south);
|
||||
} else if let Some(north_west) = fetch(-1, -1) {
|
||||
rgb_pixels.push(north_west);
|
||||
} else if let Some(north_east) = fetch(1, -1) {
|
||||
rgb_pixels.push(north_east);
|
||||
} else if let Some(south_east) = fetch(1, 1) {
|
||||
rgb_pixels.push(south_east);
|
||||
} else if let Some(south_west) = fetch(-1, 1) {
|
||||
rgb_pixels.push(south_west);
|
||||
} else {
|
||||
rgb_pixels.push(Vector3::new(0, 0, 0));
|
||||
}
|
||||
} else {
|
||||
rgb_pixels.push(Vector3::new(src_pixel.x, src_pixel.y, src_pixel.z))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Blur lightmap using simplest box filter.
|
||||
let mut bytes = Vec::with_capacity((atlas_size * atlas_size * 3) as usize);
|
||||
for y in 0..(atlas_size as i32) {
|
||||
for x in 0..(atlas_size as i32) {
|
||||
if x < 1 || y < 1 || x + 1 == atlas_size as i32 || y + 1 == atlas_size as i32 {
|
||||
bytes.extend_from_slice(
|
||||
rgb_pixels[(y * (atlas_size as i32) + x) as usize].as_slice(),
|
||||
);
|
||||
} else {
|
||||
let fetch = |dx: i32, dy: i32| -> Vector3<i16> {
|
||||
let u8_pixel = rgb_pixels[((y + dy) * (atlas_size as i32) + x + dx) as usize];
|
||||
Vector3::new(u8_pixel.x as i16, u8_pixel.y as i16, u8_pixel.z as i16)
|
||||
};
|
||||
|
||||
let north_west = fetch(-1, -1);
|
||||
let north = fetch(0, -1);
|
||||
let north_east = fetch(1, -1);
|
||||
let west = fetch(-1, 0);
|
||||
let center = fetch(0, 0);
|
||||
let east = fetch(1, 0);
|
||||
let south_west = fetch(-1, 1);
|
||||
let south = fetch(0, 1);
|
||||
let south_east = fetch(-1, 1);
|
||||
|
||||
let sum = north_west
|
||||
+ north
|
||||
+ north_east
|
||||
+ west
|
||||
+ center
|
||||
+ east
|
||||
+ south_west
|
||||
+ south
|
||||
+ south_east;
|
||||
|
||||
bytes.push((sum.x / 9).clamp(0, 255) as u8);
|
||||
bytes.push((sum.y / 9).clamp(0, 255) as u8);
|
||||
bytes.push((sum.z / 9).clamp(0, 255) as u8);
|
||||
}
|
||||
}
|
||||
}
|
||||
let map = lightmap::LightMap::new(mesh, other_meshes, lights, texels_per_unit as usize);
|
||||
|
||||
Texture::from_bytes(
|
||||
TextureKind::Rectangle {
|
||||
width: atlas_size,
|
||||
height: atlas_size,
|
||||
width: map.width as u32,
|
||||
height: map.height as u32,
|
||||
},
|
||||
TexturePixelKind::RGB8,
|
||||
bytes,
|
||||
map.pixels,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user