u/Mkcuriosity

▲ 19 r/threejs

Threejs café: a rainy 90s arcade café you can walk around, with playable cabinets and easter eggs!

My games had no home, so I built them a rainy cafe. Walk in, every cabinet opens a real game, best scores go up on the walls in some games.

https://threejscafe.com/

Feedback welcome, especially if it runs badly on your machine.

u/Mkcuriosity — 14 days ago
▲ 136 r/threejs

I built six rainy worlds you can walk through in the browser

Six worlds where the rain never stops: a forest, a headland, a night drive, a canyon on a bike, a wheat field and a mountain pass. Everything runs in the browser with three.js WebGPU and TSL, no download, no account.

A few things I'm proud of: the rain sound is synthesized live (noise shaped to a real recording's spectrum, thousands of drop bursts a second), the windshield drops refract the road and the wipers actually clear them, and the mountain pass draws real terrain out to 3 km in the vertex shader. Press Y, paste a YouTube link, walk in the rain.

https://rainyworlds.com

It's a GPU experiment, so a desktop with Chrome is the best seat. Phones fall back to WebGL2 and struggle, especially Safari. First load compiles a lot of shaders, give it a minute on Windows.

Happy to answer anything about the tech.

u/Mkcuriosity — 1 month ago

I'm a Galatasaray fan, so I turned the entire squad into trading cards

I'm a Galatasaray fan and I finally did the thing I have been meaning to do for years. The whole 2026-27 squad as holographic trading cards. 35 of them.

The last two cards are Okan Buruk and Dursun Özbek. The president has a power rating of 99, and where the market value should be, his card says "he sets them".

We signed Bitshiabu while I was still building it, so he got a card before he got a shirt number.

To see the whole deck: gs2026.murat.works

Free, no sign up, English and Turkish.

I share most of what I build on X if you are into this sort of thing: https://x.com/mkcuriosity

u/Mkcuriosity — 1 month ago

Taxistanbul, drive a yellow cab around a low-poly Istanbul, in your browser, no install.

Taxistanbul, a low-poly Istanbul taxi sim running in the browser

Play it: https://taxi.murat.works/ (desktop, no install, no login) Steam page: https://store.steampowered.com/app/5004230/Taxistanbul_Istanbul_Taxi_Simulator/

I started this in the browser just to see if I could build an open city in three.js. Somewhere along the way it stopped being an experiment, so I'm taking it to Steam and building it properly.

The browser version stays up as the early prototype it is. I want to be upfront about that before anything else: there are plenty of bugs, missing art and half-finished corners in it. I'm sharing it anyway, because I'd rather put something you can actually drive in your hands than polish in silence while the real version gets built.

What's actually in it

You drive a yellow cab. The phone rings, you take the fare, the meter runs, and you get paid, minus everything the city takes back.

  • Fares and conversation. Passengers talk to you en route and you pick replies. Clean driving plus decent manners gets you five stars and a tip. A filthy cab and a lead foot gets you a one-star review that goes on your permanent profile.
  • A cab that degrades. Fuel, body damage, dirt and driver hunger all tick down. Gas stations, garages and car washes are real locations on the map. When a gauge crosses its threshold, a route to the nearest one draws itself on the minimap in that service's colour.
  • A phone with apps. Contacts (30 people, some of whom call you with jobs), a bank app holding your traffic fines, which accrue 1% daily compound interest until you pay them, a photo mode with filters, and a gallery.
  • A car radio that streams actual live Istanbul stations.
  • On foot. Get out, walk around, pet the cats, walk into a gas station shop and buy a sandwich.
  • A day/night cycle (10 real minutes) with four weather states, plus street lamps that actually light the ground at night.
  • Landmarks. Galata Tower, Hagia Sophia, the Blue Mosque, Topkapı, the Maiden's Tower, the Grand Bazaar, ferries crossing the water.
  • Breakable guardrails, because I couldn't help myself.

How it's built

three.js, TypeScript and Vite. No engine, no framework, no physics library. Driving is arcade, collision is circle against AABB.

The city comes from a map I drew. There's a 2D editor in the project: bezier roads with anchors and handles, coastline polygons, painted zones, pins for landmarks. It exports a JSON draft that the world builder turns into 3D. Roads become ribbon geometry with trimmed sidewalks, coast polygons become land with rocks and quays, and zones decide what kind of buildings spawn where.

Buildings are generated at runtime. Seven body forms crossed with colour pools, shop types, and canvas-texture signs written on the fly. Every page load gives you a different Istanbul. There's no building library to ship, just the generator.

Everything is instanced or batched. Buildings live in a BatchedMesh per material. Trees, props, coastal rocks, guardrails and street furniture go through a spatial chunking helper that splits a matrix list into a grid and emits one InstancedMesh per cell.

The performance part, which is the useful bit

The frame was costing ~14M triangles. It's now around 3M at street level. Four things got me there, and the first one is a trap I think a lot of three.js projects fall into.

1. A city-wide InstancedMesh is never frustum culled

I had roughly 74,000 trees in a handful of InstancedMeshes covering the whole map. Frustum culling tests an object's bounding sphere, and for an InstancedMesh that sphere encloses every instance. So it was map-sized, it always intersected the frustum, and every single tree behind the camera got submitted every frame. Same story for 59,000 coastal pebbles, and for all the street signs, kerbs and railings.

The fix is to chunk the instances into a spatial grid (I use 240m to 500m cells depending on the prop) and emit one InstancedMesh per cell, each with its own tight bounding sphere. Suddenly culling actually does something. It costs a few more draw calls and buys back millions of triangles.

2. The shadow pass was four times the main pass

Measured at street level: 6.84M triangles in the shadow pass against 1.77M in the main pass. Shrinking the shadow camera box changed nothing, which was the clue. The cost wasn't inside the box, it was objects too big to be culled out of it.

Two causes. First, flat sheets casting shadows. My castShadow heuristic used bounding box height, but road, pavement and terrain meshes follow the hills, so a city-spanning asphalt ribbon has a 60m tall bbox and reads as a tall object. They were casting shadows nobody can see, since a ground-hugging surface's own shadow is invisible and the shadows landing on it come from receiveShadow anyway. Excluding wide flat sheets cut 2.5M triangles.

Second, chunks clipping the shadow box. A 300m chunk whose sphere merely touches the 110m shadow box renders all of its instances into the shadow map. I now test each chunk's sphere against sun.shadow.getFrustum() every frame and switch castShadow off for the ones outside. This is visually free, because those objects were writing nothing to begin with.

The shadow pass went from 6.84M to 0.11M.

3. Three-stage building LOD with setGeometryIdAt

BatchedMesh lets each instance point at a different geometry inside the batch, which makes distance LOD almost free. No extra draw calls, because all three variants live in the same batch.

  • 0 to 260m: the full building.
  • 260 to 620m: a mass version. My generator builds each building out of small boxes, so I filter the parts by size at merge time. Anything under 2.6m on its longest edge (windows, frames, pipes, water tanks) gets dropped, leaving body and roof. The silhouette is identical, and the windows were most of the triangles.
  • 620m and beyond: a 12-triangle envelope box, vertex-coloured from the source, with roof colour on the top face and wall colour on the sides.

Buildings went from 4.2M to about 1M. At street level 3,508 of 4,373 buildings are boxes and you can't tell.

4. The models themselves were absurd

The GLB assets I was using came in at 10,922 vertices for a park bench, next to a properly modelled street lamp at 190. I ran the whole set through meshopt simplification and got 501k triangles down to 254k. The files halved too, which also cut load time.

One more that cost me an afternoon

Switching to the cockpit view caused a hard 2 to 3 second freeze. It turns out three uses a different shader program variant when rendering to a render target, because outputColorSpace differs, so the first frame of the rear-view mirror recompiled every material in the city. The fix was to bind the mirror's render target during loading and call compileAsync there, behind the progress bar.

Where it goes

The Steam version is the real one, and that's where the work is going now. Native, no browser ceiling on memory or shader compilation, and all the things I keep having to cut here. If you want to see it happen, a wishlist genuinely moves the needle:

https://store.steampowered.com/app/5004230/Taxistanbul_Istanbul_Taxi_Simulator/

And if something breaks in the browser version, and it will, I'd love to hear exactly what and where.

u/Mkcuriosity — 1 month ago
▲ 86 r/threejs

My wife's Dyson has a green laser that shows the dust. I turned it into a game.

The first time I used it I thought I was hallucinating. The floor was clean, I flicked the laser on, and suddenly there was a galaxy down there.

It is just a grazing angle. Light comes in almost parallel to the floor, so every speck throws a shadow far longer than itself. That one trick is the entire game.

https://cleaner.murat.works (free, browser, no install)

One flat, one battery, clean as much as you can before it dies.

---

**How it works, since this is r/threejs**

**The dirt is a render target.**
Room-local UV, and the cleaner head paints into it with subtractive blending, so cleaning happens entirely on the GPU with no readback. A coarse CPU grid gets eroded with the same falloff in the same frame, and that is what answers "how much is left", so the percentage never costs a stall.

**The laser is not a light.**
It is about fifteen lines in the floor shader: an angular fan, a band at a fixed distance, and a hash per grain so the dust twinkles as the beam sweeps over it. Out of the beam the floor is rendered at roughly 30% of its real filth, which is what makes the reveal work.

**Two machines, two routes to the same dirt.**
The stick vacuum shoves furniture aside. The robot is too weak for that, so it drives underneath instead. I measured it: 15.9% of all the dirt sits under things a robot can neither climb nor move, so robot runs are scored against the floor a robot can actually reach. Otherwise the mode caps out around 84% and looks broken.

**The telly plays real YouTube.**
Video frames cannot be read into a texture, so it is a genuine iframe placed in 3D with CSS3DRenderer, sitting behind the canvas. The screen mesh switches to a depth-only material and punches a hole for it to show through, so things in front still occlude it properly. That took a while to get right, because three sorts opaque draws by material id before distance, and the wall behind the telly kept winning the race and filling the hole. renderOrder = -1 on the hole fixed it.

**There is a floor plan editor.**
Draw rooms, cut doorways, drag furniture from a palette of 43 pieces, then play your own house and share it by link. Houses are plain JSON, and the flat that ships with the game is expressed in exactly the same format, so there is only one code path.

**Balance came from measurement, not feel.**
A perfect run needs 357 m of sweeping and the battery gives you about 280 s of suction. Nobody has hit 100% yet. The best run so far is 97.9%.

No assets in the whole thing. Wood grain, the artwork on the walls, every sound effect, all generated at runtime.

---

Happy to go deeper on any of it.

I post updates on the game also here: x.com/mkcuriosity

u/Mkcuriosity — 2 months ago

Godot & Claude Fable works amazing! I'm developing an elemental benders game!

So im a web developer. Since 2025 im developing small browser games with three.js.
For the last 2 months i migrated to Godot engine for my games and it really leveled up like hell! Here's a quick gameplay demo from my latest project KAM.

Four elements. Four bodies. One soul.

In KAM (working title) you are a shaman with four bodies: Hurl enemies with the wind itself, crack the earth open beneath them, whip fire like water, blink across the branches of giant trees. Burning runes, a sky that never sleeps.

Made with Godot + Claude.. Also so much prompting and optimizing from myself xD

Mostly i share building updates on X : https://x.com/mkcuriosity

u/Mkcuriosity — 3 months ago