This article talks about a pretty full-featured ray tracer. If you just want to play around with some ideas and have fun, you can make different tradeoffs and still get very nice and fast renderings. Here's a ray tracer that I made that's GPU-accelerated and can make some nice looking images quickly in 1000 lines of code. https://www.shadertoy.com/view/4ddcRn The tradeoff is that it's all procedural graphics. So there are no triangle meshes and it would also be a bit tricky to implement a bidirectional ray tracer like this.
Whoaaa. If you look from the bottom up through the glass floor, it looks crazy awesome.
I think that's the most impressive little demo I've seen in ... quite some time. I can think of more impressive ones, but they're all extraordinarily complicated in comparison.
How'd you do the glass if there are no meshes? I'm about to pass out, else I'd dig in.
I'm especially curious how you're getting the refraction right. I forget my basic physics, but is it as simple as deflecting the ray slightly? But then how does it work on a curved surface? I guess it happens per pixel, so it shouldn't be too surprising, but... still, I was expecting a lot more aliasing than this: https://i.imgur.com/fdDVcCT.jpg
I guess I've never seen a real time raytracer that happened to trace through ripply glass, which is why it feels so neat.
The sibling comment explains it too, but here's a simple description: The object still has a geometric definition, it just isn't a mesh. It has equations describing the curves. Yes, per pixel, using the curve geometry, you calculate where the ray intersects that curve, and at what angle to figure out the deflection from there.
Think of a simplification in 2D. Sunlight comes from directly vertical above a sine wave. For every x-coordinate where you cast a sun ray, you just need the slope of the sine-wave at that x-coordinate to calculate the angle for what will happen at the intersection.
A mesh isn't any fundamental unit of physics or geometry, it's just our customary way of approaching the rendering to get good-enough fast-enough results from our current hardware.
Not the person you're replying to, but in general refraction is pretty easy to do in a ray tracer. When the ray hits the object you use the normal vector, the incident ray direction, and the index of refraction of the material (or materials, if you're transitioning from one substance to another with different index of refractions) to calculate the new ray direction and continue from there. Usually there's some amount of reflection, too (the amount varying based on the angle of the incoming ray with respect to the surface), so you might spawn two rays: one continuing through the object, and the other bouncing off.
What's much harder to do is simulating the light that's refracted or reflected off of objects (called "caustics"), like the light on the bottom of a swimming pool. To do that in a physically correct way generally requires falling back on some kind of global illumination technique like path tracing or photon mapping.
The geometry in my raytracer can either be ray-traced primitives, like spheres or boxes, or it can be "signed distance functions" (SDFs), which let you define all kinda of crazy shapes. Inigo Quilez does a good job of explaining SDFs here: https://www.iquilezles.org/www/articles/distfunctions/distfu...
The refraction math in my code is around line 799 or 809 depending on what you're looking for. There are a few errors in the refraction code in this version. :/ But caustics are handled well by my renderer.
Are there any good resources for learning how to render dichroic thin films with multiple internal reflections and refraction indexes? Basically, I would love to render something like this[1] in real time.
For a very simple form, you can sample a specific wavelength with each ray, trace it using wavelength-dependent refraction indices (e.g., from the Sellmeier equation) whenever you compute refraction or internal reflection. Then tint the ray by the color and visible intensity of the wavelength when averaging it into the image pixel.
A naive spectral path tracer can be less complex (code wise) than a "normal" one. You just trace each photon with its wavelength instead of tracing RGB (or some other full spectrum) per path. Since this abstraction is closer to physics, it makes a lot of code easier to reason about. Wavelength dependent refraction is straightforward, you just use the index of refraction for the photon you are tracing.
I find this code base extremely easy to read despite not being a C++ (It has that Carmackian quality). Pretty easy to use to port to the language of your choice.
https://github.com/TomCrypto/Lambda
To get the kind of effect you see in the youtube video you'd render diamond or something with extreme refraction.
This is far from real time though of course. It will run an order of magnitude slower than anything you can do with full spectrum rays. So doing it in realtime would probably be just like subsurface scattering is done in real time: you just have to cheat. Perhaps there can be some inspiration from the real thing though.
I'm just an amateur who played around with rendering in various ways, raytraced and rasterized. In order to get the results similar to the video (without hacks), you're gonna need caustics, refraction and reflections, all together with realistic lightning. You're not gonna be able to get that looking nice without a physically accurate render engine, which will most likely be using raytracing. And raytracing (compared to rasterization) is not gonna be able to run anywhere near real-time (more like at least seconds per frame, if you really ramp down the samples [image will be more noisy]).
Also what I was thinking of. Fabien Sanglard did a nice article[0] about a business card raytracer Andrew Kensler made awhile back, too (HN discussion[1]).
> you could write your own scheduler or use a library
I never wrote ray tracers, but in my experience the best way to parallelize things is usually “none of the above”.
C++ runtime has a built-in scheduler, OpenMP https://www.openmp.org/ If the algorithm needs to dynamically spawn tasks (graph search, flood fill or similar), Windows kernel has another one, see SubmitThreadpoolWork API.
These things tend to work better than third-party libraries, and are way easier to consume than developing custom schedulers.
I don't think so. I believe OpenMP qualifies as a (very mature and widely supported) library as well as language extension.
Assuming it meets your needs it has (AFAIK) support from all the major compilers (Intel, MS, GCC, LLVM) as well as being able to abstract over GPUs and other sorts of hardware.
Technically, sure. But practically — what’s your estimation how many C++ programmers target anything besides these 4, gcc, clang, vc++ or intel? My estimation is [ 0.1% .. 1% ]
Intel TBB has a steep initial learning curve, but is incredible once you've passed that initial hump. If I was working on a greenfield project, I woudl start with TBB.
> Now you want to actually trace something. There is at least three ways to it:
> * write all intersection and BVH code by yourself;
> * build and use open-source third-party library;
> * use something like Intel Embree.
How many more useful ways are there? Pretty much the only better thing that comes to my mind is "study something like Embree and then write a shader compiler that produces tracing code on-the-fly", and even that perhaps might not be actually better (although it's my understanding that OSL may actually be doing something like this).
Nice resource but the headline really brings up the question about what language we should use to "quantify" code. I'd go with something like C or even Fortran: expressive enough to do this kind of work but low-level enough that we can easily imaging the assembly code behind it.
I often feel sorry people are shy about inventing new languages and using them in production nowadays. I'd rather have a reasonably different language for every class of problems which would eliminate boilerplate code.
This article is very good! It covers many beautiful techniques and has many beautiful images. However, I would quibble with the title; if you want to know how much boilerplate code you need to write a ray-tracer, you will not find the answer in it.
I wrote the following programs in an attempt to get a better answer to that question.
https://gitlab.com/kragen/bubbleos/blob/master/yeso/sdf.lua a page of Lua using signed distance function raymarching that runs in real time at a lousy framerate; to run it, git clone the repo, install the prerequisites listed in the README, and run `make` in the yeso directory, before running sdf.lua.
But none of these can load triangle meshes, render caustics, render area light sources, average multiple samples, do bidirectional rendering, etc.
Reznik says, "A lot of projects using something like PPM file format, which is very simple, but in the end, you need to write ... extra code for this simplicity (like tone mapping, converting from floating point values to uint8, etc.)," but the amount of extra code required is really very minimal:
But on some platforms you don't need even that much; my Clojure raytracer[s] just use[s] javax.imageio.ImageIO/write to encode a JPEG. (Admittedly, that one in particular cheats pretty shamelessly on tone mapping!)
Having spent over a decade working on one of the major commercial renderers, I'll say that there can be a lot of complexity involved in a production renderer, but you can still get pretty far with a surprisingly small amount of code.
This is awesome! I should have put something like that in there, but I wouldn't even have thought of the dithering, and since I don't know much about graphics I didn't know about Reinhard tone mapping.
There's an interesting thing that happens where sometimes if you constrain yourself to 1% or 0.1% of the code you would normally use, you end up getting interesting uncontrolled effects. cf. http://canonical.org/~kragen/bytebeat
The underlying algorithms of ray tracing, ignoring the details of the graphics system and IO, really are very simple (Snell's law). The 1K of clojure example is pretty much that.
All the rest is pretty uninteresting, in my opinion- the various tricks to make better rendering in shorter times is mostly grad-level math and some computer engineering, while the asset loading is a long solved problem with ultra-mature libraries.
None of my examples actually implement Snell's law, just diffuse and sometimes specular reflection, but I agree that adding Snell's law (and thus refraction) does not add much complexity. Recently it's become fashionable to use the Fresnel equations and "more filmic" tone mapping to get less plasticky images, too.
I think my interests are probably pretty different from yours, because I find the details of the graphics system and I/O pretty interesting, in particular how they can be improved. I also think the various tricks for better rendering in shorter time are super interesting, and even the question of how to do liability loading has some very interesting new answers, like FlatBuffers. Probably the liability-loading strategies that we came up with in the previous millennium for spinning rust that transferred 40 megabytes per second after an 8 000 000 nanosecond seek time are not optimal for SSDs that transfer 4000 megabytes per second with a 1000 nanosecond "seek time".
Another thing worth mentioning is the question of how you model the 3-D shapes you want to render in the first place, which includes considerations of HCI, sometimes physics simulation, and so on. One of the most interesting things about SDFs is the tempting possibility of an efficiently renderable representation that naturally supports operations like CSG and filleting (though not usually both at once!)
That is to say, I think the stuff these minimal raytracers leave out is actually very interesting indeed. But I also think it's very interesting to see what's left over when you do leave it out: a relatively small amount of math and code that can still produce visually arresting images.
I'm confused- don't all ray tracers use snell's law or is there a different equation for reflection (https://en.wikipedia.org/wiki/Specular_reflection)?
Nearly everybody I've worked with who's implemented a ray tracer says there's only one thing that matters, and that's implementing the reflection equation (I mean for first order ray tracing, not interesting stuff).
As for asset loading, like I said, I think it's a solved problem. When I start Cyperpunk 2077 on my machine (fresh, no cache), it takes less than 30 seconds to get to a playable state and my SSD is basically streaming data into RAM as fast as it can.
You ask, "Don't all ray tracers use snell's law or is there a different equation for reflection?" The answer is that, well, you can derive both Snell's law and Heron's law of reflection from Fermat's principle of least time (which you can derive from the wave equation), but Heron's law of specular reflection isn't usually considered to be a consequence of Snell's law; even when you're implementing TIR you can't just use the Snell's-law result because it's gone imaginary.
If you only want diffuse reflection and shadows, which is a thing you could potentially use ray tracing for, you don't even need the specular-reflection law. This might be a sensible thing to do if, for example, you were rendering a 3-D IFS; using a raytracer with BVHs allows you to do this lazily and thus get immensely more detail than a rasterizer could do.
(An alternative to conventional raytracing I've been thinking might be fun is to train a PINN on a scene with the wave equation --- a differentiable neural network which maps (x, y, z) tuples to "electromagnetic field" vectors or just pointwise amplitude and phase, trained using a loss that incorporates its deviation from the wave equation and the given boundary conditions. Then you can evaluate the amplitude of light at every pixel on an imaging plane behind a lens or pinhole camera, thus simulating bokeh, reflection, refraction, defocus, and potentially even diffraction and polarization effects. Then instead of explicitly programming Heron's reflection law, Snell's law, the Fresnel equations, etc., they'd just appear as emergent properties of the wave equation. May not work out in practice tho.)
Perhaps if Cyperpunk 2077 were using a liability pipeline that was rethought with SSDs in mind, you wouldn't have to have all that data in your RAM at once; most of it could be left on the SSD until later, thus avoiding the 30-second delay. (As I suggested, SSDs are maybe two orders of magnitude higher bandwidth, but have nearly four orders of magnitude lower read latency.) This kind of thing is a bigger problem for open-world-type games like Minetest where the data volume is potentially unbounded because players add to it over time, and where the 30-second glitches can't be papered over with cutscenes and the like.
BTW, your parenthetical 3rd paragraph is a fairly deep question and fast approximations to it would be useful beyond just rendering scenes realistically. I assume you're familiar with the use of lenses to implement FTs long before they were computationally tractable (https://en.wikipedia.org/wiki/Fourier_optics)?
The PINN idea seems interesting but it may or may not work out; if you're interested in that kind of thing, there's more of it in Dercuano, Derctuo, and Dernocua. Some of the ideas in there have been tested and work, others have been tested and found not to work, but most are still untested.
As for Fourier optics, yeah, I worked with a guy last millennium who had worked on some optical analog pattern recognition stuff at university. I've never gotten it to work myself; I suck at lab work so far.
I don't know too much about it, other than that a symplectic geometer friend helped out, but light field networks seem interesting: https://www.vincentsitzmann.com/lfns/
Something I've thought would be interesting is simulating the entire light field by some kind of finite element analysis, but where each element uses a neural network to more quickly converge to the correct lightfield -- and, perhaps like that paper, to use neural networks to give a compact representation of the lightfield at that element.
The stratospheric view of what a raytracer even is is a monte carlo sampler to evaluate a (fairly complicated) integral. It seems like many tricks for raytracing are about finding better ways to estimate good sampling distributions (for example, when scattering, a heuristic is to send more rays toward light sources). So, being able to simulate the light field more accurately and quicker could get the rendering integral to converge quicker, too.
It sort of reminds me of game-playing AIs like AlphaGo. There's a tree search: the AI is playing a game where a ray is bouncing in a scene, and whenever the beam is scattering it gets to choose which direction to send it -- its goal is to collect light that gives close to the correct pixel color with the least number of beams.
Dekhn's comment said, "asset loading is a long solved problem," but they're evidently talking about loading things like textures and triangle meshes. Calling those "assets" is counting them on the wrong side of the ledger; they're liabilities, not assets.
Nice! APL-family languages are really great at this kind of thing; I should see how small I can squinch a raytracer with just NumPy. Stevan Apter's programs (like that one) constantly make me wish I knew K: even if I don't want my code to look like that, it seems like it would be great to be able to type it in that way.
That also leads to a one-page OCaml version and a two-page C++ version by Jon D. Harrop (the Flying Frog guy), both also using PPM output and rendering only spheres, much like most of my examples: https://web.archive.org/web/20070605173709/http://www.ffcons...
In https://www.pouet.net/prod.php?which=83222 Holtsetio wrote a raytracer in two pages of MySQL SQL, but it's sort of obfuscated. It emits the output in BMP format (almost as simple as PPM) and uses lots of imperative MySQL extensions to SQL, and it ended up as 10 KiB instead of the 1 KiB of my Clojure version. But it handles triangles, not just spheres, so it can render the Stanford bunny.
https://github.com/chunky/sqlraytracer/blob/master/raytracer... is instead about three pages of SQL, using recursive CTEs instead of imperative assignments, and supporting only spheres but with different materials, like my C version. He's using recursive CTEs as implemented in Postgres: compliant with ANSI SQL but not implemented in most other SQL engines. In particular, he started out with SQLite but it wasn't strong enough.
You may be happy to learn that in modern Node, the 3200x2400 example which used to take 6.8 seconds now runs in 0.25 seconds. I unfortunately don't have a clojure setupt to compare against.
For me (on AMD Ryzen 9 5950X, Arch Linux, Clojure 1.10.3 [OpenJDK 17.0.1] and NodeJS 17.4.0), circle.js takes ~0.15 seconds and the circle.clj takes ~0.19 seconds. After running circle.clj 100 times in a running JVM though, it takes the time down to ~0.092 seconds for each run.
Yes, if you dig around V8 performance profiles you'll see some massive "Optimizing Code" blocks that appear at the bottom of a frequently-hit routine once, then subsequent calls to that same routine will run faster.
That being said, while I haven't investigated this super thoroughly, in my experience the delta is usually small.
Trying again with timers internal to the code (previously used `time node ray.js`), initial go is 190ms, then 170ms, 160ms, and after that hovers around 140-150ms for the remaining 97 runs.
If I remove the FS operations it's instead 160ms initially, then drops to 130ms by the third go and hovers around there.
Thanks! Does this include the time to actually write the image data to the filesystem, or just append it to an internal buffer that will be eventually written?
On the PPM side, a very easy alternative is to use stb_image (even though I abhor header only libs), or OS specific ones like GDI+ / CoreImage, if using something like C++.
On .NET side, I used ImageSharp.
Thanks for sharing the Clojure version of a RayTracer.