Skip to content

raylib generic uber shader and custom shaders

Anton Curmanschii edited this page Mar 11, 2024 · 14 revisions

Dealing with custom shaders and making them generic is not an easy task. There are many things to consider for a shader because, after all, shaders are responsible for processing all the data sent to the GPU (mesh, materials, textures, lighting) to generate the final frame.

Finding a unified generic shader to deal with all kinds of stuff is very complicated so, after analyzing some of the big engines out there, I decided to go for a custom uber-shader-based solution.

By default, raylib's shader struct is defined as:

typedef struct Shader {
    unsigned int id;   // Shader program id
    int *locs;         // Shader locations array (MAX_SHADER_LOCATIONS)
} Shader;

This struct provides an array to store shader locations, those locations can be accessed by position using predefined enum values for convenience:

// Shader location point type
typedef enum {
    SHADER_LOC_VERTEX_POSITION = 0,
    SHADER_LOC_VERTEX_TEXCOORD01,
    SHADER_LOC_VERTEX_TEXCOORD02,
    SHADER_LOC_VERTEX_NORMAL,
    SHADER_LOC_VERTEX_TANGENT,
    SHADER_LOC_VERTEX_COLOR,
    SHADER_LOC_MATRIX_MVP,
    SHADER_LOC_MATRIX_MODEL,
    SHADER_LOC_MATRIX_VIEW,
    SHADER_LOC_MATRIX_PROJECTION,
    SHADER_LOC_VECTOR_VIEW,
    SHADER_LOC_COLOR_DIFFUSE,
    SHADER_LOC_COLOR_SPECULAR,
    SHADER_LOC_COLOR_AMBIENT,
    SHADER_LOC_MAP_ALBEDO,          // SHADER_LOC_MAP_DIFFUSE
    SHADER_LOC_MAP_METALNESS,       // SHADER_LOC_MAP_SPECULAR
    SHADER_LOC_MAP_NORMAL,
    SHADER_LOC_MAP_ROUGHNESS,
    SHADER_LOC_MAP_OCCUSION,
    SHADER_LOC_MAP_EMISSION,
    SHADER_LOC_MAP_HEIGHT,
    SHADER_LOC_MAP_CUBEMAP,
    SHADER_LOC_MAP_IRRADIANCE,
    SHADER_LOC_MAP_PREFILTER,
    SHADER_LOC_MAP_BRDF
} ShaderLocationIndex;

#define SHADER_LOC_MAP_DIFFUSE      SHADER_LOC_MAP_ALBEDO
#define SHADER_LOC_MAP_SPECULAR     SHADER_LOC_MAP_METALNESS

When loading a shader, raylib tries to find some default attributes and uniforms locations:

#define RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION     "vertexPosition"    // Binded by default to shader location: 0
#define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD     "vertexTexCoord"    // Binded by default to shader location: 1
#define RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL       "vertexNormal"      // Binded by default to shader location: 2
#define RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR        "vertexColor"       // Binded by default to shader location: 3
#define RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT      "vertexTangent"     // Binded by default to shader location: 4
#define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2    "vertexTexCoord2"   // Binded by default to shader location: 5

#define RL_DEFAULT_SHADER_UNIFORM_NAME_MVP         "mvp"               // model-view-projection matrix
#define RL_DEFAULT_SHADER_UNIFORM_NAME_VIEW        "matView"           // view matrix
#define RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION  "matProjection"     // projection matrix
#define RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL       "matModel"          // model matrix
#define RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL      "matNormal"         // normal matrix (transpose(inverse(matModelView))
#define RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR       "colDiffuse"        // color diffuse (base tint color, multiplied by texture color)
#define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0  "texture0"          // texture0 (texture slot active 0)
#define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE1  "texture1"          // texture1 (texture slot active 1)
#define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE2  "texture2"          // texture2 (texture slot active 2)

The uniform locations belong to VertexShader or FragmentShader:

uniform mat4 mvp;             // VS: ModelViewProjection matrix
uniform mat4 matView;         // VS: View matrix
uniform mat4 matProjection;   // VS: Projection matrix
uniform mat4 matModel;        // VS: Model matrix
uniform mat4 matNormal;       // VS: Normal matrix

uniform vec4 colDiffuse;      // FS: Diffuse color
uniform sampler2D texture0;   // FS: GL_TEXTURE0
uniform sampler2D texture1;   // FS: GL_TEXTURE1
uniform sampler2D texture2;   // FS: GL_TEXTURE2

Those are the attributes/uniform names used by the internal default shader. It's recommended to use that naming convention on custom shaders as well to make sure everything will be automatically set up on LoadShader(). However, some of the default attribute names could be re-defined in config.h in case it was necessary.

Shaders are also directly related to the Material struct:

// Material type (generic)
typedef struct Material {
    Shader shader;          // Material shader
    MaterialMap *maps;      // Material maps array (MAX_MATERIAL_MAPS)
    float *params;          // Material generic parameters (if required)
} Material;

By default, Material supports a number of maps (texture and properties) that can be accessed for convenience using the provided values:

// Material map type
typedef enum {
    MATERIAL_MAP_ALBEDO    = 0,       // MATERIAL_MAP_DIFFUSE
    MATERIAL_MAP_METALNESS = 1,       // MATERIAL_MAP_SPECULAR
    MATERIAL_MAP_NORMAL    = 2,
    MATERIAL_MAP_ROUGHNESS = 3,
    MATERIAL_MAP_OCCLUSION,
    MATERIAL_MAP_EMISSION,
    MATERIAL_MAP_HEIGHT,
    MATERIAL_MAP_CUBEMAP,             // NOTE: Uses GL_TEXTURE_CUBE_MAP
    MATERIAL_MAP_IRRADIANCE,          // NOTE: Uses GL_TEXTURE_CUBE_MAP
    MATERIAL_MAP_PREFILTER,           // NOTE: Uses GL_TEXTURE_CUBE_MAP
    MATERIAL_MAP_BRDF
} TexmapIndex;

#define MATERIAL_MAP_DIFFUSE      MATERIAL_MAP_ALBEDO
#define MATERIAL_MAP_SPECULAR     MATERIAL_MAP_METALNESS

When drawing, maps may be internally bound depending on the availability:

// Default material loading example
Material material = LoadMaterialDefault();                      // Default shader assigned to material
material.maps[MATERIAL_MAP_DIFFUSE].texture = LoadTexture("tex_diffuse.png");    // texture unit 0 activated (available in material shader)
material.maps[MATERIAL_MAP_SPECULAR].texture = LoadTexture("tex_specular.png");  // texture unit 1 activated (available in material shader)

User can load any custom shader using provided Material and Shader structs:

Material material = { 0 };     // Empty material

material.shader = LoadShader("custom_shader.vs", "custom_shader.fs");

// Set up location points in case names are not predefined ones or more locations are required
// Use: GetShaderLocation() and SetShaderValue*() functions

material.maps[0].texture = LoadTexture("tex_albedo.png");
material.maps[1].texture = LoadTexture("tex_metalness.png");
material.maps[2].texture = LoadTexture("tex_normal.png");
Clone this wiki locally