/******************************************************************************************* * * raylib [shaders] example - deferred rendering * * NOTE: This example requires raylib OpenGL 3.3 or OpenGL ES 3.0 * * 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 "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 #include // Required for: NULL #define MAX_CUBES 30 // GBuffer data typedef struct GBuffer { unsigned int framebuffer; unsigned int positionTexture; unsigned int normalTexture; unsigned int albedoSpecTexture; unsigned int depthRenderbuffer; } GBuffer; // Deferred mode passes typedef enum { DEFERRED_POSITION, DEFERRED_NORMAL, DEFERRED_ALBEDO, DEFERRED_SHADING } DeferredMode; //------------------------------------------------------------------------------------ // Program main entry point //------------------------------------------------------------------------------------ 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(); 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)) { 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 float CUBE_SCALE = 0.25; Vector3 cubePositions[MAX_CUBES] = { 0 }; float cubeRotations[MAX_CUBES] = { 0 }; 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); } DeferredMode mode = 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)) mode = DEFERRED_POSITION; if (IsKeyPressed(KEY_TWO)) mode = DEFERRED_NORMAL; if (IsKeyPressed(KEY_THREE)) mode = DEFERRED_ALBEDO; if (IsKeyPressed(KEY_FOUR)) mode = DEFERRED_SHADING; // Update light values (actually, only enable/disable them) for (int i = 0; i < MAX_LIGHTS; i++) UpdateLightValues(deferredShader, lights[i]); //---------------------------------------------------------------------------------- // Draw // --------------------------------------------------------------------------------- BeginDrawing(); ClearBackground(RAYWHITE); // Draw to the geometry buffer by first activating it rlEnableFramebuffer(gBuffer.framebuffer); rlClearScreenBuffers(); // Clear color and 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 (mode) { 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. rlBindFramebuffer(RL_READ_FRAMEBUFFER, gBuffer.framebuffer); rlBindFramebuffer(RL_DRAW_FRAMEBUFFER, 0); rlBlitFramebuffer(0, 0, screenWidth, screenHeight, 0, 0, screenWidth, screenHeight, 0x00000100); // GL_DEPTH_BUFFER_BIT 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 DEFERRED_POSITION: { DrawTextureRec((Texture2D){ .id = gBuffer.positionTexture, .width = screenWidth, .height = screenHeight, }, (Rectangle) { 0, 0, (float)screenWidth, (float)-screenHeight }, Vector2Zero(), RAYWHITE); DrawText("POSITION TEXTURE", 10, screenHeight - 30, 20, DARKGREEN); } break; case DEFERRED_NORMAL: { DrawTextureRec((Texture2D){ .id = gBuffer.normalTexture, .width = screenWidth, .height = screenHeight, }, (Rectangle) { 0, 0, (float)screenWidth, (float)-screenHeight }, Vector2Zero(), RAYWHITE); DrawText("NORMAL TEXTURE", 10, screenHeight - 30, 20, DARKGREEN); } break; case DEFERRED_ALBEDO: { DrawTextureRec((Texture2D){ .id = gBuffer.albedoSpecTexture, .width = screenWidth, .height = screenHeight, }, (Rectangle) { 0, 0, (float)screenWidth, (float)-screenHeight }, Vector2Zero(), RAYWHITE); DrawText("ALBEDO TEXTURE", 10, screenHeight - 30, 20, DARKGREEN); } break; default: break; } DrawText("Toggle lights keys: [Y][R][G][B]", 10, 40, 20, DARKGRAY); DrawText("Switch G-buffer textures: [1][2][3][4]", 10, 70, 20, DARKGRAY); DrawFPS(10, 10); 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; }