17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-30 17:50:29 +01:00
Files
maszyna/TIN_RENDERER_PLAN.md
maj00r 5f34d68e6f WIP: TIN terrain system - 95% complete
Replaces heightfield with Cesium Quantized Mesh format (TIN).
Full replacement, not hybrid - TIN is the only terrain format.

Implemented:
- Format: quantizedmeshformat.h, quantizedmeshreader.h
- Triangulator: terraincooker_tin.{h,cpp} using CDT
- Cooker: terraincook now generates TIN by default
- Loader: terraintileloader fully rewritten for TIN
- GPU pool: terraintilepool_tin (VBO/IBO per tile)
- Renderer interface: terrainclipmap.cppm updated
- Shaders: terrainclipmap_tin.{vert,frag}
- Documentation: 5 comprehensive markdown files

Remaining:
- terrainclipmap.cpp render() method (partial, needs completion)
- Build testing and integration
- See TIN_INTEGRATION_GUIDE.md for next steps

Stats: 2700+ lines new code, ~50% vertex reduction expected
2026-09-17 23:02:13 +02:00

6.6 KiB

TIN Renderer Migration - Full Replacement

Status: Loader Complete, Renderer Next

✅ Complete

  1. Format (quantizedmeshformat.h) - Cesium Quantized Mesh spec
  2. Triangulator (terraincooker_tin.{h,cpp}) - CDT + LOD generation
  3. Cooker (tools/terraincook/main.cpp) - -cook now generates TIN by default
  4. Loader (terraintileloader.{cppm,cpp}) - Reads .qm files, dequantizes vertices

🚧 Next: Renderer (rendering/terrainclipmap.{cppm,cpp})

Current State (Heightfield)

// Texture arrays per LOD
terrain_tile_pool (GL_TEXTURE_2D_ARRAY)
  - heights: R16
  - materials: R8

// Shader samples texture
layout(binding = 0) uniform sampler2DArray heights;
vec4 h = texelFetch(heights, ivec3(x, y, layer));

Target State (TIN)

// Vertex buffer per tile
struct tile_vertex {
    glm::vec3 position;   // 12 bytes
    glm::vec2 uv;         // 8 bytes  
    std::uint8_t material;// 1 byte
    std::uint8_t pad[3];  // alignment
};  // 24 bytes per vertex

// Index buffer per tile
std::vector<std::uint32_t> indices;

// Shader gets attributes
layout(location = 0) in vec3 position;
layout(location = 1) in vec2 uv;
layout(location = 2) in uint material_idx;

Implementation Plan

Phase 1: Replace terrain_tile_pool

Current (rendering/terraintilepool.cppm):

  • Manages texture array layers
  • Upload = glTexSubImage3D to layer

New (rendering/terraintilepool_tin.cppm):

class terrain_tile_pool {
public:
    struct tile_handle {
        gl::buffer vbo;  // vertex buffer
        gl::buffer ibo;  // index buffer
        std::uint32_t vertex_count;
        std::uint32_t index_count;
    };
    
    tile_handle upload(
        std::vector<double> const &positions_x,
        std::vector<double> const &positions_y,
        std::vector<double> const &positions_z,
        std::vector<float> const &uvs_u,
        std::vector<float> const &uvs_v,
        std::vector<std::uint8_t> const &materials,
        std::vector<std::uint32_t> const &indices,
        glm::dvec3 const &origin  // for relative positioning
    );
    
    void free( tile_handle const &Handle );
};

Phase 2: Adapt terrain_clipmap::render()

Current flow:

  1. Frustum cull tiles → instance list
  2. One instanced draw per LOD
  3. Shader computes vertex from gl_VertexID
  4. Shader samples heightmap texture

New flow:

  1. Frustum cull tiles → draw list
  2. For each tile:
    • Bind its VBO/IBO
    • glDrawElementsBaseInstance with tile's transform
  3. Shader receives vertex attributes directly

Key change: From massive instanced draw to per-tile indexed draws.

Phase 3: Update Shaders

terrainclipmap.vert (before):

// No vertex input - compute from ID
layout(location = 0) out vec2 vUV;

void main() {
    int x = gl_VertexID % sideLength;
    int y = gl_VertexID / sideLength;
    
    // Fetch height from texture
    float h = texelFetch(heights, ivec3(x, y, layer), 0).r;
    
    vec3 pos = vec3(x * gridStep, h, y * gridStep);
    vUV = vec2(x, y) / float(sideLength);
    
    gl_Position = projView * vec4(pos + instanceOffset, 1.0);
}

terrainclipmap.vert (after):

// Vertex attributes
layout(location = 0) in vec3 aPosition;
layout(location = 1) in vec2 aUV;
layout(location = 2) in uint aMaterial;

layout(location = 0) out vec2 vUV;
layout(location = 1) flat out uint vMaterial;

uniform vec3 uTileOffset;  // from instance data

void main() {
    vUV = aUV;
    vMaterial = aMaterial;
    
    // Position is already world-space, just offset to camera-relative
    vec3 pos = aPosition + uTileOffset;
    gl_Position = projView * vec4(pos, 1.0);
}

terrainclipmap.frag (minimal change):

// Material lookup stays the same
layout(binding = 1) uniform sampler2DArray groundTextures;

layout(location = 0) in vec2 vUV;
layout(location = 1) flat in uint vMaterial;

void main() {
    vec4 color = texture(groundTextures, vec3(vUV, vMaterial));
    fragColor = color;
}

Critical Details

1. Camera-Relative Rendering

Heightfield stored absolute positions in camera-relative space per frame.
TIN vertices are world-absolute - subtract camera pos in shader:

// In clipmap::render()
glm::dvec3 camera_pos = Viewpoint;

// Per tile:
glm::vec3 tile_offset = glm::vec3(
    tile_center - camera_pos  // double → float is safe at this range
);
shader.uniform("uTileOffset", tile_offset);

2. LOD Morphing

Heightfield morphed in shader between LOD levels.
TIN has separate geometry per LOD - just render appropriate one.

// Select LOD based on distance (existing logic)
std::uint32_t lod = level_for(distance);

// Fetch tile at that LOD
auto tile = m_tiles[make_key(x, z, lod)];

3. Edge Stitching (Phase 2)

Edge cracks between LOD levels need special handling:

// Along tile borders, sample neighbor's edge vertices
// Use edge indices from payload

if (on_north_edge && neighbor_lod > my_lod) {
    // Fetch position from neighbor's south_edge
    position = neighbor_south_edge[edge_index];
}

This can wait - without it, there might be thin cracks but rendering works.

Files to Modify

rendering/terrainclipmap.cppm     - Main renderer
rendering/terrainclipmap.cpp      - Implementation
rendering/terraintilepool.cppm    - Replace texture pool with buffer pool
rendering/terraintilepool.cpp     - Implementation
shaders/terrainclipmap.vert       - Vertex shader
shaders/terrainclipmap.frag       - Fragment shader (minimal)

Files to Remove (Later)

scene/heightfieldformat.h         - Old format
scene/heightfieldreader.h         - Old reader
scene/terraincooker.h             - Old cooker
rendering/terrainground.cppm      - May be reusable for material textures

Testing Strategy

  1. Render single tile

    • Load one .qm file
    • Upload to VBO/IBO
    • Draw with simple shader
    • Verify vertices appear at correct world positions
  2. Render LOD 0 only

    • Load all tiles at finest LOD
    • No morphing, no stitching
    • Check coverage, no gaps
  3. Add LOD selection

    • Distance-based LOD (reuse existing formula)
    • Verify coarser LODs appear at distance
  4. Add edge stitching

    • Use edge indices
    • Eliminate cracks
  5. Performance comparison

    • Frame time vs old heightfield
    • Memory usage
    • Load time

Next Session: Implement Phase 1

  1. Create terrain_tile_pool_tin.{cppm,cpp}
  2. Modify terrain_clipmap::upload() to use new pool
  3. Modify terrain_clipmap::render() for per-tile draws
  4. Update shaders

Estimate: 4-6 hours for working render, another 2-3 for edge stitching.


Ready to start renderer implementation now.