Previous | Up

CHAPTER 2

Deep Knowledge



Overview

In the previous chapter you have seen how to create a simple scene, composed by elementary blocks such as lights, materials, objects and cameras.
What you miss now is a deeper knowledge of the interactions between these entities. These interactions are provided by a sort of glue that connects all of the elements, the most important class of the Lightflow Rendering Interface: the scene.


Scene

Here is an overview of the scene class methods with step by step explanations. If you want to use Lightflow productively you should read it carefully, because you may find some unexpected behaviours, that you will comprehend and appreciate only after some times.


int newCamera (string type, list parameters)
Creates a camera. Objects of this type simulate the functionality of real photographic cameras, and their task is to produce a two-dimensional image from a three-dimensional scene.
The usual way to employ these entities is by passing them to the method render, whose explanation follows.
int newImager (string type, list parameters)
Creates an imager. An imager is a special output device that is used by cameras when rendering. These devices may not only able to store the resulting image to a file, but they may also accomplish particular operations onto it. An example is the halo imager class, which simulates haloing around the visible light sources, reproducing a common lens effect.
Note that each camera is linked to a single imager, the one made current by the last invocation of imagerBegin that preceds the creation of the camera itself. However most imagers provide a mechanism to be linked to other imagers, so as to produce a chain of superimposed effects.
int newInterface (string type, list parameters)
Creates an interface object. Interfaces are special data containers which mantain global information relative to the scene. This type is necessary to hold all the data and the various settings that are to be used during the rendering stage, and that are not known a priori by the Lightflow Rendering Interface. This need arises by the fact that the Lightflow Rendering Interface is completely extensible and it is not based on any particular and monolithic rendering device. This implies that each extension class may have its own global data.
Interfaces may also be used to store rendering information that should pass through different rendering elements contained in the scene: for example an external module could provide a new set of classes for lights and materials that have to communicate data obtained from the scene geometry while rendering. In this case that module would provide also an interface that should be instanced before rendering, and that would probably contain also some particular settings accessible through the parameter list at its creation.
int newInterior (string type, list parameters)
Creates an interior. Interiors are materials that evaluate the behaviour of light inside the volumetric spaces defined by solids. An example could be a gas contained into a sphere, or a suspension contained into a glass pot. Note however that interiors are a property of materials, not of geometries. That is to say that each material possesses one and only one interior evaluator. Then this property transfers to geometric objects since each object possesses a material. The mechanism to attach interiors to materials is similar to the one used to attach materials to objects: blocks of materials sharing the same interior are defined using interiorBegin and interiorEnd before and after their creation.
int newLight (string type, list parameters)
Creates a new light. By default lights are turned off. LightOn and LightOff should be called to change their state, that is to say to specify which materials (and interiors) they illuminate.
int newMaterial (string type, list parameters)
Creates a material. Each material defines the way light is reflected by a surface, and by the volume this surface encloses. The volumetric behavior of a material is defined by the current interior.
Since materials reflect light coming from light sources, each material (and each interior too) possesses a list of contributing lights. This list contains all the lights that were turned on at the creation of the material (or interior) itself.
int newObject (string type, list parameters)
Creates an object. Objects are the geometric entities that define a scene. Each object is made up of a unique material, which is the one that was current at the object's creation. For this reason objects should always be declared inside a material block.
int newPattern (string type, list parameters)
Creates a pattern. A pattern is a function that associates a value to each surface or volume element it is evaluated on. For this reason there are two distinct types of patterns: volumetric and superficial. Volumetric patterns may be used to determine a property of an interior, while the superficial ones may be used for materials.
The value of a pattern may be of four types: a number, a color, a point or a normal. Numeric and color patterns may be used to control numeric or color fields, while point and normal patterns are usually used for displacement and bump-mapping.
A pattern may be used to control all the parameters of materials (or interiors) that accept an handle to a pattern as input. For example the "standard" material has two fields named "ka" and "kc" that accept both a color and a pattern, a field named "kd" that accepts both a float and a pattern and two fields named "displacement" and "bump" that accept only patterns.
Note that the use of patterns is not really restricted to materials and interiors only: an object or a light could provide pattern channels as well.
int newTexture (string name)
Loads a texture and returns its handle. This may be used by some patterns that perform texture mapping, even if this is not the only possible application.
Supported file formats are Lightflow Texture (.lft) and uncompressed 24bit Targa (.tga).
int newTrimmer (string type, list parameters)
Creates a trimmer. Trimmers are functions that cut away parts of a parametric surface, and thus they are commonly used by objects. For example the "NURBS" object possesses a channel named "trimmer" that accepts a trimmer handle as input, and that allows to specify which portions of the surface should be considered.
void imagerBegin (int imager) / void imagerEnd (void)
Define an imager block. These blocks may be nested, since these functions mantain a stack which is global to the scene. Each block defines the current imager, which is used by all the cameras that are created into it. There is no default imager, so each camera should be created into an imager block to produce a result.
void interiorBegin (int interior) / void interiorEnd (void)
Define an interior block. These blocks may be nested, since these functions mantain a stack which is global to the scene. Each block defines the current interior, which is used by all the materials that are created into it. By default there is no interior, and materials could be created even outside of a block.
void lightOn (int light) / void lightOff (int light)
Turn on or off a light. The state of each light influences the materials and the interiors, since each material and each interior has a list of contributors that illuminate it: these contributors are the lights that were turned on at its creation. Note that an interior and the material it was associated to may possess different sets of contributors.
void lightBegin (void) / void lightEnd (void)
Define a lighting block. These blocks may be nested, since these functions mantain a stack which is global to the scene. These functions are used to store the active state of lights at a given time in order to restore it later on. This is useful when there is a fixed set of lights that illuminate all of the materials and then there is an additional set that varies from material to material. For example if our scene contains the sun and two lamps and we want the sun to illuminate everything, and each lamp to illuminate only its neighbourhood, we could do this:
sun    = s.newLight( "directional", ... )
lamp1 = s.newLight( "point", ... )
lamp2 = s.newLight( "point", ... )

s.lightOn( sun )
... # materials of distant objects

s.lightBegin()
s.lightOn( lamp1 )
... # materials of the neighbours of lamp1
s.lightEnd()

... # materials of distant objects

s.lightBegin()
s.lightOn( lamp2 )
... # materials of the neighbours of lamp2
s.lightEnd()

... # materials of distant objects
Obviously this is not the only method, since we could also switch on/off lamp1 and lamp2 before and after their neighbours' definition, but in scenes where the lights are many and their configuration is complex the possibility of forgetting to turn off some of them may become very high.
void materialBegin (int material) / void materialEnd (void)
Define a material block. These blocks may be nested, since these functions mantain a stack which is global to the scene. Each block defines the current material, which is used by all the objects that are created into it. There is no default material, so each object should be created into a material block.
void addObject (int object)
Adds an object to the scene. Objects that are created but not added are not rendered.
void transformBegin (transform transformation) / void transformEnd (void)
Defines a transformation block. These blocks may be nested to compose multiple transformations. A transformation is an affine function that moves spatial points. This set of functions comprises translations, rotations, scalings and their compositions, which are documented within the definition of the transform type. Each block defines the current transformation, which is applied to the objects, lights, materials, interiors and patterns that are created into it. Note that the current transformation is the result of the composition of all the nested transformation blocks. This composition is performed in reverse order to facilitate hierarchical modeling, that is to say that if you want to move an object at (1, 0, 0) and rotate it by 90 degrees around the y axis that passes for this point, you should state the rotation first and the translation then. Otherwise you would move the object at (1, 0, 0) and then rotate it around the origin, bringing it to (0, 0, 1), instead of changing its orientation only.
This order is very useful since if you want to model some chained joints (as the classical robot arm) you may start modeling the first, then give the transformation for the second joint and create it, then repeat these operations for the third and so on.
void radiosity (void)
Computes the radiosity distribution over the scene.
void render (int camera, int width, int height, float startcol=0.0, float startrow=0.0, float endcol=1.0, float endrow=1.0)
Renders a camera view. width and height specify the resolution of the image, while the optional parameters specify the portion of the image to be rendered. These numbers may be specified both as integers going from 0 to width for the start/end columns and from 0 to height for the start/end rows, and as fractionals going from 0 to 1: in this case they are interpreted as fractions of the relative image dimensions.


Examples

Now that the review of the scene class is completed we may start to illustrate its actual behaviour by compiling some ad hoc examples. The next will show you how to create a gaseous cloud using interiors.


from lightflow import *

s = scene()

s.lightOn( s.newLight( "point", [ "position", vector3( 5.0, -5.0, 4.0 ), "color", vector3( 300.0, 300.0, 300.0 ) ] ) )


gas = s.newInterior( "dust", [ "kr", vector3(1.0, 0.9, 0.8), "kaf", 0.3, "density", 0.3, "sampling", 40.0, "caching", vector3(-1.2,-1.2,-1.2), vector3(1.2,1.2,1.2) ] )

s.interiorBegin( gas )

cloud = s.newMaterial( "transparent", [] )

s.interiorEnd()


s.materialBegin( cloud )

s.addObject( s.newObject( "sphere", [ "radius", 1.2 ] ) )

s.materialEnd()


plastic = s.newMaterial( "standard", [ "ka", vector3( 0, 0, 0.5 ), "kc", vector3( 1, 0.5, 0.5 ), "kd", 0.5, "km", 0.1 ] )

s.materialBegin( plastic )

s.addObject( s.newObject( "sphere", [ "radius", 0.5 ] ) )

s.materialEnd()


saver = s.newImager( "tga-saver", [ "file", "ball3.tga" ] )

s.imagerBegin( saver )

camera = s.newCamera( "pinhole", [ "eye", vector3( 0, -4, 0 ), "aim", vector3( 0, 0, 0 ) ] )

s.imagerEnd()

s.render( camera, 300, 300 )
(view image)
The cloud effect here has been obtained by creating a sphere made of a "transparent" material with a "dust" interior. This type of interior simulates the look of suspended dusts in the air, and its parameters would require a long explanation, that you can find in the class documentation.
For now it's better to see another example, to highlight the use of transformation and lighting stacks.
from lightflow import *

s = scene()

s.lightOn( s.newLight( "point", [ "position", vector3(  4.0, -6.0, -5.0 ), "color", vector3( 200.0, 200.0, 200.0 ) ] ) )
light1 = s.newLight( "point", [ "position", vector3( -7.5, -6.0, 2.0 ), "color", vector3( 300.0, 150.0, 150.0 ) ] )
light2 = s.newLight( "point", [ "position", vector3( -2.0,  0.0, 8.0 ), "color", vector3( 150.0, 300.0, 150.0 ) ] )
light3 = s.newLight( "point", [ "position", vector3(  8.0, -6.0, 5.0 ), "color", vector3( 150.0, 150.0, 300.0 ) ] )


s.lightBegin()
s.lightOn( light1 )

plastic1 = s.newMaterial( "standard", [ "ka", vector3( 0.1, 0.1, 0.1 ), "kc", vector3( 1, 1, 1 ), "kd", 0.5, "km", 0.1 ] )

s.lightEnd()

s.lightBegin()
s.lightOn( light2 )

plastic2 = s.newMaterial( "standard", [ "ka", vector3( 0.1, 0.1, 0.1 ), "kc", vector3( 1, 1, 1 ), "kd", 0.5, "km", 0.1 ] )

s.lightEnd()

s.lightBegin()
s.lightOn( light3 )

plastic3 = s.newMaterial( "standard", [ "ka", vector3( 0.1, 0.1, 0.1 ), "kc", vector3( 1, 1, 1 ), "kd", 0.5, "km", 0.1 ] )

s.lightEnd()


s.transformBegin( transform().translation( vector3( -2.0, 0, 0 ) ) )

s.materialBegin( plastic1 )

s.addObject( s.newObject( "sphere", [ "radius", 1.0 ] ) )

s.materialEnd()

s.transformEnd()


s.materialBegin( plastic2 )

s.addObject( s.newObject( "sphere", [ "radius", 1.0 ] ) )

s.materialEnd()


s.transformBegin( transform().translation( vector3( 2.0, 0, 0 ) ) )

s.materialBegin( plastic3 )

s.addObject( s.newObject( "sphere", [ "radius", 1.0 ] ) )

s.materialEnd()

s.transformEnd()


saver = s.newImager( "tga-saver", [ "file", "lights.tga" ] )

s.imagerBegin( saver )

camera = s.newCamera( "pinhole", [ "eye", vector3( 0, -5, 0 ), "aim", vector3( 0, 0, 0 ), "distance", 0.75 ] )

s.imagerEnd()

s.render( camera, 300, 300 )
(view image)
As you may see the three balls seem to be made of the same material and the only difference stands in their illumination, which is clearly impossible to model in the real life.
This capability of connecting lights to objects (indeed materials) is usually called light-linking, and it allows very interesting effects, especially during animations. If you are new to the field of Computer Graphics, remember that the illumination is probably the most important aspect of a scene, since our eyes directly perceive light, which is then interpreted by our brain to obtain information about forms. To make this concept clear, think about a fabulous construction, say a jewel, that is illuminated in all directions with the same intensity: the result would be an indistinguishable mass, no matter how perfect is its geometry. To enhance its appearence you should create contrast among its parts, for example illuminating a side more than the front face by putting a light behind it. Light-linking allows you to obtain even more, by provoking the eye of the watcher and capturing his attention. In particular it is very helpful to make secondary illumination, that is to say to illuminate the details of the individual objects.

The next chapter will introduce you to the actual use of the most powerful Lightflow Rendering Tools, the ones that 'do the difference'.
Before reading it you may try to model some scenes of your own, consulting also the class documentation to learn how to create some other geometries, materials and patterns. Remember that experience is a necessary step, that cannot be substituted by any manual or teacher.

Next