|
Post by tracer on Jan 18, 2023 2:15:12 GMT
Hi everyone, I've been following along with the book very easily until I reached refraction. My Refraction is not working properly, and I've been trying to find the issue for 6 hours, and I still don't see what is the error.
Problems:
- All my tests from Chapter 11 are passing except the Test #7: Finding the Refracted Color
- Even though my schlick function pass the tests, it is returning weird values, like 25, 30, etc when rendering a real scene.
I believe the 2 problems are independent from each other, but I'm not a 100% sure. In this post I'm focusing on the second issue.
When I try to render my scene, this is what I see:
I was investigating, and it looks like that even though my schlick function is passing my tests, when I'm rendering a scene it returns weird values like 30, 25, and such. Is that normal? I think no, because that will cause my color addition ((refraction * (1.0 - reflectance))) to become a "negative color", and that is causing the black/white circles.
(when I'm transforming the colors from the 0.0-1.0 range to 0-255 for display, I'm clamping the values. Anything below 0 becomes 0, anything above 255 becomes 255. I believe this is the artifact we see, and it's caused by the schlick return value, but again, not sure)
However, I reviewed my function 100s of times, and I don't see the error:
Color RayTracerRenderer::colorAt(const Ray &ray, const int remaining) const { auto intersections = m_world->intersect(ray); auto hit = intersections.hit();
if (hit.has_value()) { auto computations = prepareComputations(hit.value(), intersections, ray);
Color finalColor { 0, 0, 0, 1 }; for (auto &light: m_world->getLights()) { finalColor += lighting(light, computations); }
auto reflection = reflectedColor(computations, remaining); auto refraction = refractedColor(computations, remaining); auto material = computations.object->getMaterial();
if (material.reflectivity > 0 && material.transparency > 0) { auto reflectance = schlick(computations); return finalColor + (reflection * reflectance) + (refraction * (1.0 - reflectance)); } else { return finalColor + reflection + refraction; } } else { return Color::black(); } }
double RayTracerRenderer::schlick(const Computations &comps) const { auto angle = glm::dot(comps.eye, comps.normal);
if (comps.n1 > comps.n2) { auto n = comps.n1 / comps.n2; auto sin2T = pow(n, 2) * (1.0 - pow(angle, 2)); if (sin2T > 1.0) { return 1.0; }
auto cosT = sqrt(1.0 - sin2T); angle = cosT; }
double r0 = pow((comps.n1 - comps.n2) / (comps.n1 + comps.n2), 2.0); auto result = r0 + (1.0 - r0) * pow((1.0 - angle), 5.0); return result; }
Reflections are seemingly working fine, and the part that is causing the problems is: (refraction * (1.0 - reflectance)); (If I remove that part, I don't see artifacts).
Any ideas what can be the problem?
|
|
|
Post by tracer on Jan 18, 2023 18:36:47 GMT
Created a simplified scene description file. I would be grateful if someone could render it for me and publish the results, so I can compare it with mine:
- add: camera width: 1000 height: 1000 field-of-view: 1.152 from: [0.0, 1, -5] to: [0, 0, 0] up: [0, 1, 0]
# ====================================================== # Light sources # ======================================================
- add: light at: [3, 5, -5] intensity: [1, 1, 1]
# ====================================================== # Materials # ======================================================
- define: wall-material value: pattern: type: checkers colors: - [ 0.1, 0.1, 0.1 ] - [ 0.9, 0.9, 0.9 ] specular: 0 reflective: 0.1
# ====================================================== # Objects # ======================================================
- add: plane transform: - [ translate, 0, -1, 0 ] material: wall-material
- add: plane transform: - [ rotate-x, -1.5707963268 ] - [ translate, 0, 0, 10 ] material: wall-material
- add: sphere transform: - [ translate, 0, 0, 0 ] - [ scale, 1, 1, 1 ] material: color: [0.3, 0, 0] ambient: 0.1 diffuse: 0.1 specular: 1.0 shininess: 300 reflective: 1.0 transparency: 1.0 refractive-index: 1.0
|
|
|
Post by tracer on Jan 18, 2023 21:01:05 GMT
|
|
|
Post by jdunlap on Jan 19, 2023 1:16:16 GMT
Your schlick() function is not the problem as the code exactly matches mine (except that mine is in C#, and I wrote my own vector functions, but I am sure that glm is correct). None of the values that are returned by my schlick() function, though, are outside of the range (0, 1.0), so if yours is returning values that are 30, 25, and such, then the only issue that remains is that comps.n1 and/or comps.n2 are set incorrectly. Have you checked that they are set correctly. Here is the scene file rendered by my renderer: Attachments:
|
|
|
Post by tracer on Jan 19, 2023 1:42:42 GMT
Thanks for checking, and rendering the scene.
I did not check n1/n2 computation as those are passing the tests. I quickly triple-checked the code compared to the pseudo-code and it seems fine, but tomorrow I will debug more.
I added some print staetements, and it looks like in all cases when the schlick returns large numbers like 25 the n1 and n2 are both equal to 1. So there must be something....
Thanks, I will post my findings tomorrow.
|
|
|
Post by icolomby on Jan 19, 2023 1:49:56 GMT
You missed part in prepareComputations() in which you have to negate the normal vector. It is described in chapter 7, page 96. There is a test for this in book on that page "The hit, when an intersection occurs on the inside".
After you calculate the normal in prepareComputations() and before computing the over and under points you should add the following code to negate the normal when necessary:
if (glm::dot(normal, -ray.getDirection()) < 0) { // -ray.getDirection() is the eye vector normal = -normal; } Ian.
|
|
|
Post by jdunlap on Jan 19, 2023 1:54:17 GMT
That makes no sense. If n1 == n2 == 1, then you would not take the first branch and would only do the part outside the if. Since n1 == n2, then r0 = 0. So, result = 1.0 - (eye dot normal) ^ 5.
However, since eye dot normal = cos(angle), it will always be (-1.0, 1.0), so result will always be (0.0, 1.0) which is as it should be, even if n1 == n2.
This will not be true, however, if your eye and normal vectors are not normalized. Are they normalized? Try normalizing them within this function to see if it fixes it. If it does, you know what the problem is and can track down where it is.
|
|
|
Post by tracer on Jan 19, 2023 20:17:59 GMT
You missed part in prepareComputations() in which you have to negate the normal vector. It is described in chapter 7, page 96. There is a test for this in book on that page "The hit, when an intersection occurs on the inside". After you calculate the normal in prepareComputations() and before computing the over and under points you should add the following code to negate the normal when necessary: if (glm::dot(normal, -ray.getDirection()) < 0) { // -ray.getDirection() is the eye vector normal = -normal; } Ian.
THAT WAS IT. Many thanks, you saved my project
I have no idea how I missed that part. I re-read that part again, and I don't remember it at all. I think I skipped that for some reason...
|
|
|
Post by doblit on Jan 28, 2023 20:26:53 GMT
Hello! I have been trolling through the forum to see if anyone else was having the same issue as me, and came your post. I see you've subsequently had some luck, which is awesome! This is what my renderer creates with your scene and it is driving me bananas I've checked my schlick function, and it only returns values between 0 - 1, so I don't think it's that. Happy to post source code as well, but was just wondering if this is any easily recognised issue!(fingers crossed for a simple fix! )
|
|