model

The model object represents a 2D or 3D model. By default, the model object serves as a preview and cannot be used any further. The model declaration can however be combined with the keyword image or animation, making the render available as a texture in the remainder of the script, but forcing it to have a fixed resolution, specified by the dimensions modifier.

Its syntax can be one of:

model <name> : <modifier1, modifier2, ...>;
model image <name> : <modifier1, modifier2, ...>;
model animation <name> : <modifier1, modifier2, ...>;

The available modifiers are listed below.

Modifiers

You can specify a subset of the following modifiers. Only the vertex specification is mandatory.

  • vertex(<function>, <primitive type>, <vertex count / list / generator>) – specifies the model's vertex shader and source of vertex data. function is a GLSL function that receives either the index of the current vertex as int and computes the vertex from it, or it receives a single vertex from the supplied vertex list or generated by a vertex generator, and returns that vertex's gl_Position (vec4) – meaning that any coordinate transformations must be applied in this function. The function may optionally output varying data for the subsequent shader stage through its first output argument. Otherwise, it may be absent. For animated models, function may optionally accept the current time (float) as its last argument.
    vec4 vertexFunction(out VaryingType outputData, int vertexIndex);
    vec4 vertexFunction(out VaryingType outputData, in VertexType vertexData);
    The possible primitive types are points, lines, line_strip, line_loop, triangles, triangle_strip, and triangle_fan.

  • fragment(<function>) – specifies the function that computes the fragment color and returns it as vec4. If the preceding shader stage outputs varying data, the function should accept it as its argument. For animated models, function may optionally accept the current time (float) as its second argument. If no fragment function is specified, the model will be rendered solid white.
    vec4 fragmentFunction(in VaryingType fragmentData);
    vec4 fragmentFunction(); // if no data output by preceding shader stage
  • geometry( ... ) – see geometry and tessellation shaders
  • tessellation( ... ) – see geometry and tessellation shaders
  • animated(<true / false>) – makes the model animated if neither image nor animation keyword is present
  • dimensions(<width, height>) – specifies the initial / fixed dimensions of the viewport
  • background(<color>) – specifies the background color, which is used to fill the viewport before drawing the model. Black with zero alpha is the default
  • blend(<mode>) – specifies the blending mode to be used. The possible values are:
    • none
    • transparency
    • transparency_premultiplied
    • additive
    • multiplicative
    • min
    • max
  • depth(<true / false>) – enables or disables the depth buffer (enabled by default)
  • cull(<true / false>) – enables or disables backface culling (disabled by default)
  • wireframe(<true / false>) – enables or disables wireframe mode (disabled by default)
  • multisample(<factor>) – enables (>0) or disables (0) multisample anti-aliasing. The value of the argument is the maximum number of samples per pixel
  • heavy_mode(<true / false>) – this option is required for computationally intensive shaders, where rendering time may exceed 1 second. Without it, there is a risk of GPU driver crash
  • srgb(<true / false>) – see image srgb modifier

Image/animation modifiers

The following modifiers are only available with the image or animation keyword.

  • filter – see image filter modifier
  • map – see image map modifier
  • hidden – see image hidden modifier
  • resizable – see image resizable modifier
  • full_range – see image full_range modifier

Geometry and tessellation shaders

Apart from the basic primitive types listed above, four additional adjacency primitive types can be set to the vertex modifier: lines_adjacency, line_strip_adjacency, triangles_adjacency, and triangle_strip_adjacency. These are only meaningful when used as the input of geometry or tessellation shaders. Only some of their vertices are part of the mesh geometry, while the remaining ones provide information about adjacent primitives. The geometry or tessellation shader may then use this information to compute the actual geometry.

Tessellation

Tessellation is one of the more recent OpenGL features, and therefore requires OpenGL 4 and compatible hardware. Your script needs to have #version set to at least 150, but preferably to 400 or higher.

Tessellation is a feature of modern GPU's, which allows for a very efficient subdivision of triangles or other primitives into many more. It takes place immediately after the vertex shader. The primitive generated by the vertex shader that is being subdivided is called a patch. This process involves two new types of shaders, the tessellation control shader and the tessellation evaluation shader.

The purpose of the control shader is to compute the levels of detail of the subdivision, typically based on camera distance, and making sure that the common edges of adjacent patches are divided in the same way. Simply dividing a flat triangle into many smaller triangles that fill it does not provide any additional detail by itself. To do that, you must manipulate the generated vertices in the evaluation shader, which is invoked for each vertex separately.

The following modifier enables tessellation:

  • tessellation(<control function>, <evaluation function>, <spacing>, <winding>)

spacing dictates how edges are divided for non-integer levels of detail. The possible values are equal_spacing, fractional_even_spacing, and fractional_odd_spacing. winding sets whether the generated triangles have clockwise (cw) or counter-clockwise (ccw) winding. A special value point_mode may be used instead, which causes the tessellation algorithm to produce intersection point vertices instead. Counter-clockwise winding is used by default if this argument is omitted.

control function is the body of the tessellation control shader. Its first one or two (see below) output arguments are the computed tessellation levels. Outer levels are for the patch's individual outer edges and the inner level is for the patch's interior. The remaining arguments are the vertex positions and varying data of the whole patch as outputted by the vertex shader.

void tessellationControlFunction(
    out OuterLevelType outerLevels,
    out InnerLevelType innerLevel, // absent for isolines
    in vec4[N] patchVertexPositions,
    in VaryingType[N] patchData // absent if no vertex shader output
);

evaluation function is the body of the tessellation evaluation shader. If the subsequent shader stage expects varying data, it must be outputted through the first argument, similarly to a vertex shader. The following input arguments are the same as those received by the control function – the vertex positions and input varying data for the whole patch. The actual position of the resulting vertex must be computed from the patch data and the last argument – the tessellation interpolation coordinate, which specifies the position of the current vertex within the patch. The return value is the vertex position, just like in a vertex shader.

vec4 tessellationEvaluationFunction(
    out OutVaryingType outputData, // absent if no output varying data
    in vec4[N] patchVertexPositions,
    in InVaryingType[N] patchData, // absent if no vertex shader output
    TessCoordType tessCoord
);

When tessellation is enabled, two additional values can be set as the primitive type in the vertex modifier – quads and isolines. On the other hand, all connected primitive types (strip, loop, and fan types) are disabled. In both functions, you must make sure that the patch size N matches the number of vertices per primitive of the vertex primitive type. That is:

  • N = 1 for points
  • N = 2 for lines
  • N = 3 for triangles
  • N = 4 for lines_adjacency, quads, and isolines
  • N = 6 for triangles_adjacency

By default, the vertex primitive type dictates the tessellation patch type. The three types of tessellation patches are:

  • trianglesOuterLevelType = float[3], InnerLevelType = float, TessCoordType = vec3 (barycentric)
  • quadsOuterLevelType = float[4], InnerLevelType = vec2 (horizontal and vertical), TessCoordType = vec2 (unit square)
  • isolinesOuterLevelType = float[2], InnerLevelType = void (argument omitted), TessCoordType = vec2 (unit square)

A more advanced approach is to use the tessellation shaders in a similar fashion to a geometry shader, and from a given primitive type, produce a tessellated patch of any type. Although this isn't recommended unless you have a very thorough understanding of OpenGL tessellation shaders, the following variant of the modifier is available for setting the tessellation patch type separately from the vertex primitive type:

  • tessellation(<control function>, <evaluation function>, <patch type>, <spacing>, <winding>)

Example:

In the following example, the varying data type is vec2 – a texture coordinate. The control function sets the tessellation levels to a fixed value (e.g. provided as a parameter) and the evaluation function performs a very simple form of texture-based displacement mapping. Note how the output position and texture coordinate is interpolated using the barycentric tessellation coordinate.

void fixedTessControl(out float[3] outerLevels, out float innerLevel, in vec4[3] unused1, in vec2[3] unused2) {
    outerLevels[0] = fixedTessLevel;
    outerLevels[1] = fixedTessLevel;
    outerLevels[2] = fixedTessLevel;
    innerLevel = fixedTessLevel;
}
vec4 displacementTessEvaluation(out vec2 texCoord, in vec4[3] pos, in vec2[3] patchTexCoords, vec3 tessCoord) {
    vec4 vertexPos = tessCoord[0]*pos[0] + tessCoord[1]*pos[1] + tessCoord[2]*pos[2];
    texCoord = tessCoord[0]*patchTexCoords[0] + tessCoord[1]*patchTexCoords[1] + tessCoord[2]*patchTexCoords[2];
    vertexPos.xyz += texture(DisplacementMap, texCoord).xyz;
    return vertexPos;
}

Geometry shaders

A geometry shader is optional and if present, runs after the vertex shader and tessellation shaders and before the fragment shader. It inspects primitives generated by the previous shader stage and replaces them with a wholly new geometry. The geometry shader stage is enabled by the following modifier:

  • geometry(<function>, <emitted primitive type>, <maximum emitted vertices>)

A geometry shader may output a wholly different kind of geometry from the one it receives. Therefore, the emitted primitive type and maximum emitted vertices must be set. Only points, line_strip, or triangle_strip can be set as the emitted primitive type. However, the continuity of strips can be broken up at any point.

function is the body of the geometry shader that receives the vertex positions and varying data of a whole primitive (triangle, line, ...) as array arguments:

void geometryFunction(in vec4[N] vertexPositions, in VaryingType[N] inputData);
void geometryFunction(in vec4[N] vertexPositions); // if preceding shader stage has no output

You must make sure that N matches the number of vertices of the input primitive:

  • N = 1 for points and tessellation shader point_mode
  • N = 2 for lines, line_strip, line_loop, and tessellated isolines
  • N = 3 for triangles, triangle_strip, triangle_fan, and tessellated quads
  • N = 4 for lines_adjacency and line_strip_adjacency
  • N = 6 for triangles_adjacency and triangle_strip_adjacency

The mechanism for generating the new geometry is provided by the following functions, which are only available within the geometry shader:

void shadron_EmitVertex(vec4 emittedVertexPosition, OutVaryingType fragmentData);
void shadron_EmitVertex(vec4 emittedVertexPosition); // if no data for fragment shader
void shadron_EndPrimitive();

shadron_EmitVertex may be called at most maximum emitted vertices times. If it isn't called at all, the input vertices will be consumed and no output will be generated. shadron_EndPrimitive can be used to restart the strip, and e.g. produce a set of disconnected triangles instead.

Example:

The following geometry shader function operating with triangles shrinks each one slightly.

void shrinkTriangle(in vec4[3] vertexPositions, in FragmentDataType[3] fragmentData) {
    float intensity = 0.25;
    vec4 center = (vertexPositions[0]+vertexPositions[1]+vertexPositions[2])/3.0;
    shadron_EmitVertex(mix(vertexPositions[0], center, intensity), fragmentData[0]);
    shadron_EmitVertex(mix(vertexPositions[1], center, intensity), fragmentData[1]);
    shadron_EmitVertex(mix(vertexPositions[2], center, intensity), fragmentData[2]);
    shadron_EndPrimitive();
}