▶ Live demos: Water · Point Cloud — run them from the samples launcher.
Two geo render features that draw over the lit scene rather than feeding the stock geometry pass: water (Cesium-style translucent surfaces from a terrain water mask) and point clouds (
.pnts3D-Tiles content, billboarded with eye-dome lighting). They share one architecture, so they make sense to learn together. Sources: samples/geo_water.ts, samples/geo_point_cloud.ts, src/geo/water_feature.ts, src/geo/point_cloud_feature.ts.
The shared pattern: a render feature over the HDR#
Everything in tutorials 1–4 went through GeoFeature, which appends tile draws
to frame.opaque so the engine's deferred geometry pass renders them. Water and
point clouds can't work that way — translucent water needs the lit scene behind
it to refract, and points aren't triangles. So each is its own RenderFeature
with its own WGSL that:
- reads the already-lit
frame.hdr(and the GBuffer depth,frame.gbuffer.depth), - draws its content, occluding against that scene depth so terrain or buildings in front correctly hide it,
- composites the result back into
frame.hdr.
Because they chain frame.hdr, both register before the tonemap feature:
import { TonemapFeature } from 'taos/engine/index.js';
engine.addFeatureBefore(waterFeature, TonemapFeature.name);
engine.addFeatureBefore(pointCloudFeature, TonemapFeature.name);That's the whole structural idea. The rest is per-feature.
Part A — Water#
Quantized-mesh terrain (Cesium World Terrain, MapTiler quantized mesh) ships a
per-tile water mask marking which texels of each tile are water. GeoScene
turns those into water-surface draws and exposes them as geo.waterDraws — you
just hand that list to a GeoWaterFeature:
import { GeoWaterFeature } from 'taos/geo/index.js';
geo.addTerrain(await resolveIonAsset(ION_ASSETS.worldTerrain, ION_TOKEN),
{ imagery: ESRI_WORLD_IMAGERY });
engine.addFeatureBefore(new GeoWaterFeature({
normalTexture: rippleNormalTexture, // a tileable normal map for the surface chop
drawItems: () => geo.waterDraws as GeoWaterDraw[],
sun: () => ({ direction: sunDir, color: sunColor, intensity: 3 }),
settings: waterSettings, // live look controls (below)
}), TonemapFeature.name);It re-draws each water tile's terrain-conformed surface after lighting and samples the mask texture per-fragment, so the coastline is texel-sharp (not a blocky tessellated edge). It reads a snapshot of the lit HDR for refraction and the depth buffer for occlusion plus a soft shoreline fade.
Water needs a water mask, so it needs quantized-mesh terrain. The keyless raster-DEM source (
AWS_TERRARIUM) carries no mask — use Cesium ion or MapTiler terrain (tutorial 11).geo_water.tsfalls back to a bundled ion token so it runs out of the box.
GeoWaterSettings is read live every frame, so a UI can drive it:
const waterSettings: GeoWaterSettings = {
opacity: 0.78, // base opacity over deep water
tint: [0.015, 0.08, 0.11], // underwater color
sky: [0.30, 0.45, 0.62], // grazing-angle (Fresnel) reflection color
normalTileM: 90, // meters one ripple-normal tile spans
distortion: 0.012, // screen-space refraction wobble
shoreFade: 6, // meters of depth over which it fades in at the shore
fresnelPower: 5,
specPower: 200,
normalScale: 0.6,
debug: false, // flat-blue "show me the mask" view
};Flip debug: true to render the mask flat-opaque blue — the quickest way to see
exactly which texels the terrain considers water.
Part B — Point clouds#
.pnts is the 3D-Tiles point-cloud content type (LiDAR scans, photogrammetry
points). A point tile has no mesh, so GeoFeature can't draw it — instead each
tile's baked GPU point buffer surfaces on content.points, and you feed those to
a GeoPointCloudFeature, which expands every point into a billboard quad:
import { GeoPointCloudFeature, type GeoPointCloudDraw } from 'taos/geo/index.js';
const pointFeature = new GeoPointCloudFeature({ drawItems: () => livePointDraws });
pointFeature.pointSize = 3; // billboard diameter in px
pointFeature.attenuate = true; // near points grow, far points shrink (reads with depth)
pointFeature.edl = true; // eye-dome lighting — darken depth edges (the LiDAR look)
pointFeature.edlStrength = 12;
engine.addFeatureBefore(pointFeature, TonemapFeature.name);Collect the draws each frame from the rendered 3D-tile contents — a point tile
carries content.points; the model matrix is the same origin-shift mesh tiles use:
livePointDraws.length = 0;
for (const content of geo.lastResult.tileContents) {
if (content.points) {
const s = geo.frame.worldFromEcefPoint(content.bakeOriginEcef);
const m = Mat4.identity();
m.data[12] = s.x; m.data[13] = s.y; m.data[14] = s.z;
livePointDraws.push({ points: content.points, modelMatrix: m });
}
}Real .pnts datasets come from Cesium ion. To stay keyless, the
geo_point_cloud.ts sample builds a
synthetic cloud the same way a streamed tile would — an interleaved
position(float32×3) + color(unorm8×4) buffer in origin-relative world space,
wrapped in a PointCloudGpu — and drops it on keyless AWS terrain. The same
GeoPointCloudFeature draws either.
Eye-dome lighting#
edl is the feature that makes a flat splatter of points read as 3D: a
screen-space pass darkens fragments that sit behind their neighbors in depth, so
contours and silhouettes pop. Toggle it in the sample (E) to see the difference
— without it the cloud is a hard-to-read confetti; with it, shape emerges.
Putting it together#
Both features stack onto the same scene without conflict — water blends over the lit terrain while a point cloud draws above it — and both honor the scene depth, so they occlude and are occluded correctly. The registration order among the HDR-chaining features (water, point cloud, labels, lens flare, …) only matters where they overlap on screen; all just need to be before tonemap.
Next#
- Tutorial 12 — Performance, Quality & Debugging:
these features draw real geometry —
maxSSEand the memory budgets still apply to the terrain/tiles feeding them. - The full reference is the Geo module guide.