Shader
A linked shader program, built from vertex and fragment source. One native call does the whole compile-and-link, so a failure comes back as a log rather than as five separate error checks. Check IsOk() before drawing; GetLog() carries the compiler's message when it is false.
Example
shader := Shader->New(vertex_source, fragment_source);
if(<>shader->IsOk()) {
shader->GetLog()->ErrorLine();
return;
};
shader->Use();Operations
- New
- AddLitVaryings
- BillboardInstanced
- DepthOnly
- Flat
- Free
- FromFiles
- GetLog
- HasUniform
- IsOk
- Lit
- LitFragmentSource
- LitShader
- LitShadowed
- LitTextured
- LitTexturedInstanced
- LitTexturedNormalMapped
- LitTexturedNormalMappedPointShadowed
- LitTexturedNormalMappedShadowed
- LitTexturedPointShadowed
- LitTexturedShadowed
- Normals
- SetFloat
- SetInt
- SetMatrix4
- SetVec3
- SetVec4
- Sky
- Textured
- TexturedFog
- TexturedTinted
- Use
AddLitVaryings # function
The outputs every lit vertex shader must write, declared in ONE place. Both lit vertex sources -- the ordinary one and the instanced one -- feed the same fragment shader, so they have to agree about this list exactly. They did not: world_position was added here for point lights and specular and the instanced source was not updated, which macOS's linker rejected outright while Windows and Linux linked it happily and left the value undefined. So point lights and specular on instanced geometry were reading garbage on two platforms out of three and only the strictest one said so. Declared once, and asserted by a test that reads a point light through the instanced shader rather than only checking that it compiles.
function : AddLitVaryings(source:ShaderSource, shadowed:Bool) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| source | ShaderSource | the shader being built |
| shadowed | Bool | whether the shadow varying is needed too |
BillboardInstanced # function
Camera-facing quads, drawn many at a time. A billboard is a quad that always turns to face the viewer, and it is what a particle, a spark, a sprite enemy, a distant tree and a floating label all are. None of them had any route here: every shader in this bundle puts geometry where its model matrix says, and a billboard is defined by refusing to. Drive it with a PropBatch over Mesh->Quad, which is why this is the instanced form and there is no single-billboard variant -- one sprite is a batch of one, and a thousand are still one draw call. sparks := PropBatch->New(Mesh->Quad(0.5), Shader->BillboardInstanced()); sparks->SetCamera(camera); sparks->SetAdditive(true); sparks->SetMaterial(Material->New(spark_texture)); ## How it faces you The instance matrix supplies the CENTRE and the SIZE and its rotation is ignored: the quad's corners are rebuilt in world space from the camera's own right and up vectors, which are the first two ROWS of the view matrix's upper 3x3. So SetScale(w, h, 1.0) on an instance's transform sets the sprite's width and height, and turning it does nothing -- deliberately. This is why SetCamera exists on PropBatch. The batch is handed a view_projection by Scene and that alone cannot be decomposed back into the camera's basis, so the view matrix comes separately. Uniforms: "view_projection", "view", the sampler "tex", "tint" and "opacity" -- the last two being what Material writes, so a material works here exactly as it does on a lit prop. Fragments below 2% alpha are DISCARDED rather than blended, so a sprite sheet's transparent border does not write depth and punch holes in whatever is drawn after it.
function : BillboardInstanced() ~ ShaderReturn
| Type | Description |
|---|---|
| Shader | the program; check IsOk() |
DepthOnly # function
Writes depth and nothing else: the shadow map's own pass. No colour output at all, because there is no colour buffer attached to a depth-only target to write one into. Its only uniform is "mvp", which for this pass is the LIGHT's projection times view times the model matrix -- ShadowMap->DrawCaster does that arithmetic.
function : DepthOnly() ~ ShaderReturn
| Type | Description |
|---|---|
| Shader | the program; check IsOk() |
Flat # function
A shader that fills with one colour, set through the "color" uniform as three floats. The uniform every built-in shader here shares is "mvp", a mat4. Set it with SetMatrix4 and nothing else is required.
function : Flat() ~ ShaderReturn
| Type | Description |
|---|---|
| Shader | the program; check IsOk() |
Free #
Release the program. GL objects are invisible to the collector, so this is not optional. Call it while the GL context is still current -- see the shutdown order on the GL class. After GLContext->Delete there is no context for the underlying glDeleteProgram to act on.
method : public : Free() ~ NilFromFiles # function
Compile a program from two files. Worth it for anything beyond a few lines: a driver's error log reports GLSL line numbers, and those mean nothing when the source was assembled from string literals scattered through a method.
function : FromFiles(vertex_file:String, fragment_file:String) ~ ShaderParameters
| Name | Type | Description |
|---|---|---|
| vertex_file | String | path to the vertex shader |
| fragment_file | String | path to the fragment shader |
Return
| Type | Description |
|---|---|
| Shader | the program; IsOk() is false when either file could not be read, and GetLog says which |
GetLog #
method : public : GetLog() ~ StringReturn
| Type | Description |
|---|---|
| String | the compile/link log; empty when IsOk() is true |
HasUniform #
Whether this program declares a uniform of this name. Ask before setting one that may not be there. A missing uniform is reported through GL->GetLastError, which is right for a typo and wrong for a deliberate "set it only if the shader wants it" -- Scene uses this to hand a model matrix to lit shaders without filing a diagnostic every frame for the unlit ones. Note GLSL removes uniforms it can prove are unused, so a declared but unreferenced uniform answers false. That is not a bug: there is nothing to set.
method : public : HasUniform(name:String) ~ BoolParameters
| Name | Type | Description |
|---|---|---|
| name | String | the uniform's name |
Return
| Type | Description |
|---|---|
| Bool | true when it can be set |
IsOk #
method : public : IsOk() ~ BoolReturn
| Type | Description |
|---|---|
| Bool | true when the program compiled and linked |
Lit # function
Lit by one directional light, with a flat colour instead of a texture. Same uniforms as LitTextured, but "color" (a vec3) in place of "tex".
function : Lit() ~ ShaderReturn
| Type | Description |
|---|---|
| Shader | the program; check IsOk() |
LitFragmentSource # function
The lit fragment program, optionally sampling a tangent-space normal map. ## Why there is no tangent vertex attribute Tangent-space normal mapping needs a tangent frame. The usual way is a fourth vertex attribute, which would change the vertex layout, the native upload path, and every mesh already built -- including OBJ files, which mostly do not carry tangents anyway. This derives the frame in the fragment shader from screen-space derivatives of the position and the texture coordinate instead. It costs a few instructions per pixel and it works with every mesh that already exists, unchanged. The trade is real: the frame is per-triangle rather than smoothly interpolated, so a very low-poly curved surface shows faceting that a vertex tangent would not. For dressing walls, floors and props -- what this library is for -- that is not visible.
function : LitFragmentSource(textured:Bool, shadowed:Bool, point_shadowed:Bool, normal_mapped:Bool) ~ StringParameters
| Name | Type | Description |
|---|---|---|
| textured | Bool | sample an albedo texture rather than a flat colour |
| shadowed | Bool | sample a directional shadow map |
| point_shadowed | Bool | sample a point light's cube shadow |
| normal_mapped | Bool | perturb the normal with a tangent-space map |
Return
| Type | Description |
|---|---|
| String | the fragment source |
LitShader # function
Build a lit program and give its opacity a usable value. A GLSL uniform starts at ZERO, and `opacity` is declared and live in every lit fragment shader. So a program using one of these presets WITHOUT a Material or a Scene -- which is what three of the shipped examples do -- has alpha 0. It looks right only while blending is off; the moment anything calls GL->BeginTransparency the geometry vanishes, with no GL error to explain it.
function : LitShader() ~ ShaderParameters
| Name | Type | Description |
|---|---|---|
LitShadowed # function
Lit and shadowed, with a flat colour instead of a texture.
function : LitShadowed() ~ ShaderReturn
| Type | Description |
|---|---|
| Shader | the program; check IsOk() |
LitTextured # function
Textured and lit by one directional light. Uniforms: "mvp" and "model" (Scene sets both), the sampler "tex", and the three a Light writes -- "light_direction", "light_color" and "ambient". Optionally "fog_distance": set it to darken with distance, leave it alone for no fog. That is here rather than in a separate preset because Lit times Textured times Fogged would be eight programs for the sake of one multiply. ## The normal matrix, and why there is no Matrix3 here A surface normal cannot be transformed by the model matrix. Under non-uniform scale that tilts it off the surface, and the shading goes subtly wrong in a way that reads as a modelling mistake. The correct transform is the inverse transpose of the model matrix. Computing that would normally mean a Matrix3 type, a glUniformMatrix3fv binding and a per-object CPU inverse. GLSL has had `inverse` and `transpose` since 1.40, and this targets 3.30, so the shader does it instead -- one line, no new API, and nothing for a caller to forget.
function : LitTextured() ~ ShaderReturn
| Type | Description |
|---|---|
| Shader | the program; check IsOk() |
LitTexturedInstanced # function
Textured and lit, taking its model matrix per INSTANCE rather than from a uniform. Use with Mesh->SetInstanceMatrices and DrawInstanced. Reads the matrix from attribute locations 3 to 6, and has no "model" uniform at all -- so Scene, which sets one, cannot drive this. That is the trade instancing makes: the placement stops being something the CPU sends per object and becomes part of the vertex stream.
function : LitTexturedInstanced() ~ ShaderReturn
| Type | Description |
|---|---|
| Shader | the program; check IsOk() |
LitTexturedNormalMapped # function
Textured and lit, with a tangent-space normal map. Adds "normal_map" (texture unit 3) and "has_normal_map". Material sets both, so a material with no map drawn by this shader lights by its geometric normal exactly as LitTextured would. wall := Material->New(stone); wall->SetNormalMap(stone_bumps);
function : LitTexturedNormalMapped() ~ ShaderReturn
| Type | Description |
|---|---|
| Shader | a new program |
LitTexturedNormalMappedPointShadowed # function
Textured, lit, shadowed by a POINT light's cube map, and normal mapped. The combination the lantern demo wants: a carried light, shadows in every direction, and stone that is not flat.
function : LitTexturedNormalMappedPointShadowed() ~ ShaderReturn
| Type | Description |
|---|---|
| Shader | a new program |
LitTexturedNormalMappedShadowed # function
Textured, lit, shadowed by a directional light, and normal mapped.
function : LitTexturedNormalMappedShadowed() ~ ShaderReturn
| Type | Description |
|---|---|
| Shader | a new program |
LitTexturedPointShadowed # function
Textured and lit, with shadows from a POINT light's cube map. Adds "shadow_cube", "point_light_position", "point_light_range" and "point_bias" to LitTextured's uniforms, all written by PointShadow->ApplyTo. Note this is a separate program from LitTexturedShadowed rather than an option on it: a samplerCube and a sampler2D are different types, and a shader carrying both would pay for a cube map lookup in scenes that have no point light. Pick the one the scene needs.
function : LitTexturedPointShadowed() ~ ShaderReturn
| Type | Description |
|---|---|
| Shader | the program; check IsOk() |
LitTexturedShadowed # function
Textured, lit, and shadowed. Use it with a ShadowMap. Adds "light_space", "shadow_map", "shadow_bias" and "shadow_texel" to LitTextured's uniforms, all of which ShadowMap->ApplyTo writes -- so a program sets none of them by hand.
function : LitTexturedShadowed() ~ ShaderReturn
| Type | Description |
|---|---|
| Shader | the program; check IsOk() |
New # constructor
Compile and link a program.
New(vertex_source:String, fragment_source:String)Parameters
| Name | Type | Description |
|---|---|---|
| vertex_source | String | GLSL vertex shader source, starting with "#version 330 core" |
| fragment_source | String | GLSL fragment shader source |
Normals # function
Draws each surface's normal as a colour: +X red, +Y green, +Z blue. A diagnostic, and the one worth reaching for first when geometry looks wrong. A face whose normal points the wrong way, a mesh whose attributes landed in the wrong slots, and a transform that has quietly mirrored something are all invisible under a texture and obvious here.
function : Normals() ~ ShaderReturn
| Type | Description |
|---|---|
| Shader | the program; check IsOk() |
SetFloat #
Set a float uniform. Makes this program current -- see SetMatrix4.
method : public : SetFloat(name:String, value:Float) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| name | String | the uniform's name in the shader |
| value | Float | the value |
SetInt #
Set an int uniform. Also how a sampler is pointed at a texture unit. Makes this program current -- see SetMatrix4.
method : public : SetInt(name:String, value:Int) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| name | String | the uniform's name in the shader |
| value | Int | the value |
SetMatrix4 #
Set a mat4 uniform. SETTING A UNIFORM MAKES THIS PROGRAM CURRENT, as if Use had been called. That is deliberate rather than incidental: glUniform* writes to whatever program is bound, not to the one the name was looked up in, so a setter that did not bind would silently write another shader's uniforms whenever two were alive. The consequence to know is that setting uniforms across several shaders leaves the last one bound -- so call Use immediately before Draw, not once at startup.
method : public : SetMatrix4(name:String, matrix:Float[]) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| name | String | the uniform's name in the shader |
| matrix | Float | 16 floats in column-major order, as Matrix4 produces |
SetVec3 #
Set a vec3 uniform: a colour, a direction, a position. Makes this program current -- see SetMatrix4.
method : public : SetVec3(name:String, x:Float, y:Float, z:Float) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| name | String | the uniform's name in the shader |
| x | Float | first component |
| y | Float | second component |
| z | Float | third component |
SetVec3 #
Set a vec3 uniform from a Vector3.
method : public : SetVec3(name:String, value:Vector3) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| name | String | the uniform's name in the shader |
| value | Vector3 | the vector |
SetVec4 #
Set a vec4 uniform: four floats packed into one, which is how a light's kind and cone travel without spending three more uniform arrays. Makes this program current -- see SetMatrix4.
method : public : SetVec4(name:String, x:Float, y:Float, z:Float, w:Float) ~ NilParameters
| Name | Type | Description |
|---|---|---|
| name | String | the uniform's name in the shader |
| x | Float | first component |
| y | Float | second component |
| z | Float | third component |
| w | Float | fourth component |
Sky # function
The sky: a vertical gradient with a sun disc, evaluated per pixel from the view direction. Uniforms: "view_projection" (the camera's rotation only -- Skybox strips the translation), "horizon", "zenith", "sun_color", "sun_direction", "sun_size" and "has_sun". gl_Position uses xyww rather than xyzw so the depth after the perspective divide is exactly 1.0 -- the far plane. With GL_LEQUAL that would let the sky draw where nothing else has; Skybox turns depth WRITES off instead, which achieves the same thing without depending on the depth function the caller happens to have set.
function : Sky() ~ ShaderReturn
| Type | Description |
|---|---|
| Shader | a new sky program |
Textured # function
A shader that samples a texture. Uniforms: "mvp" and the sampler "tex". Reads texture coordinates from LOCATION 2, matching every Mesh primitive -- location 1 is the normal. Bind a texture to unit 0 and call SetInt("tex", 0).
function : Textured() ~ ShaderReturn
| Type | Description |
|---|---|
| Shader | the program; check IsOk() |
TexturedFog # function
Textured, darkening with distance. Without lighting, a textured 3D scene reads flat -- every surface is the same brightness however far away it is, so the eye cannot tell depth. Fog is the cheapest fix and both 3D examples had hand-rolled their own version of it. Uniforms: "mvp", "tex", and "fog_distance" if the default of 40 units is wrong for your scene.
function : TexturedFog() ~ ShaderReturn
| Type | Description |
|---|---|
| Shader | the program; check IsOk() |
TexturedTinted # function
Textured, multiplied by a flat colour. What a HUD needs: one white texture drawn as any colour, and glyphs -- whose shape lives in their alpha -- drawn as any colour without re-rasterising them. Uniforms: "mvp", "tex", "overlay_tint" (a vec4, default opaque white).
function : TexturedTinted() ~ ShaderReturn
| Type | Description |
|---|---|
| Shader | the program; check IsOk() |