bramz' diary : the bumpy road to the linux build

January 27th, 2007 by bramz

The description of LiAR says: “‘LiAR isn’t a raytracer’ is an open source, cross platform, object-oriented and extendable ray tracer written in C++ and Python by Bram de Greve”. However, that’s not entirely true since currently, LiAR only runs on the Windows platform. It’s time to live up to the promise. To be honnest, I’ve attempted it before but I ran into troubles and I gave up. Now I want to pursue it to the end, however. And while I encounter and overcome the obstacles, I’ll try to document them.

The goal is to use Python’s distutils and write a single setup.py to build and install LiAR. So no make or autotools are allowed. The idea is that eventually, the setup.py can be used to build LiAR on Windows as well. I’ve decided against using setuptools (eggs) and SCons because distutils is already included in the standard libraries of Python. But first, I’ll be trying to get it working on linux alone, what is already good for quite some challenges.

First, a recap of how LiAR is built. It consists of different (extension) modules organized in a star topology. The center module is called the kernel and provides general definitions (Spectrum, Intersection, …) and abstract classes (SceneObject, Shader, Texture, …) that are used as vessels and ports by which the concrete modules (scenery, shaders, textures) must communicate. Each concrete module only depends on kernel (and of course 3rd party libraries if appropriate), and since LiAR is implemented in C++ this means including its headers and linking to it dynamically.

Unfortunately, distutils is not designed for architecture like that. It can build multiple extension modules in a single package, but they’re supposed to be independent of each other. They cannot be linked in a binary way, communication should be done through Python. Modules don’t have the usual libfoo.so name anyway. However, distutils does support the build of C libraries that the extension modules can link against, but only for static libraries and what we need is dynamic linkage.

This calls for a bit of hacking. Luckily, distutils has a mechanism that allows to add extra custom commands. This can be used to hijack the build_clib command (for static libraries) and use it to build shared libraries. A new command liar_build_shared_lib is derived from build_clib and the relevant methods are overridden. It’s a bit too long to post here (you can see for yourself in the code repository), but basically it’s a blend between build_clib and build_ext. The cmdclass argument in setup() is used to inject liar_build_shared_lib as the new build_clib.

The concrete modules need to be linked to kernel, but a Python extension module does not have the regular libfoo.so name, but simply foo.so instead. What I’ve done to solve this, is to create libkernel.so which is kernel sans kernel_init.cpp, and a shallow module kernel.so that consists only of kernel_init.cpp. This way, all modules can link to a regular library, and kernel.so exposes its content to Python.

By now, things pretty much compile. I can even import liar in Python, though I must use the LD_LIBRARY_PATH environment variable hack to tell the modules where they can find libkernel.so. And once I start running some scripts I quickly get segmentation faults. So there are still some things to solve =)

Also, I need to clean up setup.py a bit, because things were simply thrown together until it worked. Options are still pretty much hardcoded. And also, I would like to be install the kernel headers so that custom LiAR extensions can be build using the installed LiAR.

bramz' diary : clip mapping

January 23rd, 2007 by bramz

I’ve added clip mapping (*) to LiAR, which is some sort of alpha channel for surfaces. It can be used to create holes in a surface using a texture. The texture is compared to a threshold to determine where the surface should be removed, so that rays can travel undisturbed through the removed parts.

This is different from using a fully transparent BSDF because the latter will split ray paths by inserting a no-op scattering event. This is especially a major difference for shadow rays in a direct lighting pass: they block on the first scattering event causing the transparent parts to cast shadows. When using clip mapping, no scattering event will be inserted because the surface is not present, and thus shadows will be casted as expected.

a sphere clip mapped by a CheckerVolume

It’s implemented as a special scene object ClipMap that takes a child object and a clip texture. When the object is tested for ray intersections, a candidate intersection with the child object is searched for, and if one is found the clip texture is evaluated using the local intersection context of the child object. If this yields a value above a threshold, the intersection is accepted. Otherwise, a new candidate intersection is searched for with an adjusted near limit of the bounded ray.

It was pretty easy to implement, however not without cost. The intersection context needs to be evaluated independently of the one used for shading. Worse, the used intersection context may be different depending on which level the shader is defined. Also, the function isIntersecting can no longer use any shortcuts, but must perform a full intersection test.

while (true)
{
    child_->intersect(sample, ray, intersection);
    if (!intersection)
    {
        result.swap(intersection);
        return;
    }
    child_->localContext(sample, ray, intersection,
        context);

    if (clipMap_->lookUp(sample, context).average()
        >= threshold_)
    {
        intersection.push(this);
        result.swap(intersection);
        return;
    }
    ray = bound(ray, intersection.t(), ray.farLimit());
}

You can try it out for yourself using the clip_map.py example in the examples/scenery directory.

(*) The term clip mapping is being used in LightWave 3D and some other renders, and also appears in the book Digital Lighting & Rendering by Jeremy Birn. However is also used for naming the act of loading small parts of huge mipmaps to reduce memory requirements.

bramz' diary : subdivision surfaces and UV interpolation problem

January 10th, 2007 by bramz

I’m trying to use the subdivision surfaces I’ve implemented to get a smoother surface for car i’m rendering for PHD Motorsports. The original model has UV coordinates that need to be interpolated as well. As each triangle is split in four, each edge is split two. I’ve been using the naive assumption that for the UV coordinates of the new vertices, I can use the average of the two original vertices of the edge. As can be seen from the following renders, that doesn’t quite work. The UV mapping gets severely distorted.

distorition of UV coordinates when using naive UV interpolation in subdivision surfaces

So, I’ll have to search for something more clever. Probably something similar as for the vertex coordinates. However, I wonder what to do with seams in the texture mapping … To be continued …

bramz' diary : loop subdivision surfaces

December 12th, 2006 by bramz

I’ve implemented subdivision surfaces for the TriangleMesh using the Loop’s scheme. Triangles are split in four, and the vertices are weighted using masks to produce a smoother surface. This process is repeated a few times, indicated by the subdivision level.

The implementation also supports the concept of creases. If an edge of the mesh has a crease level different than zero, the subdivision must leave the edge sharp for a number of iterations equal to the crease level. For example, if an edge has crease level three, and a subdivision of five levels is applied, then the algorithm must leave the edge sharp for the first three iterations, and may only smooth it for the last two. This will give the edge a sharper appearance than the rest of the mesh. If the crease level is equal or greater than the subdivision level (for example both level five), then the edge will stay ultra sharp, since the algorithm will never be allowed to smooth it.

In the following image, the same cube is shown for different subdivision and crease levels. There are no vertex normals used, to clearly show the different faces of each resulting triangle mesh. So no sneaky normal interpolation to give the mesh a smooth appearance!

The same cube with different settings of subdivision and crease level

At three o’clock, there’s a perfect cube with subdivison level zero. Going in counterclockwise order, the subdivision is each time increased by one level. At nine o’clock, subdivision level six is reached, resulting in a very smooth object. Remember there are no vertex normals here!

Continuing in counterclockwise order, the subdivision level is now kept at constant of six, but the crease level of the cube’s edges is each time increased by one. The circle would be closed again at three o’clock with both the subdivision and creasing at level six, but this is virtually the same as the original cube (both at level zero), only with a much finer triangle mesh.

You can try all this for yourself using the example loop_subdivision.py in the examples/scenery subdirectory.

PS: you will have to update your Lass installation to use this, since the actual subdivision code is part of Lass.

bramz' diary : FZR in Uffizi (and thin dielectrics)

December 2nd, 2006 by bramz

I’ve reproduced the older FZR render using the Ashikhmin & Shirley BSDF and the Uffizi Gallery light probe. For the glass of the windows and the head lights, I’ve implemented a new material called ThinDielectric that acts as a thin (but not very) pane of glass. Given an inner refraction index and transparency factor for the medium, it simulates multiple reflections and transmissions due to the bouncing of the light inside the pane. Maybe I will write it more properly some day, but here’s how it goes in a nutshell:

  • R = r \left(1 + \frac{\left(1 \minus r\right)^2 t^2}{1 – r^2 t^2}\right)
  • T = \frac{\left(1 – r\right)^2 t^2}{1 – r^2 t^2}

with:

  • R and T the resulting reflectance and transmittance \left(R + T < 1\right)
  • r is your usual fresnel reflectance
  • t = \tau ^ {\frac 1 {\cos \theta_t} is the transmittance of the medium using Beer’s law where \tau is the transparency of the medium and \cos \theta_t is the cosine of the angle of the refracted rays inside the material

FZR with PHD-Motorsports skin in Uffizi gallery

Model by Live for Speed, light probe by Paul Debevec, custom skin by PHD-Motorsports.

update: this render is also featured in the #flipcode gallery.

update: Nicholas “Ono-Sendai” Chapman of the Indigo Renderer has pointed out that Radiance is using a very similar BSDF for their glass material. This is not so surprising, since it’s a very simple extension of the Fresnel equations. The only difference is the distinction between TE and TM waves, which is more correct than what I’m currently doing.

update: Leonhard Gruenschloss noticed a few typos in my formulas (see comments). I’ve corrected them now Thanks Leonhard! (20 feb. 2007)

bramz' diary : caustics in the cornell box

November 15th, 2006 by bramz

It’s been more than a month since last update, so it’s time for a status report. I’ve been further playing with photon mapping and some nice shaders. The main additions to the photon mapper itself are the (ir)radiance caching when using the final gather step, and the caustics map.

The former was – I believe – originally suggested by Per Christensen in Faster Photon Map Global Illumination. The idea comes from the observation that when using final gathering, a lot of time is consumed by the radiance estimates where final gather rays hit. When you cache these estimates at selected positions, You can gain a factor of 5 to 10 in speed, while the error you get by using this approximation will hardly be noticeable due to the nature of the final gathering.

For the caustics photon map, I’ve used the technique mentioned by Keller and Wald in Efficient Importance Sampling Techniques for the Photon Map. All photons that have gone through a specular event before hitting a diffuse surface, are stored in the caustics map. All other photons are stored in the global photon map after being selected with a probability of 1/Q, with Q the caustics quality factor. The Q factor is an easy way to set the resolution of the caustics map. If you set it higher, it will contain more photons and deliver crispier caustics. There’s still some issues with the caustics map though. I need to add some filtering, and use the convex hull as an area estimate to get crispier caustics and prevent leaking.

To experiment with the caustics, I’ve added a dielectric Fresnel shader with proper reflection and refraction (see my article Reflections and Refractions in Ray Tracing), which is of course used for the yellow sphere. I’ve deployed a simple medium material system (volumetric shaders), so that I could attenuate the light going through the sphere using Beer’s law. The yellow colour is entirely due to this Beer medium, as the Fresnel shader is … well … colourless (greyscale).

For the blue sphere in the back, I’ve implemented the famous Ashikhmin & Shirley BRDF (An Anisotropic Phong BRDF Model and An Anisotropic Phong Light Reflection Model). This of course has nothing to do with caustics, but it’s a very cool shader nevertheless. Notice the slightly glossy reflection and the Fresnel behaviour at grazing angles. I do have problems for keeping the photons at constant powers though, the specular lobe bumps power up, but I’m not really sure if it can be avoided.

Caustics in the Cornell box

Where’s the road going from here? One thing that I certainly want to add to the photon mapper is importance driven photon shooting, so that we can go crazy with sky lights and windows … The other is some priority stack for medium materials, to solve the water-in-a-glass problem (more about that later). And while we’re at it, we should add more in- and outscattering to the medium materials so we can have fog =) I’m also trying to put one of the PHD-Motorsports cars in the Cornell box, but I seem to have some problems to smooth the mesh properly.

PS: we have reached revision 42 in the subversion repository. w00t! ;)

bramz' diary : photon mapping with the cornell box

October 10th, 2006 by bramz

Summer is over (already 20 days since September 21), and as promised, we’re back with some more LiAR stuff =)

I’ve been busy with a few things, the main one being the photon mapper. It’s not completed yet. Currently we only have a global photon map and a gather step with a full radiance estimate. On the agenda are precomputed radiance estimates, importance driven photon shooting, caustic photon maps, …

The photon mapper does not use the traditional diffuse/specular/absorption Russian roulette of Jensen, but rather a full BSDF approach as described by Pharr. This required a complete refactoring of the shaders, which was the other big thing I was working on.

As you could read in an earlier post, I was not so happy with my material implementation. The shaders didn’t match well with things like photon mapping, so I completely ditched the shader approach for a BSDF one. I was a bit worried about having to evaluate expensive textures for each BSDF call, so I had to find a way to buffer them. The solution I’ve choosen is to compute many values in one call by some kind of iterator approach.

I guess that’s it for today. But of course, there’s still some render to show. OK, it’s only a dull cornell box, but it allows me to check my results more easily because I know what to expect. Overall, it looks good, but you might notice some problems at the edges of the wall. Apparently, the final gather doesn’t work so well over there.

traditional cornell box rendered with photon mapping

news : summer break …

June 20th, 2006 by bramz

Hi all,

It’s been a while since the last update. We can assure you that LiAR isn’t dead, but unfortunately we have been through some busy months, and some more are coming up, which puts everything on a hold. So there probably won’t be many updates in the next couple of months either.

See you after the break =)

bramz' diary : image based lighting

April 26th, 2006 by bramz

My first render using an environment map as skylight … Using the technique described in Infinite Area Light Source with Importance Sampling by Pharr and Humphreys, it’s implemented in a breeze … (thanks Thomas, for pointing out that article =)

three differnent spheres illuminated by the grace cathedral probe (light probe by Paul Debevec)

(Light probe: Grace Cathedral by Paul Debevec)

bramz' diary : Fixing transformations and local geometry

April 20th, 2006 by bramz

Today, I noticed there was something seriously wrong with the way local differential geometry was treated by transformation objects.

Basically, rendering goes like this: find intersection, find geometry of intersection, get shader at intersection, shade using the geometry. However, currently, if the object being intersected is a transformed one (by scenery::Transformation), all local geometry (3D point, normal, …) was transformed to global space, the top level of the transformations. So, all shading was done in global space. Is that bad? Yes. Imagine a sphere that uses a 3D texture like CheckerVolume, and this sphere moves from the left to the right. Because the value of the texture is looked up in global space, this means that the sphere will move while the checkerboard pattern is stuck to its global position. This is not what is wanted. The pattern should be fixed to the object!

OK, seems easy to solve that: don’t transform local geometry! Instead, when shading, transform all global information (eye rays, light rays, …) to local space. Well, not exactly … There’s a bit of a problem to this approach: suppose you create a complex object using CSG and some transformations, then every part of that object will have its own local space. If you apply one 3D texture to the complex object, you’ll notice that you won’t get a continuous texture: it will jump from one local space to another.

So, we need a combination of both. Basically, from the observation above, we want to do the shading in the coordinate space of the (compound) object the shader was attached to (let’s call it shader space). The untransformed sphere in the first case, the complete CSG object in the latter. When retrieving local geometry, transform it up (towards global space) until you get to the object with the shader attached. From there, keep track of a single transformation from shader to global space.

Using this approach, we get the following result. One sphere with radius 1 is instanced three times: once without transformation, and two times scaled up by a factor 2.

  • The middle sphere is the untransformed sphere and a shader attached with a standard CheckerVolume texture. For this one, local space = global space = shader space.
  • The left sphere is a second instance of the middle sphere, but scaled up by a factor of two. The shader space is still the local space of the untransformed sphere. As result the texture is scaled up together with the object.
  • The right sphere is a third instance of the same sphere, however this time, the shader is attached to the transformed sphere. This means that the shader space = global space. As result, the pattern has the same size of the untransformed sphere.

Transformations and shader space

script: examples/scenery/transformation.py