| @ -0,0 +1,208 @@ | |||
| /******************************************************************************************* | |||
| * | |||
| * raylib [textures] example - framebuffer rendering | |||
| * | |||
| * Example complexity rating: [★★☆☆] 2/4 | |||
| * | |||
| * Example originally created with raylib 5.6, last time updated with raylib 5.6 | |||
| * | |||
| * Example contributed by Jack Boakes (@jackboakes) 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) 2026-2026 Jack Boakes (@jackboakes) | |||
| * | |||
| ********************************************************************************************/ | |||
| #include "raylib.h" | |||
| #include "raymath.h" | |||
| //------------------------------------------------------------------------------------ | |||
| // Module Functions Declaration | |||
| //------------------------------------------------------------------------------------ | |||
| static void DrawCameraPrism(Camera3D camera, float aspect, Color color); | |||
| //------------------------------------------------------------------------------------ | |||
| // Program main entry point | |||
| //------------------------------------------------------------------------------------ | |||
| int main(void) | |||
| { | |||
| // Initialization | |||
| //-------------------------------------------------------------------------------------- | |||
| const int screenWidth = 800; | |||
| const int screenHeight = 450; | |||
| const int splitWidth = screenWidth/2; | |||
| InitWindow(screenWidth, screenHeight, "raylib [textures] example - framebuffer rendering"); | |||
| // Camera to look at the 3D world | |||
| Camera3D subjectCamera = { 0 }; | |||
| subjectCamera.position = (Vector3){ 5.0f, 5.0f, 5.0f }; | |||
| subjectCamera.target = (Vector3){ 0.0f, 0.0f, 0.0f }; | |||
| subjectCamera.up = (Vector3){ 0.0f, 1.0f, 0.0f }; | |||
| subjectCamera.fovy = 45.0f; | |||
| subjectCamera.projection = CAMERA_PERSPECTIVE; | |||
| // Camera to observe the subject camera and 3D world | |||
| Camera3D observerCamera = { 0 }; | |||
| observerCamera.position = (Vector3){ 10.0f, 10.0f, 10.0f }; | |||
| observerCamera.target = (Vector3){ 0.0f, 0.0f, 0.0f }; | |||
| observerCamera.up = (Vector3){ 0.0f, 1.0f, 0.0f }; | |||
| observerCamera.fovy = 45.0f; | |||
| observerCamera.projection = CAMERA_PERSPECTIVE; | |||
| // Set up render textures | |||
| RenderTexture2D observerTarget = LoadRenderTexture(splitWidth, screenHeight); | |||
| Rectangle observerSource = { 0.0f, 0.0f, (float)observerTarget.texture.width, -(float)observerTarget.texture.height }; | |||
| Rectangle observerDest = { 0.0f, 0.0f, (float)splitWidth, (float)screenHeight }; | |||
| RenderTexture2D subjectTarget = LoadRenderTexture(splitWidth, screenHeight); | |||
| Rectangle subjectSource = { 0.0f, 0.0f, (float)subjectTarget.texture.width, -(float)subjectTarget.texture.height }; | |||
| Rectangle subjectDest = { (float)splitWidth, 0.0f, (float)splitWidth, (float)screenHeight }; | |||
| const float textureAspectRatio = (float)subjectTarget.texture.width/(float)subjectTarget.texture.height; | |||
| // Rectangles for cropping render texture | |||
| const float captureSize = 128.0f; | |||
| Rectangle cropSource = { (subjectTarget.texture.width - captureSize)/2.0f, (subjectTarget.texture.height - captureSize)/2.0f, captureSize, -captureSize }; | |||
| Rectangle cropDest = { splitWidth + 20, 20, captureSize, captureSize}; | |||
| SetTargetFPS(60); | |||
| DisableCursor(); | |||
| //-------------------------------------------------------------------------------------- | |||
| // Main game loop | |||
| while (!WindowShouldClose()) // Detect window close button or ESC key | |||
| { | |||
| // Update | |||
| //---------------------------------------------------------------------------------- | |||
| UpdateCamera(&observerCamera, CAMERA_FREE); | |||
| UpdateCamera(&subjectCamera, CAMERA_ORBITAL); | |||
| if (IsKeyPressed(KEY_R)) observerCamera.target = (Vector3){ 0.0f, 0.0f, 0.0f }; | |||
| // Build LHS observer view texture | |||
| BeginTextureMode(observerTarget); | |||
| ClearBackground(RAYWHITE); | |||
| BeginMode3D(observerCamera); | |||
| DrawGrid(10, 1.0f); | |||
| DrawCube((Vector3){ 0.0f, 0.0f, 0.0f }, 2.0f, 2.0f, 2.0f, GOLD); | |||
| DrawCubeWires((Vector3){ 0.0f, 0.0f, 0.0f }, 2.0f, 2.0f, 2.0f, PINK); | |||
| DrawCameraPrism(subjectCamera, textureAspectRatio, GREEN); | |||
| EndMode3D(); | |||
| DrawText("Observer View", 10, observerTarget.texture.height - 30, 20, BLACK); | |||
| DrawText("WASD + Mouse to Move", 10, 10, 20, DARKGRAY); | |||
| DrawText("Scroll to Zoom", 10, 30, 20, DARKGRAY); | |||
| DrawText("R to Reset Observer Target", 10, 50, 20, DARKGRAY); | |||
| EndTextureMode(); | |||
| // Build RHS subject view texture | |||
| BeginTextureMode(subjectTarget); | |||
| ClearBackground(RAYWHITE); | |||
| BeginMode3D(subjectCamera); | |||
| DrawCube((Vector3){ 0.0f, 0.0f, 0.0f }, 2.0f, 2.0f, 2.0f, GOLD); | |||
| DrawCubeWires((Vector3){ 0.0f, 0.0f, 0.0f }, 2.0f, 2.0f, 2.0f, PINK); | |||
| DrawGrid(10, 1.0f); | |||
| EndMode3D(); | |||
| DrawRectangleLines((subjectTarget.texture.width - captureSize)/2, (subjectTarget.texture.height - captureSize)/2, captureSize, captureSize, GREEN); | |||
| DrawText("Subject View", 10, subjectTarget.texture.height - 30, 20, BLACK); | |||
| EndTextureMode(); | |||
| //---------------------------------------------------------------------------------- | |||
| // Draw | |||
| //---------------------------------------------------------------------------------- | |||
| BeginDrawing(); | |||
| ClearBackground(BLACK); | |||
| // Draw observer texture LHS | |||
| DrawTexturePro(observerTarget.texture, observerSource, observerDest, (Vector2){0.0f, 0.0f }, 0.0f, WHITE); | |||
| // Draw subject texture RHS | |||
| DrawTexturePro(subjectTarget.texture, subjectSource, subjectDest, (Vector2){ 0.0f, 0.0f }, 0.0f, WHITE); | |||
| // Draw the small crop overlay on top | |||
| DrawTexturePro(subjectTarget.texture, cropSource, cropDest, (Vector2){ 0.0f, 0.0f }, 0.0f, WHITE); | |||
| DrawRectangleLinesEx(cropDest, 2, BLACK); | |||
| // Draw split screen divider line | |||
| DrawLine(splitWidth, 0, splitWidth, screenHeight, BLACK); | |||
| EndDrawing(); | |||
| //---------------------------------------------------------------------------------- | |||
| } | |||
| // De-Initialization | |||
| //-------------------------------------------------------------------------------------- | |||
| UnloadRenderTexture(observerTarget); | |||
| UnloadRenderTexture(subjectTarget); | |||
| CloseWindow(); // Close window and OpenGL context | |||
| //-------------------------------------------------------------------------------------- | |||
| return 0; | |||
| } | |||
| //---------------------------------------------------------------------------------- | |||
| // Module Functions Definition | |||
| //---------------------------------------------------------------------------------- | |||
| static void DrawCameraPrism(Camera3D camera, float aspect, Color color) | |||
| { | |||
| float length = Vector3Distance(camera.position, camera.target); | |||
| // Define the 4 corners of the camera's prism plane sliced at the target in Normalized Device Coordinates | |||
| Vector3 planeNDC[4] = { | |||
| { -1.0f, -1.0f, 1.0f }, // Bottom Left | |||
| { 1.0f, -1.0f, 1.0f }, // Bottom Right | |||
| { 1.0f, 1.0f, 1.0f }, // Top Right | |||
| { -1.0f, 1.0f, 1.0f } // Top Left | |||
| }; | |||
| // Build the matrices | |||
| Matrix view = GetCameraMatrix(camera); | |||
| Matrix proj = MatrixPerspective(camera.fovy * DEG2RAD, aspect, 0.05f, length); | |||
| // Combine view and projection so we can reverse the full camera transform | |||
| Matrix viewProj = MatrixMultiply(view, proj); | |||
| // Invert the view-projection matrix to unproject points from NDC space back into world space | |||
| Matrix inverseViewProj = MatrixInvert(viewProj); | |||
| // Transform the 4 plane corners from NDC into world space | |||
| Vector3 corners[4]; | |||
| for (int i = 0; i < 4; i++) | |||
| { | |||
| float x = planeNDC[i].x; | |||
| float y = planeNDC[i].y; | |||
| float z = planeNDC[i].z; | |||
| // Multiply NDC position by the inverse view-projection matrix | |||
| // This produces a homogeneous (x, y, z, w) position in world space | |||
| float vx = inverseViewProj.m0*x + inverseViewProj.m4*y + inverseViewProj.m8*z + inverseViewProj.m12; | |||
| float vy = inverseViewProj.m1*x + inverseViewProj.m5*y + inverseViewProj.m9*z + inverseViewProj.m13; | |||
| float vz = inverseViewProj.m2*x + inverseViewProj.m6*y + inverseViewProj.m10*z + inverseViewProj.m14; | |||
| float vw = inverseViewProj.m3*x + inverseViewProj.m7*y + inverseViewProj.m11*z + inverseViewProj.m15; | |||
| corners[i] = (Vector3){ vx/vw, vy/vw, vz/vw }; | |||
| } | |||
| // Draw the far plane sliced at the target | |||
| DrawLine3D(corners[0], corners[1], color); | |||
| DrawLine3D(corners[1], corners[2], color); | |||
| DrawLine3D(corners[2], corners[3], color); | |||
| DrawLine3D(corners[3], corners[0], color); | |||
| // Draw the prism lines from the far plane to the camera position | |||
| for (int i = 0; i < 4; i++) | |||
| { | |||
| DrawLine3D(camera.position, corners[i], color); | |||
| } | |||
| } | |||