Skip to content

anuvgupta/cityscape

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

50 Commits
 
 
 
 

Repository files navigation

Procedural 3D Cityscape in WebGL — Notes

Date: May 15, 2026

Question

Is it possible to procedurally generate a 3D cityscape with nice shaders for sunlight and maybe nice grass with wind as well, in a web browser (WebGL)? And have it look good and run on iPad browser?

Feasibility Assessment

Yes, all of that is very doable in WebGL, and iPad Safari handles it well as long as you budget for mobile GPU limits. Three.js is the practical choice — it gives you WebGL2, instanced meshes, post-processing, and a sky/sun shader (the built-in Sky shader does Preetham atmospheric scattering and looks great with a directional light driving shadows).

City

Instanced boxes with procedural facade textures (or shader-generated window patterns) let you render thousands of buildings in one draw call.

Grass

The standard technique is instanced blade geometry where a vertex shader bends each blade using sampled noise + time. Three.js demos like "grass" by Mr.doob and several Shadertoy ports run this pattern.

Sunlight & Shadows

Cascaded shadow maps or a single shadow map with a tight frustum around the camera give you nice sunlight contact shadows.

iPad-Specific Guidelines

  • Stick to WebGL2 (supported since iOS 15)
  • Keep canvas at devicePixelRatio capped around 1.5
  • Use a single shadow map at 1024–2048px
  • Instance everything
  • Avoid heavy post-processing (one bloom pass is fine, SSAO usually isn't)
  • Aim for ~30–50k instances total
  • Expect 60fps on M-series iPad, 30fps on older ones

Prototyping Setup

Single HTML file, Three.js via CDN import map, all code in one <script type="module"> block. No build step, no npm — just save and refresh.

Current State (v0.38)

Rendering / Scene

  • Three.js (r166) loaded from CDN, single-file ES module
  • WebGL renderer, sRGB output, devicePixelRatio capped at 1.5
  • Brighter #de5b68 sky; fog disabled for now — will revisit once the horizon look is decided — v0.27
  • Camera far plane bumped to 3000 so the ground disc renders all the way to its real edge — v0.26
  • Ambient + directional lighting (no shadows yet)
  • #541628 circular ground disc (1600-unit diameter cylinder, 1 unit thick, top flush with y=0) — v0.27
  • Camera starts further back at (250, 175, 250); MAX_DISTANCE bumped to 700 — v0.22

Procedural City

  • Grid oversized to ~2.2× target buildings so some lots stay empty and multi-cell footprints fit — v0.8
  • Per-row / per-column road widths jittered around the base 3-unit spacing (ROAD ± 1.6) — v0.8
  • Variable footprints: ~55% 1×1, ~15% 2×1, ~15% 1×2, ~15% 2×2, so some buildings are squares, some rectangles, some big blocks — v0.8
  • Random cell-fill fraction per building (small lots 55–90%, larger lots 78–96%)
  • Random heights bucketed into four tiers — skyscrapers (50–80, 2× tall), tall (25–40), medium (8–15), small (3–7) — with distribution controlled by sliders — v0.22
  • 95% of skyscrapers and 75% of tall buildings are placed into cluster centers (count controlled by the Clusters slider, centers constrained to the inner 3/4 of the city radius and spread apart via take-the-farthest sampling, radius scaled to expected per-cluster cell count); the rest plus all medium/small buildings scatter randomly — v0.31
  • All buildings share a single MeshStandardMaterial tinted #ed2651v0.9
  • Window shader (onBeforeCompile on the building material) draws rectangular windows on side faces only, aligned to a world-space 1.4×1.6 grid, with ~45% of cells "lit" via a hash and the bottom 1.5 units skipped so ground floors stay solid; each lit window's color mixes per-cell between warm yellow #ffd040 and orange #ff7a00, routed through emissive so they stay bright on shaded faces instead of getting dimmed by the standard lighting — v0.38
  • "Generate" button reseeds the layout

Generation Settings (v0.7)

  • Collapsible panel (chevron toggle), defaults collapsed so only Generate + chevron are visible
  • Buildings slider: total count 1–1000 (default 700) — v0.27
  • Clusters slider: number of skyscraper/tall clusters 1–5 (default 1) — v0.33
  • Skyscrapers / Tall / Medium sliders: percentages, scaled-down proportionally when their sum would exceed 100 (defaults 5% / 17% / 50%, leaving 28% small) — v0.32
  • Small slider: disabled, auto-updates to 100 − skyscrapers − tall − medium
  • Expanded panel max-height is 1500px and has 10px padding-bottom so the last slider's thumb isn't clipped by the overflow: hidden used for the collapse animation — v0.23
  • Scene only regenerates when Generate is pressed; sliders just update labels

Camera Controls

Two parallel input paths feeding the same camera math (orbit, zoom, pan, elevate):

On-screen pad (bottom-center, glassy backdrop-blur UI, collapsible via a chevron toggle and starts collapsed — v0.24/v0.26):

  • Move pad (forward/back/left/right) — v0.1
  • Rotate pad (yaw + pitch) — v0.1
  • Elevate pad (up/down) — v0.4
  • Zoom pad (+/−) — v0.1
  • Held buttons keep applying via an activeActions set in the render loop

Touch / pointer on canvas:

  • 1-finger drag = orbit
  • 2-finger pinch = zoom
  • 2-finger drag = pan — v0.3
  • Wheel = zoom

Camera is clamped: distance 2–200, polar angle 0.05 → ~π/2, elevation 1–200.

UI Chrome

  • Top-left info panel with version label and live cam x, y, z debug readout — v0.5
  • Top-right "Generate" button — v0.6
  • JetBrains Mono font, lime accent color (#d4ff5f), safe-area insets for iPad notch/home-bar
  • Mobile breakpoint shrinks pad to 36px buttons

Not Yet Implemented

Compared to the feasibility notes above, still missing: sky/sun shader, shadows, instancing, grass with wind, facade textures.

About

3d city scape procedurally generated with three.js

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages