|
Post by Richard Moss on Jul 30, 2019 18:39:28 GMT
Hello, I've been playing around with glass effects as when I first implemented these I was really unhappy with the test renders I did - they just looked like flat circles. Tonight I created a basic scene comprised of two transparent cubes nested inside each other. Given it is transparent, I should be able to see all 6 faces - but I don't, I only see the outer surfaces. I assume I've done something daft in my render, but all tests pass and the code seems to match all the pseudo code. Does anyone have any suggestions on where I'm going wrong? I did try rendering the smooth teapot using glass material but it's been running in a release build for 24 hours now and the progress is only about 20% so I've got a long while to wait to see how that renders (I told it to render at 1920x1080). Thanks; Richard Moss
|
|
|
Post by sbehnke on Jul 30, 2019 19:00:03 GMT
I'm not sure, but I'm guessing this is a side effect of taking only the nearest non-zero intersection for the hit. For the transparent objects I imagine you'd have to take all positive hits, or something to that effect. Again, this is just a guess.
|
|
|
Post by Jamis on Jul 30, 2019 22:27:06 GMT
Richard, given that you've got a refractive index set, I would expect to see some refraction effects, but it looks like your rays are passing through unbent. If I had to make a wild guess without looking at your code, I'd say there's something in your world intersection code that is causing the index of refraction to be disregarded. But your tests are passing, so I'm kind of at a loss. Do you have your code available anywhere that I could take a look?
|
|
|
Post by Richard Moss on Jul 31, 2019 16:44:21 GMT
Hello, Thanks for the replies. I went back over my code and I did find an error where I hadn't updated World.ColorAt to pass in the intersection list into comps.prepare. I checked ColorAt, ShadeHit, GetReflectedColor, GetRefractedColor but didn't find any further errors. Oddly, fixing this bug has made things worse. This screenshot is without refraction, just a single transparent cube. Except you can only see 3 faces, and I assume you should be able to see the other 3 through the "glass". This second screenshot is with a refraction value, but still a single cube. As you can see it has gone pretty wrong! This third screenshot is the original scene with refraction and the nested cubes - and it looks even more awful. I tested some other scenes and they look just as bad. I checked my intersection function and it is returning items ordered by t. I'm completely stumped by this. I don't have my code published online yet, if the screenshots above give a clue as to the problem area I can post that code? I have posted some of the colour code below, given that refraction definitely breaks it and reflection isn't so hot either Any help gratefully appreciated! public double Schlick { get { double result; double cos;
// find the cosine of the angle between the eye and normal vectors cos = Tuple.Dot(_eyev, _normalv);
// total internal reflection can only occur if n 1> n2 if (_n1 > _n2) { double n; double sin2t;
n = _n1 / _n2; sin2t = Math.Pow(n, 2) * (1 - Math.Pow(cos, 2));
if (sin2t > 1) { result = 1; } else { cos = Math.Sqrt(1 - sin2t);
result = 0; } } else { result = 0; }
if (result != 1) { double r0;
r0 = Math.Pow((_n1 - _n2) / (_n1 + _n2), 2);
result = r0 + (1 - r0) * Math.Pow(1 - cos, 5); }
return result; } }
public static Computation Prepare(Intersection hit, Ray ray, Intersection[] xs) { double t; Tuple point; Tuple eyev; Tuple normalv; Shape shape; bool inside; Tuple overpoint; Tuple reflectv; double n1; double n2; Tuple underPoint;
t = hit.T; point = ray.Position(t); eyev = -ray.Direction; shape = hit.Object; normalv = shape.NormalAt(hit, point);
if (Tuple.Dot(normalv, eyev) < 0) { inside = true; normalv = -normalv; } else { inside = false; }
overpoint = point + normalv * Extensions.Epsilon; underPoint = point - normalv * Extensions.Epsilon; reflectv = Tuple.Reflect(ray.Direction, normalv);
n1 = 1; n2 = 1;
if (xs.Length != 0) { Computation.CalculateN(hit, xs, ref n1, ref n2); }
return new Computation(t, shape, point, eyev, normalv, inside, overpoint, reflectv, n1, n2, underPoint); }
public Color ColorAt(Ray ray, int remaining) { Color result; Intersection[] xs; Intersection hit;
xs = this.Intersects(ray); hit = Intersection.Hit(xs);
if (!hit.IsEmpty) { Computation comps;
comps = Computation.Prepare(hit, ray, xs);
result = this.ShadeHit(comps, remaining); } else { result = Color.Black; }
return result; }
public Color GetReflectedColor(Computation comps, int remaining) { Color result;
if (remaining != 0) { double reflective;
reflective = comps.Object.GetMaterial().Reflective;
if (reflective == 0) { result = Color.Black; } else { Ray reflect;
reflect = Ray.Create(comps.OverPoint, comps.Reflectv);
result = this.ColorAt(reflect, remaining - 1) * reflective; } } else { result = Color.Black; ; }
return result; }
public Color GetRefractedColor(Computation comps, int remaining) { Color result;
if (comps.Object.GetMaterial().Transparency == 0 || remaining == 0) { result = Color.Black; } else { double nRatio; double cosi; double sin2t;
// find the ratio of the first index of refraction to the second // (this is inverted from the definition of Snell's Law) nRatio = comps.N1 / comps.N2;
// cos(theta_i) is the same as the dot product of the two vectors cosi = Tuple.Dot(comps.Eyev, comps.Normalv);
// find sin(theta_t)^2 via trigonometric identity sin2t = Math.Pow(nRatio, 2) * (1 - Math.Pow(cosi, 2));
if (sin2t <= 1) { double cost; Tuple direction; Ray refractRay;
// fnid the cos(theta_t) via trigonometric identity cost = Math.Sqrt(1 - sin2t);
// compute the direction of the refracted ray direction = comps.Normalv * (nRatio * cosi - cost) - comps.Eyev * nRatio; refractRay = Ray.Create(comps.UnderPoint, direction);
// find the color of the refacted ray, making sure to multiply // by the transparency value to account for any opacity result = this.ColorAt(refractRay, remaining - 1) * comps.Object.GetMaterial().Transparency; } else { result = Color.Black; } }
return result; }
Regards; Richard Moss
|
|
|
Post by Jamis on Jul 31, 2019 18:43:36 GMT
Richard,
Progress! Here are two things that might help a bit more.
First, I think your cube might actually be almost rendering correctly. To me, it just looks like the "total internal reflection" case isn't being handled correctly. Could you share your ShadeHit implementation, so I can see how the Schlick and GetRefractedColor methods are being used?
Second (and here's why you probably aren't seeing the other three sides of the cube like you're expecting): each face of your cube is a surface that is blocking the light for all surfaces behind it. That means that _inside_ the cube it is perfectly, pitch dark, so there's no light to illuminate those far sides. Instead, the only color they're getting is from light being transmitted through them, which doesn't affect the diffuse and specular lighting components (which are the two that would give the faces distinct shading). Realistic shading for transparent surfaces is a tricky thing, and beyond the scope of my book, but here's what I've done to kind of "cheat" a little: you declare a shape "exempt" from casting shadows. Then, when deciding whether a shape is blocking the light ray, you ignore any intersection with a shape that is exempt from casting shadows. This will allow light to strike (and thus affect) the internal surfaces of your cube.
- Jamis
|
|
|
Post by Jamis on Jul 31, 2019 18:46:01 GMT
Oh, I just realized: in your first screenshot, you have "reflective" set to 0. I assume this is true for the other screenshots as well? If so, try setting "reflective" to the same value as "transparency". That is to say, if you have "transparency" set to 0.9, trying setting "reflective" to 0.9 as well. This will enable your Schlick function to do what it does best: blend the reflected and refracted rays into a single, beautiful color.
|
|
|
Post by Richard Moss on Aug 1, 2019 5:36:48 GMT
Hello, Thanks for the replies. I thought I'd included my ShadeHit implementation too, sorry about that - it's below. public Color ShadeHit(Computation comps, int remaining) { Color result; bool shadowed; Color surface; Color reflected; Color refracted; Material material;
material = comps.Object.GetMaterial();
shadowed = this.IsShadowed(comps.OverPoint); surface = Light.Lighting(material, comps.Object, _light, comps.OverPoint, comps.Eyev, comps.Normalv, shadowed); reflected = this.GetReflectedColor(comps, remaining); refracted = this.GetRefractedColor(comps, remaining);
if (material.Reflective > 0 && material.Transparency > 0) { double reflectance;
reflectance = comps.Schlick;
result = surface + reflected * reflectance + refracted * (1 - reflectance); } else { result = surface + reflected + refracted; }
return result; }
I've tried setting the materials reflective to match the transparency, which results in this screenshot. Turning the inner cube back on results in this, which still seems like something I wouldn't see in the real world I'll give the shadow-cheat you mention a whirl tonight when I get in from work and see what happens. It never occurred to me that it wasn't working because it was a sealed shape that was dark inside. Thanks again for the help! One last request - is it possible to get a YAML definition for the Fresnel Effect scene on page 160 of the paperback? It would be useful if I could generate this as a reference to check that stuff is rendering correctly. Regards; Richard Moss
|
|
|
Post by Jamis on Aug 1, 2019 16:30:47 GMT
Richard,
I think those look pretty good, actually! They look a little washed out, which might be from the ambient term. Try setting ambient to 0 when rendering glass (or any highly reflective or transparent media).
I'll see about putting together a YAML definition for that scene you mentioned.
- Jamis
|
|
|
Post by Richard Moss on Aug 1, 2019 17:54:04 GMT
Jamis, Thanks for the follow up. If this is what they are supposed to look like then I guess the only issues were the forgetting to pass the full intersection list into comps.prepare from ColorAt, and not setting material properties to sensible values. I haven't managed to look into the passthrough shadowing yet, I decided it was high time to implement the YAML parser so I can start comparing reference images before moving onto the penultimate chapter. Setting the ambient to 0 did have a impact, it does look better than it did although I still find them rather strange Regards; Richard Moss
|
|
|
Post by Richard Moss on Aug 2, 2019 19:36:54 GMT
Hello, I thought I'd add a final update on this transparency / reflection issue. I'd previously grabbed all the YAML definitions I can find on the forums, and tonight I added a loader for them. With the exception of the Christmas and Group scenes I can render them all, which is really neat. I rendered reflect-refract.yml and it looks very close to the reference image. The upper left section of the foreground green sphere is a bit washed out compared to the reference, but there's also jpg artefacts in that area on the reference so I can't get a feel of why it's like this. Need to do some more digging just in case, but it does look like everything is working nicely... my sample scenes just suck in comparison to other peoples I also noticed I had strange single pixel artefacts periodically on the ends of open shapes in the cylinders.yml scene. Given that I also have an issue with cones rendering with artefacts, and I derived that shape from the cylinder I must have mucked something up there so I'll do some more poking here to, hopefully I'll find and resolve the error without having to write another post. My crap imagination for creating decent sample scenes aside, this book has probably been the most fun technical book to "read" I've ever had. While there's likely no way under the sun I could have done this without the copious pseudo code, the test driven nature meant I learned more than I would have had I just read it. I had doubted that I would get the point where I could render Appendix I (maths is hard!) but tonight I proved myself wrong. I hope you do other similar books in future Regards; Richard Moss
|
|
|
Post by Jamis on Aug 2, 2019 19:45:04 GMT
Thank you so much for the kind words, Richard! I love to hear success stories like this. I have an idea to rework my "Mazes for Programmers" in this test-driven style; we'll see if that pans out. Re: the single-pixel artifacts you're seeing on the cylinders, if they're what I'm thinking of, they are often caused by rounding errors. I encountered some similar issues when I was working out the first version of my cylinder code. I don't remember now exactly what I did to overcome them, but I'll wrack my brain and see if I can come up with any suggestions for you. Thanks again! (And if you have a minute, I'd really appreciate it if you could basically repeat yourself in a review of the book on Amazon...reviews like that are pure gold for authors! www.amazon.com/Ray-Tracer-Challenge-Test-Driven-Renderer/dp/1680502719)
|
|
|
Post by Richard Moss on Aug 3, 2019 14:50:31 GMT
Hello, I went back and looked over the cylinder code and finally found my error, it wasn't processing the epsilons correctly when calculating the normal. Below is my original function public override Tuple LocalNormalAt(Intersection hit, Tuple point) { Tuple normal; double dist; double x; double y; double z;
x = point.X; y = point.Y; z = point.Z;
// compute the square of the distance forom the y axis dist = (x * x) + (z * z);
if (dist < 1 && y >= _maximum - Extensions.Epsilon) { normal = Tuple.Vector(0, 1, 0); } else if (dist < 1 && y <= _minimum + Extensions.Epsilon) { normal = Tuple.Vector(0, -1, 0); } else { normal = Tuple.Vector(x, 0, z); }
return normal; }
The fix was to wrap the epsilon addition/subtraction to raise the precedence ... if (dist < 1 && y >= (_maximum - Extensions.Epsilon)) { normal = Tuple.Vector(0, 1, 0); } else if (dist < 1 && y <= (_minimum + Extensions.Epsilon)) { ...
No more brightly coloured pixels around the edge of open cylinders! I also finally figured out why cones were rendering with a strange outline in front of them. On page 189 you speak of "a" being zero and "b" not being zero, but I'd written the implementation with "a" being "approximately" zero - this triggers some rather strange rendering artefacts. Now my example cone scene renders perfectly too. I just finished the final chapter (not counting Next Steps). I'm probably going to take a small break before attempting the bonus chapters (plus I need to make my YAML importer handle groups) but then I'll tackle the bonus chapters. As far as the review goes, that's pretty much a given I normally post to amazon.co.uk (and GoodReads) as that's where I purchase but I'll see if I can cross post to .com too. It'll probably be in a few days once I've put my thoughts in order now that I've finished the book. On the subject of the Maze book, I'd already decided to grab that one as well in the upcoming weeks, even though it's not in the same style. But if you do publish further ones in this style then I'll certainly grab them. Regards; Richard Moss
|
|