|
Post by vorpal on Jun 25, 2019 0:50:56 GMT
I'm pretty far along, to the point where I'd like to implement multiple light sources. Looking back over the previous chapters, I see this on page 96:
"The world object described here supports only a single light source, but it’s not terribly difficult to support more than one. You would need to make sure your shade_hit() function iterates over all of the light sources, calling lighting() for each one and adding the colors together."
That's not all we have to do, is it? Don't we have to take shadows and shading into account somehow, along with hits? I tried this strategy mentioned for shade_hit and re-rendered the cover of the book, and it looks less like the actual cover.
(It's been awhile since I've worked on this, so I may have forgotten some details and might be misunderstanding some details.)
|
|
|
Post by Jamis on Jun 26, 2019 4:02:29 GMT
Hey vorpal ! You're right, shadows need to be taken into account, but it should all be encapsulated in the shade_hit function. I regret saying that it's "not terribly difficult", though -- if I had it to do again, I'd change the wording, because while the solution is short in terms of lines of code, it is not necessarily intuitive. In really, really rough pseudocode, what you're going to do is something like this: initialize surface color to black
for each light source: calculate shadow at the point of intersection pass light information and shadow information to the lighting() function add result to surface color
apply reflection/refraction return final color
Does that clarify at all? I can work on something more detailed tomorrow if you'd like.
|
|
|
Post by vorpal on Jun 27, 2019 3:59:20 GMT
Hi Jamis! I'll give it a try tomorrow and let you know. I think that's along the lines of what I did but I must have done something slightly askew, as the shadowing didn't match the book cover. I'll try to derive a better example that clearly shows the two light sources. Ha... despite being a mathematician, I'm not the most geometrically, spatially gifted person, so coming up with my own scenes is a challenge. Thanks so much! I really appreciate it and I am so glad to be back to working on my ray tracer.
|
|
|
Post by comps4food on Jun 27, 2019 6:11:38 GMT
Granted I have not finished, but I'm implementing multiple lights as I go. I'm on Chapter 8. The few changes I made are shade_hit iterates through each light in the world passing the light to is_shadowed and incorporating the results in lighting function. I had to make changes to the signature of is_shadowed to is_shadowed(world, point, light). I also need to make changes to the tests to pass the first light in the default world. Here is my code public RtColor ShadeHit(Computations computations) { var colorTotal = RtColor.Black;
foreach (var light in Lights) { var inShadow = IsShadowed(computations.OverPoint, light); var lighting = computations.Shape.Material.Lighting(light, computations.Point, computations.EyeVector, computations.NormalVector, inShadow);
colorTotal += lighting; }
return colorTotal; }
public bool IsShadowed(RtPoint point, PointLight light) { var vector = light.Position - point; var distance = vector.Magnitude(); var direction = vector.Normalized();
var ray = new Ray(point, direction); var intersetions = Intersect(ray);
var hit = intersetions.Hit();
if (hit != null && hit.Time < distance) { return true; } else { return false; } }
|
|
sv97
New Member
Posts: 3
|
Post by sv97 on Sept 6, 2019 12:34:06 GMT
Hey, I've implemented multiple light sources like this(Code is in rust, I've put in some annotations): //These are methods on `World`, so self is of type World (and &self a reference to a world) pub fn is_shadowed(&self, point: &Point) -> Vec<bool> { self.lights .iter() // iterate all light sources .map(|light| { //apply the normal operations on each one let v = &light.position - point; let distance = v.mag(); let direction = v.unit(); let ray = Ray::new((*point).clone(), direction.clone()); let is = self.intersect(&ray); let h = is.hit(); match h { Some(hit) => hit.t < distance, None => false, } }) .collect::<Vec<_>>() // collect the resulting iteration back into a vector }
pub fn shade_hit(&self, comp: &PreComp, remaining_recursions: usize) -> Color { self.lights .iter() // iterate over all light sources .zip(self.is_shadowed(&comp.over_point).into_iter()) // pair every light source with the corresponding is_shadowed value .map(|(light, shadowed)| { // normal code let surface = light.lighting( Arc::clone(&comp.object), &comp.object.material, &comp.point, &comp.eye, &comp.normal, shadowed, ); let reflected = self.reflected_color(comp, remaining_recursions); let refracted = self.refracted_color(comp, remaining_recursions); surface + reflected + refracted }) // fold over all pairs of colors (starting with a `None` in the place of one of the colors // to handle the case of only one lightsource .fold(None, |blend: Option<Color>, new_color| match blend { // if there are multiple colors then blend them together with a lighten-only blending // algorithm (which is RGB(max(r1, r2), max(g1, g2), max(b1, b2))) Some(blend) => Some(new_color.blend_lighten_only(blend)), None => Some(new_color), }) .unwrap() } No idea if that's the right way to do it but it seems to work fine github.com/SV-97/RTC-RayTracer/blob/master/media/Disco.gif
|
|
|
Post by chrisd on Jul 26, 2020 23:59:47 GMT
Resurrecting an old thread in order to keep everything about multiple light sources in one place. For those of you that have implemented multiple light sources, did anyone take steps to make sure that the ambient value only gets included once? I think there would be two ways of doing this: passing multiple lights and intensities/in_shadow values into the lighting function and adding the diffuse and specular for each light, and adding the ambient only once. This is probably the best approach, and would require minor changes to the existing tests to pass in lists of lights and is_shadowed or intensity_at values; or
- making the lighting function not include the ambient value, and adding it in shade_hit. This would probably break the tests that expect the lighting function to add it.
|
|
|
Post by Jamis on Jul 27, 2020 5:01:13 GMT
I leave the ambient contribution in for each light. If you think of the ambient contribution as the amount of light from each light source that indirectly contributes to the scene (e.g. the light that bounces off walls and thus slightly illuminates the shaded portions of objects), it makes sense to have each light contribute its own fraction toward ambient.
This does mean, though, that when dealing with multiple light sources, you need to dial their intensities back, to avoid lighting values greater than 1. I do this manually in scenes with multiple light sources.
|
|
|
Post by vorpal on Jan 8, 2023 3:45:00 GMT
I know this is well after the time that I asked the question, but I just started working on a new implementation of a ray tracer in Kotlin instead of trying to be fancy and do it in C++ with the option of making the ray tracer completely constexpr, which is what I did the first time and it just proved to be too ambitious and compilers don't all treat the same expressions as constexpr. This was really incredibly helpful, comps4food. I'm working in Kotlin and I wasn't sure if I should be passing a light source to isShadowed and returning a Boolean or if I should have isShadowed as per the signature in the book and then map over the list of Light and return a list of Boolean. This approach is much easier.
|
|
|
Post by vorpal on Jan 8, 2023 3:46:02 GMT
I see that sv97 did things the way I had initially planned, having isShadowed returning a vector of bool, so I suppose either approach can work just fine.
|
|