Grass in Ghost of Tsushima

Just a guy who loves to write code and watch anime.
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:
Takes a grid position
Adds random jitter for natural placement
Samples terrain textures to determine grass type and height
Does frustum/distance culling right there on GPU
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.






