bramz' diary : shared libraries, RTTI and dlopen

January 28th, 2007 by bramz

When I tried to run a first render in linux, I got a segmentation fault in the photon mapper because there were no lights. Of course, this shouldn’t cause a crash (fixed by now), but why were there no lights? I was pretty sure I defined one, and a quick investigation of the scene graph confirmed this.

Before rendering is started, SceneLight instances are harvested from the scene graph by a visitor LightContextGatherer to collect all instances of SceneLight objects. It’s an acyclic visitor as described by Andrei Alexandrescu.

class LightContextGatherer:
    public util::VisitorBase,
    public util::Visitor<SceneObject>,
    public util::Visitor<SceneLight>
{
    void doVisit(SceneObject&);
    void doVisit(SceneLight&);
};

The way it works is that if a scene object, for example a LightArea, accepts a visitor, it tries to cast the visitor to util::Visitor<LightArea>. If it succeeds, it will call the appropriate doVisit function. Otherwise, it tries again using its parent type, in this case SceneLight. As a result, all lights instances end up in doVisit(SceneLight&), and all other objects in doVisit(SceneObject&).

But for some reason, neither doVisit was ever called. It turned out that the dynamic_cast, used to cast the visitor to the appropriate type, always returned null, even when trying to cast to util::Visitor<SceneObject>. Huh?

Some googling revealed that GCC 3.0 uses address comparisons to determine type equality. This obviously has a performance advantage over string comparisons, but doesn’t work quite well if left and right have a different set of typeinfo instances. This can when using dynamic linkage, and it certainly doesn’t help that Python uses dlopen to load extension modules.

Fortunately, the GCC FAQ also mentions a solution which is to link with the -E flag to add all symbols to the dynamic symbol table, and to use dlopen with the RTLD_GLOBAL flag set. For LiAR, the former means adding -Wl,-E to extra_link_args in setup.py. The latter can be done using sys.setdlopenflags. Proper try/except wrapping ensures that it is only called if appropriate. At least one linux system did not provide the dl module, but fortunately the same constants also live in DLFCN (also not always available), so that’s why the heavy nesting. The code is put at the beginning of __init__.py.

try:
    try:
        import dl
    except:
        try:
            import DLFCN as dl
        except:
            pass
    sys.setdlopenflags(dl.RTLD_NOW | dl.RTLD_GLOBAL)
except:
    pass

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 &#8211; r^2 t^2}\right)
  • T = \frac{\left(1 &#8211; r\right)^2 t^2}{1 &#8211; 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)