Terrain Component for Editor

Impressive work, i’m amazed.

A debug tool has been added that allows you to check which mipmap level is used for occlusion checking, as well as the pixel area.

  • The verification algorithm has also been improved.

5 Likes

An update has been made: terrain rendering is now performed with a single draw call using the new rendering type MultiDrawInstancingAccelerator , which requires support for the GL_ANGLE_multi_draw extension. This improvement does not affect the functionality of previous rendering methods.

@see MultiDrawPatchInstancing.mts

3 Likes

Improved algorithm for generating MultiDraw sets.
Drawing is completed in 1 call.

We are changing the wording of IPatchInstancing to IPatchDrawAccelerator.

@see PatchMultiDrawAccelerator.mts

Special thanks to @LeXXik for help.

1 Like

WebGPU demonstrates outstanding performance: rendering the landscape takes only 0.3 ms on an integrated UHD Graphics 730, maintaining a stable 75 fps.

I would not completely trust the GPU timer. Not that it’s miles off, but on WebGPU we can only measure duration of render and compute passes, but nothing else. Compared to WebGL, where the whole frame is measured, including data uploads and stall or similar.

What’s the best way to measure WebGPU performance today? Something like RenderDoc or PIX?

The up to date knowledge we have is here

2 Likes

This likely very much depends on how busy the GPU is. If not busy, it can be fast (3ms), but under heavy load, this will be done after the already submitted rendering is done, and take a lot longer.

1 Like

I hear you … WebGL is very limited here.
Doing it in vertex shader brings disadvantages too … its hard to say if that’s worth the win in many cases, likely only in some specialized cases.

Very cool.
One solution I used in a large scale game on xbox 360 in the past was cpu occlusion. I had large quads (could be triangles too) in the level, and per frame pick few of the near ones - those are likely to provide some occlusion. Then during the CPU culling, I would also do an occlusion test (all this was on a separate thread of course, harder in JS).

Advantage vs gpu occlusion - you can even completely remove the draw call including material uniform setup and other render state.

This is something that could work well for hilly terrain, you could generate some occluders for the larger hills.

1 Like

The system is designed for maximum efficiency: it uses a fragment buffer to minimize redundant computations and transmits only changes over the network within the editor, significantly reducing bandwidth usage and improving collaborative performance.

The system allows you to define a limit for the used RAM, specify when data should be offloaded to disk, and set the maximum number of changes stored in the stack.

1 Like

The EntityField component is in its final stage of refinement.

The following key features have been implemented so far:

  • LOD system that uses PatchesBounding as a distance selection accelerator.
  • Custom shadows with LOD support.
  • Instances can have varying levels of nesting, with tinting applied to all child render components.
  • Frustum and Occlusion group culling linked to terrain patches.
  • Automatic height updates based on heightmap changes (can be disabled in settings).
  • Editing of position, scale, and rotation for each instance.
2 Likes

:rocket: New Rendering Accelerators!

Added PatchIndirectDrawAccelerator (WebGPU only) — a powerful tool that works with hierarchical Z-buffer (HZB) and culls landscape patch rendering on the fly! By testing depth in real-time, it significantly reduces GPU load.

Optimized PatchMultiDrawAccelerator for WebGPU:

  • Removed the circular texture buffer for patch data
  • Now uses u32 firstInstance with packed data — simpler and faster!

Architecture supports Occlusion Queries and Readback HZB Tester:

  • Work together or separately for maximum flexibility
  • Readback HZB Tester: poor on Android, excellent on PC/iPhone

Shadows now support render type selection for even more optimization options!

image

2 Likes

We have performed a major overhaul of the system.

  • Chunk-based heightmaps and sampler2DArray have been removed in favor of a single unified heightmap using sampler2D. This limits support for older devices and drivers that cannot handle textures larger than 2048×2048. However, such hardware cannot provide acceptable performance for high-detail terrain anyway, so legacy workarounds were removed in favor of better performance on modern devices.
  • The height representation has been changed from normalized factors to absolute values. Previously, heightmaps stored values in the [0.0,1.0] range which were later scaled. Now we use int16 instead of uint16, allowing negative heights similar to float32.
    For u8, a simplified model is retained: values are limited to (0.0,halfHeight), while float32 and int16 use the range (−halfHeight,halfHeight).
  • u8 heightmaps are well suited for distant terrain LODs or background geometry such as mountains or water.
  • Frustum culling has been optimized.
  • Integration with the playcanvas-opti-pixel library has been added for occlusion culling.
  • A new heightmap component allows explicit control over memory usage: SharedArrayBuffer, direct access to Ammo.HEAP / Jolt.HEAP, or a wrapped abstraction.
  • For Jolt, which operates on internally managed (non-exposed) height data, we provide a direct memory access workaround via pointers (e.g., Jolt.HEAP32[ptr >> ...]). This allows GPUHeightMapManager to read height data directly without duplication.
    Note: Jolt does not support int16 or u8, so float32 must be used.
  • Full out-of-the-box Ammo support has been added using Ammo.btHeightfieldTerrainShape, replacing previous workarounds.
    Be aware that the current implementation lacks spatial subdivision optimization (similar to BVH) for efficient local queries. We recommend updating to the Bullet3 version, where this optimization is available.
Версия для русскоязычных:

Мы провели крупную переработку системы.

  • Отказались от использования чанков и sampler2DArray для карты высот, полностью перейдя на единую карту высот (sampler2D). Это ограничивает поддержку старых устройств и драйверов, которые не способны работать с текстурами размером более 2048×2048. Однако на таких устройствах использование высокодетализированного ландшафта всё равно приводит к неудовлетворительной производительности, поэтому поддержка обходных решений была удалена в пользу оптимизации под современные устройства.
  • Перешли от факторной логики высот к хранению абсолютных значений. Ранее карта высот содержала значения в диапазоне [0.0,1.0], которые затем масштабировались. Теперь используется тип int16 вместо uint16, что позволяет работать с отрицательными высотами по аналогии с float32.
    Для u8 сохранена упрощённая логика: значения ограничены диапазоном (0.0,halfHeight), тогда как для float32 и int16 используется диапазон (−halfHeight,halfHeight).
  • Карты высот в формате u8 подходят для дальних LOD-уровней, а также для фоновых объектов, таких как горы или водные поверхности.
  • Оптимизирован алгоритм фрустум-куллинга.
  • Интегрирована библиотека playcanvas-opti-pixel для отсечения окклюзированных объектов.
  • Добавлен компонент карты высот, позволяющий явно выбирать способ работы с памятью: использовать SharedArrayBuffer, прямой доступ к памяти Ammo.HEAP / Jolt.HEAP, либо стандартную обёртку.
  • Для движка Jolt, который работает с закрытым представлением данных высот, реализован обходной путь прямого доступа через указатели (например, Jolt.HEAP32[ptr >> ...]). Это позволяет GPUHeightMapManager напрямую считывать данные из памяти без дополнительного дублирования.
    Важно: Jolt не поддерживает типы int16 и u8, поэтому необходимо использовать float32.
  • Добавлена полноценная поддержка Ammo “из коробки”: используется Ammo.btHeightfieldTerrainShape вместо предыдущих обходных решений.
    Следует учитывать, что текущая версия не содержит оптимизации разбиения (по аналогии с BVH) для ускорения локальных проверок. Рекомендуется использовать версию из Bullet3, где данная оптимизация реализована.
2 Likes

We’ve had some free time recently and used it to update our component.

The codebase has been reworked and the logic is now split into tools. All tools communicate via an event bus architecture.

Currently implemented tools:

  • Cursor
  • Brush
  • PatchCursor
  • HeightMapEditor
  • SplatMapEditor
  • EntitySpawner
  • TreeEditor
  • GrassEditor

Work is in progress on porting the RoadEditor.

We also added HeightMapHistory and SplatMapHistory. These systems support offloading history data that exceeds a defined memory limit to disk or other storage. The class interfaces are flexible enough to support different storage backends.

SplatMapEditor now performs calculations on the CPU. This change was necessary to support strict logic required by SplatMapHistory. We may move it back to GPU in the future.

The component works in conjunction with our OptiPixel library.

Версия для русскоязычных:

У нас появилось немного свободного времени, и мы обновили наш компонент.

Мы переработали кодовую базу и разделили логику на инструменты. Взаимодействие между инструментами построено на архитектуре event bus.

На данный момент реализованы следующие инструменты:

  • Cursor
  • Brush
  • PatchCursor
  • HeightMapEditor
  • SplatMapEditor
  • EntitySpawner
  • TreeEditor
  • GrassEditor

Ведется работа по переносу RoadEditor.

Также добавлены HeightMapHistory и SplatMapHistory. Эти системы позволяют сгружать историю, выходящую за пределы заданного лимита памяти, на диск или в другое хранилище. Интерфейсы классов достаточно гибкие, чтобы поддерживать разные варианты хранения.

SplatMapEditor теперь выполняет расчеты на CPU. Это было необходимо для реализации строгой логики SplatMapHistory. В будущем возможно возвращение вычислений на GPU.

Компонент работает в связке с нашей библиотекой OptiPixel library.

1 Like

This video demonstrates a terrain editing workflow with real-time modification of terrain properties and full synchronization between the PlayCanvas Editor and the terrain system.

It includes integration with the Ammo.btHeightfieldTerrainShape physics shape and supports multiple height data formats such as float32, int16, and uint8. Each format fits different use cases: uint8 is efficient for water surfaces, while int16 and float32 provide better precision for terrain.

All changes are applied synchronously. The system features a history mechanism for both heightmap and splat map editing, allowing safe iteration and rollback.

The solution is designed for large open-world projects and already includes frustum culling, occlusion culling, and heightmap chunk streaming.

We developed this as a powerful and flexible tool for web-based games and are ready to integrate it into your projects.


В данном видео демонстрируется процесс редактирования терейна с изменением его свойств в реальном времени и полной синхронизацией между редактором PlayCanvas и системой терейна.

Реализована интеграция с физической формой Ammo.btHeightfieldTerrainShape с поддержкой различных форматов данных высот: float32, int16 и uint8. Каждый формат подходит для своих задач: uint8 — для воды, int16 и float32 — для более точного представления терейна.

Все изменения происходят синхронно. Добавлена система истории изменений карты высот и сплат-карты, что позволяет удобно работать с правками.

Решение ориентировано на большие открытые миры и уже поддерживает отсечение по фрустуму, окклюзионный куллинг и подгрузку чанков карты высот.

Мы разработали мощный инструмент для веб-игр и готовы предложить его для интеграции в ваши проекты.

Dear users and developers,

Please be advised that starting July 29, 2026, our library will end support for the first major version of the engine and for any engine versions older than 2.17.0. From that date the minimum supported engine version for the library will be 2.17.0. Engine v1 releases and older engine versions are officially deprecated and will no longer receive security updates, critical bug fixes, or compatibility improvements.

The decision to raise the minimum supported engine version followed careful consideration: we introduced architectural improvements, updated dependencies, and optimized core modules in the library. These changes improved stability and performance but require a more modern engine baseline. Raising the minimum to 2.17.0 enables us to focus on new features, speed up our release cadence, and provide more reliable support for current projects.

If your projects run on engine v1 or any engine versions older than 2.17.0, please prepare a migration plan. Contact our support team if you need help — we will publish migration guides and a changelog of notable differences to ease the process.

Thank you for your understanding and continued support. We remain committed to improving the library and appreciate the community’s contributions.

2 Likes

The demo runs in Chrome using WebGL2 on the PlayCanvas engine. The video showcases our vegetation placement system for terrain surfaces, featuring approximately 300,000 trees in total. The system supports up to 1024 vegetation types simultaneously.

Thanks to an advanced terrain LOD system, the JavaScript execution time in a single-threaded environment is around 1 ms on an AMD Ryzen 5 5600G with Radeon Graphics. This performance is achieved through an optimized data layout: arrays are structured to better fit CPU cache, resulting in faster computations.

At its core, the system uses a hierarchical structure similar to a BVH, partitioning the world into sectors with minimal memory overhead. This structure can be efficiently updated when terrain height is modified via a heightmap editor. Additionally, an occlusion culling system is used to significantly reduce rendering workload.

Демо запущено в Chrome с использованием WebGL2 на движке PlayCanvas. В видео демонстрируется наша система размещения растительности на поверхности ландшафта, где общее количество деревьев составляет около 300 000. Система поддерживает до 1024 типов растительности одновременно.

Благодаря продвинутой системе LOD для поверхности, время выполнения на JavaScript в однопоточном режиме составляет около 1 мс на AMD Ryzen 5 5600G with Radeon Graphics. Такой результат достигнут за счёт оптимизированного подхода к хранению данных: структуры массивов лучше укладываются в кэш-память процессора, что ускоряет вычисления.

В основе системы лежит иерархическая структура, аналогичная BVH, которая разбивает мир на сектора с минимальным потреблением памяти. Эта структура быстро обновляется при изменении высоты ландшафта через редактор карты высот. Дополнительно используется система окклюзионного отсечения, позволяющая существенно снизить нагрузку на рендеринг.

1 Like