# ==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]]