|
Post by micahcantor on Aug 2, 2021 17:20:50 GMT
Hi all, I'm hoping someone can help me get unstuck on Chapter 11. Right now, every test in the book is passing except for the 7th test of chapter 11, 'finding the refracted color'. However, I suspect that I actually have a deeper issue, since the test only involves refraction, but I've played around with rendering some simple scenes only requiring reflection, and it just doesn't look quite right.
I'm implementing the raytracer in Haskell (yes, I know, silly me). I don't expect someone to help to be an expert in Haskell, but I've printed out all the relevant values to this test, and if someone could compare to their (working) results and let me know where there is an difference, that would help me enormously in tracking down the issue. When I run the test as described in the book, I get the following values when I call my `refractedColor` function:
eye: Vec (-0.0) (-1.0) (-0.0) normal: Vec (-0.0) (-0.9797960612277509) (-0.1999991960048481) under: Point 0.0 0.4899097979606123 0.10000199999196005 n1: 1.5 n2: 1.0 refractRay: Ray (Point 0.0 0.4899097979606123 0.10000199999196005) (Vec 0.0 0.9946657614066033 (-0.10315048756754372)) refractColor: Color 1.0 1.0 1.0
As you can see, it is somehow calculating white as the refracted color, when that is just completely wrong. I'm just at a loss, since I'm not really sure if there is an issue with my `prepareComputation` function, or if there is a deeper issue with intersections, but if someone could see a difference in the results that might help.
And though it might not be of much help if you aren't familiar with Haskell/this project, here is my refractedColor function:
refractedColor :: World -> Computation -> Int -> Color refractedColor w comps remaining | remaining <= 0 = black | sin2_t > 1 = black -- total internal reflection, so no refraction | matTransparency == 0 = black | otherwise = refractColor where Computation {object, eye, normal, under, n1, n2} = comps matTransparency = transparency (getMaterial object) nRatio = n1 / n2 cos_i = eye `dot` normal sin2_t = (nRatio ^ 2) * (1 - (cos_i ^ 2)) cos_t = sqrt (1 - sin2_t) direction = ((nRatio * cos_i - cos_t) `vMult` normal) `vSub` (nRatio `vMult` eye) refractRay = Ray under direction refractColor = matTransparency `scale` colorAt w refractRay (remaining - 1) code here
Thanks so much!
|
|
|
Post by inventordave on Aug 2, 2021 18:31:29 GMT
Hi!
I have been away from my raytracer for a while (it's an ongoing backup project to make something extensible and fun - helps with my linear algebra too!), but I read your post and decided to load up my raytracer and see what happened. It's been so long (well over a year) since I dealt with the actual chapter 11, and I genuinely expected my invocation of the same test to fail, as since I've moved on from the chapter work, my refraction code seems to have become broken, no longer showing contained objects inside a (partially-)transparent outer object. To my surprise and pleasure, I got the correct results from the test ({ "r": 0, "g": 0.9988745506795582, "b": 0.04721898034382347 })
So, I am currently tracing my internal values on invocation of refracted_color(...)....
I have all the same internal values you listed, and your code has the same semantics/effective syntax as mine (albeit my raytracer is currently implemented in js), the only difference I see is that because my color object is derived from the same 'tuple' function object (if you know how js works) as vector and point, thus whereas you use two seperate functions to multiply a vector by a scalar ('vMult'), and a color by a scalar ('scale'), I use the same function {as it goes, it's called multiplyScalar(scalar, tuple)}. As all your previous tests are passing, including obviously test #6 etc, there is no obvious indication of what is going wrong, unless the 2 functions 'scale' and 'vMult' are not essentially identical.
The only thing I can think of is that 'matTransparency' should be 1. Check, because perhaps 'scale' is multiplying the returned value of 'colorAt' by a number higher than 1, and if the 'scale' function clamps values greater than 1.0, perhaps that is what is happening.
Straightforward test:
calc result of 'colorAt' BEFORE you pass it to 'scale', and see if it's return value is correct, i.e. it contains the color object above (the expected output), if so, then I imagine if you were to peek at matTransparency it would be > 1, but it shouldn't be.
Let me know if this is helpful, or if I'm an idiot.
|
|
|
Post by micahcantor on Aug 2, 2021 23:56:13 GMT
Thanks for the response. First of all you are right, 'scale' and 'vMult' are essentially the same function, just the former for scaling colors and the latter for vectors.
Unfortunately there is nothing funny going on with matTransparency -- that is just 1.0. I also checked and colorAt is producing white. That's why I thought that my 'refractRay' would be off, since that is the main input to that function, but if you have the same value for that vector, then there must be a problem in 'colorAt' and therefore either in my hit/intersection functionality, or the shadeHit function. Going to look into those further now, thanks.
|
|
|
Post by micahcantor on Aug 3, 2021 2:30:48 GMT
Looking into my 'colorAt' function, the bug has to either be in the selection of the intersection, in the 'shadeHit' function, or in preparing the computation incorrectly for the selected intersection. Here's what I have for the list of intersections generated by 'World.intersect' for the same refractRay from the test:
[Intersection (-1.4656667886640167) (Sphere {scaling = 1 1 1, ...}), Intersection (-0.9539499043976923) (Sphere {scaling = .5 .5 .5, ...}), Intersection (-1.2589906602578083e-5) (Sphere {scaling = .5 .5 .5, ...}), Intersection 0.5117042943597216 (Sphere {scaling = 1 1 1, ...}) ]
The second and third intersections intersect the smaller sphere from the default world.
My 'hit' function selects the last here, as its the only with a positive t value. I'm not really sure how to evaluate this however. Of course, the tests for these functions are passing but I'm not sure how to determine if these are the correct intersections.
Barring an issue there, the bug could lie even deeper, somewhere in my 'lighting' function generating the wrong surface color in this case? Or an issue in preparing the computation as I said earlier. Still have more digging to do.
|
|
|
Post by inventordave on Aug 3, 2021 10:09:31 GMT
I have just woken up, and I will run some tests, but... why are 3 of the 4 intersections for the same sphere? Surely it's 2 intersections for each of the 2 spheres?
|
|
|
Post by inventordave on Aug 3, 2021 13:25:03 GMT
Yeah, I just checked the intersect_world() return array of intersections, and all the t values are correct, but the 4th intersection, the one with t=~0.5 should be the sphere with scaling 1, not the inner sphere.
|
|
|
Post by inventordave on Aug 3, 2021 13:40:08 GMT
Yeah, i've just stepped through my code, I know certainly the error is within the call chain of intersect_world(), within that is intersect(), then shape.local_intersect(), or at least that is how my control flow is structured. If your code were structured identically to mine, it is very likely the problem is with the shape.local_intersect() function - it would be if the bug were in my codebase. So, I am goind to download your codebase and see how you've implemented that part of the code. I am fairly sure I can narrow the problem down. Back in a jiffy.
|
|
|
Post by inventordave on Aug 3, 2021 14:53:45 GMT
Not gonna lie, your TU structure leads me to be confused as to where exactly your ch11 test #7 actually is... its okay i found it, but clearly, its *very weird* that you should get the correct t-value, somehow assigned to the wrong object/shape (from one sphere to the completely other sphere). Reading your code (i am a multi-syntax guy, don't know haskell but I do 'get' the general idea, it's functional programming), I believe so far that the problem is either in the 'toIntersections' function (defined in Types.hs), called specifically from your test, which is called "testRefractedColorRefraction" in 'WorldTest.hs' (line 163), because there is a subtle difference(?) between Intersection and [Intersection] (i don't actually know that for a fact), or "intersect" on line #43 of 'World.hs' is scrambling the intersection array - is there a bug with mconcat or map, that trashes the object references??
|
|
|
Post by micahcantor on Aug 3, 2021 16:10:06 GMT
Oops. Sorry about that, I copy-pasted wrong in my last post, the last intersection does go with the larger (non-scaled) sphere. Thanks for looking into this more, it looks like we have the same intersections, which is a good sign. That means the issue must not be there, and instead how I'm coloring there. Main issue seems to be why shadeHit would be returning white, so that's what I'll look into now.
|
|
|
Post by micahcantor on Aug 3, 2021 16:18:24 GMT
To clarify the intersection code a little bit: I have an 'Intersection' type which contains the t value and the intersected shape. Then there is the 'Intersections' type which is a 'SortedList' of 'Intersection', which just enforces the precondition that the list of intersections must be sorted at the type level. The 'toIntersections' function just converts from [Intersection] (unsorted list) to the SortedList type 'Intersections'.
|
|
|
Post by inventordave on Aug 4, 2021 0:50:06 GMT
How's it going? Still need help?
|
|
|
Post by micahcantor on Aug 4, 2021 3:05:13 GMT
Wow, okay. I fixed the issue. Kind of disappointing, the issue was actually in the test. I tracked down the issue to the fact that the surface color returned by shadeHit after the refraction was just white, rather than the point of intersection as it would be if testPattern was working.
It turns out I didn't think through testPattern and the lighting of objects that do not have a pattern. The issue was it was treating testPattern as if the material did not have a pattern, so it was just returning the color of the sphere, which is white, rather than the point of intersection.
So now I changed the type of Material to be a record with Maybe Pattern, to represent that a material may or may not have a pattern. Then I can represent this behavior correctly in 'lighting' and voila, issue resolved and all tests passing.
Thanks for the help again!
|
|
|
Post by inventordave on Aug 4, 2021 12:45:00 GMT
The problem is, I didnt think to consider what the setup was for the scene, which would've been defined in earlier tests and the chapter up to that page. So I would never have considered outside your question. I am learning! You're asking about a state machine, gotta have a good enough understanding of the state machine you're debugging.
It's just occured to me, I didn't even ask myself what the shade_hit() functions etc do concretely, it obviously references the material of a shape it intersects with. I am dumb.
|
|
|
Post by micahcantor on Aug 4, 2021 14:30:13 GMT
Right, I needed to just sit down and very concretely think through the exact set of steps involved, and once I did that it became clear what the issue was, when before I was just browsing my code looking for an error and it seemed intractable. A good lesson in debugging for sure.
|
|
|
Post by mrfresh3141 on Mar 17, 2023 12:20:00 GMT
[Intersection (-1.4656667886640167) (Sphere {scaling = 1 1 1, ...}), Intersection (-0.9539499043976923) (Sphere {scaling = .5 .5 .5, ...}), Intersection (-1.2589906602578083e-5) (Sphere {scaling = .5 .5 .5, ...}), Intersection 0.5117042943597216 (Sphere {scaling = 1 1 1, ...}) ]
Are those values from the first call to IntersectsWorld() after the call to RefractColour()? My return values are: -1.465567 (object A) -0.953855 (Object B) 0.000103 (Object B) 0.511814 (Object A) The third interaction being an outlier. And enough to force the hit on 2 instead of 3. The deltas for each intersection are very close to 0.0001 (epsilon)?! 0.000100
0.000095
0.000090
0.000110 A clue, perhaps? Edit: Well, yes, sort of. I'd made an optimisation which broke the bit that negates the normal vector... ooops! However, if I set transparency to 0.9 and refractive index to 1.0 I get a completely transparent sphere and it looks okay, I suppose. If it set the refractive index to 1.5 (with transparency at 0.9) I get an opaque sphere with the refractive elements "textured" over the top. Half way there, then. Updated values from the initial IntersectWorlds() call: -1.465749
-0.954027
-0.000107
0.511614
This hasn't fixed the issue with the test, but the values are getting better. The reflectivity value for the default_world was incorrect, adding to the issue, and now 158 passes! Hurray!
|
|