|
Post by Jamis on Aug 16, 2021 15:05:48 GMT
Curious!
It's notable that in your first image (with the plane bisecting the spheres) the spheres were not black (except for the odd little ring). This suggests to me that something isn't quite right with the case where light is bouncing around *inside* the sphere. When no light is reflected internally (because the plane is inside the sphere), the transparency works fine. When the light passes through the sphere, something breaks and no light escapes (resulting in black).
If you have your ray tracer code anywhere I can look at it, I'd be happy to take a peek at your refraction and reflection code, to see if I can notice anything odd. Otherwise, my advice for debugging this would be to pick a single (problematic) pixel in your image and trace your code as it renders that one pixel, doing the math by hand as you go to double-check it. It's a tedious process, but it's helped me squash some tricky bugs in my own code, more than once.
|
|
|
Post by disembleergon on Aug 18, 2021 8:57:16 GMT
|
|
|
Post by Jamis on Aug 18, 2021 14:15:26 GMT
Thanks for sharing your repo! Your code looks really great overall. The only difference I can see is in the very last line of the schlick function, here: github.com/Disembleergon/3d-raytracer/blob/master/3d-raytracing-renderer/World.cpp#L133return std::pow(r0 + (1 - r0) * (1 - cos), 5); Here, you're raising r0 + (1 - r0) * (1 - cos) to the fifth power. What should actually be happening is taking (1 - cos) to the fifth power, and multiplying that by (1 - r0), e.g.: return r0 + (1 - r0) * std::pow((1 - cos), 5); If you look closely at the lower edges of your black spheres, you can see that there is a little bit of refraction going on. At the very edges of your sphere, I believe the value of the schlick function approaches 1, which means even raising it to the fifth power will make little difference, resulting in the bits of refraction you see in your image. I'm optimistic that if you fix that last line to raise only (1 - cos) to the fifth power, the rest of the sphere should render correctly, too! Let me know if that helps, Jamis
|
|
|
Post by disembleergon on Aug 19, 2021 7:21:41 GMT
Hi Jamis, I'm now raising (1 - cos) to the fith power and the reflection at the edges definitely looks better now, but still, the sphere is completely black.
|
|
|
Post by Jamis on Aug 19, 2021 16:51:45 GMT
Okay, let's try this, then. When I render (at 300x300) the pixel at 141,141 and display the state of the renderer at that point, here's what I get:
color: v(0.4126,0.4126,0.4126) t: 4.00337188413327 point: p(-0.0519,0.0519,-0.9973) inside: false eyev: v(0.0130,-0.0130,-0.9998) normalv: v(-0.0519,0.0519,-0.9973) reflectv: v(-0.1164,0.1164,-0.9864) over point: p(-0.0519,0.0519,-0.9974) under point: p(-0.0519,0.0519,-0.9972) n1: 1 n2: 1.5 schlick: 0.04000000000127377
Because of variation in your scene and your renderer, you may not get exactly these numbers, but they should be in about the same ballpark. Maybe any differences will suggest where things are not right in your implementation?
edit: I should clarify -- these numbers are for the very first intersection, the one nearest the camera, with the outermost sphere.
|
|
|
Post by disembleergon on Aug 20, 2021 9:16:41 GMT
Hi Jamis,
I displayed the values and here are the results:
t: 4.0033718841332702 point: {x=-0.051913417595411494 y=0.051913417595411494 z=-0.99730135573312229 ...} inside: false eyev: {x=0.012967423236687578 y=-0.012967423236687578 z=-0.99983183179432800 ...} normalv: {x=-0.051913417595411626 y=0.051913417595411626 z=-0.99730135573312484 ...} reflectv: {x=-0.11635686450645022 y=0.11635686450645022 z=-0.98636816664187577 ...} over_point: {x=-0.051913936729587447 y=0.051913936729587447 z=-0.99731132874667960 ...} under_point: {x=-0.051912898461235542 y=0.051912898461235542 z=-0.99729138271956497 ...} n1: 1.0000000000000000 n2: 1.5000000000000000 schlick: 0.04 color: r: 0.0140636 g: 0.0140636 b: 0.0140636 edit: when I display the normal result of color_at (Intersection is the hit) qt the coordinate {141; 141}, I get this color:
r: 9.31058e-95 g: 9.31058e-95 b: 9.31058e-95
|
|
|
Post by Jamis on Aug 23, 2021 13:53:43 GMT
Okay, here are a few more numbers to try, same pixel, same resolution as before.
At the first intersection with the inner sphere:
color: v(0.4775,0.4775,0.4775) t: 0.5017801325608208 point: p(-0.0475,0.0475,-0.4955) inside: false eyev: v(-0.0087,0.0087,-0.9999) normalv: v(-0.0951,0.0951,-0.9909) reflectv: v(-0.1800,0.1800,-0.9670) over point: p(-0.0475,0.0475,-0.4956) under point: p(-0.0475,0.0475,-0.4954) n1: 1.5 n2: 1.0000034 schlick: 0.03999934854893808
At the second intersection with the inner sphere:
color: v(0.5527,0.5527,0.5527) t: 0.9829494388012451 point: p(-0.0820,0.0820,0.4864) inside: true eyev: v(0.0350,-0.0350,-0.9988) normalv: v(0.1639,-0.1639,-0.9728) reflectv: v(0.2872,-0.2872,-0.9138) over point: p(-0.0819,0.0819,0.4863) under point: p(-0.0820,0.0820,0.4865) n1: 1.0000034 n2: 1.5 schlick: 0.03999934854622657
And at the second intersection with the outer sphere:
color: v(0.6397,0.6397,0.6397) t: 0.5017795642243366 point: p(-0.1214,0.1214,0.9851) inside: true eyev: v(0.0786,-0.0786,-0.9938) normalv: v(0.1214,-0.1214,-0.9851) reflectv: v(0.1638,-0.1638,-0.9728) over point: p(-0.1214,0.1214,0.9851) under point: p(-0.1214,0.1214,0.9852) n1: 1.5 n2: 1 schlick: 0.04000000000127249
Do any of those give any more clues?
|
|
|
Post by disembleergon on Aug 26, 2021 16:24:05 GMT
Hi Jamis, to display the values, I'm setting breakpoints in the color_at method and the shade_hit method, but because of the recursion I actually can't find the right t value etc... Here's how I choose first inner sphere intersection: Color color_at(World &world, Ray &r, const int remaining)
{
IntersectionList xs = r.intersect_world(world);
Intersection i = xs[1]; //hit(xs);
if (!i.isDefined())
return Color{};
Computations tcomps = prepare_computations(i, r, xs);
return shade_hit(world, tcomps, remaining);
And here's how I choose the right coordinate: Canvas World::render(Camera &cam)
{
Canvas image{cam.hsize, cam.vsize};
for (int y = 0; y < cam.vsize; y++)
{
for (int x = 0; x < cam.hsize; x++)
{
if (x == 141 && y == 141)
{
Ray r = cam.ray_for_pixel(x, y);
Color clr = color_at(*this, r, 4);
std::cout << "r: " << clr.red << " g: " << clr.green << " b: " << clr.blue << std::endl;
image.write_pixel(x, y, clr.normize());
}
}
}
return image;
}
Sorry that I had to ask, but I haven't debugged my ray tracer this way before
|
|
|
Post by Jamis on Aug 26, 2021 16:34:53 GMT
I'm pretty old school when it comes to debugging, I just put in a bunch of print statements and see what comes out. For the output I posted, I've got something like the following in my color_at method (which is in Ruby, but you should be able to get the gist of what I mean): def color_at(ray, max_depth: 5) # ... info = hit.prepare(ray, xs) result = shading(info, max_depth: max_depth)
if max_depth > 0 puts "----" puts "color: #{result}" puts "t: #{info.t}" puts "point: #{info.point}" puts "inside: #{info.inside}" puts "eyev: #{info.eyev}" puts "normalv: #{info.normalv}" puts "reflectv: #{info.reflectv}" puts "over point: #{info.over_point}" puts "under point: #{info.under_point}" puts "n1: #{info.n1}" puts "n2: #{info.n2}" puts "schlick: #{info.schlick}" if info.n1 end
# ... end This will (of course) dump quite a bit of info, and most of the output is not useful (coming from very deep in the recursion). Judging from the `point` value I take the output that corresponds to the intersections I expect, and those are the ones I shared. It's very much a "shotgun" style of debugging, but it has served me well in the past.
|
|
|
Post by disembleergon on Aug 27, 2021 14:35:51 GMT
Hi Jamis, here are the values in order:
1
t=> 0.501871
point=> x: -0.0475425 y: 0.0475425 z: -0.495459 w: 1
inside => false
eyev => x: -0.00870824 y: 0.00870824 z: -0.999924 w: 0
normalv => x: -0.095085 y: 0.095085 z: -0.990918 w: 0
reflectv => x: -0.180035 y: 0.180035 z: -0.967044 w: 0
over point => x: -0.0475434 y: 0.0475434 z: -0.495469 w: 1
under point => x: -0.0475415 y: 0.0475415 z: -0.495449 w: 1
n1 => 1.5
n2 => 1
schlick => 0.0399993
2
t=> 0.983032
point=> x: -0.0819696 y: 0.0819696 z: 0.486376 w: 1
inside => true
eyev => x: 0.0350223 y: -0.0350223 z: -0.998773 w: 0
normalv => x: 0.163939 y: -0.163939 z: -0.972753 w: 0
reflectv => x: 0.287296 y: -0.287296 z: -0.913741 w: 0
over point => x: -0.0819712 y: 0.0819712 z: 0.486386 w: 1
under point => x: -0.0819679 y: 0.0819679 z: 0.486367 w: 1
n1 => 1
n2 => 1.5
schlick => 0.0399993
3 (DIFFERENCES)
t=> 0.501871
point=> x: -0.137898 y: 0.137898 z: -0.9808 w: 1
inside => true
eyev => x: 0.180035 y: -0.180035 z: 0.967044 w: 0
normalv => x: 0.137898 y: -0.137898 z: 0.9808 w: 0
reflectv => x: 0.0952447 y: -0.0952447 z: 0.990887 w: 0
over point => x: -0.137899 y: 0.137899 z: -0.98081 w: 1
under point => x: -0.137896 y: 0.137896 z: -0.98079 w: 1
n1 => 1.5
n2 => 1
schlick => 0.04
From the color_at method (called from the render method), I get the following color (NEGATIVE VALUES):
{red=-9.2559631349317831e+61 green=-9.2559631349317831e+61 blue=-9.2559631349317831e+61 }
|
|
|
Post by Jamis on Aug 31, 2021 15:25:20 GMT
Tricky! I'm really not sure. The one thing that stands out to me is in your third intersection (where, as you noted, there were differences), with the computed point of intersection (and subsequently, all the related vectors and points). In my third intersection, the z value for that point is positive, whereas yours is negative.
This may be a red herring -- it might be as simple as you showing the reflection ray from the surface of the inner sphere, while I showed the refraction ray. But if it turns out that your third intersection is supposed to be the refraction ray, then there might be something further to investigate there, because the point of intersection is *behind* the ray's point of origin. (I hope that makes sense!)
I wish I could suggest something more helpful! These bugs can be frustratingly difficult to track down, but it's not impossible. It takes a lot of patience, coupled with carefully stepping through your code and manually checking each calculation. Drawing the result on paper as you go helps, too---checking to see if the numbers that are computed actually make sense intuitively.
Please let me know if there is anything else you'd like me to try on my end. I'm happy to provide more numbers from my own ray tracer if that would help.
|
|
|
Post by disembleergon on Sept 4, 2021 9:59:01 GMT
Hi Jamis,
I just ran all the tests again and I noticed, that in "Test #7: Finding the Refracted Color" my resulting color seems completely off...
r: 0.3 g: 0.3 b: 0.3 Here's the test:
World w = DEFAULT_WORLD();
Shape *a = w.objects[0].get();
a->material.ambient = 1;
a->material.pattern = makePattern_ptr<TestPattern>(TestPattern{});
Shape *b = w.objects[1].get();
b->material.transparency = 1;
b->material.refractive_index = 1.5; // aka RefractiveIndex::glass
Ray r{point(0, 0, 0.1), vector(0, 1, 0)};
IntersectionList xs = {{-0.9899, a}, {-0.4899, b}, {0.4899, b}, {0.9899, a}};
Computations comps = prepare_computations(xs[2], r, xs);
Color clr = refracted_color(w, comps, 5);
TESTS::printColor(clr); Is this information helpful?
EDIT: the resulting color should look like this:
r: 0 g: 0.99888 b: 0.04725)
|
|
|
Post by icolomby on Sept 4, 2021 16:38:49 GMT
Hi, I've been looking at your code and pinpointed the issue to your prepare_computations() method. You need to negate the normal before calculating the over_point and under_point. Here is a snippet my C# code: Normalv = Object.NormalAt(Point);
if (Normalv.Dot(Eyev) < 0) { Inside = true; Normalv = -Normalv; } else { Inside = false; }
// after computing and (if appropriate) negating // the normal vector... OverPoint = Point + (Normalv * Constants.EPSILON); UnderPoint = Point - (Normalv * Constants.EPSILON);
I ran your code after making that change and your failing passed with the correct color, and the scene produced the correct image. Ian.
|
|
|
Post by disembleergon on Sept 8, 2021 17:22:10 GMT
Hello Ian, thank you so much for your help and your effort, I finally fixed this issue because of Jamis and you! Thank you both for helping me
|
|