how to properly setup raylib app to talk to glsl shaders

· sepi's blog

#glsl #opengl #3d #game #graphics #raylib #odin #programming

Well it took me a couple of days to finally understand how to properly use opengl shaders in raylib. As I described in my previous post, there are a few tricks you need to know if you do want to create a custom 3d scene. Raylib promotes itself as a simple framework for creating games and graphical applications, so it tries to hide all the complextities that comes with 3d applications behind a clean API.

By default raylib takes care of the basic 3d scene setup so you do not need to initialize and configure the camera (and all the matrix operations that come with it), vertext binding, texture mapping, etc... in fact you can create a full (simple looking) 3d game with all the default configurations.

However when your requirement goes a bit beyond these default configuration you need to know a few tricks to be able to customize things, one of the things I was wondering how to do was simple lighting. Unfortunately there is no simple way in raylib to setup a scene with light. by default the objects will be fully lit, in fact there is not darkness and shades. something like this:

ugly.png

In this post I'll try to show you how to setup a scene basic lighting plus the required vertex and fragment shaders, I use odin for the scene code. The final result will look like this:

image.png

Bellow is the project setup we will be using:

.
├── data/
│   ├── vs.glsl
│   └── fs.glsl
├── src/
│   └── game.odin
└── Makefile

and the game.odin code:

 1package main
 2
 3import "core:fmt"
 4import "core:math"
 5import la "core:math/linalg"
 6import rl "vendor:raylib"
 7
 8main :: proc() {
 9  rl.SetTraceLogLevel(rl.TraceLogLevel.WARNING)
10  rl.InitWindow(400, 400, "simple lighting")
11
12  shader := rl.LoadShader("data/vs.glsl", "data/fs.glsl")
13
14  camera: rl.Camera3D
15  {
16    using rl.CameraProjection
17    camera = rl.Camera3D{{0.0, 1.0, 4.0}, {0.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, 45.0, PERSPECTIVE}
18  }
19
20  model := rl.LoadModelFromMesh(rl.GenMeshTorus(0.4, 1, 16, 32))
21  model.materials[0].shader = shader // if you miss this, then the shader would not be applied to this object
22
23  ambientColor_loc := rl.GetShaderLocation(shader, "ambientColor")
24  ambientColor := la.Vector3f32{1.0, 1.0, 1.0}
25
26  {
27    using rl.ShaderUniformDataType
28    rl.SetShaderValue(shader, rl.ShaderLocationIndex(ambientColor_loc), &ambientColor, VEC3)
29  }
30
31  rl.SetTargetFPS(60)
32  rl.SetCameraMode(camera, rl.CameraMode.ORBITAL)
33  for (!rl.WindowShouldClose()) {
34    rl.UpdateCamera(&camera)
35    rl.BeginDrawing()
36    rl.ClearBackground(rl.BLACK)
37    rl.BeginMode3D(camera)
38    rl.DrawModel(model, la.Vector3f32{0, 0, 0}, 1.0, rl.WHITE)
39    rl.EndMode3D()
40    rl.DrawFPS(10, 10)
41    rl.EndDrawing()
42  }
43  rl.UnloadModel(model)
44  rl.UnloadShader(shader)
45  rl.CloseWindow()
46}
47

One of the things that I learned was that it is not enough to have a shader program, when you have shapes, you need to bind the program to the shape, this helps raylibg to properly do the wiering behind the scene. now here is the code for vertext and fragment shaders:

vs.glsl

 1#version 330 core
 2
 3in vec3 vertexPosition;
 4in vec3 vertexNormal;
 5in vec4 vertexColor;
 6
 7out vec4 fragColor;
 8out vec3 fragPosition;
 9out vec3 fragNormal;
10
11uniform mat4 matModel;
12uniform mat4 matView;
13uniform mat4 matProjection;
14
15void main()
16{
17    fragPosition = vec3(matModel * vec4(vertexPosition, 1.0));
18    fragNormal = vertexNormal;  
19    fragColor = vertexColor;
20    
21    gl_Position = matProjection * matView * vec4(fragPosition, 1.0);
22}

fs.glsl

 1#version 330
 2
 3out vec4 finalColor;
 4
 5in vec4 fragColor;
 6in vec3 fragPosition;
 7in vec3 fragNormal;
 8  
 9uniform vec3 ambientColor;
10
11vec3 lightPosition = vec3(0.0, 10.0, -10.0);
12float ambientStrength = 0.01;
13
14void main()
15{
16    // ambient
17    vec3 ambient = ambientStrength * ambientColor;
18    
19    // diffuse 
20    vec3 norm = normalize(fragNormal);
21    vec3 lightDir = normalize(lightPosition - fragPosition);
22    float diff = max(dot(norm, lightDir), 0.0);
23    vec3 diffuse = diff * ambientColor;
24            
25    vec3 result = (ambient + diffuse) * fragColor.rgb;
26    finalColor = vec4(result, fragColor.a);
27} 

and finally a small Makefile to get this running:

1.PHONY: run
2run:
3  @odin run src -out:game