Materials and Lighting
In the previous tutorial we used MaterialData.unlit() — a flat color
with no lighting. Real 3D scenes need PBR (Physically Based Rendering)
materials that respond to light.
This tutorial shows:
- PBR materials with roughness and metallic properties
- Multiple entities in one scene
- How different material properties affect appearance
- Simple animation using the update loop
We store entity references as fields so update() can animate them.
1
2
3
4
5
6
7
private Entity roughSphere;
private Entity metallicSphere;
private Entity ground;
@Override
protected void init() {
camera().lookAt(new Vec3(0, 3, 8), Vec3.ZERO, Vec3.UNIT_Y);
PBR Materials
MaterialData.pbr(albedo, roughness, metallic) creates a
physically-based material:
- albedo — the base color (Vec3, 0–1 per channel)
- roughness — 0.0 = mirror-smooth, 1.0 = completely rough
- metallic — 0.0 = dielectric (plastic/wood), 1.0 = metal
The engine’s Slang shaders compute lighting automatically using these properties.
A rough orange sphere — like clay or terracotta. High roughness (0.9) means light scatters broadly.
1
2
3
4
5
6
7
8
roughSphere = scene().createEntity();
roughSphere.add(PrimitiveMeshes.sphere());
roughSphere.add(MaterialData.pbr(
new Vec3(0.8f, 0.4f, 0.1f), // warm orange
0.9f, // very rough
0.0f // not metallic
));
roughSphere.add(Transform.at(-2, 1, 0));
A shiny metallic sphere — like polished chrome. Low roughness (0.1) means sharp reflections. High metallic (0.9) means the color tints reflections.
1
2
3
4
5
6
7
8
metallicSphere = scene().createEntity();
metallicSphere.add(PrimitiveMeshes.sphere());
metallicSphere.add(MaterialData.pbr(
new Vec3(0.9f, 0.9f, 0.9f), // silver-white
0.1f, // very smooth
0.9f // highly metallic
));
metallicSphere.add(Transform.at(2, 1, 0));
Ground plane
A large flat plane gives the scene a sense of space and catches light/shadows. We use medium roughness for a matte floor look.
1
2
3
4
5
6
7
8
9
ground = scene().createEntity();
ground.add(PrimitiveMeshes.plane(10, 10));
ground.add(MaterialData.pbr(
new Vec3(0.3f, 0.3f, 0.3f), // dark grey
0.8f, // fairly rough
0.0f // non-metallic
));
ground.add(Transform.at(0, 0, 0));
}
Animation
The update() method runs every frame. deltaTime is the time
since the last frame in seconds (typically ~0.016 for 60 FPS).
Here we make the spheres gently bob up and down using sine waves.
time() returns the total elapsed time since the app started.
1
2
3
4
5
@Override
protected void update(float deltaTime) {
float t = (float) time();
float aspect = (float) window().width() / Math.max(window().height(), 1);
camera().setPerspective((float) Math.toRadians(60), aspect, 0.1f, 100f);
Animate the rough sphere: bob up and down
1
2
roughSphere.update(Transform.class, tr ->
tr.withPosition(-2, 1 + (float) Math.sin(t) * 0.3f, 0));
Animate the metallic sphere: bob with a phase offset
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
metallicSphere.update(Transform.class, tr ->
tr.withPosition(2, 1 + (float) Math.sin(t + Math.PI) * 0.3f, 0));
}
public static void main(String[] args) {
var config = EngineConfig.builder()
.window(WindowDescriptor.builder("Tutorial 02 — Materials and Lighting").size(1280, 720).build())
.platform(DesktopPlatform.builder().build())
.graphicsBackend(OpenGlBackend.factory(
new dev.engine.windowing.glfw.GlfwWindowToolkit(dev.engine.windowing.glfw.GlfwWindowToolkit.OPENGL_HINTS),
new dev.engine.providers.lwjgl.graphics.opengl.LwjglGlBindings()))
.build();
new T02_MaterialsAndLighting().launch(config);
}