You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于THREE.JS的50×50类SimCity网格地形高度过渡平滑化技术咨询

Hey there! Let's work through this smooth terrain transition problem for your 50×50 SimCity-style grid in Three.js. I’ve got a couple of practical, actionable approaches that keep your grid structure intact while fixing that blocky height issue, plus ways to support terrain type edits.


Approach 1: Subdivided Plane Geometry with Vertex Height Interpolation

Instead of using BoxGeometry for each cell, switch to a subdivided PlaneGeometry for every grid cell. This lets you adjust intermediate vertices to blend heights between adjacent cells naturally.

How it works:

  1. For each 50×50 cell, create a plane with horizontal/vertical subdivisions (e.g., 4x4 segments).
  2. Fetch height data from the current cell and its four neighbors (top, bottom, left, right).
  3. Use bilinear interpolation to calculate smooth Y-values for each vertex in the plane.
  4. Assign materials based on the cell's terrain type.

Code Example:

const gridSize = 50;
const cellSize = 1;
const subdivisions = 4; // More subdivisions = smoother transitions

// Assume gridData is your 50x50 array of {x, z, y, terrainType}
for (let x = 0; x < gridSize; x++) {
  for (let z = 0; z < gridSize; z++) {
    const cell = gridData[x][z];
    
    // Create plane and rotate to XZ plane (default is XY)
    const geometry = new THREE.PlaneGeometry(cellSize, cellSize, subdivisions, subdivisions);
    geometry.rotateX(-Math.PI / 2);
    
    // Get heights of current cell and neighbors (handle edge cases)
    const currentH = cell.y;
    const topH = z > 0 ? gridData[x][z-1].y : currentH;
    const bottomH = z < gridSize-1 ? gridData[x][z+1].y : currentH;
    const leftH = x > 0 ? gridData[x-1][z].y : currentH;
    const rightH = x < gridSize-1 ? gridData[x+1][z].y : currentH;
    
    // Adjust each vertex's Y height
    geometry.vertices.forEach(vertex => {
      // Convert local vertex coords from [-0.5, 0.5] to [0, 1]
      const localX = vertex.x + 0.5;
      const localZ = vertex.z + 0.5;
      
      // Bilinear interpolation for smooth height blending
      const height = THREE.MathUtils.lerp(
        THREE.MathUtils.lerp(leftH, rightH, localX),
        THREE.MathUtils.lerp(topH, bottomH, localX),
        localZ
      );
      vertex.y = height;
    });
    geometry.verticesNeedUpdate = true;
    
    // Assign terrain-specific material
    let material;
    switch(cell.terrainType) {
      case 'grass':
        material = new THREE.MeshLambertMaterial({ color: 0x7cb342 });
        break;
      case 'dirt':
        material = new THREE.MeshLambertMaterial({ color: 0x8d6e63 });
        break;
      case 'stone':
        material = new THREE.MeshLambertMaterial({ color: 0x757575 });
        break;
      default:
        material = new THREE.MeshLambertMaterial({ color: 0xcccccc });
    }
    
    // Position the cell in the grid
    const mesh = new THREE.Mesh(geometry, material);
    mesh.position.set(x * cellSize, 0, z * cellSize);
    scene.add(mesh);
  }
}

Approach 2: Shader-Based Height Blending (Better Performance)

For larger grids or smoother dynamic updates, use a ShaderMaterial to handle height blending in the GPU. This avoids modifying geometry vertices directly and keeps performance high.

How it works:

  1. Convert your grid data into two textures: a height map (stores cell Y-values) and a terrain type map (stores grass/dirt/stone as color channels).
  2. Use a vertex shader to sample the height map and interpolate heights across the grid.
  3. Use a fragment shader to apply terrain colors and height-based shading.

Code Example:

// Vertex Shader
const vertexShader = `
varying vec2 vUv;
uniform sampler2D heightMap;

void main() {
  vUv = uv;
  // Sample height from texture (normalized 0-1, adjust to your actual height range)
  float height = texture2D(heightMap, vUv).r * 10.0;
  
  vec3 pos = position;
  pos.y = height;
  gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}
`;

// Fragment Shader
const fragmentShader = `
varying vec2 vUv;
uniform sampler2D terrainMap;
uniform sampler2D heightMap;

void main() {
  float terrainType = texture2D(terrainMap, vUv).r;
  float height = texture2D(heightMap, vUv).r;
  
  vec3 color;
  // Map terrain type values to colors
  if (terrainType == 0.0) color = vec3(0.486, 0.702, 0.259); // Grass
  else if (terrainType == 0.5) color = vec3(0.557, 0.431, 0.388); // Dirt
  else if (terrainType == 1.0) color = vec3(0.459, 0.459, 0.459); // Stone
  else color = vec3(0.8); // Default
  
  // Add subtle height-based shading
  color *= 0.8 + height * 0.2;
  gl_FragColor = vec4(color, 1.0);
}
`;

// Convert grid data to textures
const heightArray = new Float32Array(gridSize * gridSize);
const terrainArray = new Float32Array(gridSize * gridSize);

for (let x = 0; x < gridSize; x++) {
  for (let z = 0; z < gridSize; z++) {
    const idx = x + z * gridSize;
    heightArray[idx] = gridData[x][z].y / 10.0; // Normalize to 0-1
    switch(gridData[x][z].terrainType) {
      case 'grass': terrainArray[idx] = 0.0; break;
      case 'dirt': terrainArray[idx] = 0.5; break;
      case 'stone': terrainArray[idx] = 1.0; break;
      default: terrainArray[idx] = 0.0;
    }
  }
}

const heightTexture = new THREE.DataTexture(heightArray, gridSize, gridSize, THREE.RedFormat, THREE.FloatType);
heightTexture.needsUpdate = true;
heightTexture.wrapS = heightTexture.wrapT = THREE.ClampToEdgeWrapping;

const terrainTexture = new THREE.DataTexture(terrainArray, gridSize, gridSize, THREE.RedFormat, THREE.FloatType);
terrainTexture.needsUpdate = true;

// Create single grid mesh
const geometry = new THREE.PlaneGeometry(gridSize, gridSize, gridSize-1, gridSize-1);
geometry.rotateX(-Math.PI / 2);

const material = new THREE.ShaderMaterial({
  uniforms: {
    heightMap: { value: heightTexture },
    terrainMap: { value: terrainTexture }
  },
  vertexShader,
  fragmentShader
});

const gridMesh = new THREE.Mesh(geometry, material);
gridMesh.position.set(gridSize/2, 0, gridSize/2);
scene.add(gridMesh);

Supporting User Edits

To let users modify terrain type or height:

  1. For Approach 1: When a cell is edited, re-calculate its vertices' heights and set geometry.verticesNeedUpdate = true.
  2. For Approach 2: Update the corresponding values in heightArray or terrainArray, then set heightTexture.needsUpdate = true or terrainTexture.needsUpdate = true.

内容的提问来源于stack exchange,提问作者AKB2

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 08:43:16