diff --git a/assets/models/blue-sphere.bin b/assets/models/blue-sphere.bin new file mode 100644 index 0000000..c26ad24 Binary files /dev/null and b/assets/models/blue-sphere.bin differ diff --git a/assets/models/blue-sphere.blend b/assets/models/blue-sphere.blend new file mode 100644 index 0000000..517c9f5 Binary files /dev/null and b/assets/models/blue-sphere.blend differ diff --git a/assets/models/blue-sphere.gltf b/assets/models/blue-sphere.gltf new file mode 100644 index 0000000..be26466 --- /dev/null +++ b/assets/models/blue-sphere.gltf @@ -0,0 +1,121 @@ +{ + "asset":{ + "generator":"Khronos glTF Blender I/O v4.0.44", + "version":"2.0" + }, + "scene":0, + "scenes":[ + { + "name":"Scene", + "nodes":[ + 0 + ] + } + ], + "nodes":[ + { + "mesh":0, + "name":"Sphere" + } + ], + "materials":[ + { + "doubleSided":true, + "name":"Material.001", + "pbrMetallicRoughness":{ + "baseColorFactor":[ + 0.04434913769364357, + 0, + 0.8024659156799316, + 1 + ], + "metallicFactor":0.8912280797958374, + "roughnessFactor":0.3807017505168915 + } + } + ], + "meshes":[ + { + "name":"Sphere", + "primitives":[ + { + "attributes":{ + "POSITION":0, + "NORMAL":1, + "TEXCOORD_0":2 + }, + "indices":3, + "material":0 + } + ] + } + ], + "accessors":[ + { + "bufferView":0, + "componentType":5126, + "count":559, + "max":[ + 0.9999997019767761, + 1, + 0.9999993443489075 + ], + "min":[ + -0.9999990463256836, + -1, + -1 + ], + "type":"VEC3" + }, + { + "bufferView":1, + "componentType":5126, + "count":559, + "type":"VEC3" + }, + { + "bufferView":2, + "componentType":5126, + "count":559, + "type":"VEC2" + }, + { + "bufferView":3, + "componentType":5123, + "count":2880, + "type":"SCALAR" + } + ], + "bufferViews":[ + { + "buffer":0, + "byteLength":6708, + "byteOffset":0, + "target":34962 + }, + { + "buffer":0, + "byteLength":6708, + "byteOffset":6708, + "target":34962 + }, + { + "buffer":0, + "byteLength":4472, + "byteOffset":13416, + "target":34962 + }, + { + "buffer":0, + "byteLength":5760, + "byteOffset":17888, + "target":34963 + } + ], + "buffers":[ + { + "byteLength":23648, + "uri":"blue-sphere.bin" + } + ] +} diff --git a/assets/models/crystal-monkey.bin b/assets/models/crystal-monkey.bin new file mode 100644 index 0000000..2db4596 Binary files /dev/null and b/assets/models/crystal-monkey.bin differ diff --git a/assets/models/crystal-monkey.blend b/assets/models/crystal-monkey.blend new file mode 100644 index 0000000..a78412f Binary files /dev/null and b/assets/models/crystal-monkey.blend differ diff --git a/assets/models/crystal-monkey.blend1 b/assets/models/crystal-monkey.blend1 new file mode 100644 index 0000000..247bc47 Binary files /dev/null and b/assets/models/crystal-monkey.blend1 differ diff --git a/assets/models/crystal-monkey.gltf b/assets/models/crystal-monkey.gltf new file mode 100644 index 0000000..52286e8 --- /dev/null +++ b/assets/models/crystal-monkey.gltf @@ -0,0 +1,134 @@ +{ + "asset":{ + "generator":"Khronos glTF Blender I/O v4.0.44", + "version":"2.0" + }, + "extensionsUsed":[ + "KHR_materials_sheen" + ], + "scene":0, + "scenes":[ + { + "name":"Scene", + "nodes":[ + 0 + ] + } + ], + "nodes":[ + { + "mesh":0, + "name":"Suzanne" + } + ], + "materials":[ + { + "doubleSided":true, + "extensions":{ + "KHR_materials_sheen":{ + "sheenColorFactor":[ + 1.0, + 1.0, + 1.0 + ], + "sheenRoughnessFactor":0.5 + } + }, + "name":"Material.001", + "pbrMetallicRoughness":{ + "baseColorFactor":[ + 0.8002911806106567, + 0, + 0.04051172733306885, + 1 + ], + "metallicFactor":0.8771929740905762, + "roughnessFactor":0.23684212565422058 + } + } + ], + "meshes":[ + { + "name":"Suzanne", + "primitives":[ + { + "attributes":{ + "POSITION":0, + "NORMAL":1, + "TEXCOORD_0":2 + }, + "indices":3, + "material":0 + } + ] + } + ], + "accessors":[ + { + "bufferView":0, + "componentType":5126, + "count":1966, + "max":[ + 1.3671875, + 0.984375, + 0.8515625 + ], + "min":[ + -1.3671875, + -0.984375, + -0.8515625 + ], + "type":"VEC3" + }, + { + "bufferView":1, + "componentType":5126, + "count":1966, + "type":"VEC3" + }, + { + "bufferView":2, + "componentType":5126, + "count":1966, + "type":"VEC2" + }, + { + "bufferView":3, + "componentType":5123, + "count":2904, + "type":"SCALAR" + } + ], + "bufferViews":[ + { + "buffer":0, + "byteLength":23592, + "byteOffset":0, + "target":34962 + }, + { + "buffer":0, + "byteLength":23592, + "byteOffset":23592, + "target":34962 + }, + { + "buffer":0, + "byteLength":15728, + "byteOffset":47184, + "target":34962 + }, + { + "buffer":0, + "byteLength":5808, + "byteOffset":62912, + "target":34963 + } + ], + "buffers":[ + { + "byteLength":68720, + "uri":"crystal-monkey.bin" + } + ] +} diff --git a/assets/shaders/pbr.frag b/assets/shaders/pbr.frag new file mode 100644 index 0000000..c0d9c8d --- /dev/null +++ b/assets/shaders/pbr.frag @@ -0,0 +1,160 @@ +#version 330 core + +in vec3 v_normal; +in vec2 v_tex; +in vec3 v_position; // view-space position + +out vec4 frag_color; + +// Material inputs +uniform sampler2D u_base_color_map; // sRGB +uniform int u_has_base_color_map; +uniform vec4 u_base_color_factor; + +uniform sampler2D u_metallic_roughness_map; // linear, (r=occlusion in glTF separate, g=roughness, b=metallic) +uniform int u_has_metallic_roughness_map; +uniform float u_metallic_factor; +uniform float u_roughness_factor; + +uniform sampler2D u_occlusion_map; // linear (r) +uniform int u_has_occlusion_map; + +uniform sampler2D u_emissive_map; // sRGB +uniform int u_has_emissive_map; +uniform vec3 u_emissive_factor; + +// Directional light (in view space) +uniform vec3 u_dir_light_dir; // direction from light towards the scene (L) +uniform vec3 u_dir_light_color; +uniform float u_dir_light_intensity; + +// Point lights (in view space) +#define MAX_POINT_LIGHTS 4 +uniform int u_point_light_count; +uniform vec3 u_point_light_pos[MAX_POINT_LIGHTS]; +uniform vec3 u_point_light_color[MAX_POINT_LIGHTS]; +uniform float u_point_light_intensity[MAX_POINT_LIGHTS]; +uniform float u_point_light_range[MAX_POINT_LIGHTS]; + +const float PI = 3.14159265359; + +vec3 fresnel_schlick(float cosTheta, vec3 F0) { + return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0); +} + +float distribution_ggx(float NdotH, float alpha) { + float a2 = alpha * alpha; + float denom = (NdotH * NdotH) * (a2 - 1.0) + 1.0; + return a2 / (PI * denom * denom + 1e-6); +} + +float geometry_schlick_ggx(float NdotV, float k) { + return NdotV / (NdotV * (1.0 - k) + k + 1e-6); +} + +float geometry_smith(float NdotV, float NdotL, float alpha) { + // Schlick-GGX with k from alpha for direct lighting + float k = (alpha + 1.0); + k = (k * k) / 8.0; + float ggx1 = geometry_schlick_ggx(NdotV, k); + float ggx2 = geometry_schlick_ggx(NdotL, k); + return ggx1 * ggx2; +} + +struct PBRMaterial { + vec3 baseColor; + float metallic; + float roughness; + float ao; + vec3 emissive; +}; + +PBRMaterial getMaterial() { + // Base color (sampled as sRGB then assumed linear by GL; still gamma-correct final output) + vec3 baseTex = (u_has_base_color_map != 0) ? texture(u_base_color_map, v_tex).rgb : vec3(1.0); + vec3 baseColor = baseTex * u_base_color_factor.rgb; + + float metallicTex = (u_has_metallic_roughness_map != 0) ? texture(u_metallic_roughness_map, v_tex).b : 1.0; + float roughnessTex = (u_has_metallic_roughness_map != 0) ? texture(u_metallic_roughness_map, v_tex).g : 1.0; + + float metallic = clamp(u_metallic_factor * metallicTex, 0.0, 1.0); + float roughness = clamp(u_roughness_factor * roughnessTex, 0.04, 1.0); + + float ao = (u_has_occlusion_map != 0) ? texture(u_occlusion_map, v_tex).r : 1.0; + + vec3 emissiveTex = (u_has_emissive_map != 0) ? texture(u_emissive_map, v_tex).rgb : vec3(0.0); + vec3 emissive = emissiveTex * u_emissive_factor; + + PBRMaterial m; + m.baseColor = baseColor; + m.metallic = metallic; + m.roughness = roughness; + m.ao = ao; + m.emissive = emissive; + return m; +} + +vec3 evaluatePBR(vec3 N, vec3 V, vec3 L, vec3 lightColor, float lightIntensity, PBRMaterial m) { + vec3 H = normalize(V + L); + float NdotV = max(dot(N, V), 0.0); + float NdotL = max(dot(N, L), 0.0); + float NdotH = max(dot(N, H), 0.0); + float HdotV = max(dot(H, V), 0.0); + + if (NdotL <= 0.0 || NdotV <= 0.0) return vec3(0.0); + + float alpha = m.roughness * m.roughness; + + vec3 F0 = mix(vec3(0.04), m.baseColor, m.metallic); + vec3 F = fresnel_schlick(HdotV, F0); + float D = distribution_ggx(NdotH, alpha); + float G = geometry_smith(NdotV, NdotL, alpha); + + vec3 numerator = F * D * G; + float denominator = max(4.0 * NdotV * NdotL, 1e-6); + vec3 specular = numerator / denominator; + + vec3 kS = F; + vec3 kD = (vec3(1.0) - kS) * (1.0 - m.metallic); + + vec3 diffuse = (m.baseColor / PI) * kD; + + vec3 radiance = lightColor * lightIntensity; + + return (diffuse + specular) * radiance * NdotL; +} + +void main() { + PBRMaterial mat = getMaterial(); + + vec3 N = normalize(v_normal); + vec3 V = normalize(-v_position); // camera at origin in view-space + + vec3 color = vec3(0.0); + + // Directional light contribution + vec3 Ld = normalize(u_dir_light_dir); + color += evaluatePBR(N, V, Ld, u_dir_light_color, u_dir_light_intensity, mat); + + // Point lights + for (int i = 0; i < u_point_light_count && i < MAX_POINT_LIGHTS; ++i) { + vec3 toLight = u_point_light_pos[i] - v_position; + float dist = length(toLight); + vec3 L = toLight / max(dist, 1e-4); + + float cutoff = 1.0 - smoothstep(0.9 * u_point_light_range[i], u_point_light_range[i], dist); + float attenuation = (1.0 / max(dist * dist, 1e-4)) * cutoff; + + vec3 contrib = evaluatePBR(N, V, L, u_point_light_color[i], u_point_light_intensity[i] * attenuation, mat); + color += contrib; + } + + // Simple ambient and AO + vec3 ambient = 0.03 * mat.baseColor * (1.0 - mat.metallic) * mat.ao; + color += ambient + mat.emissive; + + // Gamma correction to sRGB + color = pow(color, vec3(1.0 / 2.2)); + + frag_color = vec4(color, u_base_color_factor.a); +} \ No newline at end of file diff --git a/assets/shaders/pbr.vert b/assets/shaders/pbr.vert new file mode 100644 index 0000000..a1642c5 --- /dev/null +++ b/assets/shaders/pbr.vert @@ -0,0 +1,23 @@ +#version 330 core + +in vec3 position; +in vec3 normal; +in vec2 tex_coords; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 projection; +uniform vec2 uv_offset; +uniform vec2 uv_scale; + +out vec3 v_normal; +out vec2 v_tex; +out vec3 v_position; // view-space position + +void main() { + mat4 modelview = view * model; + v_normal = transpose(inverse(mat3(modelview))) * normal; + v_tex = tex_coords * uv_scale + uv_offset; + v_position = (modelview * vec4(position, 1.0)).xyz; + gl_Position = projection * modelview * vec4(position, 1.0); +} diff --git a/ecs/src/components.rs b/ecs/src/components.rs index 758f0f4..29f826c 100644 --- a/ecs/src/components.rs +++ b/ecs/src/components.rs @@ -15,3 +15,10 @@ impl Transform { } pub struct ModelHandle(pub ModelID); + +#[derive(Copy, Clone)] +pub struct PointLight { + pub color: Vec3, + pub intensity: f32, + pub range: f32, +} diff --git a/game/src/main.rs b/game/src/main.rs index 05ae11e..f98868c 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -5,14 +5,14 @@ use raidillon_engine::system::SystemContext; use raidillon_platform::{Platform, Camera}; use raidillon_assets::model_path; use raidillon_core::engine::EngineTrait; -use raidillon_ecs::components::ModelHandle; +use raidillon_ecs::components::{ModelHandle, PointLight}; use raidillon_ecs::Transform; use raidillon_core::scene::Scene; #[cfg(feature = "glium")] use raidillon_glium::GliumPlatform; use winit::event::{Event, WindowEvent}; -const TEST_GLTF: &str = "pink-monkey.gltf"; +const TEST_GLTF: &str = "blue-sphere.gltf"; const MAIN_SCENE_ID: &str = "main_scene"; @@ -58,12 +58,25 @@ impl System for RenderingTestSystem { ctx.scene.world.spawn(( Transform { - translation: Vec3::new(0.0, 0.0, 0.0), - rotation: Quat::IDENTITY, - scale: Vec3::new(1.0, 1.0, 1.0), + translation: Vec3::new(0.0, 0.0, 0.0), + rotation: Quat::IDENTITY, + scale: Vec3::new(1.0, 1.0, 1.0), }, ModelHandle(TEST_GLTF), )); + + ctx.scene.world.spawn(( + Transform { + translation: Vec3::new(0.0, 3.0, 0.0), + rotation: Quat::IDENTITY, + scale: Vec3::ONE, + }, + PointLight { + color: Vec3::ONE, + intensity: 1.0, + range: 10.0, + }, + )); } fn frame_update(&mut self, ctx: &mut SystemContext) { @@ -79,7 +92,6 @@ impl System for RenderingTestSystem { t.rotation *= Quat::from_rotation_y(*self.rotation_speed.borrow() * ctx.platform_context.time_ctx.fixed_dt); }); } - } fn main() { diff --git a/glium_platform/src/platform.rs b/glium_platform/src/platform.rs index 16df321..d6fb33c 100644 --- a/glium_platform/src/platform.rs +++ b/glium_platform/src/platform.rs @@ -14,7 +14,7 @@ use raidillon_core::engine::EngineTrait; use raidillon_core::time; use raidillon_core::time::Time; use crate::render::debug_ui::ImguiBridge; -use crate::render::BasicMeshRenderingSystem; +use crate::render::PbrMeshRenderingSystem; use crate::GliumAssetManager; pub struct GliumPlatform> { @@ -45,7 +45,7 @@ impl> Platform for GliumPlatfor let time = time::Time::new(time_cfg); // Install rendering systems - rendering_system_manager.add::(&display, &window); + rendering_system_manager.add::(&display, &window); rendering_system_manager.add::(&display, &window); Self { diff --git a/glium_platform/src/render/mod.rs b/glium_platform/src/render/mod.rs index c31a0d9..25c194c 100644 --- a/glium_platform/src/render/mod.rs +++ b/glium_platform/src/render/mod.rs @@ -1,4 +1,4 @@ -mod basic; +mod pbr; pub mod debug_ui; -pub use basic::BasicMeshRenderingSystem; +pub use pbr::PbrMeshRenderingSystem; diff --git a/glium_platform/src/render/pbr.rs b/glium_platform/src/render/pbr.rs new file mode 100644 index 0000000..8851759 --- /dev/null +++ b/glium_platform/src/render/pbr.rs @@ -0,0 +1,192 @@ +// AI Generated + +use glium::{uniform, Display, Program, Surface}; +use glium::glutin::surface::WindowSurface; +use glium::texture::{RawImage2d, SrgbTexture2d, Texture2d}; +use glium::uniforms::{MagnifySamplerFilter, MinifySamplerFilter, SamplerWrapFunction}; +use glam::{Vec3, Vec4, Mat4}; +use crate::RenderingSystem; +use crate::system::RenderingContext; +use raidillon_assets::include_shader; +pub use raidillon_platform::Camera; +use raidillon_ecs::components::{ModelHandle, PointLight}; +use raidillon_ecs::Transform; +use crate::model::Model; + +pub struct PbrMeshRenderingSystem { + program: Program, + white_srgb: SrgbTexture2d, + black_srgb: SrgbTexture2d, + white_linear: Texture2d, + params: glium::DrawParameters<'static>, +} + +impl RenderingSystem for PbrMeshRenderingSystem { + fn initialize(display: &Display, _window: &glium::winit::window::Window) -> Self { + const VERT_SRC: &str = include_shader!("pbr.vert"); + const FRAG_SRC: &str = include_shader!("pbr.frag"); + + let program = Program::from_source(display, VERT_SRC, FRAG_SRC, None).unwrap(); + + let white_srgb = { + let data = vec![255u8, 255u8, 255u8, 255u8]; + let raw = RawImage2d::from_raw_rgba(data, (1, 1)); + SrgbTexture2d::new(display, raw).unwrap() + }; + let black_srgb = { + let data = vec![0u8, 0u8, 0u8, 255u8]; + let raw = RawImage2d::from_raw_rgba(data, (1, 1)); + SrgbTexture2d::new(display, raw).unwrap() + }; + let white_linear = { + let data = vec![255u8, 255u8, 255u8, 255u8]; + let raw = RawImage2d::from_raw_rgba(data, (1, 1)); + Texture2d::new(display, raw).unwrap() + }; + + let params = glium::DrawParameters { + depth: glium::Depth { + test: glium::draw_parameters::DepthTest::IfLess, + write: true, + .. Default::default() + }, + .. Default::default() + }; + + Self { + program, + white_srgb, + black_srgb, + white_linear, + params, + } + } + + fn render(&mut self, ctx: &mut RenderingContext) { + // Acquire camera + let cam = match ctx.scene.world.query::<&Camera>().iter().next() { + Some((_, cam)) => *cam, + None => { + eprintln!("[renderer] No camera component found. Skipping frame"); + return; + } + }; + + let view: Mat4 = cam.view(); + let projection: Mat4 = cam.projection(); + + // Directional light setup + let dir_light_dir_world: Vec3 = Vec3::new(-0.5, 3.0, -0.25).normalize(); + let dir_light_dir_view: Vec3 = (view * Vec4::new(dir_light_dir_world.x, dir_light_dir_world.y, dir_light_dir_world.z, 0.0)).truncate().normalize(); + let dir_light_color: Vec3 = Vec3::splat(1.0); + let dir_light_intensity: f32 = 3.5; + + // Collect point lights + const MAX_POINT_LIGHTS: usize = 3; + let mut pl_pos = [[0.0f32; 3]; MAX_POINT_LIGHTS]; + let mut pl_col = [[0.0f32; 3]; MAX_POINT_LIGHTS]; + let mut pl_int = [0.0f32; MAX_POINT_LIGHTS]; + let mut pl_range = [0.0f32; MAX_POINT_LIGHTS]; + let mut pl_count: i32 = 0; + + for (_, (tr, pl)) in ctx.scene.world.query::<(&Transform, &PointLight)>().iter() { + if (pl_count as usize) >= MAX_POINT_LIGHTS { break; } + let pos_view = (view * Vec4::new(tr.translation.x, tr.translation.y, tr.translation.z, 1.0)).truncate(); + pl_pos[pl_count as usize] = [pos_view.x, pos_view.y, pos_view.z]; + pl_col[pl_count as usize] = [pl.color.x, pl.color.y, pl.color.z]; + pl_int[pl_count as usize] = pl.intensity; + pl_range[pl_count as usize] = pl.range; + pl_count += 1; + } + + let asset_manager = ctx.asset_manager.borrow(); + + for (_, (tr, mh)) in ctx.scene.world.query::<(&Transform, &ModelHandle)>().iter() { + let model_any = match asset_manager.get_model(&mh.0) { Some(m) => m, _ => continue }; + let model = match model_any.downcast_ref::() { Some(m) => m, None => continue }; + + let mesh = &model.mesh; + let mat = &model.material; + + // Base color texture (sRGB) + let base_color_tex: &SrgbTexture2d = mat.base_color.as_ref().unwrap_or(&self.white_srgb); + let mut base_sampler = base_color_tex.sampled(); + base_sampler = base_sampler.wrap_function(SamplerWrapFunction::Repeat); + base_sampler = base_sampler.minify_filter(MinifySamplerFilter::Linear); + base_sampler = base_sampler.magnify_filter(MagnifySamplerFilter::Linear); + let has_base_color_map: i32 = if mat.base_color.is_some() { 1 } else { 0 }; + + // MR map (linear) + let mr_tex: &Texture2d = mat.metallic_roughness.as_ref().unwrap_or(&self.white_linear); + let mut mr_sampler = mr_tex.sampled(); + mr_sampler = mr_sampler.wrap_function(SamplerWrapFunction::Repeat); + mr_sampler = mr_sampler.minify_filter(MinifySamplerFilter::Linear); + mr_sampler = mr_sampler.magnify_filter(MagnifySamplerFilter::Linear); + let has_mr_map: i32 = if mat.metallic_roughness.is_some() { 1 } else { 0 }; + + // Occlusion map (linear, R) + let occlusion_tex: &Texture2d = mat.occlusion.as_ref().unwrap_or(&self.white_linear); + let mut occl_sampler = occlusion_tex.sampled(); + occl_sampler = occl_sampler.wrap_function(SamplerWrapFunction::Repeat); + occl_sampler = occl_sampler.minify_filter(MinifySamplerFilter::Linear); + occl_sampler = occl_sampler.magnify_filter(MagnifySamplerFilter::Linear); + let has_occlusion_map: i32 = if mat.occlusion.is_some() { 1 } else { 0 }; + + // Emissive map (sRGB) + let emissive_tex: &SrgbTexture2d = mat.emissive.as_ref().unwrap_or(&self.black_srgb); + let mut emissive_sampler = emissive_tex.sampled(); + emissive_sampler = emissive_sampler.wrap_function(SamplerWrapFunction::Repeat); + emissive_sampler = emissive_sampler.minify_filter(MinifySamplerFilter::Linear); + emissive_sampler = emissive_sampler.magnify_filter(MagnifySamplerFilter::Linear); + let has_emissive_map: i32 = if mat.emissive.is_some() { 1 } else { 0 }; + + let bc = mat.base_color_factor; + let ef = mat.emissive_factor; + + let uniforms = uniform! { + model: tr.matrix().to_cols_array_2d(), + view: view.to_cols_array_2d(), + projection: projection.to_cols_array_2d(), + uv_offset: [mat.uv_offset.x, mat.uv_offset.y], + uv_scale: [mat.uv_scale.x, mat.uv_scale.y], + + // Material + u_base_color_map: base_sampler, + u_has_base_color_map: has_base_color_map, + u_base_color_factor: [bc[0], bc[1], bc[2], bc[3]], + + u_metallic_roughness_map: mr_sampler, + u_has_metallic_roughness_map: has_mr_map, + u_metallic_factor: mat.metal_factor, + u_roughness_factor: mat.roughness_factor, + + u_occlusion_map: occl_sampler, + u_has_occlusion_map: has_occlusion_map, + + u_emissive_map: emissive_sampler, + u_has_emissive_map: has_emissive_map, + u_emissive_factor: [ef[0], ef[1], ef[2]], + + // Directional light (view-space) + u_dir_light_dir: [dir_light_dir_view.x, dir_light_dir_view.y, dir_light_dir_view.z], + u_dir_light_color: [dir_light_color.x, dir_light_color.y, dir_light_color.z], + u_dir_light_intensity: dir_light_intensity, + + // Point lights (view-space) + u_point_light_count: pl_count, + u_point_light_pos: pl_pos, + u_point_light_color: pl_col, + u_point_light_intensity: pl_int, + u_point_light_range: pl_range, + }; + + ctx.target.draw( + &mesh.vbuf, + &mesh.ibuf, + &self.program, + &uniforms, + &self.params, + ).unwrap(); + } + } +}