Skip to main content

Command Palette

Search for a command to run...

Grass in Ghost of Tsushima

Updated
5 min read
Grass in Ghost of Tsushima

Part 1: The Naive Approach (and why it fails)

Imagine you want grass. The most obvious thing is: make a grass blade mesh, place thousands of them.

A single blade might be 6-10 triangles. You want 100,000 blades. That's up to a million triangles, which modern GPUs can handle. But here's the killer: if each blade is a separate object, you're making 100,000 draw calls. Your CPU dies before the GPU even breaks a sweat.

Lesson learned: raw triangle count isn't usually the bottleneck. CPU-GPU communication is.


Part 2: Alpha Cards (the classic solution)

Instead of geometry, fake it. A flat quad with a texture of several grass blades, transparency cutting out the background. One quad might represent 10-20 blades visually but cost only 2 triangles.

Problems:

  • Overdraw: when cards overlap, GPU has to blend them, figure out depth order. Stack 10 cards and you're rendering the same pixel 10 times

  • Looks flat: from certain angles you see it's just a plane

  • Animation is limited: the whole card moves together, not individual blades

This was the standard for like 15+ years. Still used in many games today because it's cheap and good enough for background foliage.


Part 3: Billboarding (making cards less obvious)

To hide the flatness, make the cards rotate to face the camera. Now you never see them edge-on.

Cylindrical billboarding: only rotates around Y axis, good for vertical stuff like grass Spherical billboarding: rotates fully to face camera, good for particles

Problem is if you notice the rotation happening it looks weird. And it doesn't solve the overdraw issue.


Part 4: Hybrid per-blade geometry (modern approach)

What if each blade was real geometry, but we're smart about it?

A grass blade can be just 7-15 vertices. A tapered shape, maybe curved. That's way more triangles than alpha cards, but:

  • No transparency = no overdraw

  • No alpha sorting issues

  • Each blade can animate independently

  • Looks good from any angle

The catch is you need to render potentially millions of these. Enter GPU instancing.


Part 5: GPU Instancing (the enabler)

Instead of 100,000 draw calls, you make ONE. You tell the GPU: "here's a blade mesh, here's a buffer with 100,000 transforms, go."

The vertex shader receives:

  • Per-vertex data (the blade shape, same for all)

  • Per-instance data (position, rotation, scale, color, unique for each blade)

It combines them to place each blade. The GPU parallelizes this massively, it's what GPUs are built for.


Part 6: Compute Shaders (generating instance data)

Ghost of Tsushima takes it further. Instead of precomputing all blade positions on CPU, they run a compute shader that:

  1. Takes a grid position

  2. Adds random jitter for natural placement

  3. Samples terrain textures to determine grass type and height

  4. Does frustum/distance culling right there on GPU

  5. Outputs instance data only for visible blades

This means the CPU never touches individual blade data. Everything stays on GPU.


Part 7: The Blade Shape (Bezier curves)

Each blade is modeled as a cubic Bezier curve. Why Bezier?

  • Easy to evaluate: just plug in t from 0 to 1, get position along blade

  • Easy derivatives: gives you tangent for normals

  • Control points = intuitive shape control for artists

  • Animation is just moving control points

The control points are determined by parameters like:

  • Tilt: how far the tip leans from the base

  • Bend: how much the middle bows out

  • Facing: which direction the blade points


Part 8: Making It Look Full (the tricks)

Raw geometry grass can look sparse. Ghost of Tsushima uses several tricks:

View-space thickening: when a blade is edge-on to camera (orthogonal), shift its vertices slightly toward camera. Prevents paper-thin appearance.

Curved normals: instead of flat shading, tilt the normals outward to simulate a rounded blade. Cheaper than adding geometry.

Blade folding: if a blade is short, fold the vertices to make TWO blades from one. Double density for zero extra cost.


Part 9: Clumping (making it natural)

Random placement looks artificial. Real grass clumps because soil conditions vary.

They use Voronoi noise: divide space into cells, each cell has a center point. Every blade knows which cell it belongs to and uses that to influence:

  • Height

  • Color

  • Facing direction

  • How much blades lean toward clump center

This creates natural-looking variation without manual placement.


Part 10: Wind Animation

Two layers:

Global wind: 2D Perlin noise scrolled in wind direction. Sampled at each blade's position to get wind strength. This bends blades.

Local bobbing: sine wave per blade, with phase offset based on blade hash and position along blade. Creates that swaying look where the tip moves more than the base.

The wind and bob combine. Because each blade has unique hash, they all move slightly differently.


Part 11: LOD (Level of Detail)

Can't render a million full-detail blades to the horizon.

Near: 15 vertices per blade, full curvature Far: 7 vertices per blade, simplified shape Very far: replace entire grass field with a single texture on terrain

The transition between LODs is tricky. Ghost of Tsushima blends vertex positions as you approach the switch point, so there's no pop.

They also thin out blades: when switching to larger tiles, 3 out of 4 blades are dropped. The lower density is less noticeable at distance.


Part 12: Culling (don't render what you can't see)

Frustum culling: is this blade inside the camera view pyramid? If no, skip.

Distance culling: is this blade beyond max grass distance? Skip.

Occlusion culling: is something blocking this blade? Skip. (They found this was marginal improvement for grass specifically)

All of this happens in the compute shader before any vertex processing.


Part 13: Shadows (the hard problem)

Running the full grass pipeline for shadow maps is expensive, and you need to do it per light.

Their solution: imposters. Raise the terrain vertices to grass height, write depth in a dithered pattern. When shadow filtering happens, it approximates grass shadow density.

For fine detail, they rely on screen-space shadows which work well for thin geometry.


Part 14: Interaction (player pushing through grass)

They maintain a "displacement buffer" that stores where characters/objects are pushing grass. The compute shader samples this and bends nearby blades away.

This doesn't require CPU to know about individual blades, it's just another texture input to the shader.