Get your first Materia 3D scene running in under 5 minutes.
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
mavenCentral()
}
}
// build.gradle.kts
plugins {
kotlin("multiplatform") version "2.1.20"
}
kotlin {
jvm()
js(IR) {
browser {
commonWebpackConfig {
outputFileName = "app.js"
}
}
binaries.executable()
}
sourceSets {
commonMain.dependencies {
implementation("io.materia:materia-engine:0.1.0-alpha01")
}
}
}
// build.gradle.kts
plugins {
kotlin("jvm") version "2.1.20"
application
}
dependencies {
implementation("io.materia:materia-engine:0.1.0-alpha01")
}
application {
mainClass.set("MainKt")
}
The unified API works identically on JVM and JS platforms:
import io.materia.engine.renderer.WebGPURenderer
import io.materia.engine.renderer.WebGPURendererConfig
import io.materia.engine.scene.Scene
import io.materia.engine.scene.EngineMesh
import io.materia.engine.camera.PerspectiveCamera
import io.materia.engine.material.BasicMaterial
import io.materia.engine.material.StandardMaterial
import io.materia.engine.core.RenderLoop
import io.materia.engine.core.DisposableContainer
import io.materia.engine.window.KmpWindow
import io.materia.engine.window.KmpWindowConfig
import io.materia.geometry.BufferGeometry
import io.materia.core.math.Color
import io.materia.core.math.Vector3
class SimpleScene {
private val resources = DisposableContainer()
// Create the scene graph
val scene = Scene()
// Create a perspective camera
val camera = PerspectiveCamera(
fov = 75f,
aspect = 16f / 9f,
near = 0.1f,
far = 1000f
)
// Track our animated cube
lateinit var cube: EngineMesh
fun setup() {
// Position the camera
camera.position.set(0f, 2f, 5f)
camera.lookAt(Vector3.ZERO)
// Create a simple box geometry
val geometry = createBoxGeometry(1f, 1f, 1f)
resources.track(geometry)
// Create a PBR material
val material = StandardMaterial(
color = Color(0f, 1f, 0f), // Green
metalness = 0.3f,
roughness = 0.4f
)
resources.track(material)
// Create mesh
cube = EngineMesh(geometry, material)
resources.track(cube)
scene.add(cube)
// Add a ground plane
val groundGeometry = createPlaneGeometry(10f, 10f)
val groundMaterial = StandardMaterial(
color = Color(0.5f, 0.5f, 0.5f),
roughness = 0.9f
)
resources.track(groundGeometry)
resources.track(groundMaterial)
val ground = EngineMesh(groundGeometry, groundMaterial)
ground.rotation.x = -Math.PI.toFloat() / 2 // Horizontal
ground.position.y = -0.5f
resources.track(ground)
scene.add(ground)
}
fun update(deltaTime: Float) {
// Rotate the cube
cube.rotateY(deltaTime * 0.5f)
cube.rotateX(deltaTime * 0.3f)
}
fun dispose() {
resources.dispose()
}
}
// Helper to create box geometry
fun createBoxGeometry(width: Float, height: Float, depth: Float): BufferGeometry {
val hw = width / 2
val hh = height / 2
val hd = depth / 2
return BufferGeometry().apply {
// Positions for a cube (6 faces, 2 triangles each)
setAttribute("position", floatArrayOf(
// Front face
-hw, -hh, hd, hw, -hh, hd, hw, hh, hd,
-hw, -hh, hd, hw, hh, hd, -hw, hh, hd,
// Back face
hw, -hh, -hd, -hw, -hh, -hd, -hw, hh, -hd,
hw, -hh, -hd, -hw, hh, -hd, hw, hh, -hd,
// Top face
-hw, hh, hd, hw, hh, hd, hw, hh, -hd,
-hw, hh, hd, hw, hh, -hd, -hw, hh, -hd,
// Bottom face
-hw, -hh, -hd, hw, -hh, -hd, hw, -hh, hd,
-hw, -hh, -hd, hw, -hh, hd, -hw, -hh, hd,
// Right face
hw, -hh, hd, hw, -hh, -hd, hw, hh, -hd,
hw, -hh, hd, hw, hh, -hd, hw, hh, hd,
// Left face
-hw, -hh, -hd, -hw, -hh, hd, -hw, hh, hd,
-hw, -hh, -hd, -hw, hh, hd, -hw, hh, -hd
), 3)
}
}
fun createPlaneGeometry(width: Float, height: Float): BufferGeometry {
val hw = width / 2
val hh = height / 2
return BufferGeometry().apply {
setAttribute("position", floatArrayOf(
-hw, 0f, -hh, hw, 0f, -hh, hw, 0f, hh,
-hw, 0f, -hh, hw, 0f, hh, -hw, 0f, hh
), 3)
}
}
Create src/jvmMain/kotlin/Main.kt:
import io.materia.engine.renderer.WebGPURenderer
import io.materia.engine.renderer.WebGPURendererConfig
import io.materia.engine.core.RenderLoop
import io.materia.engine.window.KmpWindow
import io.materia.engine.window.KmpWindowConfig
fun main() {
// Create window
val window = KmpWindow(KmpWindowConfig(
width = 1280,
height = 720,
title = "Materia Demo"
))
// Initialize renderer with unified WebGPU API
val renderer = WebGPURenderer(WebGPURendererConfig(
surface = window.surface,
width = 1280,
height = 720,
clearColor = floatArrayOf(0.1f, 0.1f, 0.15f, 1f)
))
// Create scene
val simpleScene = SimpleScene()
simpleScene.setup()
// Start render loop
val renderLoop = RenderLoop { deltaTime ->
window.pollEvents()
simpleScene.update(deltaTime)
renderer.render(simpleScene.scene, simpleScene.camera)
}
window.show()
renderLoop.start()
// Cleanup (when window closes)
renderLoop.stop()
simpleScene.dispose()
renderer.dispose()
window.dispose()
}
Create src/jsMain/kotlin/Main.kt:
import io.materia.engine.renderer.WebGPURenderer
import io.materia.engine.renderer.WebGPURendererConfig
import io.materia.engine.core.RenderLoop
import io.materia.engine.window.KmpWindow
import io.materia.engine.window.KmpWindowConfig
import kotlinx.browser.document
import org.w3c.dom.HTMLCanvasElement
fun main() {
val canvas = document.getElementById("canvas") as HTMLCanvasElement
// Create window wrapper for canvas
val window = KmpWindow(KmpWindowConfig(
width = canvas.width,
height = canvas.height,
canvas = canvas
))
// Initialize renderer (same API as JVM!)
val renderer = WebGPURenderer(WebGPURendererConfig(
surface = window.surface,
width = canvas.width,
height = canvas.height,
clearColor = floatArrayOf(0.1f, 0.1f, 0.15f, 1f)
))
// Create scene
val simpleScene = SimpleScene()
simpleScene.setup()
// Start render loop (uses requestAnimationFrame on JS)
val renderLoop = RenderLoop { deltaTime ->
simpleScene.update(deltaTime)
renderer.render(simpleScene.scene, simpleScene.camera)
}
renderLoop.start()
}
Create src/jsMain/resources/index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Materia Demo</title>
<style>
body { margin: 0; overflow: hidden; }
canvas { display: block; width: 100vw; height: 100vh; }
</style>
</head>
<body>
<canvas id="canvas" width="1280" height="720"></canvas>
<script src="app.js"></script>
</body>
</html>
# JVM (Desktop with Vulkan/WebGPU backend)
./gradlew jvmRun
# JavaScript (Browser with WebGPU)
./gradlew jsBrowserRun
Now that you have a basic scene running, explore these topics:
// Unlit basic material (for UI, debugging)
val unlitMaterial = BasicMaterial(
color = Color(1f, 0f, 0f) // Red
)
// PBR material with metallic look
val metalMaterial = StandardMaterial(
color = Color(0.9f, 0.9f, 0.95f),
metalness = 1.0f,
roughness = 0.2f
)
// Emissive glowing material
val glowMaterial = StandardMaterial(
color = Color(0.1f, 0.1f, 0.1f),
emissive = Color(0f, 1f, 0.5f),
emissiveIntensity = 2.0f
)
val geometry = createBoxGeometry(0.5f, 0.5f, 0.5f)
for (i in 0 until 10) {
val material = StandardMaterial(
color = Color(i / 10f, 0.5f, 1f - i / 10f),
metalness = i / 10f,
roughness = 1f - i / 10f
)
val mesh = EngineMesh(geometry, material)
mesh.position.x = (i - 5) * 1.2f
scene.add(mesh)
}
window.onResize { width, height ->
renderer.setSize(width, height)
camera.aspect = width.toFloat() / height
camera.updateProjectionMatrix()
}
val resources = DisposableContainer()
// Add resources as you create them
val material = StandardMaterial(color = Color.RED)
resources.track(material)
val geometry = createBoxGeometry(1f, 1f, 1f)
resources.track(geometry)
// Cleanup everything at once
resources.dispose()
The repository includes several example projects to learn from:
| Example | Description | Run Command | |---------|-------------|-------------| | Triangle | Basic triangle rendering | ./gradlew :examples:triangle:runJvm | | Basic Scene | Scene with multiple objects | ./gradlew :examples:basic-scene:runJvm | | VoxelCraft | Minecraft-style voxel world | ./gradlew :examples:voxelcraft:runJvm | | Force Graph | 3D force-directed graph | ./gradlew :examples:force-graph:runJvm | | Embedding Galaxy | Particle visualization | ./gradlew :examples:embedding-galaxy:runJvm |
For browser targets, append jsBrowserRun instead of runJvm:
./gradlew :examples:embedding-galaxy:jsBrowserRun
If you see "WebGPU not supported", ensure:
chrome://flags/#enable-unsafe-webgpu if neededdom.webgpu.enabled flagIf you get Vulkan initialization errors:
VK_ICD_FILENAMES environment variable on LinuxIf you see shader compilation errors on JVM:
resources/shaders/AGENTS.md for shader management detailsCommon causes:
camera.lookAt())near/far planes)