Skip to main content

Overview

Atlas Engine provides a flexible shader system that supports custom GLSL shaders compiled for multiple graphics backends. The engine automatically translates shaders for OpenGL, Vulkan, and Metal through the Opal abstraction layer.

Shader Architecture

The shader system consists of several components:
  • VertexShader: Transforms vertex positions and passes data to fragment shaders
  • FragmentShader: Computes per-pixel colors and lighting
  • GeometryShader: Optional stage for generating primitives
  • TessellationShader: Optional stages for adaptive mesh refinement
  • ShaderProgram: Links shader stages into a complete rendering pipeline

Creating Custom Shaders

Vertex Shader Example

// Create a custom vertex shader from GLSL source
const char* vertSource = R"(
    #version 410 core
    layout(location = 0) in vec3 aPos;
    layout(location = 1) in vec3 aColor;
    out vec3 fragColor;
    uniform mat4 model;
    uniform mat4 view;
    uniform mat4 projection;
    void main() {
        gl_Position = projection * view * model * vec4(aPos, 1.0);
        fragColor = aColor;
    }
)";

VertexShader vertShader = VertexShader::fromSource(vertSource);
vertShader.compile();

Fragment Shader Example

// Create a custom fragment shader from GLSL source
const char* fragSource = R"(
    #version 410 core
    in vec3 fragColor;
    out vec4 FragColor;
    void main() {
        FragColor = vec4(fragColor, 1.0);
    }
)";

FragmentShader fragShader = FragmentShader::fromSource(fragSource);
fragShader.compile();

Complete Shader Program

// Link vertex and fragment shaders into a program
ShaderProgram program;
program.vertexShader = vertShader;
program.fragmentShader = fragShader;
program.compile();

// Set uniforms
program.setUniform3f("lightPosition", 10.0f, 5.0f, -3.0f);
program.setUniform1i("useTexture", 1);
program.setUniformMat4f("model", modelMatrix);

Using Default Shaders

Atlas provides many built-in shaders for common rendering tasks:
// Use default shaders
VertexShader defaultVert = VertexShader::fromDefaultShader(
    AtlasVertexShader::Main);
FragmentShader defaultFrag = FragmentShader::fromDefaultShader(
    AtlasFragmentShader::Main);

// Or create a complete default program
ShaderProgram defaultProgram = ShaderProgram::fromDefaultShaders(
    AtlasVertexShader::Main, 
    AtlasFragmentShader::Main);

Available Default Vertex Shaders

Outputs solid magenta color for debugging visibility.
Renders solid colors with per-vertex color attributes.
Full PBR shader supporting lighting, textures, normals, and materials.
Basic textured object rendering.
Cubemap sampling for environment rendering.
Efficient particle system rendering.
Tessellated terrain with displacement mapping.
Volumetric light scattering effects.

Available Default Fragment Shaders

PBR shading with support for:
  • Multiple light types (directional, point, spot, area)
  • Shadow mapping
  • Normal mapping and parallax occlusion
  • Metallic/roughness workflow
  • Image-based lighting (IBL)
  • ACES tone mapping
G-buffer output for deferred rendering pipeline.
Screen-space ambient occlusion calculation.
Multi-pass blur for post-processing.
Bloom texture chain processing.

Shader Capabilities

Shaders can declare capabilities that inform the rendering pipeline:
vertShader.capabilities = {
    ShaderCapability::Lighting,
    ShaderCapability::Textures,
    ShaderCapability::Shadows,
    ShaderCapability::Material
};

Available Capabilities

  • Lighting - Handles lighting calculations
  • Textures - Supports texture mapping
  • Shadows - Implements shadow mapping
  • EnvironmentMapping - Cube map reflections
  • IBL - Image-based lighting
  • Deferred - Deferred rendering G-buffer
  • Material - Material property support
  • Instances - Instanced rendering
  • Environment - Fog and rim lighting

Advanced Features

Tessellation Shaders

// Create terrain tessellation shaders
TessellationShader controlShader = 
    TessellationShader::fromDefaultShader(
        AtlasTessellationShader::TerrainControl);
        
TessellationShader evalShader = 
    TessellationShader::fromDefaultShader(
        AtlasTessellationShader::TerrainEvaluation);

ShaderProgram terrainProgram = ShaderProgram::fromDefaultShaders(
    AtlasVertexShader::Terrain,
    AtlasFragmentShader::Terrain,
    GeometryShader(),
    {controlShader, evalShader}
);

Geometry Shaders

// For point light shadow mapping (cube map generation)
GeometryShader geomShader = GeometryShader::fromDefaultShader(
    AtlasGeometryShader::PointLightShadow);

ShaderProgram shadowProgram = ShaderProgram::fromDefaultShaders(
    AtlasVertexShader::PointLightShadow,
    AtlasFragmentShader::PointLightShadow,
    geomShader
);

Cross-Platform Compilation

Atlas automatically compiles shaders for the active graphics backend:
1

OpenGL

GLSL shaders are compiled directly using OpenGL shader compilation
2

Vulkan

GLSL is compiled to SPIR-V bytecode for Vulkan pipelines
3

Metal

GLSL is translated to Metal Shading Language (MSL) for macOS/iOS
The engine handles all backend-specific details through the Opal abstraction layer:
// The same shader code works across all backends
ShaderProgram program = ShaderProgram::fromDefaultShaders(
    AtlasVertexShader::Main,
    AtlasFragmentShader::Main
);
program.compile(); // Automatically uses correct backend

Shader Caching

The engine caches compiled shaders to avoid recompilation:
// Static caches prevent duplicate compilation
std::map<AtlasVertexShader, VertexShader> VertexShader::vertexShaderCache;
std::map<AtlasFragmentShader, FragmentShader> FragmentShader::fragmentShaderCache;

std::map<std::pair<AtlasVertexShader, AtlasFragmentShader>, ShaderProgram> 
    ShaderProgram::shaderCache;

Uniform Management

Set shader uniforms with type-safe methods:
// Scalar uniforms
program.setUniform1f("time", currentTime);
program.setUniform1i("textureSlot", 0);
program.setUniformBool("enableLighting", true);

// Vector uniforms
program.setUniform2f("resolution", width, height);
program.setUniform3f("cameraPos", pos.x, pos.y, pos.z);
program.setUniform4f("color", r, g, b, a);

// Matrix uniforms
program.setUniformMat4f("projection", projectionMatrix);
program.setUniformMat4f("view", viewMatrix);

Real-World Example: Main Fragment Shader

The built-in Main fragment shader demonstrates advanced features:
  • PBR lighting with GGX distribution
  • Multiple light types (directional, point, spot, area)
  • Cascaded shadow mapping with PCF filtering
  • Normal mapping and parallax occlusion mapping
  • Metallic-roughness material workflow
  • Image-based lighting with HDR environment maps
  • Rim lighting effects
  • ACES tone mapping
Source locations:
  • OpenGL: shaders/opengl/main.frag (shader.h:278)
  • Vulkan: shaders/vulkan/main.frag (shader.h:278)
  • Metal: shaders/metal/main.frag.metal (shader.h:278)

Best Practices

Use Default Shaders

Start with built-in shaders for common tasks before writing custom ones

Declare Capabilities

Set shader capabilities to enable pipeline optimizations

Cache Aggressively

Reuse ShaderProgram instances to avoid recompilation

Test All Backends

Verify custom shaders work on OpenGL, Vulkan, and Metal
Shader compilation happens through the Opal abstraction layer, which automatically handles backend-specific details like descriptor sets, uniform buffers, and resource bindings.

Build docs developers (and LLMs) love