# ==Raylib== for 3D <p class="doc-sub">// status: budding</p> [Raylib](https://www.raylib.com/) is Ramon Santamaria's "videogame programming without the fuss" library — a thin C layer over OpenGL (desktop 2.1/3.3/4.3, ES 2.0/3.0, or WebGL) that gets a triangle on screen in about ten lines. I used it in [[Raym - Interactive Terrain Generation with Marching Cubes|Raym]] to focus on the algorithm rather than the boilerplate, and these are the bits of its 3D side worth writing down. ## Why pick raylib for 3D - **Small C API** — include `raylib.h` and link the compiled static/shared library. No framework-sized build-system archaeology. - **Batteries included** — windowing, input, audio, file I/O, image loading, and a math library (`raymath.h`). The similarly styled [`raygui`](https://github.com/raysan5/raygui) is a separate companion library. - **Readable internals** — the code is genuinely small and well-commented. When you hit a weird behaviour, reading the source is faster than searching for docs. - **Portability** — same code compiles to desktop, web (emscripten), Android, and RPi. Where it stops being the right tool: anything that needs a modern GPU pipeline (raylib's rlgl compute path targets OpenGL 4.3), large-scene culling, or a more involved lighting architecture. At that point, [[OpenGL - learning log|raw OpenGL]] or [[Vulkan - learning log|Vulkan]] gives you more control. ## The hello-cube ```c #include "raylib.h" int main(void) { InitWindow(1280, 720, "hello 3d"); SetTargetFPS(60); Camera3D camera = { .position = { 6.0f, 6.0f, 6.0f }, .target = { 0.0f, 0.0f, 0.0f }, .up = { 0.0f, 1.0f, 0.0f }, .fovy = 45.0f, .projection = CAMERA_PERSPECTIVE, }; while (!WindowShouldClose()) { UpdateCamera(&camera, CAMERA_ORBITAL); BeginDrawing(); ClearBackground(RAYWHITE); BeginMode3D(camera); DrawCube((Vector3){0,0,0}, 2,2,2, MAROON); DrawCubeWires((Vector3){0,0,0}, 2,2,2, BLACK); DrawGrid(10, 1.0f); EndMode3D(); DrawFPS(10, 10); EndDrawing(); } CloseWindow(); return 0; } ``` Everything inside `BeginMode3D(camera)` / `EndMode3D()` uses the 3D pipeline. Outside, it's 2D and UI. ## Cameras `Camera3D` is a plain struct. Five built-in modes via `UpdateCamera(&camera, mode)`: - `CAMERA_FREE` — fly-through with mouse + WASD + E/Q. - `CAMERA_ORBITAL` — auto-orbits the target. Great for demos. - `CAMERA_FIRST_PERSON` — mouselook + WASD, stays on ground. - `CAMERA_THIRD_PERSON` — follows a target with mouse orbit. - `CAMERA_CUSTOM` — do nothing, you drive it yourself. For anything real, bypass `UpdateCamera` and move position/target yourself — the built-in modes are prototype-grade. The camera exposes its matrices via `GetCameraMatrix` and `GetCameraProjectionMatrix` when you need them. ## What to draw Two families of API sit on top of the same mesh pipeline: - **Immediate-mode primitives** — `DrawCube`, `DrawSphere`, `DrawCylinder`, `DrawPlane`, `DrawLine3D`, `DrawRay`, `DrawBoundingBox`, `DrawGrid`. Good for debug viz, placeholder geometry, and gizmos. These helpers submit geometry through rlgl's internal path; reuse a `Mesh` or `Model` when persistent geometry is the better fit. - **Meshes & models** — persistent GPU buffers. `Mesh`, `Material`, and `Model` structs. This is what you want for anything drawn more than once. `DrawGrid` and `DrawRay` are the two I keep reaching for while debugging. Any time a 3D coordinate seems off, dropping a `DrawSphere` at that point answers it faster than a printf. ## Meshes A `Mesh` is a flat struct of CPU-side geometry arrays plus GPU object IDs: ```c typedef struct Mesh { int vertexCount; int triangleCount; float *vertices; // positions (xyz) float *texcoords; // (uv) float *normals; // (xyz) float *colors; // optional (rgba) unsigned short *indices; // optional // ... boneIds, boneWeights, tangents, texcoords2, animVertices, animNormals ... unsigned int vaoId; unsigned int *vboId; } Mesh; ``` `UploadMesh(&mesh, dynamic)` copies CPU-side arrays to GPU buffers. `UpdateMeshBuffer(mesh, i, data, size, offset)` patches one buffer for dynamic geometry. `UnloadMesh(mesh)` frees both sides. Built-in generators for the usual primitives: ```c Mesh sphere = GenMeshSphere(1.0f, 32, 32); Mesh torus = GenMeshTorus(0.3f, 1.0f, 32, 24); Mesh knot = GenMeshKnot(1.0f, 2.0f, 32, 24); Mesh plane = GenMeshPlane(10, 10, 10, 10); // subdivided Mesh hf = GenMeshHeightmap(imgHeight, (Vector3){16,4,16}); Mesh cubicmap = GenMeshCubicmap(imgMap, (Vector3){1,1,1}); ``` `GenMeshHeightmap` and `GenMeshCubicmap` are the "terrain in one line" shortcuts — the first turns a grayscale image into a plane with displaced Y, the second turns a black-and-white image into blocky stacked cubes. For anything organic, generate your own vertex arrays and call `UploadMesh`. ## Models & materials A `Model` bundles one or more meshes with materials and an optional skeleton: ```c Model model = LoadModel("castle.glb"); model.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = LoadTexture("castle_diffuse.png"); DrawModel(model, (Vector3){0,0,0}, 1.0f, WHITE); UnloadModel(model); ``` Depending on the build configuration, current raylib supports OBJ, IQM (skeletal animation), glTF/glb, VOX (MagicaVoxel), and M3D. glTF/glb is a practical interchange format for an asset pipeline. A `Material` is a default-shader + up to 12 texture `maps`: | Map | Slot | |-----|------| | `MATERIAL_MAP_DIFFUSE` / `ALBEDO` | base colour | | `MATERIAL_MAP_METALNESS` | metallic | | `MATERIAL_MAP_NORMAL` | normal map | | `MATERIAL_MAP_ROUGHNESS` | roughness | | `MATERIAL_MAP_OCCLUSION` | AO | | `MATERIAL_MAP_EMISSION` | emissive | | `MATERIAL_MAP_HEIGHT` | height/parallax | | `MATERIAL_MAP_CUBEMAP` | environment | | `MATERIAL_MAP_IRRADIANCE` | diffuse IBL | | `MATERIAL_MAP_PREFILTER` | specular IBL | | `MATERIAL_MAP_BRDF` | split-sum LUT | The stock shader is unlit and primarily handles the base texture and tint. For lighting or [[Physically Based Rendering|PBR]], you ship a custom shader and wire the required maps and uniforms yourself. The [raylib-extras organisation](https://github.com/raylib-extras) collects useful community examples and helpers. ## Shaders `Shader` is a vertex+fragment program pair loaded from files: ```c Shader sh = LoadShader("shaders/mine.vs", "shaders/mine.fs"); int locTime = GetShaderLocation(sh, "uTime"); model.materials[0].shader = sh; // in the draw loop float t = (float)GetTime(); SetShaderValue(sh, locTime, &t, SHADER_UNIFORM_FLOAT); DrawModel(model, ...); ``` Locations raylib discovers automatically when a custom shader uses its conventional names: - `mvp` — full MVP matrix. - `matModel`, `matView`, `matProjection` — components. - `colDiffuse` — the tint passed to `DrawModel`. - `texture0`..`texture2` — up to three samplers. Other application-specific uniforms still need an explicit location. Camera-dependent examples commonly assign `SHADER_LOC_VECTOR_VIEW` themselves with `GetShaderLocation(sh, "viewPos")`. Pass `NULL` for either stage to use the built-in one: ```c Shader sh = LoadShader(NULL, "shaders/fragment.fs"); // default VS, custom FS ``` Cross-platform tip: the shader source still has to match the selected graphics backend. Raylib's examples usually choose separate GLSL 330 desktop and GLSL 100/300 ES files at compile time; `LoadShaderFromMemory` is useful when you prefer to assemble those variants in code. ## Lighting — `rlights.h` Raylib doesn't ship built-in lighting. The canonical sample adds `examples/shaders/rlights.h`, a small helper that plugs Blinn-Phong (or PBR, in the PBR sample) into a forward shader with up to four lights: ```c #include "rlights.h" Shader sh = LoadShader("shaders/lighting.vs", "shaders/lighting.fs"); sh.locs[SHADER_LOC_VECTOR_VIEW] = GetShaderLocation(sh, "viewPos"); int ambientLoc = GetShaderLocation(sh, "ambient"); float ambient[4] = { 0.1f, 0.1f, 0.1f, 1.0f }; SetShaderValue(sh, ambientLoc, ambient, SHADER_UNIFORM_VEC4); Light lights[MAX_LIGHTS] = { CreateLight(LIGHT_POINT, (Vector3){-2,1, 2}, Vector3Zero(), YELLOW, sh), CreateLight(LIGHT_DIRECTIONAL, (Vector3){ 2,2,-2}, Vector3Zero(), WHITE, sh), }; ``` For serious lighting you graduate to your own pipeline (see [[Deferred vs forward rendering]]). `rlights.h` is "demo quality" but perfect for small projects. ## Textures & render targets ```c Texture2D tex = LoadTexture("grass.png"); Image img = LoadImage("grass.png"); // CPU-side RenderTexture2D rt = LoadRenderTexture(1920, 1080); // FBO ``` Render-to-texture is the gateway to post-processing: ```c BeginTextureMode(rt); ClearBackground(BLACK); BeginMode3D(camera); DrawModel(model, ...); EndMode3D(); EndTextureMode(); BeginDrawing(); ClearBackground(BLACK); BeginShaderMode(postFX); DrawTextureRec(rt.texture, (Rectangle){0, 0, rt.texture.width, -rt.texture.height}, // Y flip! (Vector2){0, 0}, WHITE); EndShaderMode(); EndDrawing(); ``` The negative `height` is the Y-flip for the flipped FBO coordinate system — easy to forget and produces an "everything is upside down" bug that baffles for longer than it should. ## Instancing `DrawMeshInstanced` takes an array of transforms and a custom shader that reads them from the conventional `instanceTransform` `mat4` attribute. Use raylib's semantic location instead of hard-coding a numeric attribute index: ```c #define N 10000 Matrix *transforms = MemAlloc(sizeof(Matrix) * N); for (int i = 0; i < N; i++) transforms[i] = MatrixTranslate(randX(), 0, randZ()); Shader instanceSh = LoadShader("shaders/instance.vs", "shaders/default.fs"); instanceSh.locs[SHADER_LOC_MATRIX_MVP] = GetShaderLocation(instanceSh, "mvp"); instanceSh.locs[SHADER_LOC_VERTEX_INSTANCETRANSFORM] = GetShaderLocationAttrib(instanceSh, "instanceTransform"); Material mat = LoadMaterialDefault(); mat.shader = instanceSh; DrawMeshInstanced(cubeMesh, mat, transforms, N); ``` The instance VS multiplies by `instanceTransform` before MVP. Raylib uploads the transforms into a VBO each call — for fully static instance sets you'll want your own persistent SSBO, at which point you're dropping into rlgl. ## rlgl — the layer below `rlgl.h` is Raylib's ==OpenGL abstraction layer==: a thin wrapper that presents an immediate-mode interface (`rlBegin`, `rlVertex3f`, `rlColor4ub`, `rlEnd`) and batches draws internally. It is how `DrawCube` is implemented, and how you drop below the friendly API when you need to: - Build custom meshes vertex-by-vertex without touching OpenGL directly. - Push/pop matrix state (`rlPushMatrix`, `rlRotatef`, `rlTranslatef`) à la old GL. - Manage your own VBOs/VAOs/SSBOs (`rlLoadVertexBuffer`, `rlLoadVertexArray`). - Dispatch compute shaders (`rlLoadComputeShaderProgram`, `rlComputeShaderDispatch`) on GL 4.3+. When you need a modern feature Raylib doesn't expose at the top level, rlgl is almost always where it lives. If that fails, you can mix raw OpenGL into a raylib program — the GL context is just there. ### Custom mesh via rlgl The cleaner path is usually: build float arrays, call `UploadMesh`, done. When you need immediate-mode for viz: ```c rlBegin(RL_TRIANGLES); rlColor4ub(255, 0, 0, 255); rlVertex3f(0, 0, 0); rlVertex3f(1, 0, 0); rlVertex3f(0, 1, 0); rlEnd(); ``` Raylib's internal batcher flushes these when the primitive/type/texture changes, or when `EndMode3D`/`EndDrawing` runs. Keep all your immediate-mode calls contiguous per material to avoid unnecessary flushes. ## Skyboxes Standard cube-map skybox — the `examples/models/models_skybox_rendering.c` sample is the reference: 1. Load an image in a supported cubemap layout with `LoadTextureCubemap`, or convert an equirectangular panorama to a cubemap with a shader pass. 2. Create a unit cube mesh and assign a material with a custom shader that samples the cubemap. 3. Draw from inside the cube with back-face culling and depth writes temporarily disabled. The sample shader removes view translation so the skybox stays centred on the camera. HDR equirectangular → cubemap conversion is also one example away, using a `LoadShader` pass and an FBO. ## Things that tripped me up - **Y axis on render textures** — `DrawTextureRec` with `height = -texture.height` is the fix. The Raylib coordinate system is Y-up but FBO textures are stored Y-down. - **Repeated high-detail primitives add up** — for many spheres or other repeated shapes, profile the immediate helper and consider drawing a reused mesh or instanced model. - **Material slot 0 is the common case** — a successfully loaded model normally has a default material there, and most tutorials assume it. Multi-material models have the per-mesh material index in `model.meshMaterial[i]`. - **Model-relative assets matter** — `LoadModel` resolves referenced textures from the model's directory. Keep the expected asset layout intact or wire textures explicitly. - **Shader uniforms must be set while bound** — `SetShaderValue` binds the shader implicitly, but cache the locations with `GetShaderLocation` once, not per-frame. - **Y-up vs Y-down in glTF** — both coordinates conventions appear in the wild. A flipped normal map usually means your tangent space disagrees with the asset's. - **Render-state ownership** — `BeginMode3D` switches projection/view state and enables depth testing. If you mix `rlgl` or raw OpenGL calls with Raylib helpers, verify the state at the point of use instead of assuming every state is reset. - **Batch capacity is configurable** — rlgl batches immediate-mode geometry, and compile-time settings such as `RL_DEFAULT_BATCH_BUFFER_ELEMENTS` control its capacity. Defaults can change between Raylib versions, so check the version you build against before tuning them. - **rlgl compute is backend-specific** — raylib exposes it for the OpenGL 4.3 backend, not its WebGL path. Cross-platform projects need another implementation for that work. ## Ecosystem - **[raygui](https://github.com/raysan5/raygui)** — immediate-mode UI in the same spirit. - **[raylib-extras](https://github.com/raylib-extras)** — community extras: PBR, particle systems, glTF tooling, camera controllers. - **[rres](https://github.com/raysan5/rres)** — Raylib's resource packaging format. - **raymath.h** — the built-in math lib. Quaternions, matrices, SLERP, everything you'd want. ## References - [raylib.com](https://www.raylib.com/) — docs and API cheatsheet. - [raylib examples](https://github.com/raysan5/raylib/tree/master/examples/models) — the `models/` and `shaders/` folders are the most useful. - _Raylib cheatsheet_ — one-page API reference on the site. - Ramon Santamaria's talks on the philosophy and internals of Raylib. --- Back to [[Notes/Index|Notes]] · see also [[Ray Picking Through the Rendering Pipeline]] · [[Real-Time Anti-Aliasing - MSAA, FXAA, SMAA, and TAA|real-time anti-aliasing]] · [[Debugging Graphics Without Guessing]] · [[Raym - Interactive Terrain Generation with Marching Cubes|Raym]]