Raylib for 3D
// status: budding
Raylib 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 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.hand 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 styledrayguiis 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, raw OpenGL or Vulkan gives you more control.
The hello-cube
int 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 aMeshorModelwhen persistent geometry is the better fit. - Meshes & models — persistent GPU buffers.
Mesh,Material, andModelstructs. 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:
typedef struct Mesh 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:
Mesh sphere = ;
Mesh torus = ;
Mesh knot = ;
Mesh plane = ; // subdivided
Mesh hf = ;
Mesh cubicmap = ;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:
Model model = ;
model.materials.maps.texture = ;
;
;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 PBR, you ship a custom shader and wire the required maps and uniforms yourself. The raylib-extras organisation collects useful community examples and helpers.
Shaders
Shader is a vertex+fragment program pair loaded from files:
Shader sh = ;
int locTime = ;
model.materials.shader = sh;
// in the draw loop
float t = ;
;
;Locations raylib discovers automatically when a custom shader uses its conventional names:
mvp— full MVP matrix.matModel,matView,matProjection— components.colDiffuse— the tint passed toDrawModel.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:
Shader sh = ; // default VS, custom FSCross-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:
Shader sh = ;
sh.locs = ;
int ambientLoc = ;
float ambient = ;
;
Light lights = ;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
Texture2D tex = ;
Image img = ; // CPU-side
RenderTexture2D rt = ; // FBORender-to-texture is the gateway to post-processing:
;
;
;
;
;
;
;
;
;
;
;
;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:
Matrix *transforms = ;
for
transforms = ;
Shader instanceSh = ;
instanceSh.locs = ;
instanceSh.locs =
;
Material mat = ;
mat.shader = instanceSh;
;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:
;
;
;
;
;
;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:
- Load an image in a supported cubemap layout with
LoadTextureCubemap, or convert an equirectangular panorama to a cubemap with a shader pass. - Create a unit cube mesh and assign a material with a custom shader that samples the cubemap.
- 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 —
DrawTextureRecwithheight = -texture.heightis 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 —
LoadModelresolves referenced textures from the model's directory. Keep the expected asset layout intact or wire textures explicitly. - Shader uniforms must be set while bound —
SetShaderValuebinds the shader implicitly, but cache the locations withGetShaderLocationonce, 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 —
BeginMode3Dswitches projection/view state and enables depth testing. If you mixrlglor 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_ELEMENTScontrol 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 — immediate-mode UI in the same spirit.
- raylib-extras — community extras: PBR, particle systems, glTF tooling, camera controllers.
- rres — Raylib's resource packaging format.
- raymath.h — the built-in math lib. Quaternions, matrices, SLERP, everything you'd want.
References
- raylib.com — docs and API cheatsheet.
- raylib examples — the
models/andshaders/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 · see also Ray Picking Through the Rendering Pipeline · real-time anti-aliasing · Debugging Graphics Without Guessing · Raym