1+ /*******************************************************************************************
2+ *
3+ * raylib [textures] example - framebuffer rendering
4+ *
5+ * Example complexity rating: [★★☆☆] 2/4
6+ *
7+ * Example originally created with raylib 5.6, last time updated with raylib 5.6
8+ *
9+ * Example contributed by Jack Boakes (@jackboakes) and reviewed by Ramon Santamaria (@raysan5)
10+ *
11+ * Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
12+ * BSD-like license that allows static linking with closed source software
13+ *
14+ * Copyright (c) 2026-2026 Jack Boakes (@jackboakes)
15+ *
16+ ********************************************************************************************/
17+
18+ #include "raylib.h"
19+ #include "raymath.h"
20+
21+ //------------------------------------------------------------------------------------
22+ // Module Functions Declaration
23+ //------------------------------------------------------------------------------------
24+ static void DrawCameraPrism (Camera3D camera , float aspect , Color color );
25+
26+ //------------------------------------------------------------------------------------
27+ // Program main entry point
28+ //------------------------------------------------------------------------------------
29+ int main (void )
30+ {
31+ // Initialization
32+ //--------------------------------------------------------------------------------------
33+ const int screenWidth = 800 ;
34+ const int screenHeight = 450 ;
35+ const int splitWidth = screenWidth /2 ;
36+
37+ InitWindow (screenWidth , screenHeight , "raylib [textures] example - framebuffer rendering" );
38+
39+ // Camera to look at the 3D world
40+ Camera3D subjectCamera = { 0 };
41+ subjectCamera .position = (Vector3 ){ 5.0f , 5.0f , 5.0f };
42+ subjectCamera .target = (Vector3 ){ 0.0f , 0.0f , 0.0f };
43+ subjectCamera .up = (Vector3 ){ 0.0f , 1.0f , 0.0f };
44+ subjectCamera .fovy = 45.0f ;
45+ subjectCamera .projection = CAMERA_PERSPECTIVE ;
46+
47+ // Camera to observe the subject camera and 3D world
48+ Camera3D observerCamera = { 0 };
49+ observerCamera .position = (Vector3 ){ 10.0f , 10.0f , 10.0f };
50+ observerCamera .target = (Vector3 ){ 0.0f , 0.0f , 0.0f };
51+ observerCamera .up = (Vector3 ){ 0.0f , 1.0f , 0.0f };
52+ observerCamera .fovy = 45.0f ;
53+ observerCamera .projection = CAMERA_PERSPECTIVE ;
54+
55+ // Set up render textures
56+ RenderTexture2D observerTarget = LoadRenderTexture (splitWidth , screenHeight );
57+ Rectangle observerSource = { 0.0f , 0.0f , (float )observerTarget .texture .width , - (float )observerTarget .texture .height };
58+ Rectangle observerDest = { 0.0f , 0.0f , (float )splitWidth , (float )screenHeight };
59+
60+ RenderTexture2D subjectTarget = LoadRenderTexture (splitWidth , screenHeight );
61+ Rectangle subjectSource = { 0.0f , 0.0f , (float )subjectTarget .texture .width , - (float )subjectTarget .texture .height };
62+ Rectangle subjectDest = { (float )splitWidth , 0.0f , (float )splitWidth , (float )screenHeight };
63+ const float textureAspectRatio = (float )subjectTarget .texture .width /(float )subjectTarget .texture .height ;
64+
65+ // Rectangles for cropping render texture
66+ const float captureSize = 128.0f ;
67+ Rectangle cropSource = { (subjectTarget .texture .width - captureSize )/2.0f , (subjectTarget .texture .height - captureSize )/2.0f , captureSize , - captureSize };
68+ Rectangle cropDest = { splitWidth + 20 , 20 , captureSize , captureSize };
69+
70+ SetTargetFPS (60 );
71+ DisableCursor ();
72+ //--------------------------------------------------------------------------------------
73+
74+ // Main game loop
75+ while (!WindowShouldClose ()) // Detect window close button or ESC key
76+ {
77+ // Update
78+ //----------------------------------------------------------------------------------
79+ UpdateCamera (& observerCamera , CAMERA_FREE );
80+ UpdateCamera (& subjectCamera , CAMERA_ORBITAL );
81+
82+ if (IsKeyPressed (KEY_R )) observerCamera .target = (Vector3 ){ 0.0f , 0.0f , 0.0f };
83+
84+ // Build LHS observer view texture
85+ BeginTextureMode (observerTarget );
86+
87+ ClearBackground (RAYWHITE );
88+
89+ BeginMode3D (observerCamera );
90+
91+ DrawGrid (10 , 1.0f );
92+ DrawCube ((Vector3 ){ 0.0f , 0.0f , 0.0f }, 2.0f , 2.0f , 2.0f , GOLD );
93+ DrawCubeWires ((Vector3 ){ 0.0f , 0.0f , 0.0f }, 2.0f , 2.0f , 2.0f , PINK );
94+ DrawCameraPrism (subjectCamera , textureAspectRatio , GREEN );
95+
96+ EndMode3D ();
97+
98+ DrawText ("Observer View" , 10 , observerTarget .texture .height - 30 , 20 , BLACK );
99+ DrawText ("WASD + Mouse to Move" , 10 , 10 , 20 , DARKGRAY );
100+ DrawText ("Scroll to Zoom" , 10 , 30 , 20 , DARKGRAY );
101+ DrawText ("R to Reset Observer Target" , 10 , 50 , 20 , DARKGRAY );
102+
103+ EndTextureMode ();
104+
105+ // Build RHS subject view texture
106+ BeginTextureMode (subjectTarget );
107+
108+ ClearBackground (RAYWHITE );
109+
110+ BeginMode3D (subjectCamera );
111+
112+ DrawCube ((Vector3 ){ 0.0f , 0.0f , 0.0f }, 2.0f , 2.0f , 2.0f , GOLD );
113+ DrawCubeWires ((Vector3 ){ 0.0f , 0.0f , 0.0f }, 2.0f , 2.0f , 2.0f , PINK );
114+ DrawGrid (10 , 1.0f );
115+
116+ EndMode3D ();
117+
118+ DrawRectangleLines ((subjectTarget .texture .width - captureSize )/2 , (subjectTarget .texture .height - captureSize )/2 , captureSize , captureSize , GREEN );
119+ DrawText ("Subject View" , 10 , subjectTarget .texture .height - 30 , 20 , BLACK );
120+
121+ EndTextureMode ();
122+ //----------------------------------------------------------------------------------
123+
124+ // Draw
125+ //----------------------------------------------------------------------------------
126+ BeginDrawing ();
127+
128+ ClearBackground (BLACK );
129+
130+ // Draw observer texture LHS
131+ DrawTexturePro (observerTarget .texture , observerSource , observerDest , (Vector2 ){0.0f , 0.0f }, 0.0f , WHITE );
132+
133+ // Draw subject texture RHS
134+ DrawTexturePro (subjectTarget .texture , subjectSource , subjectDest , (Vector2 ){ 0.0f , 0.0f }, 0.0f , WHITE );
135+
136+ // Draw the small crop overlay on top
137+ DrawTexturePro (subjectTarget .texture , cropSource , cropDest , (Vector2 ){ 0.0f , 0.0f }, 0.0f , WHITE );
138+ DrawRectangleLinesEx (cropDest , 2 , BLACK );
139+
140+ // Draw split screen divider line
141+ DrawLine (splitWidth , 0 , splitWidth , screenHeight , BLACK );
142+
143+ EndDrawing ();
144+ //----------------------------------------------------------------------------------
145+ }
146+
147+ // De-Initialization
148+ //--------------------------------------------------------------------------------------
149+ UnloadRenderTexture (observerTarget );
150+ UnloadRenderTexture (subjectTarget );
151+ CloseWindow (); // Close window and OpenGL context
152+ //--------------------------------------------------------------------------------------
153+
154+ return 0 ;
155+ }
156+
157+ //----------------------------------------------------------------------------------
158+ // Module Functions Definition
159+ //----------------------------------------------------------------------------------
160+ static void DrawCameraPrism (Camera3D camera , float aspect , Color color )
161+ {
162+ float length = Vector3Distance (camera .position , camera .target );
163+ // Define the 4 corners of the camera's prism plane sliced at the target in Normalized Device Coordinates
164+ Vector3 planeNDC [4 ] = {
165+ { -1.0f , -1.0f , 1.0f }, // Bottom Left
166+ { 1.0f , -1.0f , 1.0f }, // Bottom Right
167+ { 1.0f , 1.0f , 1.0f }, // Top Right
168+ { -1.0f , 1.0f , 1.0f } // Top Left
169+ };
170+
171+ // Build the matrices
172+ Matrix view = GetCameraMatrix (camera );
173+ Matrix proj = MatrixPerspective (camera .fovy * DEG2RAD , aspect , 0.05f , length );
174+ // Combine view and projection so we can reverse the full camera transform
175+ Matrix viewProj = MatrixMultiply (view , proj );
176+ // Invert the view-projection matrix to unproject points from NDC space back into world space
177+ Matrix inverseViewProj = MatrixInvert (viewProj );
178+
179+ // Transform the 4 plane corners from NDC into world space
180+ Vector3 corners [4 ];
181+ for (int i = 0 ; i < 4 ; i ++ )
182+ {
183+ float x = planeNDC [i ].x ;
184+ float y = planeNDC [i ].y ;
185+ float z = planeNDC [i ].z ;
186+
187+ // Multiply NDC position by the inverse view-projection matrix
188+ // This produces a homogeneous (x, y, z, w) position in world space
189+ float vx = inverseViewProj .m0 * x + inverseViewProj .m4 * y + inverseViewProj .m8 * z + inverseViewProj .m12 ;
190+ float vy = inverseViewProj .m1 * x + inverseViewProj .m5 * y + inverseViewProj .m9 * z + inverseViewProj .m13 ;
191+ float vz = inverseViewProj .m2 * x + inverseViewProj .m6 * y + inverseViewProj .m10 * z + inverseViewProj .m14 ;
192+ float vw = inverseViewProj .m3 * x + inverseViewProj .m7 * y + inverseViewProj .m11 * z + inverseViewProj .m15 ;
193+
194+ corners [i ] = (Vector3 ){ vx /vw , vy /vw , vz /vw };
195+ }
196+
197+ // Draw the far plane sliced at the target
198+ DrawLine3D (corners [0 ], corners [1 ], color );
199+ DrawLine3D (corners [1 ], corners [2 ], color );
200+ DrawLine3D (corners [2 ], corners [3 ], color );
201+ DrawLine3D (corners [3 ], corners [0 ], color );
202+
203+ // Draw the prism lines from the far plane to the camera position
204+ for (int i = 0 ; i < 4 ; i ++ )
205+ {
206+ DrawLine3D (camera .position , corners [i ], color );
207+ }
208+ }
0 commit comments