Method and implementation

One evening, while at Jeff's place, I got caught by the sight of his really cool chinese lamp. For this assignment, I thought it might be nice to try to mimick the effect of his lamp. For that purpose, he sent me some reference images (above).

In this assignment, I've experimented with Renderman shaders, and implemented a combination of surface and displacement shaders to achieve the effect. The lamp's geometry is just a basic sphere, all the fine geometry was generated by the surface shader (such as wrinkles, bumps and threads), and fine photometric features were generated by means of procedural (semi-random) texture.

Layered Approach
I employed the layered approach to shaders, as described in RManNotes. I break down complicated surface patterns and texture into layers. Each layer is fairly easy to write. Then, I combine the layers by compositing. In this example, the different layers in the lamp surface shader are:

The layers of the lamp displacement shader are: Additionally, I also used a glow surface shader for a 'mock' light bulb inside the lamp, to fake an inside light with geometry. I also placed a pointlight inside, to generate the light that can be seen throught the top and bottom holes of the lamp.

Displaced horizontal ribs
The reference picture shows how the horizontal ribs affect the overall geometry of the sphere. In the lamp displacement shader the ribs are generated by displacing the geometry along the surface normal at regular intervals along the t coordinate. In between the rib maxima, the displacement profile follows a simple quadratic function.

Vertical 'semi-random' slabs
I use the noise function to generate seemingly random vertical slabs. The randomness is in the slab width, aswell as in the slab orientation. Random top and bottom widths are computed, and the width then randomly varies around the linear slope between top and bottom. Moreover, the bottom position is offset with another random variable to achieve semi-random orientation (as opposed to perfectly vertical slabs). In the next image, you can see the result so far. Note the horizontal rib displacements, particularily along the silhouette, the slight semi-transparency, and the light coming from inside (through the bottom hole).

Wrinkles and bumps in the cloth
In order to make the lamp cloth more realistic, I used a displacement shader to generate some wrinkles and bumps in the cloth, using the noise generator. The result is shown on the right.
 

Light Bulb
To achieve a the translucent lamp 'glare' caused by the light bulb inside the light, I used the recent Renderman raytracing features. The gather() construct (similar to the illuminate() construct) is used to collect a number of samples on a mock 'bulb' geometry (just a smaller sphere) inside the lamp. The bulb has a surface 'glow' shader attached to it, that achieves two goals:

  1. The intensity of the light 'emitted' by the sphere falls off proportional to the distance to the center.
  2. Assign the 'emitted intensity' to an special output variable in the shader, such that the gather loop can look for it in the lamp surface shader. The gather() loop effectively collects those values from the glow shader only, because the other surfaces do not ouput this variable. It is a nice trick to selectively gather rays that hit certain geometry, and it makes sure that the color that rays not intersecting with the glowing bulb geometry don't contribute to the light glare.
The mock 'bulb' is made visible to traced rays by setting the visibility 'trace' attribute, but invisible to the camera by setting the visibility 'camera' attribute for this surface to false, otherwise it would show up through the (slightly) transparent lamp surface:
// the light RiColor(white); RiAttribute("visibility", "trace", (RtPointer)&trace, RI_NULL); RiAttribute("visibility", "camera", (RtPointer)&camera, RI_NULL); RiSurface("glow", RI_NULL); RiTranslate(0.1, -0.15, 0.0); RiSphere(0.45, -0.45, 0.45, 360.0, RI_NULL); RiTranslate(-0.1, 0.0, 0.0);
The gather construct in the lamp shader looks like this:
/* compute translucent component */ vector worldV = vtransform("current", V); gather("", P, -worldV, radians(angle), samples, "surface:outvar", raycolor, "maxdist", 1) { sum += raycolor; } Ctrans = sum / samples; Ctrans *= 2;

Here I show the bulb illumination layer separately. These intensity values are used to modulated with the diffuse lighting component of the lamp's surface, and then used to bias the final shaded color. I've intentionally limited the amount of gather rays, with a relatively wide gather cone centered along the view direction, to achieve the typical 'grainy' translucent light look.

 

Chinese Sign Texture
This was pretty easy. You have to make sure to apply the texture diffuse component separately from the combined 'translucent' glow diffuse component, otherwise it would seem that the (opaque) chinese sign is translucent.

Final result and discussion

The head to head comparison above shows that I am moving in the right direction, but alot could still be improved upon. Especially the area of the outer ring of the lamp sphere, there has to be more contribution from the inside bulb light. Simply boosting up the light's intensity doesn't work, because it oversaturates the other areas. Probably a more physically-based method would be preferred over the 'glowing sphere' trick.

I thought it would be nice to have a foggy light shaft coming out of the bottom hole of the lamp. I tried hard to get the volume shaders from the Advanced Renderman book working in my scene, but without luck. I suspect I'll have to use a

Code

The code can be found here. It includes the implementation of all the shaders, the C code (with Makefile and headers) to generate the RIB file, the generated RIB and necessary texture images.