Bläddra i källkod

examples/shaders: Add an example for deferred shading (#3496)

* add example for deferred rendering/shading

* adapt convention

---------

Co-authored-by: 27justin <[email protected]>
Justin 1 år sedan
förälder
incheckning
3645244f9f

+ 2 - 1
examples/Makefile

@@ -562,7 +562,8 @@ SHADERS = \
     shaders/shaders_mesh_instancing \
     shaders/shaders_multi_sample2d \
     shaders/shaders_write_depth \
-    shaders/shaders_hybrid_render
+    shaders/shaders_hybrid_render \
+    shaders/shaders_deferred_render
 
 AUDIO = \
     audio/audio_module_playing \

+ 2 - 1
examples/Makefile.Web

@@ -468,7 +468,8 @@ SHADERS = \
     shaders/shaders_mesh_instancing \
     shaders/shaders_multi_sample2d \
     shaders/shaders_write_depth \
-    shaders/shaders_hybrid_render
+    shaders/shaders_hybrid_render \
+    shaders/shaders_deferred_render
 
 AUDIO = \
     audio/audio_module_playing \

+ 5 - 4
examples/README.md

@@ -176,6 +176,7 @@ Examples using raylib shaders functionality, including shaders loading, paramete
 | 114 | [shaders_mesh_instancing](shaders/shaders_mesh_instancing.c) | <img src="shaders/shaders_mesh_instancing.png" alt="shaders_mesh_instancing" width="80"> | ⭐️⭐️⭐️⭐️ | 3.7 | **4.2** | [seanpringle](https://github.com/seanpringle) |
 | 115 | [shaders_multi_sample2d](shaders/shaders_multi_sample2d.c) | <img src="shaders/shaders_multi_sample2d.png" alt="shaders_multi_sample2d" width="80"> | ⭐️⭐️☆☆ | 3.5 | 3.5 | [Ray](https://github.com/raysan5) |
 | 116 | [shaders_spotlight](shaders/shaders_spotlight.c) | <img src="shaders/shaders_spotlight.png" alt="shaders_spotlight" width="80"> | ⭐️⭐️☆☆ | 2.5 | 3.7 | [Chris Camacho](https://github.com/codifies) |
+| 117 | [shaders_deferred_render](shaders/shaders_deferred_render.c) | <img src="shaders/shaders_deferred_render.png" alt="shaders_deferred_render" width="80"> | ⭐️⭐️⭐️⭐️ | 4.5 | 4.5 | [Justin Andreas Lacoste](https://github.com/27justin) |
 
 ### category: audio
 
@@ -183,10 +184,10 @@ Examples using raylib audio functionality, including sound/music loading and pla
 
 | ## | example  | image  | difficulty<br>level | version<br>created | last version<br>updated | original<br>developer |
 |----|----------|--------|:-------------------:|:------------------:|:------------------:|:----------|
-| 117 | [audio_module_playing](audio/audio_module_playing.c) | <img src="audio/audio_module_playing.png" alt="audio_module_playing" width="80"> | ⭐️☆☆☆ | 1.5 | 3.5 | [Ray](https://github.com/raysan5) |
-| 118 | [audio_music_stream](audio/audio_music_stream.c) | <img src="audio/audio_music_stream.png" alt="audio_music_stream" width="80"> | ⭐️☆☆☆ | 1.3 | **4.2** | [Ray](https://github.com/raysan5) |
-| 119 | [audio_raw_stream](audio/audio_raw_stream.c) | <img src="audio/audio_raw_stream.png" alt="audio_raw_stream" width="80"> | ⭐️⭐️⭐️☆ | 1.6 | **4.2** | [Ray](https://github.com/raysan5) |
-| 120 | [audio_sound_loading](audio/audio_sound_loading.c) | <img src="audio/audio_sound_loading.png" alt="audio_sound_loading" width="80"> | ⭐️☆☆☆ | 1.1 | 3.5 | [Ray](https://github.com/raysan5) |
+| 118 | [audio_module_playing](audio/audio_module_playing.c) | <img src="audio/audio_module_playing.png" alt="audio_module_playing" width="80"> | ⭐️☆☆☆ | 1.5 | 3.5 | [Ray](https://github.com/raysan5) |
+| 119 | [audio_music_stream](audio/audio_music_stream.c) | <img src="audio/audio_music_stream.png" alt="audio_music_stream" width="80"> | ⭐️☆☆☆ | 1.3 | **4.2** | [Ray](https://github.com/raysan5) |
+| 120 | [audio_raw_stream](audio/audio_raw_stream.c) | <img src="audio/audio_raw_stream.png" alt="audio_raw_stream" width="80"> | ⭐️⭐️⭐️☆ | 1.6 | **4.2** | [Ray](https://github.com/raysan5) |
+| 121 | [audio_sound_loading](audio/audio_sound_loading.c) | <img src="audio/audio_sound_loading.png" alt="audio_sound_loading" width="80"> | ⭐️☆☆☆ | 1.1 | 3.5 | [Ray](https://github.com/raysan5) |
 
 ### category: others
 

+ 55 - 0
examples/shaders/resources/shaders/glsl330/deferred_shading.fs

@@ -0,0 +1,55 @@
+#version 330 core
+out vec4 finalColor;
+
+in vec2 texCoord;
+in vec2 texCoord2;
+
+uniform sampler2D gPosition;
+uniform sampler2D gNormal;
+uniform sampler2D gAlbedoSpec;
+
+struct Light {
+    int enabled;
+    int type; // Unused in this demo.
+    vec3 position;
+    vec3 target; // Unused in this demo.
+    vec4 color;
+};
+
+const int NR_LIGHTS = 4;
+uniform Light lights[NR_LIGHTS];
+uniform vec3 viewPosition;
+
+const float QUADRATIC = 0.032;
+const float LINEAR = 0.09;
+
+void main() {
+    vec3 fragPosition = texture(gPosition, texCoord).rgb;
+    vec3 normal = texture(gNormal, texCoord).rgb;
+    vec3 albedo = texture(gAlbedoSpec, texCoord).rgb;
+    float specular = texture(gAlbedoSpec, texCoord).a;
+
+    vec3 ambient = albedo * vec3(0.1f);
+    vec3 viewDirection = normalize(viewPosition - fragPosition);
+
+    for(int i = 0; i < NR_LIGHTS; ++i)
+    {
+        if(lights[i].enabled == 0) continue;
+        vec3 lightDirection = lights[i].position - fragPosition;
+        vec3 diffuse = max(dot(normal, lightDirection), 0.0) * albedo * lights[i].color.xyz;
+
+        vec3 halfwayDirection = normalize(lightDirection + viewDirection);
+        float spec = pow(max(dot(normal, halfwayDirection), 0.0), 32.0);
+        vec3 specular = specular * spec * lights[i].color.xyz;
+
+        // Attenuation
+        float distance = length(lights[i].position - fragPosition);
+        float attenuation = 1.0 / (1.0 + LINEAR * distance + QUADRATIC * distance * distance);
+        diffuse *= attenuation;
+        specular *= attenuation;
+        ambient += diffuse + specular;
+    }
+
+    finalColor = vec4(ambient, 1.0);
+}
+

+ 11 - 0
examples/shaders/resources/shaders/glsl330/deferred_shading.vs

@@ -0,0 +1,11 @@
+#version 330 core
+
+layout (location = 0) in vec3 vertexPosition;
+layout (location = 1) in vec2 vertexTexCoord;
+
+out vec2 texCoord;
+
+void main() {
+    gl_Position = vec4(vertexPosition, 1.0);
+    texCoord = vertexTexCoord;
+}

+ 22 - 0
examples/shaders/resources/shaders/glsl330/gbuffer.fs

@@ -0,0 +1,22 @@
+#version 330 core
+layout (location = 0) out vec3 gPosition;
+layout (location = 1) out vec3 gNormal;
+layout (location = 2) out vec4 gAlbedoSpec;
+
+in vec3 fragPosition;
+in vec2 fragTexCoord;
+in vec3 fragNormal;
+
+uniform sampler2D diffuseTexture;
+uniform sampler2D specularTexture;
+
+void main() {
+    // store the fragment position vector in the first gbuffer texture
+    gPosition = fragPosition;
+    // also store the per-fragment normals into the gbuffer
+    gNormal = normalize(fragNormal);
+    // and the diffuse per-fragment color
+    gAlbedoSpec.rgb = texture(diffuseTexture, fragTexCoord).rgb;
+    // store specular intensity in gAlbedoSpec's alpha component
+    gAlbedoSpec.a = texture(specularTexture, fragTexCoord).r;
+}

+ 24 - 0
examples/shaders/resources/shaders/glsl330/gbuffer.vs

@@ -0,0 +1,24 @@
+#version 330 core
+layout (location = 0) in vec3 vertexPosition;
+layout (location = 1) in vec2 vertexTexCoord;
+layout (location = 2) in vec3 vertexNormal;
+
+out vec3 fragPosition;
+out vec2 fragTexCoord;
+out vec3 fragNormal;
+
+uniform mat4 matModel;
+uniform mat4 matView;
+uniform mat4 matProjection;
+
+void main()
+{
+    vec4 worldPos = matModel * vec4(vertexPosition, 1.0);
+    fragPosition = worldPos.xyz; 
+    fragTexCoord = vertexTexCoord;
+
+    mat3 normalMatrix = transpose(inverse(mat3(matModel)));
+    fragNormal = normalMatrix * vertexNormal;
+
+    gl_Position = matProjection * matView * worldPos;
+}

+ 321 - 0
examples/shaders/shaders_deferred_render.c

@@ -0,0 +1,321 @@
+/*******************************************************************************************
+*
+*   raylib [shaders] example - deferred rendering
+*
+*   NOTE: This example requires raylib OpenGL 3.3 or ES 3 versions.
+*
+*   Example originally created with raylib 4.5, last time updated with raylib 4.5
+*
+*   Example contributed by Justin Andreas Lacoste (@27justin) and reviewed by Ramon Santamaria (@raysan5)
+*
+*   Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
+*   BSD-like license that allows static linking with closed source software
+*
+*   Copyright (c) 2023 Justin Andreas Lacoste (@27justin)
+*
+********************************************************************************************/
+
+#include <stdlib.h>
+#include <GLES3/gl3.h>
+
+#include "raylib.h"
+#include "rlgl.h"
+
+#include "raymath.h"
+
+#define RLIGHTS_IMPLEMENTATION
+#include "rlights.h"
+
+#if defined(PLATFORM_DESKTOP)
+    #define GLSL_VERSION            330
+#else   // PLATFORM_ANDROID, PLATFORM_WEB
+    #define GLSL_VERSION            100
+#endif
+
+typedef struct {
+    unsigned int framebuffer;
+
+    unsigned int positionTexture;
+    unsigned int normalTexture;
+    unsigned int albedoSpecTexture;
+    
+    unsigned int depthRenderbuffer;
+} GBuffer;
+
+int main(void) {
+    // Initialization
+    // -------------------------------------------------------------------------------------
+    const int screenWidth = 800;
+    const int screenHeight = 450;
+
+    InitWindow(screenWidth, screenHeight, "raylib [shaders] example - deferred render");
+
+    Camera camera = { 0 };
+    camera.position = (Vector3){ 5.0f, 4.0f, 5.0f };    // Camera position
+    camera.target = (Vector3){ 0.0f, 1.0f, 0.0f };      // Camera looking at point
+    camera.up = (Vector3){ 0.0f, 1.0f, 0.0f };          // Camera up vector (rotation towards target)
+    camera.fovy = 60.0f;                                // Camera field-of-view Y
+    camera.projection = CAMERA_PERSPECTIVE;             // Camera projection type
+
+    // Load plane model from a generated mesh
+    Model model = LoadModelFromMesh(GenMeshPlane(10.0f, 10.0f, 3, 3));
+    Model cube = LoadModelFromMesh(GenMeshCube(2.0f, 2.0f, 2.0f));
+
+    // Load geometry buffer (G-buffer) shader and deferred shader
+    Shader gbufferShader = LoadShader("resources/shaders/glsl330/gbuffer.vs",
+                               "resources/shaders/glsl330/gbuffer.fs");
+
+    Shader deferredShader = LoadShader("resources/shaders/glsl330/deferred_shading.vs",
+                               "resources/shaders/glsl330/deferred_shading.fs");
+    deferredShader.locs[SHADER_LOC_VECTOR_VIEW] = GetShaderLocation(deferredShader, "viewPosition");
+
+    // Initialize the G-buffer
+    GBuffer gBuffer = { 0 };
+    gBuffer.framebuffer = rlLoadFramebuffer(screenWidth, screenHeight);
+
+    if(!gBuffer.framebuffer)
+    {
+        TraceLog(LOG_WARNING, "Failed to create framebuffer");
+        exit(1);
+    }
+    rlEnableFramebuffer(gBuffer.framebuffer);
+
+    // Since we are storing position and normal data in these textures, 
+    // we need to use a floating point format.
+    gBuffer.positionTexture = rlLoadTexture(NULL, screenWidth, screenHeight, RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32, 1);
+
+    gBuffer.normalTexture = rlLoadTexture(NULL, screenWidth, screenHeight, RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32, 1);
+    // Albedo (diffuse color) and specular strength can be combined into one texture.
+    // The color in RGB, and the specular strength in the alpha channel.
+    gBuffer.albedoSpecTexture = rlLoadTexture(NULL, screenWidth, screenHeight, RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, 1);
+
+    // Activate the draw buffers for our framebuffer
+    rlActiveDrawBuffers(3);
+
+    // Now we attach our textures to the framebuffer.
+    rlFramebufferAttach(gBuffer.framebuffer, gBuffer.positionTexture, RL_ATTACHMENT_COLOR_CHANNEL0, RL_ATTACHMENT_TEXTURE2D, 0);
+    rlFramebufferAttach(gBuffer.framebuffer, gBuffer.normalTexture, RL_ATTACHMENT_COLOR_CHANNEL1, RL_ATTACHMENT_TEXTURE2D, 0);
+    rlFramebufferAttach(gBuffer.framebuffer, gBuffer.albedoSpecTexture, RL_ATTACHMENT_COLOR_CHANNEL2, RL_ATTACHMENT_TEXTURE2D, 0);
+
+    // Finally we attach the depth buffer.
+    gBuffer.depthRenderbuffer = rlLoadTextureDepth(screenWidth, screenHeight, true);
+    rlFramebufferAttach(gBuffer.framebuffer, gBuffer.depthRenderbuffer, RL_ATTACHMENT_DEPTH, RL_ATTACHMENT_RENDERBUFFER, 0);
+
+    // Make sure our framebuffer is complete.
+    // NOTE: rlFramebufferComplete() automatically unbinds the framebuffer, so we don't have
+    // to rlDisableFramebuffer() here.
+    if(rlFramebufferComplete(gBuffer.framebuffer) != true)
+    {
+        TraceLog(LOG_WARNING, "Framebuffer is not complete");
+        exit(1);
+    }
+
+    // Now we initialize the sampler2D uniform's in the deferred shader.
+    // We do this by setting the uniform's value to the color channel slot we earlier
+    // bound our textures to.
+    rlEnableShader(deferredShader.id);
+
+        rlSetUniformSampler(rlGetLocationUniform(deferredShader.id, "gPosition"), 0);
+        rlSetUniformSampler(rlGetLocationUniform(deferredShader.id, "gNormal"), 1);
+        rlSetUniformSampler(rlGetLocationUniform(deferredShader.id, "gAlbedoSpec"), 2);
+
+    rlDisableShader();
+
+    // Assign out lighting shader to model
+    model.materials[0].shader = gbufferShader;
+    cube.materials[0].shader = gbufferShader;
+
+    // Create lights
+    //--------------------------------------------------------------------------------------
+    Light lights[MAX_LIGHTS] = { 0 };
+    lights[0] = CreateLight(LIGHT_POINT, (Vector3){ -2, 1, -2 }, Vector3Zero(), YELLOW, deferredShader);
+    lights[1] = CreateLight(LIGHT_POINT, (Vector3){ 2, 1, 2 }, Vector3Zero(), RED, deferredShader);
+    lights[2] = CreateLight(LIGHT_POINT, (Vector3){ -2, 1, 2 }, Vector3Zero(), GREEN, deferredShader);
+    lights[3] = CreateLight(LIGHT_POINT, (Vector3){ 2, 1, -2 }, Vector3Zero(), BLUE, deferredShader);
+
+    const int MAX_CUBES = 30;
+    const float CUBE_SCALE = 0.25;
+    Vector3 cubePositions[MAX_CUBES];
+    float cubeRotations[MAX_CUBES];
+    for(int i = 0; i < MAX_CUBES; i++)
+    {
+        cubePositions[i] = (Vector3) {
+            .x = (float)(rand() % 10) - 5,
+            .y = (float)(rand() % 5),
+            .z = (float)(rand() % 10) - 5,
+        };
+        cubeRotations[i] = (float)(rand() % 360);
+    }
+
+    enum {
+        POSITION,
+        NORMAL,
+        ALBEDO,
+        DEFERRED_SHADING
+    } activeTexture = DEFERRED_SHADING;
+
+    rlEnableDepthTest();
+
+    SetTargetFPS(60);                   // Set our game to run at 60 frames-per-second
+    //---------------------------------------------------------------------------------------
+
+    // Main game loop
+    while (!WindowShouldClose())
+    {
+        // Update
+        //----------------------------------------------------------------------------------
+        UpdateCamera(&camera, CAMERA_ORBITAL);
+
+        // Update the shader with the camera view vector (points towards { 0.0f, 0.0f, 0.0f })
+        float cameraPos[3] = { camera.position.x, camera.position.y, camera.position.z };
+        SetShaderValue(deferredShader, deferredShader.locs[SHADER_LOC_VECTOR_VIEW], cameraPos, SHADER_UNIFORM_VEC3);
+        
+        // Check key inputs to enable/disable lights
+        if (IsKeyPressed(KEY_Y)) { lights[0].enabled = !lights[0].enabled; }
+        if (IsKeyPressed(KEY_R)) { lights[1].enabled = !lights[1].enabled; }
+        if (IsKeyPressed(KEY_G)) { lights[2].enabled = !lights[2].enabled; }
+        if (IsKeyPressed(KEY_B)) { lights[3].enabled = !lights[3].enabled; }
+
+        // Check key inputs to switch between G-buffer textures
+        if(IsKeyPressed(KEY_ONE)) activeTexture = POSITION;
+        if(IsKeyPressed(KEY_TWO)) activeTexture = NORMAL;
+        if(IsKeyPressed(KEY_THREE)) activeTexture = ALBEDO;
+        if(IsKeyPressed(KEY_FOUR)) activeTexture = DEFERRED_SHADING;
+
+        
+        // Update light values (actually, only enable/disable them)
+        for (int i = 0; i < MAX_LIGHTS; i++) UpdateLightValues(deferredShader, lights[i]);
+        //----------------------------------------------------------------------------------
+
+        // Draw
+        // ---------------------------------------------------------------------------------
+        BeginDrawing();
+            // Draw to the geometry buffer by first activating it.
+            rlEnableFramebuffer(gBuffer.framebuffer);
+            rlClearScreenBuffers();  // Clear color & depth buffer
+
+            rlDisableColorBlend();
+            BeginMode3D(camera);
+                // NOTE:
+                // We have to use rlEnableShader here. `BeginShaderMode` or thus `rlSetShader`
+                // will not work, as they won't immediately load the shader program.
+                rlEnableShader(gbufferShader.id);
+                    // When drawing a model here, make sure that the material's shaders
+                    // are set to the gbuffer shader!
+                    DrawModel(model, Vector3Zero(), 1.0f, WHITE);
+                    DrawModel(cube, (Vector3) { 0.0, 1.0f, 0.0 }, 1.0f, WHITE);
+
+                    for(int i = 0; i < MAX_CUBES; i++)
+                    {
+                        Vector3 position = cubePositions[i];
+
+                        DrawModelEx(cube, position, (Vector3) { 1, 1, 1 }, cubeRotations[i], (Vector3) { CUBE_SCALE, CUBE_SCALE, CUBE_SCALE }, WHITE);
+                    }
+
+                rlDisableShader();
+            EndMode3D();
+            rlEnableColorBlend();
+
+            // Go back to the default framebuffer (0) and draw our deferred shading.
+            rlDisableFramebuffer();
+            rlClearScreenBuffers(); // Clear color & depth buffer
+
+            switch(activeTexture)
+            {
+                case DEFERRED_SHADING:
+                    BeginMode3D(camera);
+                        rlDisableColorBlend();
+                        rlEnableShader(deferredShader.id);
+                            // Activate our g-buffer textures
+                            // These will now be bound to the sampler2D uniforms `gPosition`, `gNormal`,
+                            // and `gAlbedoSpec`
+                            rlActiveTextureSlot(0);
+                            rlEnableTexture(gBuffer.positionTexture);
+                            rlActiveTextureSlot(1);
+                            rlEnableTexture(gBuffer.normalTexture);
+                            rlActiveTextureSlot(2);
+                            rlEnableTexture(gBuffer.albedoSpecTexture);
+
+                            // Finally, we draw a fullscreen quad to our default framebuffer
+                            // This will now be shaded using our deferred shader
+                            rlLoadDrawQuad();
+                        rlDisableShader();
+                        rlEnableColorBlend();
+                    EndMode3D();
+
+                    // As a last step, we now copy over the depth buffer from our g-buffer to the
+                    // default framebuffer.
+                    glBindFramebuffer(GL_READ_FRAMEBUFFER, gBuffer.framebuffer);
+                    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
+                    glBlitFramebuffer(0, 0, screenWidth, screenHeight, 0, 0, screenWidth, screenHeight, GL_DEPTH_BUFFER_BIT, GL_NEAREST);
+                    rlDisableFramebuffer();
+
+                    // Since our shader is now done and disabled, we can draw our lights in default
+                    // forward rendering
+                    BeginMode3D(camera);
+                        rlEnableShader(rlGetShaderIdDefault());
+                            for(int i = 0; i < MAX_LIGHTS; i++)
+                            {
+                                if(lights[i].enabled) DrawSphereEx(lights[i].position, 0.2f, 8, 8, lights[i].color);
+                                else DrawSphereWires(lights[i].position, 0.2f, 8, 8, ColorAlpha(lights[i].color, 0.3f));
+                            }
+                        rlDisableShader();
+                    EndMode3D();
+                    DrawText("FINAL RESULT", 10, screenHeight - 30, 20, DARKGREEN);
+                    break;
+                case POSITION:
+                    DrawTextureRec((Texture2D) {
+                        .id = gBuffer.positionTexture,
+                        .width = screenWidth,
+                        .height = screenHeight,
+                    }, (Rectangle) { 0, 0, screenWidth, -screenHeight }, Vector2Zero(), RAYWHITE);
+                    DrawText("POSITION TEXTURE", 10, screenHeight - 30, 20, DARKGREEN);
+                    break;
+                case NORMAL:
+                    DrawTextureRec((Texture2D) {
+                        .id = gBuffer.normalTexture,
+                        .width = screenWidth,
+                        .height = screenHeight,
+                    }, (Rectangle) { 0, 0, screenWidth, -screenHeight }, Vector2Zero(), RAYWHITE);
+                    DrawText("NORMAL TEXTURE", 10, screenHeight - 30, 20, DARKGREEN);
+                    break;
+
+                case ALBEDO:
+                    DrawTextureRec((Texture2D) {
+                        .id = gBuffer.albedoSpecTexture,
+                        .width = screenWidth,
+                        .height = screenHeight,
+                    }, (Rectangle) { 0, 0, screenWidth, -screenHeight }, Vector2Zero(), RAYWHITE);
+                    DrawText("ALBEDO TEXTURE", 10, screenHeight - 30, 20, DARKGREEN);
+                    break;
+            }
+
+            DrawFPS(10, 10);
+
+            DrawText("Use keys [Y][R][G][B] to toggle lights", 10, 40, 20, DARKGRAY);
+            DrawText("Use keys [1]-[4] to switch between G-buffer textures", 10, 70, 20, DARKGRAY);
+        EndDrawing();
+        // -----------------------------------------------------------------------------
+    }
+
+    // De-Initialization
+    //--------------------------------------------------------------------------------------
+    UnloadModel(model);     // Unload the models
+    UnloadModel(cube);
+
+    UnloadShader(deferredShader);   // Unload shaders
+    UnloadShader(gbufferShader);
+
+    // Unload geometry buffer and all attached textures
+    rlUnloadFramebuffer(gBuffer.framebuffer);
+    rlUnloadTexture(gBuffer.positionTexture);
+    rlUnloadTexture(gBuffer.normalTexture);
+    rlUnloadTexture(gBuffer.albedoSpecTexture);
+    rlUnloadTexture(gBuffer.depthRenderbuffer);
+
+    CloseWindow();          // Close window and OpenGL context
+    //--------------------------------------------------------------------------------------
+
+    return 0;
+}
+

BIN
examples/shaders/shaders_deferred_render.png