Celestia/3D Models/CMOD File
Introduction
[edit | edit source]Celestia's internal Model object consists of a set of "materials" followed by a set of "meshes". Each "mesh" consists of a list of vertex definitions followed by several "groups". Each "group" consists of a specification of the type of "group" that it is, followed by an enumeration of previously defined vertices belonging to the group. This structure exactly corresponds to the items contained in a CMOD file.
BN description of CMOD format
[edit | edit source]The following description has been adapted from comments in Celestia's source code.
This is an approximate Backus Naur form for the contents of ASCII cmod files. For brevity, the categories <unsigned_int> and <float> aren't defined here--they have the obvious definitions.
<modelfile> ::= <header> <model> <header> ::= #celmodel__ascii <model> ::= { <material_definition> } { <mesh_definition> } <material_definition> ::= material { <material_attribute> } end_material <material_attribute> ::= diffuse <color> | specular <color> | emissive <color> | specpower <float> | opacity <float> | texture0 <string> | blend add | (v1.5.0 and later) normalmap <string> | (v1.5.0 and later) specularmap <string> | (v1.5.0 and later) emissivemap <string> (v1.5.0 and later) <color> ::= <float> <float> <float> <string> ::= """ { letter } """ <mesh_definition> ::= mesh <vertex_description> <vertex_pool> { <prim_group> } end_mesh <vertex_description> ::= vertexdesc { <vertex_attribute> } end_vertexdesc <vertex_attribute> ::= <vertex_semantic> <vertex_format> <vertex_semantic> ::= position | normal | color0 | color1 | tangent | texcoord0 | texcoord1 | texcoord2 | texcoord3 <vertex_format> ::= f1 | f2 | f3 | f4 | ub4 <vertex_pool> ::= vertices <count> { <float> } <count> ::= <unsigned_int> <prim_group> ::= <prim_group_type> <material_index> <count> { <unsigned_int> } <prim_group_type> ::= trilist | tristrip | trifan | linelist | linestrip | points | sprites (v1.5.0 and later) <material_index> :: <unsigned_int> | -1
Clarification of some of the elements
[edit | edit source]In Celestia v1.5.0 or later, a full Material definition would look like
material diffuse 0.5 0.5 0.2 specular 1 1 1 specpower 30 opacity 1 texture0 "basetex.jpg" normalmap "norm.png" specularmap "specmask.jpg" emissivemap "lights.jpg" end_material
Some of the vertex attributes are
- position - position (required)
- texcoord0 - primary texture coordinate
- texcoord1 ... texcoord3 - additional texture coordinates (for multitexturing)
- color0 ... color1 - primary and secondary colors
- tangent - surface tangents (for bump mapping)
The vertex formats are
- f1 - one float
- f2 - two floats (typical for texture coordinates)
- f3 - three floats (positions and normals)
- f4 - four floats
- ub4 - four unsigned bytes (the usual format for colors)
The primitive types are
- trilist
- tristrip
- trifan
- linelist
- linestrip
- points
- sprites (v1.5.0 and later)
Tristrips and trifans can be much more efficient than triangle lists.
Example ASCII CMOD File
[edit | edit source]#celmodel__ascii # The above line is the 16-byte header; for binary files, it's # #celmodel_binary # material definitions follow--these must precede any meshes in the file material # index 0 emissive 0 1 0 opacity 0.5 end_material # A material with a texture. Texture filenames may use the wildcard # character, which behaves exactly as it does within a .ssc file. material # index 1 emissive 1 1 1 texture0 "tropical-beach.*" end_material # There may be one or more meshes in a model file--this file happens # to have just a single one mesh # The vertex description tells what attributes the vertices for this mesh # have. Each attribute in the description consists of a semantic and a # data format. vertexdesc position f3 normal f3 texcoord0 f2 end_vertexdesc # The vertex data--the number right after the keyword vertices is the # number of vertices in the pool. vertices 6 0 0 0 0 0 1 0 0 1 1 0 0 0 1 1 1 0 1 0 0 0 1 0 1 0 1 0 0 0 -1 0 1 1 1 0 0 0 -1 1 1 0 0 0 0 0 -1 0 0 # An arbitrary number of primitive groups follow # The primitive group type is followed by a material index, then # a count of the number of vertex indices in the group trilist 0 3 0 1 2 trilist 1 3 3 4 5 # End of the mesh end_mesh # ---- end of cmod file ----
Point Sprites
[edit | edit source]Starting with Celestia v1.5.0, the CMOD format supports point sprites. These can be used for a variety of different volumetric rendering effects for phenomena such as nebulae, accretion disks, volcanic plumes, or engine exhaust.
The changes to the cmod format relevant to point sprites are minor. There's a new material attribute (blend add), a new primitive type (sprites), and a new vertex attribute type (pointsize). The sprite texture is texture0 from the material definition. Here is a very basic example of a sprite CMOD with three red sprites:
#celmodel__ascii material diffuse 1 0 0 texture0 "gaussian.jpg" end_material mesh vertexdesc position f3 pointsize f1 end_vertexdesc vertices 3 1 0 0 0.25 2 0 0 0.25 3 0 0 0.25 sprites 0 3 0 1 2 end_mesh
Blending
[edit | edit source]There are three options available in the CMOD format which affect how the geometry--triangles, lines, points or sprites--is blended with the background. They are normal, blend and premultiplied, with 'normal' the default.
For nebula meshes, it may be useful to specify additive blending in the material definition by adding the line 'blend add'. This is especially appropriate for emission nebulae, but definitely not to be used for dark nebulae like the Horsehead. Additively blended objects have the advantage of not needing to be depth sorted with respect to each other. Here's a slightly more complex sprite cmod that uses additive blending and per vertex colors so that each sprite has a different color:
#celmodel__ascii material diffuse 1 0 0 texture0 "gaussian.jpg" blend add end_material mesh vertexdesc position f3 pointsize f1 color0 f3 end_vertexdesc # row of sprites: red, green, blue vertices 3 1 0 0 0.25 1 0 0 2 0 0 0.25 0 1 0 3 0 0 0.25 0 0 1 sprites 0 3 0 1 2 end_mesh
For point sprites, each sprite has a color and an alpha value that are multiplied by the sprite texture color and alpha values to produce the fragment color/alpha (Fr Fg Fb) and Fa. The alpha value can be thought of as the opacity, though as you'll see, it's not always treated exactly that way.
Add is the simplest mode. If the background color is (Br Bg Bb), the final color will be:
Fa * (Fr Fg Fb) + (Br Bg Bb)
Normal mode causes the background to be obscured; fragments with an alpha of 1 will completely block the background.
Fa * (Fr Fg Fb) + (1 - Fa) (Br Bg Bb)
Finally, premultiplied is somewhat like normal except it omits the multiplication by the fragment alpha. It assumes that any reduction of the color due to transparency was already done when the geometry was created, hence the name premultiplied:
(Fr Fg Fb) + (1 - Fa) (Br Bg Bb)
The advantage of premultiplied blending is that it can simulate either the add or normal blending modes with appropriately chosen alpha and color values. For example, setting alpha to zero gives the same result as additive blending. Cham: I think this is what you were talking about when you said you wanted to enable add individually for particles.
For nebulae, add is good for glowing, diffuse gases where absorption isn't too much of a factor. Premultiplied would in principle let you mix emissive and absorbing particles, but there's a catch: premultiplied and normal blending aren't generally commutative. For correct results, it's necessary to sort the particles from back to front and render them in order, which isn't something that Celestia currently does, as sorting a large number of particles can dramatically reduce performance. In fact, this order dependence of blending has been a big headache in realtime 3D graphics for a long time, and there's still no completely satisfactory solution for the problem.
Using TriStrips (by Toti)
[edit | edit source]A tristrip is an OpenGL primitive that draws a set of triangles this way:
1-----2 1-----2 \ / \ / 3 1-----2 \ /\ \ / \ 3-----4 1-----2 \ /\ \ / \ 3-----4 \ / \ / 5
So we can store this as: 1,2,3,4,5 (5 entries) With a trilist the same three triangles must be stored this way: 1,2,3 2,3,4 3,4,5 (9 entries, spaces are only to make text legible) Basically the first two vertices define a basis. For each added vertex v a new triangle is defined. This triangle if formed by the vertices [v-2][v-1][v].
In general, for a trilist of T triangles we need 3*T entries, but for a tristrip of T triangles T+2 entries are enough. So for large meshes you are saving 2/3 of the resources. The same can be applied to linestrips:
1---2 1---2 / / 3 1---2 / / 3----4 1---2 / / 3----4 / / 5
If the above mesh is defined as a linelist: 1,2 2,3 3,4 4,5 (8 entries). If it is defined as a linestrip: 1,2,3,4,5 (5 entries) A linelist needs 2*L entries for a set of L lines. A linestrip only needs L+1 entries for the same set. For large meshes the saving is about 50% of resources.
The usual way to work with these primitives is to model the object as usual and then use an optimization utility to convert from raw triangles to tristrips, etc. I don't know of any free program/library that can do this, though. Surely Chris does.
Using Trifans (by Toti)
[edit | edit source]1-------2 \ / \ / 3 1-------2 / \ / / \ / 4------ 3 5------ 1-------2 \ / \ / \ / \ / 4------ 3
Using trifans, the above mesh can be described as 1,2,3,4,5 Each tringle has the vertices [v0][v-1][v], where v0 is the first vertex of the set.
As you see, it's similar to a tristrip, but the first vertex is always the center of the trifan. You need T+2 entries to define T triangles, so it is as efficient as a tristrip (but often it can be used where a tristrip can't)
The advantage of using these primitives is that you save disk, bus and memory resources, because mesh descriptions are less voluminous.