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:
- Vertex Shaders: Responsible for transforming vertex positions and passing attributes to the fragment shader.
- Fragment Shaders: Responsible for calculating the final color of each pixel.
- Geometry Shaders: Optional shaders that can generate or modify geometry.
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:
// 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:
// 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:
// 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:
// 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.
// 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
#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
#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:
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 texturehas_norm_text
: Flag for normal map texturehas_rough_text
: Flag for roughness texturehas_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:
- 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.
- Save Your Shaders: Save your vertex and fragment shaders as separate files with appropriate extensions (.vert, .frag, .geom).
- Replace the Core Shader: Use the RenderSystem's ChangeCoreShader methods to replace the default shader with your custom one.
// 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.
// 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:
Changes the core shader program using source code in memory.
Parameters:
vertexSource
- The vertex shader source codefragmentSource
- The fragment shader source code
Changes the core shader program using source code in memory, including a geometry shader.
Parameters:
vertexSource
- The vertex shader source codefragmentSource
- The fragment shader source codegeometrySource
- The geometry shader source code
Changes the core shader program by loading shader code from files, including a geometry shader.
Parameters:
vertexPath
- Path to the vertex shader filefragmentPath
- Path to the fragment shader filegeometryPath
- Path to the geometry shader file
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
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
uniform vec3 cam_pos; // Camera position in world space
Material 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
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
Lighting Uniforms
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
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
#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:
layout(location = 0) in vec3 aPos
- Vertex positionlayout(location = 1) in vec3 aNormal
- Vertex normallayout(location = 2) in vec2 aTexCoord
- Texture coordinateslayout(location = 3) in vec3 aTangent
- Tangent vector (for normal mapping)
Limitations
- When using custom shaders, you are responsible for implementing all lighting and material calculations.
- The engine currently supports a single active light at a time through the uniform
Light lights
. - Custom shaders must handle all required uniforms even if they don't use them.
- Changing shaders during runtime can have a performance impact, so it's best to limit shader switches.
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.