[Haskell-cafe] Glome.hs-0.3 (ray tracing digression)

Andrew Coppin andrewcoppin at btinternet.com
Sun Apr 27 05:05:45 EDT 2008

Jim Snow wrote:
> I guess a pov importer wouldn't necessarily be all that difficult, or 
> an exporter, for that matter.  (Supporting every single primitive 
> type, texture, and rendering feature would be a daunting challenge, 
> but just supporting the basics might be relatively simple.)  The 
> problem comes when you want to import a nice short hand-edited script, 
> and then do a few things to it and export it again: suddenly the file 
> turns into a multi-megabyte monstrosity with all the loops unrolled 
> and all the recursive macros expanded and you lose the ability to 
> hand-edit the scene.  Depending on what you're doing, this might not 
> be a problem.

I see what you mean...

I guess you'd want to seperate the machine-generated parts from the 
hand-written parts by careful use of #includes...

>> Well, for example, Haskell doesn't have hetrogenous lists - which are 
>> trivial in any OOP language. That's quite awkward to get around. 
>> Also, both spheres and cylinders have a "radius" property, but then 
>> you end up with name clashes. Again, a non-issue in OOP languages. [I 
>> gather there's some new GHC language extension to partially solve 
>> this one - but only when types are statically known. In the general 
>> case, you'd have to write a "radius class" and define instances... 
>> yuck!]
> It's funny you mention that, those are actually problems I ran into, 
> but (having learned my lesson the hard way in Ocaml), I decided not to 
> try and force the language to do things my way, but instead try to do 
> things in a way that Haskell makes easy.
> For instance, I started out by making each primitive a separate type 
> (Sphere, Triangle, etc...), and then made a type class that defines a 
> ray intersection method (and a few other odds and ends), but then I 
> realized that I can't do something as simple as store a list of 
> primitives (or if there is in fact a way, I'm not aware of it).
> Instead, I made a single large sum type with all my primitives:

> so that now I can easily make a list of primitives.  (In fact, that's 
> what a Group is.)  I also gave up on using named fields, since coming 
> up with a separate name for each one gets ugly:  instead of radius, 
> you have sphere_radius, cylinder_radius, cone_radius disc_radius, etc...

All of which works, but now it's a pain to add new primitives. And *all* 
supported primitives must be defined in a single monolithic module. And 
you can't reuse spheres as bounding volumes without either 
reimplementing them or loosing type safety.

> That world is a very slow oyster.  I've used blobs and isosurfaces in 
> POV-Ray, and they're wonderfully expressive but also incredibly slow.

This explains why my PC contains the fastest CPU that I could afford, 
and why a while back I actually considered purchasing an 8-core server 
box that was on special offer for £600. (I'm now really kicking myself 
that I didn't buy that! 8 Xeon cores... it would have been so fast!) But 
yeah, I guess it depends on what your patience level is. I like the way 
a couple of lines of SDL can construct a blob object so intricate that 
it would be impossible to construct a triangle mesh like that with any 
known tool... but that's just me.

> There are also other problems: the isosurface solvers are sometimes 
> wrong, leading to ugly artifacts

Only if you set the max_gradient too low. And that's not surprising, 
given how the algorithm works... [Indeed, what's surprising is that they 
built a solver that works for arbitrary functions!]

> and applying a texture to an isosurface or blob isn't necessarily 
> straightforward.

That is a pitty. On the other hand, with function-based pigments, you 
can do some pretty impressive stuff. [For example, using the same 
function as the isosurface to distribute a texture map.]

> Another issue is that isosurfaces are great for filling the scene with 
> procedurally-generated complexity, but sometimes you need to do actual 
> physics computations in order to make the object do the right thing.  
> For instance, in the image I linked above, the water looks pretty 
> good, but the waves should really be reflecting off of the castle when 
> they hit it, and there's no way I know of to do that without actually 
> doing some sort of finite-element fluid simulation.  And since that's 
> already an approximation, and it's already taking up a lot of memory, 
> you might as well just represent it as a triangle mesh (or whatever 
> happens to be the easiest representation to render efficiently).

The way I did that is as follows:

1. Create a function that represents the waves.
2. Sample that function at multiple points along the edges of whatever 
is protruding through the water.
3. Add another wave source at that point with an appropreate phase 
offset as per the samples you just took.
4. The actual isosurface is the sum of all the waves thus mentioned.

Apparently the samples just need to be "close together" relative to the 
wavelength of the waves and it looks right. So if you have fairly large 
waves, you don't need many samples [and many wave functions to sum!] The 
results looked fairly good to me.

I have yet to see anybody doing water simulation using a triangle mesh. 
Invariably people just make each particle in the system into a blob 
component, and let POV-Ray smooth the surface.

> There's really a trade-off here between quality, time, and space.

Indeed. If I was trying to render a feature-length movie Pixar style, 
I'd probably be making different choices. But I'm not. I'm building 
still images, and the occasional short animation. For me, quality is the 
main concern. ;-)

> Any blob or isosurface can be approximated with triangles, and if the 
> triangles are smaller than a pixel (or sometimes even if they're quite 
> a bit bigger) the difference isn't going to be noticeable.


1. As I understand it, tesselating arbitrary surfaces is still an 
unsolved problem.
2. If you have curved reflective or refractive surfaces, these can 
magnify elements of the geometry, so even if every triangle is smaller 
than a pixel, you can still see the difference. (Ditto for shadow 

More information about the Haskell-Cafe mailing list