tableau (lx_tableau.hpp)

From The Foundry MODO SDK wiki
Jump to: navigation, search
There are security restrictions on this page



A Tableau is a snapshot of the 3D scene, partially frozen in time and suitable for rendering. It consists of a collection of various types of elements such as surfaces, volumes, lights, and filters which all contribute to the final rendered appearance. A tableau is created by traversing the item database.

Tableaus have a start time and duration interval, and method for adding new elements or instances of existing elements. This interface is provided mainly so clients can add elements to populate the tableau, so this does not give full access.

(1) SDK: ILxTableau interface
         LXxMETHOD(  LxResult,
 Time) (
         LXtObjectID              self,
         float                   *t0,
         float                   *interval);
         LXxMETHOD(  LxResult,
 AddElement) (
         LXtObjectID              self,
         LXtObjectID              element,
         LXtObjectID              shader);
         LXxMETHOD(  LxResult,
 AddInstance) (
         LXtObjectID              self,
         LXtObjectID              inst,
         LXtObjectID              element,
         LXtObjectID              shader);

Get a ChannelRead object for subframe times: 'type' is 0,1,2 for t0, t1 and frame time.

(2) SDK: ILxTableau interface
         LXxMETHOD(  LxResult,
 Channels) (
         LXtObjectID              self,
         unsigned                 type,
         void                   **ppvObj);

(3) SDK: Declarations
 #define LXiTBX_CHANS_T0                 0
 #define LXiTBX_CHANS_T1                 1
 #define LXiTBX_CHANS_FRAME              2

Returns LXe_TRUE for items that are visible in this tableau.

(4) SDK: ILxTableau interface
         LXxMETHOD (  LxResult,
 Visible) (
         LXtObjectID              self,
         LXtObjectID              item);

Get a tableau shader given an item and string tags for material tags.

(5) SDK: ILxTableau interface
         LXxMETHOD(  LxResult,
 FindShader) (
         LXtObjectID              self,
         LXtObjectID              item,
         LXtObjectID              tags,
         void                   **ppvObj);

Return a peek of the item currently being instanced.

(6) SDK: ILxTableau interface
         LXxMETHOD(  LXtObjectID,
 InstanceItem) (
         LXtObjectID              self);

These events are used for incremental updates to the tableau.

Update takes in a visitor, which acts as a callback to update the tableau data. When the tableau is "safe", such as when the render is stopped, it will perform the update. The "immediate" argument is whether or not the tableau contains dangerous / invalid data, in which case any tableau clients need to stop / abort immediately

The tableau will add-ref and release the visitor when it's done, so the client doesn't need to keep a reference around.

UpdateAll tells the tableau that it needs to be completely invalidated and rebuilt.

EltNotify notifies clients of the tableau (like the render caches) that a tableau element has changed. It takes in the element object (which the tableau source should have retained) and the event indicating what's changed.

InstNotify is effectively the same.

(7) SDK: ILxTableau interface
         LXxMETHOD(  LxResult,
 Update) (
         LXtObjectID              self,
         LXtObjectID              visitor,
         int                      immediate);
         LXxMETHOD(  LxResult,
 UpdateAll) (
         LXtObjectID              self);
         LXxMETHOD(  LxResult,
 EltNotify) (
         LXtObjectID              self,
         LXtObjectID              element,
         int                      event);
         LXxMETHOD(  LxResult,
 InstNotify) (
         LXtObjectID              self,
         LXtObjectID              instance,
         int                      event);

EltNotify can have the following events.

(8) SDK: Declarations

And InstNotify has a smaller set of events.

(9) SDK: Declarations

This allows clients to add instance of other items, not just their own elements. This takes the item to be instanced (inst), the item doing the instacing (from), the TableauInstance object giving the transforms for the item. The final arguments give a TableauVertex for particle features and an array of those features, and can both be null if there are no particle features for the instance.

(10) SDK: ILxTableau interface
         LXxMETHOD(  LxResult,
 AddInstanceItem) (
         LXtObjectID              self,
         LXtObjectID              instItem,
         LXtObjectID              fromItem,
         LXtObjectID              inst,
         LXtObjectID              vdesc,
         const float             *vertex);

Items that provide an instanceable object channel call this method to define the elements that make up the item. The element is the TableauElement to be added and the tags are the string tags to be used for getting shaders for this element. Both objects are often the same.

(11) SDK: ILxTableau interface
         LXxMETHOD(  LxResult,
 AddInstanceableElement) (
         LXtObjectID              self,
         LXtObjectID              elt,
         LXtObjectID              tags);

(12) SDK: Declarations
 #define LXu_TABLEAU     "76C4EDD9-5FF9-4342-BB08-EFCD0B344004"

(13) User Class: Tableau method
 Time0 (void)
         float                    t0, dt;
         Time (&t0, &dt);
         return t0;
 Time1 (void)
         float                    t0, dt;
         Time (&t0, &dt);
         return t0 + dt;
 IsVisible (
         ILxUnknownID             item)
         return (Visible (item) == LXe_TRUE) ? true : false;
 GetShader (
         CLxLoc_TableauShader    &shader,
         ILxUnknownID             item,
         ILxUnknownID             tags)
         LXtObjectID              obj;
         if (LXx_FAIL (FindShader (item, tags, &obj)))
                 return false;
         return shader.take (obj);
 GetChannels (
         CLxLoc_ChannelRead      &chan,
         unsigned                 type)
         LXtObjectID              obj;
         if (LXx_FAIL (Channels (type, &obj)))
                 return false;
         return chan.take (obj);
 VisitorUpdate (
         CLxImpl_AbstractVisitor         *visitor,
         int                              immediate)
         CLxInst_OneVisitor<CLxGenericVisitor>  gv;
         gv.loc.vis = visitor;
         return Update (gv, immediate);

Empty Tableau Python user class.

(14) PY: Tableau method

3D Elements

Every element should provide some bound on its location in model space. This is given as an axis-aligned bounding box encoded as a six-element vector, where the first triple is the lower corner and second is the upper corner. Elements may also indicate that they have an infinite bound.

Note: this should *not* return an error code unless there is a serious error. A missing element should return LXe_OK with zero for all bounds.

(15) 3D Element Methods
         LXxMETHOD(  LxResult,
 Bound) (
         LXtObjectID              self,
         LXtTableauBox            bbox);

(16) SDK: Types
 typedef float            LXtTableauBox[6];

(17) SDK: Declarations

Query for vertex data, like generalized VMaps. All elements can also have a vertex description selected, which should match available features.

(18) 3D Element Methods
         LXxMETHOD(  unsigned int,
 FeatureCount) (
         LXtObjectID              self,
         LXtID4                   type);
         LXxMETHOD(  LxResult,
 FeatureByIndex) (
         LXtObjectID              self,
         LXtID4                   type,
         unsigned int             index,
         const char             **name);
         LXxMETHOD(  LxResult,
 SetVertex) (
         LXtObjectID              self,
         LXtObjectID              vdesc);

This feature type and set of names define the most basic set of features that most elements support. Velocity is in object space. Normals exist only for surface samples, and radius is meaningful only for line samples.

(19) SDK: Declarations
     #define LXiTBLX_BASEFEATURE     LXxID4(' ','v','r','t')
     #define LXsTBLX_FEATURE_POS     "pos"
     #define LXsTBLX_FEATURE_OBJPOS  "opos"
     #define LXsTBLX_FEATURE_VEL     "vel"
     #define LXsTBLX_FEATURE_NORMAL  "norm"
     #define LXsTBLX_FEATURE_RADIUS  "rad"
     #define LXsTBLX_FEATURE_PARAM_LENGTH    "plen"
     #define LXsTBLX_FEATURE_PARTID  "partID"
 #define LXsTBLX_FEATURE_RADIANCE    "radiance"
 #define LXsTBLX_FEATURE_TENSION         "tension"
 #define LXsTBLX_FEATURE_TORSION     "torsion"
     #define LXiTBLX_DPDU            LXxID4('d','p','d','u')
     #define LXiTBLX_TBASIS          LXxID4('T','B','A','S')

(20) SDK: ILxTableauElement interface
 ''[[#C15|3D Element Methods]]''

(21) User Class: TableauElement method

Empty TableauElement Python user class.

(22) PY: TableauElement method

(23) SDK: Declarations
 #define LXu_TABLEAUELEMENT      "71D90AD9-3E30-4CE8-9E2B-F70DA281B2DC"
 #define LXa_TABLEAUELEMENT      "tableauElement"


2D surface elements are 3D elements that interact with the renderer as a pool of triangles. The Sample() method is called with a bounding box that intersects the surface element, a feature scale, and an interface to collect triangles. The scale parameter gives an approximate edge length scale that is used to determine surface dicing. The vertex vector description should already have been set for this element.

(24) SDK: ILxTableauSurface interface
 ''[[#C15|3D Element Methods]]''
         LXxMETHOD(  LxResult,
 Sample) (
         LXtObjectID              self,
         const LXtTableauBox      bbox,
         float                    scale,
         LXtObjectID              trisoup);

Surface elements can also provide a padding distance that will be added to the bounding box. In general this will be zero but certain surfaces need to add an extra padding. For example surfaces modeling hair may need to set the padding distance as the maximum length of hair guides.

(25) SDK: ILxTableauSurface interface
         LXxMETHOD(  LxResult,
 Padding) (
         LXtObjectID              self,
         float                   *dist);

The bounding boxes of individual segments can be read. The client need to get the segment IDs by sampling the surface, but once given they can be passed to this function. If it return any error (incluing not-impl) the client should assume that no box is available.

(26) SDK: ILxTableauSurface interface
         LXxMETHOD(  LxResult,
 SegmentBox) (
         LXtObjectID              self,
         unsigned int             segID,
         LXtTableauBox            bbox);

The Bound() method for surfaces (since it cannot return "infinite") may also return a success code indicating that the surface supports adaptive subdivision.

(27) SDK: Declarations

(28) User Class: TableauSurface method

Empty TableauSurface Python user class.

(29) PY: TableauSurface method

A triangle "soup" consists of unconnected triangles grouped into segments. The idea of a soup is that although triangles may border on each other and share normals at those borders, there is no connectivity information in the data structure. What we have is just a big batch of triangles and it's up to the renderer to assure that any transformations it applies will maintain continuity. While this places definite limitations on the renderer, it also makes it much easier to deal with gigantic meshes since they can be processed in arbitrarily small units.

The triangle soup interface is passed to the surface object from the renderer, and the surface has the job of generating all the triangles that intersect the bounding box. The triangles are grouped into segments by the surface, each with a unique ID code. Before sending a segment the surface calls the Segment() method with the ID, and the renderer can return LXe_TRUE or LXe_FALSE. If false the segment is skipped, which allows the renderer to cache segments and only update them as needed. Each segment can contain either points, lines, or triangles.

(30) SDK: ILxTriangleSoup interface
         LXxMETHOD(  unsigned int,
 TestBox) (
         LXtObjectID              self,
         const LXtTableauBox      bbox);
         LXxMETHOD(  LxResult,
 Segment) (
         LXtObjectID              self,
         unsigned int             segID,
         unsigned int             type);

(31) SDK: Declarations

Within each segment the surface calls Vertex() and Polygon() any number of times in any combination. Each call to Vertex() takes the vector of floats which define the vertex, and the vertices in the fragment are numbered starting from zero, incrementing by one each call. The Polygon() function knits vertices together into a part of the visible surface. For points and lines the Polygon() function ignores the last one or two vertex indices.

(32) SDK: ILxTriangleSoup interface
         LXxMETHOD(  LxResult,
 Vertex) (
         LXtObjectID              self,
         const float             *vertex,
         unsigned int            *index);
         LXxMETHOD(  LxResult,
 Polygon) (
         LXtObjectID              self,
         unsigned int             v0,
         unsigned int             v1,
         unsigned int             v2);

Connect allows the source to give hints about the connectivity of the elements in the soup. This is completely optional and there is no possibility of an error return.

(33) SDK: ILxTriangleSoup interface
         LXxMETHOD(  void,
 Connect) (
         LXtObjectID              self,
         unsigned int             type);

Empty TriangleSoup Python user class.

(34) PY: TriangleSoup method

This indicates that two triangles form a quad. If a quad is given by the points 0, 1, 2, 3, then the calls would be polygon(0,1,2), connect(QUAD), polygon(0,2,3). For clients that understand quads, the last point of the second triangle is the fourth point of the quad.
These mark the start and end of continuous curves. If the soup has type 2, such that each polygon is really a pair of endpoints, then this can be used to indicate that the lines are really part of a curve. For a curve consisting of the points 0, 1, 2, 3, the calling sequence is connect(BEGIN), polygon(0,1), polygon(1,2), polygon(2,3), polygon(3,4), connect(END).

(35) SDK: Declarations

The user method allows quads to be created with connectivity.

(36) User Class: TriangleSoup method
 Quad (
         unsigned int             v0,
         unsigned int             v1,
         unsigned int             v2,
         unsigned int             v3)
         LxResult                 rc;
         rc = Polygon (v0, v1, v2);
         if (LXx_FAIL (rc))
                 return rc;
         Connect (LXiTBLX_CONNECT_QUAD);
         return Polygon (v0, v2, v3);

(37) SDK: Declarations
 #define LXu_TABLEAUSURFACE      "F785D229-6BA5-4C8E-9994-3E502053B83C" 
 #define LXu_TRIANGLESOUP        "17536C2D-07BD-49C8-8348-CEBDE4D3A856"


Volume elements are 3D elements with a depth and internal structure. They need to be evaluated over the entire volume rather than just at the 2D surface.

Returns the volume evaluate flags. We support 3 types that can be implemented simultaneously on the same element:

(38) SDK: Declarations

After the tableau has fully been populated and the sample vector has been set, but before the first ray is fired, volumes are initialized.
Volumes are sampled with ray marching: during ray tracing volumes are intersected by rays and are evaluated to contribute to the ray. 'densitySlice' is the volume density shader slice 'shadingSlice' is the volume sample shader slice 'sv' is the sample vector.
Implicit surfaces need to be raycasted, these are volumes that can be evaluated as a surface usually by tracing the isosurface of the volume density field. This method returns the hit distance. It should also set the normal, position and facet packets.
Returns the density at the given position, if 'worldPos' is true it means that the position is in world space. The density is zero outside the volume so this can also be used to find out if we are inside or outside said volume.

(39) SDK: ILxTableauVolume interface
 ''[[#C15|3D Element Methods]]''
         LXxMETHOD(  int,
 Type) (
         LXtObjectID              self);
         LXxMETHOD(  LxResult,
 RenderInit) (
         LXtObjectID              self,
         LXtObjectID              sv);
         LXxMETHOD(  LxResult,
 RaySample) (
         LXtObjectID              self,
         LXtObjectID              densitySlice,
         LXtObjectID              shadingSlice,
         LXtObjectID              sv,
         LXtObjectID              raycastObj,
         LXtObjectID              raymarchObj);
         LXxMETHOD(  LxResult,
 RayCast) (
         LXtObjectID              self,
         LXtObjectID              densitySlice,
         LXtObjectID              sv,
         LXtObjectID              raycastObj,
         double                  *dist,
         int                     *localShader);
         LXxMETHOD(  LxResult,
 Density) (
         LXtObjectID              self,
         LXtObjectID              densitySlice,
         LXtObjectID              sv,
         LXtObjectID              raycastObj,
         const LXtVector          pos,
         int                      worldPos,
         double                  *dens);

(40) SDK: Declarations
 #define LXu_TABLEAUVOLUME       "97962302-4B49-4282-B259-F347F1012818"
 #define LXa_TABLEAUVOLUME       "tableauVolume"

Empty TableauVolume Python user class.

(41) PY: TableauVolume method


Lights are 3D elements, where the bound (if any) represents the effective coverage area for anything illuminated by the light. The light source can be sampled at any desired quality level, which is simply the number of samples over the emissive volume defined by the light.

'Emit' traces photons from a light.

'ShadowMap' returns the map and true if the light uses a shadow map.

(42) SDK: ILxTableauLight interface
 ''[[#C15|3D Element Methods]]''
         LXxMETHOD(  LxResult,
 Sample) (
         LXtObjectID              self,
         float                    u,
         float                    v,
         const LXtFVector         dir,
         LXtFVector               wPos,
         LXtFVector               oPos,
         float                    t);
         LXxMETHOD(  int,
 Geometry) (
         LXtObjectID              self,
         void                    *gc);
         LXxMETHOD(  LxResult,
 Emit) (
         LXtObjectID              self,
         unsigned int             nPhotons,
         int                      offset,
         LXtObjectID              slice,
         LXtSampleVectorID        sv);
         LXxMETHOD(  int,
 ShadowMap) (
         LXtObjectID              self,
         void                   **ppvObj);

Empty TableauLight Python user class.

(43) PY: TableauLight method

(44) SDK: ILxLightSample interface
         LXxMETHOD(  void,
 Vertex) (
         LXtObjectID              self,
         const float             *vertex);

(45) SDK: Declarations
 #define LXu_TABLEAULIGHT        "7FE816D1-4A7F-4BE5-9689-4991C03CAEE0"
 #define LXa_TABLEAULIGHT        "tableauLight"
 #define LXu_LIGHTSAMPLE         "43734621-9B93-4174-AC63-E2FE7DDA8794"

Empty LightSample Python user class.

(46) PY: LightSample method

The shader computes the light intensity and other parameters based on the sample and the ray. Sample source position is in homogeneous coordinates so it can include distant sources.

Quality and shadowing are determined reading packets from the shader. These are fixed for a given light, so they can be evaluated once and should not depend on any other inputs. The quality is given by a requested number of samples for the light, as well as approximate information about falloff. For local lights, 'center' and 'radius' define a region in which the light's sample points reside. For directional lights, 'radius' is half the spread in radians. 'dir' is the direction the light is pointing. 'power' is the radiant flux of the light in watts and is used for allocating emitted photons. 'infinite' should be one for directional lights, zero otherwise. 'indirect' should be one for portal lights, zero otherwise. 'item' is for light linking and points to the cinema light item that corresponds to this element.

(47) SDK: LXpLightQuality struct
 LXtFVector               center;
 LXtFVector               dir;
 float                    radius;
 float                    cosine;
 float                    intensity;
 float                    power;
 float                    importance;
 unsigned int             infinite;
 unsigned int             indirect;
 unsigned int             fallType;
 unsigned int             shadType;
 unsigned int             samples;
 unsigned int             linkMode;
 void                    *linkGroup;
 void                    *item;

Lights that want to use a shadow map can compute this packet. It indicates the center, direction, orientation, spread angle and resolution of the requested shadow map. 'exclude' is for self shadowing. Some items do their own self shadowing and don't want to be included in the shadow computation.

(48) SDK: LXpLightShadowing struct
 LXtFVector               pos;
 LXtFVector               ray, up;
 float                    angle;
 unsigned int             mapRes;
 void                    *exclude;

The light color and brightness for locations in space is computed using another shader slice. This packet is initialized with the world and object position and the ray for the light sample being shaded.

(49) SDK: LXpLightSource struct
 LXtFVector               wPos, oPos;
 LXtFVector               dir;
 float                    len;
 float                    nDotWi;
 int                      sampleIndex;
 void                    *lightSource;
 void                    *lightItem;

The colors at the given sample location are computed by the light and returned in this packet. Brightness takes into account falloff and light shape, and the colors are not pre-multiplied by the attenuation. 'unshad' is not set by the light shader itself but is used in computing shadow alpha. 'pdf' is used for multiple importance sampling.

(50) SDK: LXpLightColor struct
 LXtFVector               color;
 LXtFVector               shadow;
 LXtFVector               unshad;
 float                    diffuse;
 float                    specular;
 float                    caustics;
 float                    subsurface;
 float                    brightness;
 float                    pdf;

(51) SDK: Declarations
 #define LXsP_LGT_QUALITY        "light.quality"
 #define LXsP_LGT_SHADOWING      "light.shadowing"
 #define LXsP_LGT_SOURCE         "light.source"
 #define LXsP_LGT_COLOR          "light.color"

These are the texture effect names that apply to lights.

(52) SDK: Declarations
 #define LXs_FX_LIGHTCOLOR       "lightColor"
 #define LXs_FX_LIGHTSHADOW      "lightShadow"
 #define LXs_FX_LIGHTDIFFUSE     "lightDiffuse"
 #define LXs_FX_LIGHTSPECULAR    "lightSpecular"
 #define LXs_FX_LIGHTCAUSTICS    "lightCaustics"
 #define LXs_FX_LIGHTDISTANCE    "lightDistance"
 #define LXs_FX_LIGHTANGLE       "lightAngle"

These are the texture effect names that apply to volumetrics.

(53) SDK: Declarations
 #define LXs_FX_VOLSCATTER       "volScattering"
 #define LXs_FX_VOLSCATTER_COL   "volScatteringColor"
 #define LXs_FX_VOLABSORB        "volAbsorption"
 #define LXs_FX_VOLABSORB_COL    "volAbsorptionColor"
 #define LXs_FX_VOLDENSITY       "volDensity"
 #define LXs_FX_VOLLEVEL         "volLevel"
 #define LXs_FX_VOLSMPDENSITY    "volSampleDensity"
 #define LXs_FX_VOLAMB_COL       "volAmbientColor"
 #define LXs_FX_VOLLUMI          "volLuminosity"
 #define LXs_FX_VOLLUMI_COL      "volLuminosityColor"


A proxy is sampled to add elements to the tableau. The proxy object needs to keep track of which sub-elements have been added to the tableau so they are only added once.

(54) SDK: ILxTableauProxy interface
 ''[[#C15|3D Element Methods]]''
         LXxMETHOD(  LxResult,
 Sample) (
         LXtObjectID              self,
         const LXtTableauBox      bbox,
         LXtObjectID              tableau);

(55) SDK: Declarations
 #define LXu_TABLEAUPROXY        "FB34BD64-099C-4B30-8EF7-2BB04CA0E92C"
 #define LXa_TABLEAUPROXY        "tableauProxy"

Empty TableauProxy Python user class.

(56) PY: TableauProxy method


An instance is a copy of another element, with variations. It most likely has a different position and orientation, and it may have differences in shading or shading parameters. These differences are encoded in the instance interface. Any normal element may also present this interface, if it wants to have these attributes as well.

Instances can set packets in the sample vector for global parameters of the element to be used by its shader.

(57) SDK: ILxTableauInstance interface
         LXxMETHOD(  LxResult,
 Properties) (
         LXtObjectID              self,
         LXtObjectID              vecstack);

The transformation of an instance (or any element) can be read with this method. The endpoint can specify either the start or end of the tableau time interval.

(58) SDK: ILxTableauInstance interface
         LXxMETHOD(  LxResult,
 GetTransform) (
         LXtObjectID              self,
         unsigned                 endPoint,
         LXtVector                offset,
         LXtMatrix                xfrm);

The instance may be dissolved independently.

(59) SDK: ILxTableauInstance interface
         LXxMETHOD(  LxResult,
 GetDissolve) (
         LXtObjectID              self,
         double                  *dissolve);

An instance can define particle features. These are given by a tableau vertex to describe the feature vector, which can then be read.

(60) SDK: ILxTableauInstance interface
         LXxMETHOD(  LxResult,
 ParticleDescription) (
         LXtObjectID              self,
         void                   **ppvObj);
         LXxMETHOD(  LxResult,
 ParticleArray) (
         LXtObjectID              self,
         float                   *vector);

(61) SDK: Declarations
 #define LXu_TABLEAUINSTANCE     "77837A98-2BC5-466C-834F-DC937642558B"
 #define LXa_TABLEAUINSTANCE     "tableauInstance2" 
 #define LXiTBLX_ENDPOINT_T0      0
 #define LXiTBLX_ENDPOINT_T1      1

Empty TableauInstance Python user class.

(62) PY: TableauInstance method


All 3D elements have shaders, which are used by the renderer to compute desired sample vector packets from a set of input packets. Shaders are polymorphic with an ILxVectorType interface, so it's easy to determine what they take as input and output.

To perform computations we need to "slice" the shader, which basically selects a set of desired output packets. The output packets are determined by the vector type, and the vertex description provides information about vertex packets to use during computation.

(63) SDK: ILxTableauShader interface
         LXxMETHOD(  LxResult,
 Select) (
         LXtObjectID              self,
         LXtObjectID              teElt,
         LXtObjectID              tvDesc);
         LXxMETHOD(  LxResult,
 Slice) (
         LXtObjectID              self,
         LXtObjectID              vtOutput,
         LXtObjectID              tvDesc,
         void                   **ppvObj);

(64) User Class: TableauShader method
 GetSlice (
         CLxLoc_ShaderSlice      &slice,
         ILxUnknownID             vtOutput,
         ILxUnknownID             tvDesc)
         LXtObjectID              obj;
         if (LXx_FAIL (Slice (vtOutput, tvDesc, &obj)))
                 return false;
         return slice.take (obj);

Empty TableauShader Python user class.

(65) PY: TableauShader method

The result of a successful slice is a shader slice interface. This is also polymorphic with an ILxVectorType to select its inputs. The Evaluate() method computes the output packets for a sample vector.

(66) SDK: ILxShaderSlice interface
         LXxMETHOD(  LxResult,
 Evaluate) (
         LXtObjectID              self,
         LXtObjectID              vecstack);

(67) SDK: Declarations
 #define LXu_TABLEAUSHADER       "A0E3B574-B0AC-4530-A43F-7CC8DA536E25"
 #define LXa_TABLEAUSHADER       "tableauShader"
 #define LXu_SHADERSLICE         "47B885B9-B1D9-4F86-829F-A6AABBD7FFF7"

Empty ShaderSlice Python user class.

(68) PY: ShaderSlice method


The environment in the tableau is given as a list of shaders.

The primary function of an environment is to read the direction of the camera ray and output the color at infinity. Origin is also provided, although for environment shaders this can be ignored since the background is assumed to be at infinity. Spread is the angular size of the sampling cone, in radians.

(69) SDK: LXpCameraRay struct
 LXtFVector               dir;
 LXtFVector               origin;
 float                    spread;

(70) SDK: LXpEnvInfiniteColor struct
 LXtFVector               rgb;
 float                    intensity;

An environment can also provide flags to determine where it is visible. Typically only one environment would be visible in a specific context. If there are multiple environments visible to the camera, for example, it's undetermined what the render camera will see.

(71) SDK: LXpEnvVisibility struct
 unsigned int             camera;
 unsigned int             indirect;
 unsigned int             reflection;
 unsigned int             refraction;

In PhotoView, sometimes we render the environment as if it were an actual sphere in the background of everything. This packet is used for that feature.

(72) SDK: LXpEnvShape struct
 unsigned int             sphereVis;
 float                    size;
 float                    height;
 unsigned int             flatten;
 LXtVector                pos;
 LXtMatrix                xfrmInv;

An environment can also provide fog values indicating color and absorption values.

(73) SDK: LXpEnvFog struct
 LXtFVector               color;
 LXtFVector               opacity;

The environment shaders can also provide global render settings. Each of these packets should occur in only one shader, or else the result is undetermined. The global lighting packet sets ambient illumination and some basic raytracing settings.

(74) SDK: LXpGlobalLighting struct
 LXtFVector               ambientColor;
 float                    ambientIntensity;
 float                    rayThresh;
 float                    rayClamp;
 float                    rayOffset;
 unsigned int             shadDepth;
 unsigned int             reflDepth;
 unsigned int             refrDepth;
 unsigned int             unbiased;
 unsigned int             specSmps;
 unsigned int             animNoise;
 unsigned int             rayAccel;
 unsigned int             batchSize;
 unsigned int             impBoost;
 unsigned int             directSmps;
 unsigned int             directMIS;

Displacement settings.

(75) SDK: LXpGlobalDisplacement struct
 float                    rate;
 float                    ratio;
 float                    jitter;
 float                    edgeMin;
 unsigned int             enable;
 unsigned int             smooth;
 unsigned int             useBump;

Indirect illumination and caustics settings.

(76) SDK: LXpGlobalIndirect struct
 float                    range;
 float                    irrRays2;
 float                    irrRate;
 float                    irrRatio;
 float                    irrSmooth;
 float                    irrRetrace;
 unsigned int             enable;
 unsigned int             scope;
 unsigned int             limit;
 unsigned int             rays;
 unsigned int             subs;
 unsigned int             vols;
 unsigned int             bump;
 unsigned int             super;
 unsigned int             reject;
 unsigned int             caus;
 unsigned int             giBack;
 unsigned int             irrCache;
 unsigned int             irrDirect2;
 unsigned int             irrRays;
 unsigned int             irrVals;
 unsigned int             irrGrads;
 unsigned int             irrSample;
 unsigned int             irrData;
 unsigned int             irrStart;
 unsigned int             irrEnd;
 unsigned int             irrWalk;
 unsigned int             irrLEnable;
 unsigned int             irrSEnable;
 unsigned int             radCache;
 unsigned int             envSample;
 unsigned int             envRays;
 unsigned int             envMIS;
 char                    *irrLName;
 char                    *irrSName;

(77) SDK: LXpGlobalCaustics struct
 float                    multiplier;
 unsigned int             total;
 unsigned int             local;
 unsigned int             walk;

Miscellaneous render settings, specific to our built-in renderer.

(78) SDK: LXpGlobalRendering struct
 float                    subdRate;
 float                    coarseRate;
 float                    fineRate;
 float                    fineThresh;
 float                    aaImpMin;
 unsigned int             multiGeo;
 unsigned int             mergeFur;
 unsigned int             subdAdapt;
 unsigned int             renderType;
 unsigned int             aa;
 unsigned int             aaFilter;
 unsigned int             motionBlur;
 unsigned int             field;
 unsigned int             frmPasses;
 unsigned int             dof;
 unsigned int             stereo;
 unsigned int             stereoEye;
 unsigned int             stereoComp;
 unsigned int             upAxis;
 unsigned int             bucketX;
 unsigned int             bucketY;
 unsigned int             bktOrder;
 unsigned int             bktReverse;
 unsigned int             bktWrite;
 unsigned int             bktSkip;
 unsigned int             bktRefine;
 unsigned int             aRefine;
 unsigned int             mergeRad;
 unsigned int             bakeDir;

There are actually two camera ray packets. The default one contains the camera ray at time t0, the start interval for the tableau. The second ray packet contains the ray at time T1, the end of the interval. Good environment shaders will interpolate colors along an arc between the two rays to account for motion blurring of the environment as the camera rotates.

(79) SDK: Declarations
 #define LXsP_CAM_RAY            "camera.ray"
 #define LXsP_CAM_RAY_T1         "camera.ray_T1"
 #define LXsP_ENV_INFINITECOLOR  "environment.color"
 #define LXsP_ENV_VISIBILITY     "environment.visibility"
 #define LXsP_GLO_LIGHTING       ""
 #define LXsP_GLO_DISPLACEMENT   "global.displacement"
 #define LXsP_GLO_INDIRECT       "global.indirect"
 #define LXsP_GLO_CAUSTICS       "global.caustics"
 #define LXsP_GLO_RENDERING      "global.rendering"

Note that environment shape is intentionally NOT exposed to the SDK, since it's only for SolidWorks.


The environment shader also provides packets to describe the camera used for rendering. Some are required and some are optional, and which ones are present determines the type of camera to use for the rendering.

This required packet defines the frame buffer size in pixels and the per-pixel sampling. Sampling quality is given by a floating point number of samples per pixel, and a shading rate. These may be interpreted in any manner appropriate to the renderer.

(80) SDK: LXpCameraResolution struct
         unsigned int             width;
         unsigned int             height;
         float                    pixelAspect;
         float                    dpi;
 //      float                    samples;
 //      float                    rate;
         float                    regX0, regX1, regY0, regY1;

Orthographic cameras are specified by a set of parameters which define the view frustum, lens and aperture. Eye position and transform are the location and orientation of the camera in world coordinates. Focal length, focus distance and f-Stop define the camera's zoom and depth of field. Iris blades, rotation, and bias affect the depth of field "bokeh" shape. Distort is a barrel or pin-cushion value and is 1.0 for a flat projection. Interocular distance is the distance between the eyes for stereo rendering. The remaining parameters define the projection from 3D to 2D, ala Maya.

(81) SDK: LXpCameraFrustum struct
 LXtFVector               eyePos;
 LXtMatrix                xfrm, invXfrm;
 float                    focalLength;
 float                    focusDist, fStop;
 float                    irisRot, irisBias, distort;
 float                    ioDist, convDist;
 float                    blurLength, blurOffset;
 float                    apertureX, apertureY;
 float                    offsetX, offsetY;
 float                    squeeze;
 float                    target;
 float                    clipDist;
 int                      clipping;
 int                      filmFit;
 int                      projType;
 int                      irisBlades;
 void                    *item;
 int                      useMask;
 float                    overscan;

In addition to simple distortion, raytrace camera distortions can be computed procedurally for each pixel. If the environment has a slice that reads the camera pixel packet and writes the LXpCameraRay packet, then the slice will be evaluated before raytracing. The slice reads the buffer XY, and optionally the ray direction packet (which is initialized based on the frustum if any), and writes an updated ray direction.

(82) SDK: LXpCameraPixel struct
 float                    x, y;

A camera intended to perform UV baking is specified by providing one of these packets instead of a frustum. The packet contains the name of the feature which is the source of the UV coordinates. Any element that doesn't support this UV feature name is not rendered.

(83) SDK: LXpCameraUVBake struct
 const char              *name;

The frustum may be a source of motion blur, so the renderer will query for one at time T0 and another at time T1. If the T1 frustum is missing, the camera is assumed to be non-moving.

(84) SDK: Declarations
 #define LXsP_CAM_RESOLUTION     "camera.resolution"
 #define LXsP_CAM_FRUSTUM        "camera.frustum"
 #define LXsP_CAM_FRUSTUM_T1     "camera.frustum_T1"
 #define LXsP_CAM_PIXEL          "camera.pixel"
 #define LXsP_CAM_UVBAKE         "camera.uvBake"


Filters are fundamentally 2D elements, and operate in the image space over the frame buffer. The bound method for a filter returns a 2D bounding box and a kernel radius, plus some flags.

(85) SDK: ILxTableauFilter interface
         LXxMETHOD(  const char *,
 Order) (
         LXtObjectID              self);
         LXxMETHOD(  LxResult,
 Select) (
         LXtObjectID              self,
         LXtObjectID              framebuf);
         LXxMETHOD(  unsigned int,
 Bound) (
         LXtObjectID              self,
         float                    box[4],
         float                   *kernel);

the filter can process individual pixels, and the Pixel() method is used rather than the region method. If the kernel is also zero then the filter can be used on a single pixel at a time without needing any other region information.
the filter requires complete strips in order to run. The Region() method will be called with strips scanned from top to bottom of the image. This is typically used by filters which act as image-savers.
the filter's bounding box and kernel are given as a percentage of the minimum dimenion of the entire frame. This allows for filters that are independent of the size of the rendering.

(86) SDK: Declarations
 #define LXfTBLX_FILT_PIXEL       0x01
 #define LXfTBLX_FILT_STRIP       0x02
 #define LXfTBLX_FILT_RELATIVE    0x04

The Pixel() and Region() methods are used to do proecessing based on the filter type. Both get the frame buffer and the

(87) SDK: ILxTableauFilter interface
         LXxMETHOD(  unsigned int,
 Pixel) (
         LXtObjectID              self,
         LXtObjectID              framebuf,
         const float              pos[2]);
         LXxMETHOD(  unsigned int,
 Region) (
         LXtObjectID              self,
         LXtObjectID              framebuf,
         const float              box[4]);

(88) SDK: Declarations
 #define LXu_TABLEAUFILTER       "FD1326E7-EECF-4EAC-B9AD-B1D64E7503BE"
 #define LXa_TABLEAUFILTER       "tableauFilter"

Item Interface

A channel change can have no affect on the preview, or it may be necessary to update the geometry or the shading. The item can decide how individual channels should update the preview by returning the appropriate combination of the following values when the PreviewUpdate function is called.

(89) SDK: Declarations

Any item can potentially participate in rendering by adding elements to the tableau. Any item (or package) which presents the following interface will get called upon when it's time to create a tableau from a cinema.

This is called to let the item add elements directly to a tableau.
This is similar, but is used for adding elements needed to preview this item.
This will be called by other items which want instances of this item. The item should add elements to instance itself given the tableua instance object as reference.
Elements such as lights can affect their own shading. If that's the case, this method will be called to allow the item to create a shader to be used as a part of the shader for this item.
Elements may be able to provide curves which can be used as hair guides. The string tag interface selects a subset of curves, and the object returned is a tableau surface element which can be sampled.
If implemented, ElementType allows the tableau source to specify what types of elements it can add to the tableau. It is queried with one of the following types, and puts 1 into *supported if it will add elements of that type, 0 if it won't, or -1 if it doesn't know.

(90) SDK: Declarations
 #define LXiTBXELT_SURFACE       0
 #define LXiTBXELT_VOLUME        1
 #define LXiTBXELT_LIGHT         2
 #define LXiTBXELT_FILTER        3
 #define LXiTBXELT_PROXY         4

(91) SDK: ILxTableauSource interface
         LXxMETHOD(  LxResult,
 Elements) (
         LXtObjectID              self,
         LXtObjectID              tableau);
         LXxMETHOD(  LxResult,
 Preview) (
         LXtObjectID              self,
         LXtObjectID              tableau);
         LXxMETHOD(  LxResult,
 Instance) (
         LXtObjectID              self,
         LXtObjectID              tableau,
         LXtObjectID              instance);
         LXxMETHOD(  LxResult,
 SubShader) (
         LXtObjectID              self,
         LXtObjectID              tableau,
         void                   **ppvObj);
         LXxMETHOD(  LxResult,
 PreviewUpdate) (
         LXtObjectID              self,
         int                      chanIndex,
         int                     *update);
         LXxMETHOD(  LxResult,
 GetCurves) (
         LXtObjectID              self,
         LXtObjectID              tableau,
         LXtObjectID              tags,
         void                   **ppvObj);
         LXxMETHOD(  LxResult,
 ElementType) (
         LXtObjectID              self,
         int                      type,
         int                     *supported);

(92) SDK: Declarations
 #define LXu_TABLEAUSOURCE       "9CC7F9F4-9540-4EEA-8EE9-710D58EC68F9"

Empty TableauSource Python user class.

(93) PY: TableauSource method

Any internal package can get one of these source interfaces by providing a couple of callbacks.

(94) PUB: TableauItemFuncs struct
 void                    (*elements)  (void *, CineItemID, TableauContextID);
 void                    (*preview)   (void *, CineItemID, TableauContextID);
 void                    (*instance)  (void *, CineItemID, TableauContextID, ILxTableauInstanceID, CineItemID);
 ILxTableauShaderID      (*subShader) (void *, CineItemID, TableauContextID);
 void                    (*previewupdate) (void *, CineItemID, int, int *);
 ILxTableauSurfaceID     (*getCurves) (void *, TableauContextID, ILxStringTagID);
 int                     (*elementType) (void *, CineItemID, int);

(95) PUB: TableauItemPackage usage
 TableauItemPackage (
         const char              *pkgName,
         TableauItemFuncs        *funcs,
         void                    *data)

The cleanest way for items to provide elements that can be instanced is to implement an Instanceable object. This is stored in an OBJREF channel on the item, given by a server tag on the package. The item type will implement a modifier for computing the instanceable object from the state of the item's channels.

(96) SDK: Declarations
 #define LXu_INSTANCEABLE        "9CC7F9F4-9540-4EEA-8EE9-710D58EC68F9" 

Compare this instanceable to another of the same type and return a value on a comparison metric. The value is the same sense as strcmp(), with zero for identical objects, and positive and negative values indicating relatve order.

(97) SDK: ILxInstanceable interface
         LXxMETHOD(  int,
 Compare) (
         LXtObjectID              self,
         LXtObjectID              other);

When the elements are needed -- such as when the item itself is to be rendered or instanced -- the AddElements() method is called. The T0 and T1 instanceable objects are from the shutter open and close times, and can be read to compute state that changes while the shutter is open. The object itself is computed at the frame time. The method should call AddInstanceableElement() for all elements in the item.

(98) SDK: ILxInstanceable interface
         LXxMETHOD(  LxResult,
 AddElements) (
         LXtObjectID              self,
         LXtObjectID              tableau,
         LXtObjectID              instT0,
         LXtObjectID              instT1);

Alternately the instanceable object can provide a Surface object which will be added to the tableau.

(99) SDK: ILxInstanceable interface
         LXxMETHOD(  LxResult,
 GetSurface) (
         LXtObjectID              self,
         void                   **ppvObj);

Tableau Service

(100) SDK: ILxTableauService interface
         LXxMETHOD(  LxResult,
 ScriptQuery) (
         LXtObjectID              self,
         void                   **ppvObj);
         LXxMETHOD(  LxResult,
 AllocVertex) (
         LXtObjectID              self,
         void                   **ppvObj);

(101) User Service Class: TableauService method
 NewVertex (
         CLxLoc_TableauVertex    &vert)
         LXtObjectID              obj;
         vert.clear ();
         if (LXx_FAIL (AllocVertex (&obj)))
                 return false;
         return vert.take (obj);

This utility method will populate a vertex description with all the known features of the tableau surface object. It puts the four required features at the front as normal.

(102) SDK: ILxTableauService interface
         LXxMETHOD(  LxResult,
 VertexFromFeatures) (
         LXtObjectID              self,
         LXtObjectID              surfObj,
         LXtObjectID              vertex);

(103) SDK: Declarations
 #define LXu_TABLEAUSERVICE      "8DF92316-3172-465A-A199-254792D37732"

Empty tableau service Python user class.

(104) PY: TableauService method

Nodal Service

The nodal service is used to establish and evaluate channels as part of the Nodal Shading system. The service is used by textures & shaders in two ways: as part of the Shader Tree, their inputs can be evaluated on a per-sample basis, and their outputs can also be requested as part of the Nodal Modifier system.

The Nodal Service will transparently handle both of these cases.

AddSampleChan & AddSampleChanName are used to add a channel of an item to the nodal evaluation system. When processed as part of the Shader Tree, the evaluator index will be stored in the 'idxArray' passed, and when processed as a nodal modifier, the evaluator index will be stored internally within the nodal evaluator (and 'idxArray' may be NULL). Additionally, within the Shader Tree, if the channel is not actually driven on a per-sample basis for the current pass, the evaluator index is stored as LXiNODAL_NOT_DRIVEN. This value speeds up the evaluations by letting the Get* methods know that the value does not have to be read from the evaluator.

When adding channels for texture locators, LXfECHAN_LOCATOR should be ORed with the LXfECHAN_READ and/or LXfECHAN_WRITE flags.

GetFloat, GetInt & GetValue are used to get the current sample values from a nodal evaluator. 'index' is the index of channel in the item. For evaluations in Shader Tree, the values will be read from the evaluator using the 'idxArray' passed, unless the stored index is LXiNODAL_NOT_DRIVEN, in which case the 'orig' value will be returned. When evaluated as a nodal modifier, the internal index array will be used.

IsDriven & IsDrivenName are used to determine if a channel is driven per-sample, and therefore needs to be read at every sample. If not, the value can be read once per layer.

AnyDrivenChans is used for optimization of the evaluation. It is passed a contiguous array of evaluator indexes, and will return LXe_TRUE for the first sample-driven channel, and LXe_FALSE if none are found.

(105) SDK: ILxNodalService interface
         LXxMETHOD(  int,
 AddSampleChan) (
         LXtObjectID              self,
         LXtObjectID              eval,
         LXtObjectID              item,
         int                      chanIndex,
         int                     *idxArray,
         int                      type);
         LXxMETHOD(  int,
 AddSampleChanName) (
         LXtObjectID              self,
         LXtObjectID              eval,
         LXtObjectID              item,
         const char              *chanName,
         int                     *idxArray,
         int                      type);
         LXxMETHOD(  double,
 GetFloat) (
         LXtObjectID              self,
         LXtObjectID              etor,
         int                     *idxArray,
         int                      index,
         double                   orig);
         LXxMETHOD(  int,
 GetInt) (
         LXtObjectID              self,
         LXtObjectID              etor,
         int                     *idxArray,
         int                      index,
         int                      orig);
         LXxMETHOD(  void*,
 GetValue) (
         LXtObjectID              self,
         LXtObjectID              etor,
         int                     *idxArray,
         int                      index,
         void                    *orig);
         LXxMETHOD(  int,
 IsDriven) (
         LXtObjectID              self,
         LXtObjectID              item,
         int                      chanIndex);
         LXxMETHOD(  int,
 IsDrivenName) (
         LXtObjectID              self,
         LXtObjectID              item,
         const char              *chanName);
         LXxMETHOD(  int,
 AnyDrivenChans) (
         LXtObjectID              self,
         int                     *chans,
         int                      count);

Empty nodal service Python user class.

(106) PY: NodalService method

This structure can be used to simplify conversion from the older value textures. It contains 3 indexes, the channel index inthe item, the evaluator index for the per-layer data, and the evaluator index for the per-sample data.

(107) SDK: LXtSampleIndex struct
 unsigned         chan;
 unsigned         layer;

(108) SDK: Declarations
 #define LXfECHAN_LOCATOR        0x40000000
 #define LXu_NODALSERVICE        "2055F206-1682-11E3-9F05-6ACC6088709B"

Tableau Event Translator

In order for plugins to listen to events, we provide the following event translator interface.

A tableau channel change event is a general event such that a plugin can listen for any channel changes through the tableau. This is useful for plugins which generate tableau elements, as it gets events on both animation and general modification events, so it's simple to then fire a tableau update event.

(109) SDK: ILxTableauListener interface
         LXxMETHOD( void,
 ChannelChange) (
         LXtObjectID              self,
         LXtObjectID              tableau,
         LXtObjectID              item,
         int                      channel);

The only two other events are when a tableau indicates that every element in the tableau is being flushed, and when the tableau is warning clients that it is being destroyed.

(110) SDK: ILxTableauListener interface
         LXxMETHOD( void,
 FlushElements) (
         LXtObjectID              self,
         LXtObjectID              tableau);

(111) SDK: ILxTableauListener interface
         LXxMETHOD( void,
 TableauDestroy) (
         LXtObjectID              self,
         LXtObjectID              tableau);

(112) SDK: Declarations
 #define LXu_TABLEAULISTENER     "848C5B64-4C9F-404E-8E3F-CF725007F74D"