February 2008

Lua woes

Bad Lua!For all the great things Lua has done for my current game development endeavour, it is now proving to be a royal pain in the ass.

The problems began when I added a background loading system to load new scenes in a separate thread without interrupting play.  This all worked until it began loading Lua scripts.  At the time, there was a single Lua state (virtual machine) for all of the scripts.  This was awesome because communicating between scripts was extremely easy.  When the loading thread started adding new scripts to the Lua state which was currently in use by the main thread, however, things began to go horribly wrong.

This problem was easily solved by having one Lua state per script.  This also fixed problems with naming, as Lua has no notion of namespace.  Lua states are designed to execute in isolation, so communicating between them becomes extremely difficult.  Implementing game logic without entity communication is neigh on impossible.

There are a few potential solutions.  The first would be to construct some kind of message passing system on the C++ side.  When an actor wants to tell another actor something, it dispatches a message which is received by the actor on the next update and interpreted.  Another option would be to use Luabind’s call_function() template function.  This approach would require custom wrapper functions for each different function signature you’d need.

More on this later.

Programming

Comments (3)

Permalink

Clouds continued…

interiorvolumetest_1.jpgI’ve settled on using an interior volume shader to render my clouds. The image below shows a torus rendered with the ‘noisysmoke’ volume shader which ships with the 3Delight renderer. The shader ray-marches through the volume, grabbing density values from a density function (noise in this case) along the way.

Obviously, simply using a function to compute the density of the cloud leaves something to be desired, and is not at all flexible. Ideally, the density of the cloud should be based on particle data, which can be generated in a specialized application (RealFlow, for example). An optimized data structure containing the particle data would be queried at a point and return the number of points within a radius. The density could then be approximated by dividing the number of points returned by the volume sampled. To achieve this, I have written a DSO-shadeop like the one written by Ian Stephenson here. Results from this method are forthcoming.

Presumably, the RenderMan brickmap or pointcloud data structure should support this kind of querying (given a position and a radius, return the number of points found), saving me the trouble of implementing my own Kd-tree and sampling functions. While it’s not the density of a point cloud being computed in procedures like photon mapping and point-based ambient occlusion, this kind of querying is required.

Programming
Renderman

Comments (0)

Permalink

Cloud rendering preliminaries

clouds.jpg

Clouds present an interesting rendering challenge. Approaches to cloud rendering are varied and range from convincing sprite-based solutions that render in real-time to expensive volumetric solutions which must be rendered off-line. A sprite-based approach, while fast and convincing from a distance, does not provide a realistic representation when the cloud is viewed up close or when passing through the cloud.

Some interesting work has been done by Mark Harris on a hybrid method for rendering clouds in real-time using impostors - billboarded planes textured with a render of the cloud - to speed up rendering considerably. Hit the jump for more details.

For a Renderman-based solution, I have come up with two approaches that might merit further inquiry. The first is to use particles to build a brickmap (voxel data) then sample the brickmap in a volume shader using ray-marching. The second approach would be to define a transparent surface with an internal volume shader and ray-march using a parametric equation of some kind (Perlin noise would be a good start).

The major benefit of the first approach is that the representation of the cloud - a collection of particles - is a common format for representing fluids in computer graphics, and as such, data could be generated using an external application (RealFlow, for example) and built into a brickmap.

Baking a constant color with zero opacity could be sufficient to extract a density approximation from the brickmap. Consider the image below:

ray-march.jpg

A ray is being projected into the brickmap, and two samples are being taken. At sample A, a light blue color would be returned because there are fewer particles covered by the sample’s radius. Sample B would return a dark blue color, indicating a higher density. The final color of the ray is attenuated by the densities sampled from the brickmap in this manner. If the sample points along the ray are uniform, striations might occur in the final image. It might be wise to jitter the samples’ location (not count) to produce a smoother transition.

The second approach forgoes a particle-based cloud representation in favor of a parametric equation. This method might be useful for generating clouds without having to use expensive fluid dynamics software to generate the initial point data.

internalshader.jpg

In the image above, the cloud’s volume (the blue stuff) is generated of some parametric function instead of a collection of point data. This method has several user-centric advantages over the particle-based approach. An artist could easily create the cloud’s geometry from primitives. The equation used to generate the cloud volume could be changed and animated to suit aesthetic requirements.

In both situations,  the use of ray-marching makes it easy to compute the anisotropic scattering effects scene in clouds.  Doing so is a simple matter of altering the incident light vector by some phase function as we integrate.

Renderman

Comments (0)

Permalink

Wordpress formatting is dumb

I can’t seem to get code and HTML formatting to work properly. Sorry about the inconvenience. I’ll be fixing this once my wonderful girlfriend nurses me back to health from EXTREME WISDOM TEETH EXTRACTION.

Programming
Random

Comments (1)

Permalink

Renderman Helper Applications

Renderman Helper applications can be executed from a rib file to generate rib commands. The helper application and Renderman communicate via stdin - simply write a program which prints the rib statements you want.

Procedural "RunProgram" ["/usr/bin/python ../python/FractalFlame.py" "100000 0.001 "] [-1 1 -1 1 -1 1]

To test this, I have created a very simple implementation of the Scott Draves’ Fractal Flame algorithm; a type of iterated function system which generates nice fractal point clouds. For each iteration of the algorithm, a point is transformed by a random non-linear function. After several iterations, the system begins to converge on a shape. In Python, I utilized the __call__ method to create ‘functor’ objects:

class Sinusoidal:
def __call__(self, point):
point[0] = math.sin(point[0])
point[1] = math.sin(point[1])
point[2] = math.sin(point[2])
return point

These are added to a table from which I indexed into with a random value:

self.Functions = []
self.Functions.append(Linear())
self.Functions.append(Sinusoidal())
self.Functions.append(Spherical())
self.Functions.append(Swirl())
self.Functions.append(Polar())
self.Functions.append(Handkerchief())
self.Functions.append(Heart())
self.Functions.append(Horseshoe())

Finally, the script prints rib statements to stdin:

def MakePoints(self, iterations, size):
'Runs the chaos game and makes the points'

# Create a random point
point = [ random.random(),
random.random(),
random.random() ]

# A random color
c = [ random.random(),
random.random(),
random.random() ]

for i in range(0, iterations):
print('AttributeBegin\n')
# Select a random function
funcIndex = random.randint(0, len(self.Functions) - 1)

# Transform the point
point = self.Functions[funcIndex](point)

# Fetch the functions color from the color table
color = self.Colors[funcIndex]

# Average the color with the previous color
c[0] = (c[0] + color[0]) / 2
c[1] = (c[1] + color[1]) / 2
c[2] = (c[2] + color[2]) / 2

# Plot the point
print('Points "P" [%s %s %s]\n' % (point[0], point[1], point[2]))
print('"constantwidth" [%s]\n' % size)
print('"Cs" [%s %s %s]\n' % (c[0], c[1], c[2]))
print('"Oi" [0 0 0]\n')
print('AttributeEnd\n')

Renderman

Comments (1)

Permalink

Adventures in RenderMan: Fake Sub-surface Effect

subsurface6.png

The objective of this project is to simulate a surface which has a bumpy surface coated with a smooth glaze without actually generating two surfaces.

First Pass

Creating the smooth top surface is fairly trivial. To get the bottom surface to appear bumpy, a new normal and point must be generated and a new lighting calculation performed. My first attempt utilized RenderMan’s new object-oriented shader to combine both a surface and displacement shader. The class had member variables which stored an original undisplaced position and normal as well as a displaced position and normal as computed in the displacement method. In the surface method, specular lighting was calculated with both sets of P and N, then combine for the final result.

Class-based sub-surface

subsurface.sl

Though somewhat unrefined, the effect is present. The larger specular highlight of the top surface is unaffected by the bumpy sub-surface.Even though class-based shaders are infinitely cooler than the traditional procedural ones, they are currently not compatible with RenderMan for Maya.Second PassTo make usage in Maya possible, my second pass was a traditional procedural shader. Refining the effect a little, I added environment mapping and a nice Fresnel effect.

fakesubsurfacepass2.png

subsurfacetraditional.sl

The environment reflections and Fresnel effect add some much needed realism to the surface. Both are computed in a relatively simple manner. The environment reflections are simply sampled from an environment map via the reflected eye vector (no ray tracing). The Fresnel effect is computed relative to the angle between the eye vector and the current surface normal (eyeVec dot N) and multiplied by the reflection color value which was sampled from the environment map.

Third Pass

subsurface5small.png

My third pass was to try to replicate and polish the effect using a few custom Slim templates and RenderMan Studio in Maya. At first, I tried to rewrite my original implementation as a Slim template. I found this solution lacking both in visual quality and in scalability.

After discovering the Delux surface shader in Slim, I utilized its ability to have multiple diffuse and specular layers to create my effect. In my original implementation, I was actually displacing each micropolygon and computing light for the sub-surface. I decided for the third pass to abandon this approach in favor of a technique common to real-time shading - the normal map.Normals are expressed as a 3-tuple, and as such can be stored as an RGB color value. Instead of displacing each point and recomputing the normal, I generated a normal map from a gray-scale fractal pattern using a hue shift Slim template. I then offset the original normal value by a value sampled from the normal map using an SLBox node.

normalmapfig.png

hueshift.slim

I also found the specular highlights looked much better when the original base gray-scale image was attached to the specular component’s intensity value.

finalsubspecular.png

The diffuse component for the sub-surface is calculated in a similar manner.To further refine the effect, I added true ray-traced reflections with falloff controlled by the same Fresnel code I used in my traditional shader, now as a Slim template.

fresnel.slim

Additionally, I changed the diffuse coloration of the sub-surface to be driven by the angle between the eye vector and the light vector as well as the length of the eye vector, which produced an interesting effect.

diffusecoloration.png

Here is the final Slim network:

slimnetwork.png

Renderman

Comments (1)

Permalink