Shaders

Shaders are the core of any modern graphics engine, allowing fine control over the rendering process. In Raftel Engine, shaders are used to define how 3D content is processed and displayed on screen. The engine provides a robust shader system that allows developers to create custom visual effects while maintaining compatibility with the engine's rendering pipeline.

Key Features

  • Support for vertex, fragment, and geometry shaders
  • PBR (Physically Based Rendering) pipeline
  • Built-in forward rendering system
  • Support for shadow mapping (directional, point, and spot lights)
  • Ability to replace the core shader with custom ones
  • Predefined uniforms for common rendering tasks

Shader Architecture

Raftel Engine uses a forward rendering pipeline by default, with a core shader that handles common rendering tasks such as lighting, shadows, and material properties. While the engine handles many aspects of the rendering process internally, you can create and use your own custom shaders through the RenderSystem interface.

Shaders in Raftel Engine follow the GLSL (OpenGL Shading Language) syntax and structure. The engine supports:

Shader Creation

Raftel Engine provides several ways to create and use shaders in your application. The Shader and ShaderProgram classes are responsible for managing shader loading, compilation, and linking.

ShaderProgram Class

The ShaderProgram class is the main interface for working with shaders in Raftel. It allows loading, compiling, and linking vertex, fragment, geometry, and compute shaders. There are multiple ways to create a shader program:

Creating a ShaderProgram from Files

// Method 1: Using the constructor
Raftel::ShaderProgram myShader("shaders/vertex.vert", "shaders/fragment.frag");
                
// Method 2: Using the load method
Raftel::ShaderProgram myShader;
myShader.load("shaders/vertex.vert", "shaders/fragment.frag");
                
// With a geometry shader:
Raftel::ShaderProgram myShader("shaders/vertex.vert", "shaders/fragment.frag", "shaders/geometry.geom");
                
// With a compute shader:
Raftel::ShaderProgram computeShader("", "", "", "shaders/compute.comp");

Omitting Shader Stages

To omit a specific shader stage, simply pass an empty string ("") for that stage. For example, to create a program with only a fragment shader, you can do: Raftel::ShaderProgram fragmentOnly("", "shaders/effect.frag");

Loading Shaders from Memory

In addition to loading shaders from files, Raftel Engine allows loading shaders directly from source code in memory, which is useful for embedded or dynamically generated shaders:

Loading Shaders from Source Code

    // Method 1: From individual strings
    const char* vertexSource = "#version 460 core\n..."
    const char* fragmentSource = "#version 460 core\n..."
    Raftel::ShaderProgram myShader;
    myShader.loadFromMemory(vertexSource, fragmentSource);
    
    // Method 2: For very long shaders split into multiple strings
    std::vector<const char*> vertexSourceParts = { vertexPart1, vertexPart2 };
    std::vector<const char*> fragmentSourceParts = { fragmentPart1, fragmentPart2 };
    Raftel::ShaderProgram myShader;
    myShader.loadFromMemory(vertexSourceParts, fragmentSourceParts);
        

Error Checking

All shader loading functions return a boolean indicating whether the operation succeeded. In case of failure, you can obtain the error message using the getLastError() method:


    Raftel::ShaderProgram shader;
    if (!shader.load("vertex.vert", "fragment.frag")) {
        std::cerr << "Error loading shader: " << shader.getLastError() << std::endl;
    }

Using Shaders

Once the shader program is created, you can activate it, set uniforms, and deactivate it:

Basic Usage of a ShaderProgram
// Activate the shader
            myShader.use();
            
            // Set uniforms
            myShader.setUniform("u_color", glm::vec3(1.0f, 0.5f, 0.2f));
            myShader.setUniform("u_modelMatrix", modelMatrix);
            myShader.setUniform("u_viewMatrix", viewMatrix);
            myShader.setUniform("u_projectionMatrix", projMatrix);
            
            // Render objects...
            
            // Deactivate the shader when you're done
            myShader.unUse();

Available Uniforms

Raftel Engine provides methods to set various types of uniforms in shaders:

Basic Types

  • setUniform(name, int)
  • setUniform(name, float)

Vectors

  • setUniform(name, glm::vec3)
  • setUniform(name, glm::vec4)

Matrix

  • setUniform(name, glm::mat4)
  • setUniform(name, std::vector<glm::mat4>)

Uniform Optimization

To improve performance, Raftel Engine internally caches uniform locations. If you need to see which uniforms are active in a shader program, you can use the printActiveUniforms() method.

Low-Level Implementation with the Shader Class

Although you will usually work with the ShaderProgram class, Raftel Engine also provides a lower-level Shader class for advanced use cases where you need more detailed control over individual shaders:

Advanced Usage with the Shader Class

// Create individual shaders
Raftel::Shader vertexShader;
Raftel::Shader fragmentShader;

// Load from files
vertexShader.loadFromFile("custom.vert", GL_VERTEX_SHADER);
fragmentShader.loadFromFile("custom.frag", GL_FRAGMENT_SHADER);

// Or load from source code
vertexShader.loadFromSource(vertexCode, GL_VERTEX_SHADER);
fragmentShader.loadFromSource(fragmentCode, GL_FRAGMENT_SHADER);

// Create a program and attach the shaders
Raftel::ShaderProgram program;
program.attachShader(vertexShader);
program.attachShader(fragmentShader);

// Link the program
program.link();

Important

When working directly with the Shader class, it is the developer’s responsibility to ensure that all required shaders are attached before calling link(). Also note that the Shader class is intended primarily for internal use by ShaderProgram, and most users will not need to interact with it directly.

Using Custom Shaders

While Raftel Engine uses its own internal shaders for most rendering operations, you can replace the core shader with your own custom shader to achieve specific visual effects. This is done through the RenderSystem's ChangeCoreShader methods.

Changing the Core Shader
// Option 1: Load shaders from files
Raftel::RenderSystem::ChangeCoreShader("shaders/myVertexShader.vert", "shaders/myFragmentShader.frag");

// Option 2: Load shaders from files with a geometry shader
Raftel::RenderSystem::ChangeCoreShader("shaders/myVertexShader.vert", "shaders/myFragmentShader.frag", "shaders/myGeometryShader.geom");

// Option 3: Use shader code from memory
const char* vertexShaderCode = "..."; // Your vertex shader code
const char* fragmentShaderCode = "..."; // Your fragment shader code
Raftel::RenderSystem::ChangeCoreShader(vertexShaderCode, fragmentShaderCode);

// Option 4: Use shader code from memory with a geometry shader
const char* geometryShaderCode = "..."; // Your geometry shader code
Raftel::RenderSystem::ChangeCoreShader(vertexShaderCode, fragmentShaderCode, geometryShaderCode);

// Option 5: Reset to the default core shader
Raftel::RenderSystem::SetDefaultCoreShader();

Best Practice

When creating custom shaders, it's recommended to start with the engine's default shaders and modify them incrementally to ensure compatibility with the engine's rendering pipeline.

Required Shader Inputs and Outputs

For your custom shaders to work correctly with Raftel Engine, they must include specific inputs, outputs, and uniforms that the engine expects. Below are the required elements for your vertex and fragment shaders.

Vertex Shader Requirements

Vertex Shader Structure
#version 460 core
// Vertex attributes (inputs)
layout(location = 0) in vec3 aPos;       // Vertex position
layout(location = 1) in vec3 aNormal;    // Vertex normal
layout(location = 2) in vec2 aTexCoord;  // Texture coordinates
layout(location = 3) in vec3 aTangent;   // Tangent vector (for normal mapping)

// Outputs to fragment shader
out vec3 FragPos;              // World space position
out vec3 Normal;               // World space normal
out vec2 TexCoord;             // Texture coordinates
out vec4 FragPosLightSpace;    // Position in light space (for shadow mapping)

// Required uniforms
uniform mat4 model;            // Model matrix
uniform mat4 view;             // View matrix
uniform mat4 projection;       // Projection matrix
uniform mat4 lightSpaceMatrix; // Light space transformation matrix

// When implementing your main function, make sure to include these transformations:
// FragPos = vec3(model * vec4(aPos, 1.0));
// Normal = normalize(mat3(transpose(inverse(model))) * aNormal);
// TexCoord = aTexCoord;
// FragPosLightSpace = lightSpaceMatrix * vec4(FragPos, 1.0);
// gl_Position = projection * view * vec4(FragPos, 1.0);

Fragment Shader Requirements

Fragment Shader Structure and Required Uniforms
#version 460 core
// Outputs
out vec4 FragColor;  // Final color output

// Inputs from vertex shader
in vec3 FragPos;              // World space position
in vec3 Normal;               // World space normal
in vec2 TexCoord;             // Texture coordinates
in vec4 FragPosLightSpace;    // Position in light space (for shadow mapping)

// Material texture uniforms
layout(binding = 0) uniform sampler2D albedoTexture;    // Base color texture
uniform int has_alb_text;                              // Flag indicating if albedo texture exists

layout(binding = 1) uniform sampler2D normalTexture;    // Normal map texture
uniform int has_norm_text;                             // Flag indicating if normal map exists

layout(binding = 2) uniform sampler2D roughnessTexture; // Roughness texture
uniform int has_rough_text;                            // Flag indicating if roughness texture exists

layout(binding = 3) uniform sampler2D metallicTexture;  // Metallic texture 
uniform int has_met_text;                              // Flag indicating if metallic texture exists

// Shadow mapping uniforms
layout(binding = 10) uniform sampler2D shadowMap;       // Shadow map for directional/spot lights
layout(binding = 10) uniform samplerCube pointShadowMap; // Shadow cubemap for point lights
uniform float farPlane;                                // Far plane for point light shadows

// Material properties uniforms
uniform vec3 u_color;            // Base material color
uniform float u_shininess;       // Specular shininess factor
uniform float u_roughnessValue;  // Base roughness value
uniform float u_metallicValue;   // Base metallic value
uniform float u_fresnel;         // Fresnel effect factor
uniform float u_ssFactor;        // Subsurface scattering factor

// Camera uniform
uniform vec3 cam_pos;            // Camera position

// Ambient light uniforms
uniform int has_ambient_light;   // Flag indicating if ambient light exists
uniform vec3 ambient_color;      // Ambient light color

// Light structure uniform
struct Light {
    int type;                // Light type: 0 = directional, 1 = point, 2 = spot
    vec3 color;              // Light color
    vec3 position;           // Light position (for point/spot lights)
    vec3 direction;          // Light direction (for directional/spot lights)
    float intensity;         // Light intensity
    float range;             // Maximum range (for point/spot lights)
    float innerCone;         // Inner cone angle (for spot lights)
    float outerCone;         // Outer cone angle (for spot lights)
};
uniform Light lights;        // Light data

// Your main function should implement custom lighting and shading
// while ensuring to output the final color to FragColor

Important

The uniform names, input/output names, and binding points must match exactly as shown above for your shader to work correctly with the Raftel Engine rendering pipeline. If you need to add custom uniforms or inputs/outputs, you can do so alongside the required ones.

Shader Features

The Raftel Engine shader system supports a variety of rendering features that you can utilize in your custom shaders:

PBR Lighting

Physically Based Rendering with support for metallic-roughness workflow, allowing realistic material rendering.

Shadow Mapping

Support for shadows from directional, point, and spot lights with PCF filtering for smoother shadow edges.

Normal Mapping

Add surface detail without increasing geometry complexity by using normal maps.

Material System

Comprehensive material properties with support for albedo, roughness, metallic, and normal maps.

Fresnel Effect

Support for realistic angle-dependent reflections and edge highlighting.

Subsurface Scattering

Simulate light transport through translucent materials like skin, wax, or marble.

Light Types

Raftel Engine supports three types of lights that you can use in your shaders:

Directional Light

Light rays that are parallel and come from a specific direction, simulating distant light sources like the sun.
Light Type: 0

Point Light

Light that emanates from a single point in all directions, like a light bulb.
Light Type: 1

Spot Light

Light that emanates from a single point in a cone shape, like a flashlight.
Light Type: 2

Texture Binding Points

When using textures in your shaders, you need to adhere to the following binding points to ensure compatibility with the Raftel Engine material system:

Texture Binding Points
layout(binding = 0) uniform sampler2D albedoTexture;    // Base color texture
layout(binding = 1) uniform sampler2D normalTexture;    // Normal map texture
layout(binding = 2) uniform sampler2D roughnessTexture; // Roughness texture
layout(binding = 3) uniform sampler2D metallicTexture;  // Metallic texture
layout(binding = 10) uniform sampler2D shadowMap;       // Shadow map for directional/spot lights
layout(binding = 10) uniform samplerCube pointShadowMap; // Shadow cubemap for point lights

Texture Flag Variables

For each texture type, there is a corresponding flag variable that indicates whether the texture exists:

  • has_alb_text: Flag for albedo texture
  • has_norm_text: Flag for normal map texture
  • has_rough_text: Flag for roughness texture
  • has_met_text: Flag for metallic texture

Always check these flags before attempting to use the corresponding textures to handle cases where textures aren't provided.

Creating Custom Shaders

When creating custom shaders for Raftel Engine, follow these steps to ensure compatibility with the engine's rendering pipeline:

  1. Create Vertex and Fragment Shaders: Write your shader code following the requirements outlined above, ensuring that all required inputs, outputs, and uniforms are properly defined.
  2. Save Your Shaders: Save your vertex and fragment shaders as separate files with appropriate extensions (.vert, .frag, .geom).
  3. Replace the Core Shader: Use the RenderSystem's ChangeCoreShader methods to replace the default shader with your custom one.
Custom Shader Structure Requirements
// Vertex Shader: Must have these attributes and outputs
#version 460 core
layout(location = 0) in vec3 aPos;        // Required: Vertex position
layout(location = 1) in vec3 aNormal;     // Required: Vertex normal
layout(location = 2) in vec2 aTexCoord;   // Required: Texture coordinates
layout(location = 3) in vec3 aTangent;    // Required: Tangent vector

// Required outputs
out vec3 FragPos;              // World space position
out vec3 Normal;               // World space normal
out vec2 TexCoord;             // Texture coordinates
out vec4 FragPosLightSpace;    // Position in light space

// Required uniforms
uniform mat4 model;            // Model matrix
uniform mat4 view;             // View matrix
uniform mat4 projection;       // Projection matrix
uniform mat4 lightSpaceMatrix; // Light space transformation matrix

// Fragment Shader: Must have these inputs and uniforms
#version 460 core
out vec4 FragColor;            // Required: Final output color

// Required inputs (must match vertex shader outputs)
in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoord;
in vec4 FragPosLightSpace;

// Required texture uniforms with specific binding points
layout(binding = 0) uniform sampler2D albedoTexture;
uniform int has_alb_text;

// Plus all other required uniforms as shown in the previous section

Usage in Application

// In your application code:
// Load and use custom shader
Raftel::RenderSystem::ChangeCoreShader("shaders/custom.vert", "shaders/custom.frag");

Advanced Shader Techniques

Raftel Engine's shader system enables a variety of advanced rendering techniques. Here are some examples of what you can implement in your custom shaders:

PBR (Physically Based Rendering)

PBR is a rendering approach that simulates how light interacts with materials based on physical principles. Raftel Engine's default shaders already implement PBR, but you can customize these techniques in your own shaders.

Key PBR Components to Consider
// Reflectance at normal incidence (F0)
vec3 F0 = mix(vec3(0.04), albedo, metallic);

// Commonly used PBR functions:
// 1. Normal Distribution Function (GGX/Trowbridge-Reitz)
// 2. Geometry Function (Smith's method with GGX)
// 3. Fresnel Equation (Schlick's approximation)
// 4. BRDF (Bidirectional Reflectance Distribution Function)

Shadow Techniques

You can customize how shadows are rendered in your shaders. The engine supports several shadow mapping techniques.

Shadow Mapping Features

  • PCF (Percentage Closer Filtering): Soften shadow edges by sampling multiple points
  • Bias adjustment: Prevent shadow acne based on surface orientation
  • Cubemap shadows: For point lights that cast shadows in all directions

Using RenderSystem to Manage Shaders

The RenderSystem class provides several methods for managing and changing shaders in your application:

static void ChangeCoreShader(const char* vertexSource, const char* fragmentSource)

Changes the core shader program using source code in memory.

Parameters:

  • vertexSource - The vertex shader source code
  • fragmentSource - The fragment shader source code
static void ChangeCoreShader(const char* vertexSource, const char* fragmentSource, const char* geometrySource)

Changes the core shader program using source code in memory, including a geometry shader.

Parameters:

  • vertexSource - The vertex shader source code
  • fragmentSource - The fragment shader source code
  • geometrySource - The geometry shader source code
static void ChangeCoreShader(const std::string& vertexPath, const std::string& fragmentPath, const std::string& geometryPath)

Changes the core shader program by loading shader code from files, including a geometry shader.

Parameters:

  • vertexPath - Path to the vertex shader file
  • fragmentPath - Path to the fragment shader file
  • geometryPath - Path to the geometry shader file
static void SetDefaultCoreShader()

Resets the core shader to the default forward rendering shader provided by the engine.

Best Practices

Shader Organization

Keep your shader files organized in a dedicated "shaders" directory in your project.

Naming Conventions

Use consistent naming for shader files. For example: effect_name.vert, effect_name.frag, etc.

Performance

Be mindful of shader complexity. Complex fragment shaders can significantly impact performance, especially on mobile platforms.

Version Compatibility

Always specify the GLSL version at the top of your shader files. Raftel Engine uses GLSL 4.60 by default.

Error Handling

Always check for shader compilation and linking errors in your application logs when creating custom shaders.

Shader Reuse

Consider creating a library of reusable shader effects that you can mix and match for different visual styles.

Common Uniforms Reference

This section provides a quick reference of all the uniforms available in Raftel Engine shaders:

Transformation Uniforms

Matrices
uniform mat4 model;            // Model matrix for transforming from object to world space
uniform mat4 view;             // View matrix for transforming from world to view space
uniform mat4 projection;       // Projection matrix for transforming from view to clip space
uniform mat4 lightSpaceMatrix; // Matrix for transforming vertices to light space (for shadow mapping)

Camera Uniforms

Camera
uniform vec3 cam_pos;  // Camera position in world space

Material Uniforms

Material Properties
uniform vec3 u_color;            // Base material color
uniform float u_shininess;       // Specular shininess factor
uniform float u_roughnessValue;  // Base roughness value
uniform float u_metallicValue;   // Base metallic value
uniform float u_fresnel;         // Fresnel effect factor
uniform float u_ssFactor;        // Subsurface scattering factor

Texture Uniforms

Material Textures and Flags
layout(binding = 0) uniform sampler2D albedoTexture;    // Base color texture
uniform int has_alb_text;                              // Flag indicating if albedo texture exists

layout(binding = 1) uniform sampler2D normalTexture;    // Normal map texture
uniform int has_norm_text;                             // Flag indicating if normal map exists

layout(binding = 2) uniform sampler2D roughnessTexture; // Roughness texture
uniform int has_rough_text;                            // Flag indicating if roughness texture exists

layout(binding = 3) uniform sampler2D metallicTexture;  // Metallic texture
uniform int has_met_text;                              // Flag indicating if metallic texture exists

Lighting Uniforms

Light Structure and Ambient Light
struct Light {
    int type;                // Light type: 0 = directional, 1 = point, 2 = spot
    vec3 color;              // Light color
    vec3 position;           // Light position (for point/spot lights)
    vec3 direction;          // Light direction (for directional/spot lights)
    float intensity;         // Light intensity
    float range;             // Maximum range (for point/spot lights)
    float innerCone;         // Inner cone angle (for spot lights)
    float outerCone;         // Outer cone angle (for spot lights)
};
uniform Light lights;        // Light data

uniform int has_ambient_light;   // Flag indicating if ambient light exists
uniform vec3 ambient_color;      // Ambient light color

Shadow Mapping Uniforms

Shadow Maps
layout(binding = 10) uniform sampler2D shadowMap;       // Shadow map for directional/spot lights
layout(binding = 10) uniform samplerCube pointShadowMap; // Shadow cubemap for point lights
uniform float farPlane;                                  // Far plane for point light shadows

Example: Creating and Using a Custom Shader

Application Code
#include "raftel/systems.hpp"
#include "raftel/window.hpp"

int main() {
    // Initialize engine systems
    auto windowSystem = Raftel::WindowSystem::make();
    auto window = Raftel::Window::make("Raftel Custom Shader Demo", *windowSystem);
    
    // Initialize render system
    Raftel::RenderSystem::Initialize();
    
    // Load custom shader
    Raftel::RenderSystem::ChangeCoreShader("shaders/custom.vert", "shaders/custom.frag");
    
    // Create scene and entities
    // ...
    
    // Main render loop
    while (!window->ShouldClose()) {
        // Update and render scene
        // ...
        
        // Switch back to default shader if needed
        // Raftel::RenderSystem::SetDefaultCoreShader();
        
        window->swapBuffers();
    }
    
    // Cleanup
    Raftel::RenderSystem::Shutdown();
    
    return 0;
}

Related Topics

Materials

Learn about the material system that integrates with shaders to define surface properties.

Lighting

Explore how light sources interact with shaders for realistic illumination.

RenderSystem

Understand the rendering pipeline and how it manages shaders and rendering states.

Drawing a Triangle

Basic tutorial that introduces shader usage in Raftel Engine.

Technical Specifications

Supported GLSL Version

Raftel Engine's shader system is built on OpenGL and supports GLSL version 4.60 as a standard. Always use the #version 460 core directive at the beginning of your shader files for maximum compatibility.

Vertex Attribute Locations

When creating custom vertex shaders, you must use these specific attribute locations:

Limitations

Conclusion

The Raftel Engine shader system provides a powerful way to customize the rendering of your 3D scenes. By understanding the shader requirements and using the RenderSystem's shader management functions, you can create custom visual effects while maintaining compatibility with the engine's rendering pipeline.

Remember that while custom shaders give you more control over the rendering process, they also require a deeper understanding of GLSL and rendering techniques. Start with simple modifications to the engine's default shaders before attempting more complex effects.