# A forest of octahedral impostors

**URL:** <https://discourse.threejs.org/t/a-forest-of-octahedral-impostors/85735>\
**Category:** Showcase\
**Created:** [August 1, 2025, 6:24pm UTC](https://discourse.threejs.org/t/a-forest-of-octahedral-impostors/85735 "2025-08-01T18:24:50Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![agargaro](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.threejs.org/agargaro/32/35529_2.png) [@agargaro](https://discourse.threejs.org/u/agargaro)\
**Post date:** [August 1, 2025, 6:24pm UTC](https://discourse.threejs.org/t/a-forest-of-octahedral-impostors/85735/1 "2025-08-01T18:24:50Z")

</div>

Hi guys 😁

I am working on a new library for rendering octahedral impostors and I’ve created a small demo with a terrain 3072 x 3072 and 200k trees. It should work quite good on mobile as well.

The terrain is managed with [BatchedMesh with an extension to have LODs](https://github.com/agargaro/batched-mesh-extensions), generated with meshoptimizer.

For the trees, I used [InstancedMesh2](https://github.com/agargaro/instanced-mesh), with a BVH for frustum culling, adding two more LODs, one created with meshoptimizer (distance from 15 to 100) and the other using an octahedral impostor (distance 100+).

Demo: [https://octahedral-impostor.vercel.app](https://t.co/LiUwXEi64c)

Github : [https://github.com/agargaro/octahedral-impostor](https://t.co/WtHXk7NIxN)

As soon as I find some time, I would like to share with you the details that allowed me to optimize it on mobile devices too.

 ![image](https://canada1.discourse-cdn.com/flex035/uploads/threejs/original/3X/c/0/c0b42e436572d13d13bbab799669839a042ab442.jpeg)

---

<div class="post-metadata">

**Author:** ![Usnul](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.threejs.org/usnul/32/2355_2.png) [@Usnul](https://discourse.threejs.org/u/Usnul)\
**Post date:** [August 17, 2025, 5:48am UTC](https://discourse.threejs.org/t/a-forest-of-octahedral-impostors/85735/2 "2025-08-17T05:48:34Z")

</div>

Really cool stuff, I was working on the same thing a while back, some 3 years ago

 ![2022-09-21 13_54_41-meep](https://canada1.discourse-cdn.com/flex035/uploads/threejs/original/3X/d/6/d694e5b58d7bf6a79dec178c2d034f1e21033458.png)  
 ![2022-09-21 13_53_57-meep](https://canada1.discourse-cdn.com/flex035/uploads/threejs/original/3X/a/6/a624fa17e915839b17de7182f17e58d96cd3ae73.png)

Had some issues with interpolation, you can see lines on the sprite if you look closely. Probably lack of secondary reprojection.

At the time I was going full steam ahead on this,

- added support for complex objects, not just a single mesh
- variable atlas resolution
- variable grid resolution so you can trade spatial for angular resolution
- full octahedron and hemioct
- debug visualization tools
- margins to support mipmaps without bleeding
- depth/normal/pbr + albedo/alpha bakes, so you can fully relight the thing and do proper depth compositing

Here is an example of a reasonable and unreasonable grid resolution on the same atlas resolution

 ![Screenshot 2024-02-02 141557](https://canada1.discourse-cdn.com/flex035/uploads/threejs/original/3X/f/5/f55a1a90bb38a166544bd0df0d67fa1d0ecf2b1e.jpeg)  
 ![Screenshot 2024-02-02 142629](https://canada1.discourse-cdn.com/flex035/uploads/threejs/original/3X/c/6/c61a6d0579aff2bf62502724d37bf679aba78e3a.jpeg)

Spent an ungodly amount of time on optimizing the bake.  
Implemented Sprite fitting to reduce overdraw

 ![2022-10-02 01_50_47-meep](https://canada1.discourse-cdn.com/flex035/uploads/threejs/original/3X/f/5/f5c78986b6b2ea265b90add26e248a4bb51cb5d3.png)  
 ![2022-10-02 01_29_10-meep](https://canada1.discourse-cdn.com/flex035/uploads/threejs/original/3X/7/b/7b6fb7978869f8a49f1562635387c7fbd9ceec42.png)  
 ![2022-10-01 18_12_46-](https://canada1.discourse-cdn.com/flex035/uploads/threejs/original/3X/5/2/52f781407ce7dd917c4a7cad799c83cfa2a4c249.png)  
 ![2022-10-01 21_59_35-meep](https://canada1.discourse-cdn.com/flex035/uploads/threejs/original/3X/4/1/4191fc777806dc04dd98335d2acae21735211403.png)

Pick a number of points, say 4 - and the tool will fit a convex hull around it with that number of points. Lets you cut down on overdraw. Humus wrote about it over a decade ago, I thought it was a really cool idea.

I still want to bring this to production someday, maybe in [Shade](https://discourse.threejs.org/t/shade-webgpu-graphics/66969). Anyway, Kudos on making it work!

# My references

- [2021] [Rendering a Sphere on a Quad](https://bgolus.medium.com/rendering-a-sphere-on-a-quad-13c92025570c) by Ben Golus
- [2018] [Octahedral Impostors](https://shaderbits.com/blog/octahedral-impostors) by Ryan Brucks
- [2018] [https://github.com/xraxra/IMP](https://github.com/xraxra/IMP)
- [2018] [GitHub - wojtekpil/Godot-Octahedral-Impostors: Octahedral Impostors implementation for Godot Game Engine](https://github.com/wojtekpil/Godot-Octahedral-Impostors)
- [2011] BN11 : “Real-time Realistic Rendering and Lighting of Forests” by Eric Bruneton, Fabrice Neyret
- [2007] TSK07: “Fast (Spherical) Light Field Rendering with Per-Pixel Depth.” by Severin Todt, Christof Rezk-Salama and Andreas Kolb
- [1995] MO95: Nelson Max and Keiichi Ohsaki. Rendering trees from precomputed z-buffer views. In Eurographics Rendering Workshop, pages 45–54, 1995

---

<div class="post-metadata">

**Author:** ![Usnul](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.threejs.org/usnul/32/2355_2.png) [@Usnul](https://discourse.threejs.org/u/Usnul)\
**Post date:** [August 18, 2025, 10:07am UTC](https://discourse.threejs.org/t/a-forest-of-octahedral-impostors/85735/4 "2025-08-18T10:07:05Z")

</div>

> [@garry426milner](#):
>
> I have little question did you implement any form of crossfade or dithering to smooth the LOD switches?

For the most part this technique requires no dithering. If you do it right, the model will look exactly the same to the user with the impostor. You have to make sure to perform the substitution when the substituted model would occupy smaller or at least equal screen space than the bake resolution. The whole point of this technique is not to simplify the result, but to pre-bake it, so that the appearance matches the source exactly.

There can be popping due to insufficient angular resolution, or incorrect blending or undersampling (the point about resolution), but in general it’s not going to be required.

Problem with fading/dither is that when you do it - you’re drawing two things. Say you fade LODs, you’re drawing 2 LODs during that fade, so for a while - it’s worse than just the higher LOD.

Few engines do proper LOD fading, there are some high-profile ones that do, but in general most people don’t bother.

If you want to avoid popping - best way today is to use continuous LOD, and with things like impostors - if you use them carefully (see above) - you should see no popping at all.

* * *

As for my implementation - I didn’t even bother with fading. For @agargaro - I can’t really comment, I only had a brief look at his code but didn’t see anything like that.

---

<div class="post-metadata">

**Author:** ![agargaro](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.threejs.org/agargaro/32/35529_2.png) [@agargaro](https://discourse.threejs.org/u/agargaro)\
**Post date:** [August 18, 2025, 2:52pm UTC](https://discourse.threejs.org/t/a-forest-of-octahedral-impostors/85735/5 "2025-08-18T14:52:55Z")

</div>

Hi @Usnul!

I’ve read almost all of your fantastic posts and I’m really happy to receive more information here on this post 😁 Thank you very much!

I would like to understand how to reduce the overdraw. Do I need to create a convex hull that contains all the sprites? Since the selection of the 3 sprites to be blended is done in the vertex shader, I don’t think it’s easy to create a convex hull every time just for the 3 selected sprites.

My library already has some customizations, but there is still work to be done before it is truly usable.

```js
const impostor = new OctahedralImpostor({
  renderer,
  target: mesh/group,
  useHemiOctahedron: true,
  transparent: false,
  alphaClamp: 0.45,
  spritesPerSide: 16,
  textureSize: 2048,
  parallaxScale: 0.5,
  baseType: MeshLambertMaterial
});

scene.add(impostor);

```

@garry426milner No, I don’t think I’ll add it unless requested. @Usnul has already explained why.

---

<div class="post-metadata">

**Author:** ![Usnul](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.threejs.org/usnul/32/2355_2.png) [@Usnul](https://discourse.threejs.org/u/Usnul)\
**Post date:** [August 18, 2025, 9:34pm UTC](https://discourse.threejs.org/t/a-forest-of-octahedral-impostors/85735/6 "2025-08-18T21:34:52Z")

</div>

> [@agargaro](#):
>
> I would like to understand how to reduce the overdraw. Do I need to create a convex hull that contains all the sprites? Since the selection of the 3 sprites to be blended is done in the vertex shader, I don’t think it’s easy to create a convex hull every time just for the 3 selected sprites.

That’s a really good question. It’s what I thought about when I was working on too. Convex hull on a union of all sprites is what I went with. You can theoretically do better, but any given pixel on the screen can come from a different sprite, so hulls per-sprite don’t help a lot, as you’d have to do a lot of math later on.

One thought would be to use a 3d convex hull and project that, but that comes with a ton of other complications.

So yeah, for me - I just go with a union of all sprites. On average I had about 30% of pixels shaved off with just 5 vertices (3 triangles), but your results may vary.

> [@](#):
>
> ```javascript
> useHemiOctahedron: true,
> transparent: false,
> alphaClamp: 0.45,
> spritesPerSide: 16,
> textureSize: 2048,
> 
> ```

That’s cool, love seeing others overengineer their prototypes 😃
