Skip to content

Commit 6f9d3c1

Browse files
committed
Add support for light sources
Directional, point, and spot lights are implemented.
1 parent 898dbe7 commit 6f9d3c1

File tree

4 files changed

+224
-18
lines changed

4 files changed

+224
-18
lines changed

core/src/render.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub(super) mod re_exports {
2626
cam::Camera,
2727
clip::Clip,
2828
ctx::Context,
29+
light::Light,
2930
raster::Frag,
3031
shader::{FragmentShader, VertexShader},
3132
stats::Stats,
@@ -41,6 +42,7 @@ pub mod cam;
4142
pub mod clip;
4243
pub mod ctx;
4344
pub mod debug;
45+
pub mod light;
4446
pub mod prim;
4547
pub mod raster;
4648
pub mod scene;

core/src/render/cam.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,9 @@ impl<T> Camera<T> {
210210
}
211211
}
212212

213+
// TODO Should probably pass view and projection matrices separately
214+
pub type CameraUni<'a, B, Uni> = (&'a ProjMat3<B>, Uni);
215+
213216
impl<T: Transform> Camera<T> {
214217
/// Returns the camera matrix.
215218
pub fn world_to_view(&self) -> Mat4<World, View> {
@@ -224,6 +227,10 @@ impl<T: Transform> Camera<T> {
224227
pub fn world_to_project(&self) -> ProjMat3<World> {
225228
self.world_to_view().then(&self.project)
226229
}
230+
/// Returns the camera (view) matrix.
231+
pub fn world_to_view(&self) -> Mat4<World, View> {
232+
self.transform.world_to_view()
233+
}
227234

228235
/// Renders the given geometry from the viewpoint of this camera.
229236
pub fn render<B, Prim, Vtx: Clone, Var: Vary, Uni: Copy, Shd>(
@@ -238,15 +245,14 @@ impl<T: Transform> Camera<T> {
238245
) where
239246
Prim: Render<Var> + Clone,
240247
[<Prim>::Clip]: Clip<Item = Prim::Clip>,
241-
Shd: for<'a> Shader<Vtx, Var, (&'a ProjMat3<B>, Uni)>,
248+
Shd: for<'a> Shader<Vtx, Var, CameraUni<'a, B, Uni>>,
242249
{
243-
let tf = to_world.then(&self.world_to_project());
244-
250+
let to_proj = to_world.then(&self.world_to_project());
245251
super::render(
246252
prims.as_ref(),
247253
verts.as_ref(),
248254
shader,
249-
(&tf, uniform),
255+
(&to_proj, uniform),
250256
self.viewport,
251257
target,
252258
ctx,

core/src/render/light.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
//! Light sources
2+
3+
use core::fmt::Debug;
4+
5+
use crate::math::{Color3f, Linear, Mat4, Point3, Vec3, color::gray};
6+
7+
#[derive(Copy, Clone, Debug)]
8+
pub struct Light<B: Default> {
9+
pub color: Color3f,
10+
pub kind: Kind<B>,
11+
pub falloff: u8,
12+
}
13+
14+
impl<B: Default> Default for Light<B> {
15+
fn default() -> Self {
16+
Self {
17+
color: gray(1.0),
18+
kind: Kind::default(),
19+
falloff: 0,
20+
}
21+
}
22+
}
23+
24+
#[derive(Copy, Clone, Debug)]
25+
pub enum Kind<B: Default> {
26+
Directional {
27+
dir: Vec3<B>,
28+
},
29+
Point {
30+
pos: Point3<B>,
31+
},
32+
Spot {
33+
pos: Point3<B>,
34+
dir: Vec3<B>,
35+
radii: (f32, f32),
36+
},
37+
}
38+
39+
impl<B: Default> Default for Kind<B> {
40+
fn default() -> Self {
41+
Self::Directional { dir: -Vec3::Y }
42+
}
43+
}
44+
45+
impl<B: Debug + Default> Light<B> {
46+
pub fn new(color: Color3f, kind: Kind<B>) -> Self {
47+
Self { color, kind, ..Self::default() }
48+
}
49+
50+
pub fn direction(&self, pt: Point3<B>) -> Vec3<B> {
51+
match self.kind {
52+
Kind::Point { pos, .. } => pos - pt,
53+
Kind::Directional { dir } => dir,
54+
Kind::Spot { pos, .. } => pos - pt,
55+
}
56+
.normalize()
57+
}
58+
59+
pub fn eval(&self, pt: Point3<B>) -> (Color3f, Vec3<B>) {
60+
match self.kind {
61+
Kind::Point { pos } => (self.color, (pos - pt).normalize()),
62+
Kind::Directional { dir } => (self.color, dir),
63+
Kind::Spot { pos, dir, radii } => {
64+
// TODO pt.direction_to(pt2) method?
65+
let pt_dir = (pt - pos).normalize();
66+
let dir = dir.normalize();
67+
let dot = pt_dir.dot(&dir);
68+
let (r0, r1) = (1.0 - radii.0, 1.0 - radii.1);
69+
let color = if dot > r0 {
70+
self.color
71+
} else if dot > r1 {
72+
// TODO inv_lerp
73+
let t = (dot - r1) / (r0 - r1); // ok: r0 - r1 != 0
74+
self.color.mul(t)
75+
} else {
76+
gray(0.0)
77+
};
78+
(color, -pt_dir)
79+
}
80+
}
81+
/*if self.falloff > 0 {
82+
let dist = (pt - self.pos).len_sqr() * 0.5 + 1.0;
83+
color.mul(dist) //.powi(-(self.falloff as i32)))
84+
} else {
85+
color
86+
}*/
87+
}
88+
89+
pub fn transform<C: Default>(&self, mat: &Mat4<B, C>) -> Light<C> {
90+
let kind = match self.kind {
91+
Kind::Point { pos } => Kind::Point { pos: mat.apply(&pos) },
92+
Kind::Directional { dir } => {
93+
Kind::Directional { dir: mat.to().apply(&dir) }
94+
}
95+
Kind::Spot { pos, dir, radii } => Kind::Spot {
96+
pos: mat.apply(&pos),
97+
dir: mat.to().apply(&dir),
98+
radii,
99+
},
100+
};
101+
Light {
102+
kind,
103+
color: self.color,
104+
falloff: self.falloff,
105+
}
106+
}
107+
}

demos/src/bin/crates.rs

Lines changed: 105 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use re::core::math::color::gray;
66
use re::core::render::{
77
cam::{FirstPerson, Fov},
88
clip::Status::*,
9+
light::Kind,
910
scene::Obj,
1011
shader,
1112
tex::SamplerClamp,
@@ -16,6 +17,14 @@ use re::core::util::{pixfmt::Rgba8888, pnm::read_pnm};
1617
use re::front::sdl2::Window;
1718
use re::geom::solids::{Build, Cube};
1819

20+
struct Uniform {
21+
pub mv: Mat4<Model, View>,
22+
pub proj: ProjMat3<View>,
23+
pub light: Light<View>,
24+
}
25+
26+
type Varying = (Normal3, TexCoord);
27+
1928
fn main() {
2029
let mut win = Window::builder()
2130
.title("retrofire//crates")
@@ -29,30 +38,82 @@ fn main() {
2938
let light_dir = vec3(-2.0, 1.0, -4.0).normalize();
3039

3140
let floor_shader = shader::new(
32-
|v: Vertex3<_>, mvp: &ProjMat3<_>| vertex(mvp.apply(&v.pos), v.attrib),
41+
|v: Vertex3<_>, u: &Uniform| {
42+
vertex(u.mv.then(&u.proj).apply(&v.pos), v.attrib)
43+
},
3344
|frag: Frag<Vec2>| {
3445
let even_odd = (frag.var.x() > 0.5) ^ (frag.var.y() > 0.5);
3546
gray(if even_odd { 0.8 } else { 0.1 }).to_color4()
3647
},
3748
);
3849
let crate_shader = shader::new(
39-
|v: Vertex3<(Normal3, TexCoord)>, mvp: &ProjMat3<_>| {
40-
vertex(mvp.apply(&v.pos), v.attrib)
50+
|v: Vertex3<Varying>, u: &Uniform| {
51+
vertex(u.mv.then(&u.proj).apply(&v.pos), v.attrib)
4152
},
42-
|frag: Frag<(Normal3, TexCoord)>| {
53+
|frag: Frag<Varying>| {
4354
let (n, uv) = frag.var;
4455
let kd = lerp(n.dot(&light_dir).max(0.0), 0.4, 1.0);
4556
let col = SamplerClamp.sample(&tex, uv);
4657
(col.to_color3f() * kd).to_color4()
4758
},
4859
);
60+
let shader3 = shader::new(
61+
|v: Vertex3<Color3f>, u: &Uniform| {
62+
let pos = u.mv.apply(&v.pos);
63+
let (light_col, light_dir) = u.light.eval(pos);
64+
let light_col = light_col.add(&gray(0.05));
65+
let lam = -light_dir.y();
66+
// TODO light_col * surface_col should be compwise multiplication
67+
vertex(u.proj.apply(&pos), light_col.mul(lam).mul(v.attrib.r()))
68+
},
69+
|f: Frag<Color3f>| f.var.to_color4(),
70+
);
71+
72+
let crate_shader2 = shader::new(
73+
|v: Vertex3<Normal3>, u: &Uniform| {
74+
let n_modl = v.attrib.to();
75+
let n_view = u.mv.apply(&n_modl);
76+
let pos_view = u.mv.apply(&v.pos);
77+
let (light_col, light_dir) = u.light.eval(pos_view);
78+
79+
let refl = light_dir.reflect(n_view);
80+
81+
let specular = (-pos_view.to_vec())
82+
.normalize()
83+
.dot(&refl)
84+
.max(0.0)
85+
.powi(20);
86+
87+
let lam = n_view.dot(&light_dir).max(0.0);
88+
89+
let pos_proj = u.proj.apply(&pos_view);
90+
let color = light_col.mul(lam + specular);
91+
92+
vertex(pos_proj, color)
93+
},
94+
|frag: Frag<Color3f>| {
95+
//let [x, y, z] = ((f.var + splat(1.0)) / 2.0).0;
96+
//rgb(x, y, z).to_color4()
97+
frag.var.to_color4()
98+
},
99+
);
49100

50101
let (w, h) = win.dims;
51102
let mut cam = Camera::new(win.dims)
52103
.transform(FirstPerson::default())
53-
.viewport((10..w - 10, h - 10..10))
104+
.viewport((0..w, h..0))
54105
.perspective(Fov::Diagonal(degs(90.0)), 0.1..1000.0);
55106

107+
let light = Light::<Model> {
108+
color: rgb(1.0, 0.5, 0.1) * 1.1,
109+
kind: Kind::Spot {
110+
pos: pt3(0.0, -8.0, 0.0),
111+
dir: vec3(0.0, 1.0, 0.0),
112+
radii: (0.1, 0.3),
113+
},
114+
falloff: 0,
115+
};
116+
56117
let floor = floor();
57118
let crates = crates();
58119

@@ -84,6 +145,13 @@ fn main() {
84145
cam.transform
85146
.translate(cam_vel.mul(frame.dt.as_secs_f32()));
86147

148+
let light_to_view = translate(4.0 * Vec3::X)
149+
.then(&rotate_y(turns(frame.t.as_secs_f32() * 0.1)))
150+
.to()
151+
.then(&cam.world_to_view());
152+
153+
let light = light.transform(&light_to_view);
154+
87155
//
88156
// Render
89157
//
@@ -97,15 +165,19 @@ fn main() {
97165

98166
// Floor
99167
{
100-
let Obj { bbox, tf, geom } = &floor;
101-
let model_to_project = tf.then(&world_to_project);
102-
if bbox.visibility(&model_to_project) != Hidden {
103-
let mut b = batch
168+
let Obj { geom, bbox, tf } = &floor;
169+
let mv = tf.then(&cam.world_to_view());
170+
let proj = cam.project;
171+
let mvp = mv.then(&proj);
172+
let light = Default::default();
173+
174+
if bbox.visibility(&mvp) != Hidden {
175+
batch
104176
.clone()
105177
.mesh(geom)
178+
.uniform(&Uniform { mv, proj, light })
106179
.shader(floor_shader)
107-
.uniform(&model_to_project);
108-
b.render();
180+
.render();
109181
}
110182
}
111183

@@ -114,19 +186,21 @@ fn main() {
114186
for Obj { geom, bbox, tf } in &crates {
115187
frame.ctx.stats.borrow_mut().objs.i += 1;
116188

117-
let model_to_project = tf.then(&world_to_project);
189+
let mv = tf.then(&cam.world_to_view());
190+
let proj = cam.project;
191+
let mvp = mv.then(&proj);
118192

119193
// TODO Also if `Visible`, no further clipping or culling needed
120-
if bbox.visibility(&model_to_project) == Hidden {
194+
if bbox.visibility(&mvp) == Hidden {
121195
continue;
122196
}
123197

124198
batch
125199
// TODO Try to get rid of clone
126200
.clone()
127201
.mesh(geom)
202+
.uniform(&Uniform { mv, proj, light })
128203
.shader(crate_shader)
129-
.uniform(&model_to_project)
130204
.render();
131205

132206
frame.ctx.stats.borrow_mut().objs.o += 1;
@@ -140,6 +214,23 @@ fn main() {
140214
fn crates() -> Vec<Obj<(Normal3, TexCoord)>> {
141215
let obj = Obj::new(Cube { side_len: 2.0 }.build());
142216

217+
// let obj = Sphere {
218+
// sectors: 80,
219+
// segments: 50,
220+
// radius: 1.0,
221+
// }
222+
// .build();
223+
224+
// let obj = parse_obj(*include_bytes!("../../assets/teapot.obj"))
225+
// .unwrap()
226+
// .transform(
227+
// &scale(splat(0.4))
228+
// .then(&translate(-0.5 * Vec3::Y))
229+
// .to(),
230+
// )
231+
// .with_vertex_normals()
232+
// .build();
233+
143234
let mut res = vec![];
144235
let n = 30;
145236
for i in (-n..=n).step_by(5) {

0 commit comments

Comments
 (0)