Low Poly Grass: Ten Ways to Make a Field That Reads as Grass
Grass is the highest-leverage asset in a low poly scene. It usually covers more screen area than any other surface, and it is the first thing that looks wrong when it is wrong. There are more low poly grass techniques available than most artists actually try. This post lists ten of them, states what each one costs in draw calls and memory, says whether it animates, and says when it is the right call. Then it covers the combination that survives the constraints of a browser scene, and the two problems nobody mentions in tutorials: seam tiling and judging a field in motion.
Ten Low Poly Grass Techniques, With Costs
Cost below is expressed as draw calls per field, because draw calls dominate CPU frame time on the browser main thread. Memory is expressed per instance of the asset, not per field, unless stated otherwise.
1. A single tiling grass texture on a ground plane
One quad, one material, one draw call for the entire field. A 1024×1024 texture at typical compressed sizes is small enough that memory is a rounding error. This is the cheapest technique on the list and it is not a placeholder. At a distance, a good grass texture carries the entire read of the field because the eye reads colour variance and density, not geometry.
It does not animate. It has no silhouette contribution: along the horizon or against a bright sky, the plane is a hard line. Use it as the base layer under anything else.
2. A tileable texture at 64×64 for memory-constrained targets
Same technique, smaller source. A 64×64 texture is a few kilobytes uncompressed. At typical viewing distance, with correct mip filtering and a tiling repeat of roughly 4 to 8 units per tile, the difference from a 1024 source is mostly in the near foreground where the player stands.
The failure mode is mip bleed: at small sizes the lower mip levels average toward flat colour faster, so distant grass goes muddy sooner. Compensate with a slightly higher repeat count and anisotropic filtering set to at least 4×, which on most browser GPUs costs little. Use it on mobile targets or when the texture atlas is already near budget.
3. Vertex-coloured geometry with no texture at all
Build the grass as geometry and put colour on the vertices. No sampler, no UVs, no texture memory. This is the cleanest option when the art direction is flat-shaded and saturated, because texture filtering fights that look.
Cost is geometry, not memory: a clump of eight triangles with vertex colours is trivial, but the mesh still occupies a draw call per material unless you merge or instance it. It animates only if you drive it in the shader or update attributes. The right call is stylised scenes where the palette is doing the work and a texture would add noise.
Vertex colours multiply against nothing by default. If the material still has a base colour map bound, the tint multiplies that map and the result looks dirty. Clear the map slot before you blame the vertex data.
4. Crossed billboard cards
Two or more quads that always face the camera, each carrying a clump alpha texture. This is the classic silhouette solution. A single crossed card gives you a readable clump from any horizontal angle.
Costs: alpha-tested transparency, which on the browser means sorting is usually avoided by using alpha test rather than alpha blend. Alpha test discards fragments, which disables early-z on many GPUs and costs fill rate. On a field of a few hundred cards that is fine; on a field of several thousand it is not. It animates naturally if the texture is a sprite sheet, or in the shader if you displace vertices.
5. A two-plane "X" card per clump
Take two quads, cross them at 90 degrees, done. This is the degenerate case of technique 4 and it is the workhorse of low poly vegetation. Two planes, four triangles, no per-frame billboarding cost.
The cost is the same as technique 4 — alpha test fill rate — but with half the geometry. It reads correctly from the side and from above, and it breaks down only when the camera gets steeply top-down, where the cards become thin lines. For a browser scene with a mostly horizontal camera, which is most of them, this is the correct default.
6. Instanced clump meshes at low density
Take one clump mesh and draw it N times with a per-instance transform. One draw call for the whole field regardless of instance count. This is the single biggest win available on the browser, because it collapses N draw calls into one.
The cost moves to vertex count: N instances times the clump's vertex count must fit in budget. At low density — a clump every few units, not every few centimetres — this is cheap. The trick is that low poly grass does not need density to read. Scatter clumps at a spacing that leaves gaps and let the ground texture fill them.
7. A single large sculpted mesh for a hero field
Model the field once, by hand, as a single mesh. No repetition, no instancing, no tiling. This is the technique for a hero shot, a fixed camera, or a small playable area where the grass is the subject.
Cost is authoring time and vertex count, not draw calls: one mesh, one material, one draw call. It does not animate unless you add bones or a shader, and both are expensive on a hand-built mesh. Use it when the camera is known and the field is small.
8. A gradient tint from base to tip baked into the mesh
Bake a dark-to-light gradient into the vertex colours along the vertical axis. This fakes ambient occlusion and makes clumps read as volume without any lighting complexity.
Cost is authoring effort and, if you bake it as a texture, one small texture. Baked as vertex colours it is free at runtime. It does not animate on its own, but it composites correctly with shader sway, because the gradient is a static attribute and the sway is a position offset. This is the highest value-per-effort technique on the list for making cheap grass look deliberate.
9. Scattered colour variation via a second mesh layer
Instantiate a second clump mesh with a different base tint — yellower, bluer, darker — at a lower density, overlapping the first layer. The eye picks up the variance and stops seeing the repeat.
Cost is one additional draw call, because the second mesh needs its own material. That is the entire cost. It is the cheapest way to break tiling, and it works better than increasing texture detail. Use it whenever the field is large enough that the player will see the same clump twice.
10. An animated sway in the vertex shader
Displace vertices in the vertex shader based on their height above the object origin. Vertices near the base barely move; vertices at the tip move the most. Drive the phase with time and world position so neighbouring clumps are out of sync.
Cost is essentially zero: a few extra instructions per vertex, no extra draw calls, no skeleton, no per-frame CPU work. It animates, and it is the only animation technique on this list that scales to an instanced field without a per-instance cost. Do not rig grass. Rigging grass is how you spend a week and lose the frame budget.
If your sway phase is a pure function of world position, adjacent instances that share a position will move in lockstep and you will see a visible seam in the motion. Offset the phase by the instance index or a random attribute baked at spawn time.
Cost Summary
Technique | Draw calls | Memory | Animates |
|---|---|---|---|
| 1 | 1 texture | No |
| 1 | ~1 small texture | No |
| 1 per material | Negligible | Shader only |
| 1 per material | 1 atlas | Sprite or shader |
| 1 per material | 1 atlas | Shader |
| 1 | 1 mesh + atlas | Shader |
| 1 | Vertex count | No |
| 0 extra | Negligible | N/A |
| +1 | 1 mesh | If instanced |
| 0 extra | Negligible | Yes |
The Combination That Works in a Browser Scene
The browser constraint is not GPU vertex throughput. It is draw calls and main-thread work. A browser tab shares a process with the page, the UI, and the game loop. Every draw call you add is CPU time on a thread that is also handling input and, in many builds, physics.
Given that, the combination to ship is technique 1 plus technique 6 with technique 10 applied to the instances. One textured ground plane carries the read of the field: colour, density, the base-of-the-horizon value. Instanced clump cards carry the silhouette, so the field has height against the sky. The texture carries the read; the instances carry the silhouette. Neither alone is sufficient, and both together cost two draw calls.
Add technique 8 — the baked gradient — to every clump. It is free and it is the difference between grass and green shards. Add technique 9 if the field exceeds a few dozen units across.
Skip technique 7 unless the camera is fixed. Skip technique 4 in favour of technique 5 unless you need the extra angle coverage. Skip technique 3 unless the art direction is aggressively flat.
Draw call count is the number to watch, not triangle count. A scene with 30,000 triangles in 4 calls will outrun a scene with 8,000 triangles in 200 calls on most browser hardware. Sort your material list before you optimise your meshes.
The Seam Problem: Tiling Without a Visible Grid
A tiling grass texture fails in a specific way: the eye finds the repeat. Not because the tile is bad but because the eye is very good at detecting periodicity, and a regular grid of identical clumps reads as a grid the moment the camera moves.
Three fixes, in order of cost.
First, make the texture tile seamlessly in the strict sense — the left edge column must match the right edge column, and top must match bottom. Off-by-one here produces a hard line. Verify by tiling 3×3 in your editor and looking for structure in the middle.
Second, break the period with your other layers. The instanced clumps are placed at non-repeating positions, so even if the ground texture repeats every 8 units, the silhouette does not. This is the main reason the two-layer combination works: the texture's period is hidden behind geometry that has no period.
Third, if you must hide it further, offset the texture's UV by a low-frequency noise value sampled from world position, at a small amplitude — enough to shift the tile a fraction of its width across a large area. The tile boundaries stop lining up across the field even though each individual tile is still periodic.
Judging a Field in Motion
A grass field looks correct in a static screenshot more often than it looks correct in motion. The failures that matter only appear when the camera moves: the tile repeat becomes visible, the sway phase desyncs, the alpha-tested cards shimmer as they cross each other, the mip transitions pop.
This is where a browser-based low poly world is the fastest way to judge the result. You are already editing and running in the same place. You can change the repeat count, the instance density, the sway amplitude, and the gradient, and be looking at the moved camera within seconds. That loop is short enough that you iterate on the field instead of guessing at it.
The specific things to check in motion: rotate the camera 90 degrees and watch for the X cards going edge-on; move the camera along the ground and watch for the tiling grid appearing near the horizon; sit still and watch a single clump for ten seconds to see whether the sway phase is visibly looping.
If your field passes those three checks at the frame rate you are targeting, it is done. If it does not, the fix is almost always in the layer combination, not in the texture resolution.
Keep going
Build the world it goes in
A tileset is a material with nowhere to stand. Every world on the 3D game builder was made from one written description and published as a page you can play — including The Cyclops' Island and Mini World.
