Final project for University of Pennsylvania, CIS5650 GPU Programming and Architecture, Fall 2025
Raymond Feng, Neha Thumu, Thomas Shaw
TerrainGen is a single-page web application using WebGPU to create a real-time node-based procedural terrain generation and rendering tool. Our motivation for the project was to create an accessible modelling tool on the web to showcase the power of WebGPU.
- Milestone 1 Progress Slides
- Milestone 2 Progress Slides
- Milestone 3 Progress Slides
- Final Presentation
For a more in-depth look at how to use each node/feature, check out the project wiki.
Nodes can be added to the canvas element on the left-hand side of the screen and will be renderer on the right side window when the output nodes have the appropriate inputs. The terrain has two additional settings that can be adjusted with the terrain size and resolution sliders below the render screen.
A user can import/export their node graph.
As an example, here is a saved node graph layout file. If this is imported, then the following node graph will be loaded in.
The skybox can also be changed by uploading an HDR file.
- 🔌 Node-based description system for procedural terrain
- 🏭 Just-in-time WebGPU shader code generation
- 🏔️ Real-time terrain rendering
- Adjustable tesselation and terrain size
- Varied terrain type rendering (grass, rock, snow, etc)
- Shadow mapping
- Distance fog
- 🌲 Mesh instancing across terrain
- glTF/OBJ import for instancing
Once there is a valid node graph connected to one of our output pipelines (Terrain, Instancing, and Water) our pipeline gets computed and the shader code is generated. Our nodes of type input create uniform keys and each subsequent output handle generates a key on the fly. Each node has specific code that is generated and added to our vertex shader along with references to the aforementioned uniform keys.
The terrain is a tesselated plane with an adjustable size and resolution. Whenever the size and resolution sliders are changed, a compute shader populates a vertex buffer and an index buffer. A second compute pass gives each vertex on the terrain a normal value, which is calculated based on the position of neighboring vertices.
Once the terrain is created with the compute shaders, it is rendered every frame with lambertian shading, shadow mapping, and distance fog.
The terrain's color can be changed with a dropdown in the Terrain (Output) node.
TerrainGen also supports OBJ and glTF import, which can be used as part of the instancing pipeline. Users can create multiple instancing pipelines, each of which creates a separate buffer of instancing points on which the desired mesh will be placed. These instancing points are randomly generated on top of the terrain using the Scatter node.
With glTFs, base color textures can be displayed.
- General-purpose
- Basic math (add, sub, mult, div)
- Trig math (sin, cos, tan)
- Terrain source
- Worley noise
- Terrain input
- Vertex XYZ position
- Terrain output
- Height
- Terrain type
- Water level
- Scattering source
- Terrain height
- Instancing node
- Scattering geometry
- Built-in objects: trees, rocks, bushes
- Primitive geometry: sphere, cube, plane
- Custom models
- OBJ import
- glTF import
npm install
npm run devTo build this application for production:
npm run buildThe resulting static content will be in the dist folder. You can preview these with npm run preview.
We have created a GitHub Actions workflow (.github/workflows/deploy.yml) to automatically deploy static content to GitHub Pages.
This project uses Vitest for testing. You can run the tests with:
npm run testThis project uses eslint and prettier for linting and formatting. Eslint is configured using tanstack/eslint-config. The following scripts are available:
npm run typecheck # just runs tsc
npm run lint
npm run lint:fix # automatically fix issues
npm run format
npm run format:check # don't write to any files, just report issues- Tailwind CSS for styling.
- React for DOM manipulation
- TanStack Router. The initial setup is a code based router. Which means that the routes are defined in code (in the
./src/main.tsxfile). If you like you can also use a file based routing setup by following the File Based Routing guide. - React Flow for node editor functionality
- loaders.gl for reading/writing of external files
- io-rgbe for HDR loading