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

Three.js高度图地形Triplanar Mapping纹理映射异常问题

Three.js 高度图地形三平面映射纹理渲染异常排查

在Three.js环境下,针对基于高度图数据生成的平面网格地形,尝试通过triplanar mapping(三平面映射)技术实现纹理映射时,渲染结果不符合预期,具体实现过程与异常如下:

初始实现逻辑

纹理加载代码

let diffuseTex;
loader.load(
  "./resources/images/satellite.png",
  function (image) {
    diffuseTex = image;
  },
  undefined,
  function () {
    console.error("An error happened.");
  }
);

初始材质创建代码

加载得到的漫反射纹理会作为uniform传入RawShaderMaterial,绑定自定义顶点着色器与片元着色器:

var uniforms1 = {
    diffuseTexture: { type: "t", value: diffuseTex },
};

const _meshMaterial = new THREE.RawShaderMaterial({
    uniforms: uniforms1,
    vertexShader: terrainShader._VS,
    fragmentShader: terrainShader._FS,
});

自定义着色器代码

片元着色器内实现了基础的三平面映射逻辑:

// 顶点着色器
const _VS = `#version 300 es
precision highp float;

uniform mat4 modelMatrix;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;

in vec3 position;
in vec3 normal;

out vec3 vNormal;
out vec3 vPosition;

#define saturate(a) clamp( a, 0.0, 1.0 )

void main(){
    vNormal = normal;
    vPosition = position.xyz;
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    vPosition = vec3(gl_Position.xyz)*0.5 + 0.5;
    
  }
`;

// 片元着色器
const _FS = `#version 300 es

precision mediump sampler2DArray;
precision highp float;
precision highp int;

uniform mat4 modelMatrix;
uniform mat4 modelViewMatrix;
uniform vec3 cameraPosition;
uniform sampler2D diffuseMap;

in vec3 vNormal;
in vec3 vPosition;

out vec4 out_FragColor;

vec3 blendNormal(vec3 normal){
    vec3 blending = abs(normal);
    blending = normalize(max(blending, 0.00001));
    blending /= vec3(blending.x + blending.y + blending.z);
    return blending;
}

vec3 triplanarMapping (sampler2D tex, vec3 normal, vec3 position) {
  vec3 normalBlend = blendNormal(normal);
  vec3 xColor = texture(tex, position.yz).rgb;
  vec3 yColor = texture(tex, position.xz).rgb;
  vec3 zColor = texture(tex, position.xy).rgb;
  return (xColor * normalBlend.x + yColor * normalBlend.y + zColor * normalBlend.z);
}

void main(){
    vec3 color = triplanarMapping(diffuseMap, vNormal, vPosition);
    out_FragColor = vec4(color, 1.0);

  }
`;
export const terrainShader = { _VS, _FS };

地形网格创建代码

使用PlaneGeometry构建地形网格,写入高度图对应的顶点数据、计算顶点法线后,绑定前述自定义材质加入场景:

let geometry = new THREE.PlaneGeometry(
      partial.x,
      partial.y,
      partial.x - 1,
      partial.y - 1
    );
    geometry.setFromPoints(chunk[i]);
    geometry.computeVertexNormals();
    let mesh = new THREE.Mesh(geometry, _meshMaterial);
    mesh.castShadow = false;
    mesh.receiveShadow = true;

    scene.add(mesh);

异常现象

  • 初始版本运行时地形整体渲染为纯黑色,完全未呈现待映射的卫星纹理,与预期的纹理完整贴合地形的效果差距极大。
  • 后续调整实现逻辑,将材质创建逻辑移入纹理加载的回调函数内,地形不再为全黑,但渲染效果依然不符合纹理正确映射的预期。

调整后的纹理回调内材质创建代码:

loader.load(
    "./resources/images/satellite.png",
    function (texture) {
      var uniforms1 = {
        diffuseTexture: { type: "t", value: texture },
      };
      _meshMaterial = new THREE.RawShaderMaterial({
        uniforms: uniforms1,
        vertexShader: terrainShader._VS,
        fragmentShader: terrainShader._FS,
      });

核心问题点与修复方案

代码中存在4个直接导致渲染异常的问题:

  • Uniform变量名不匹配:JS代码中传入的纹理uniform名为diffuseTexture,但片元着色器中声明的采样器uniform名为diffuseMap,二者名称不一致会导致着色器无法获取正确的纹理数据,是初始版本全黑的核心原因。
  • 采样坐标空间错误:顶点着色器中最终传给片元着色器的vPosition是裁剪空间经过*0.5+0.5变换后的NDC坐标,和地形实际的局部/世界空间坐标范围不匹配,直接导致纹理采样坐标完全错乱。三平面映射采样需要使用局部空间或世界空间坐标,不能使用投影后的裁剪/NDC坐标。
  • 坐标空间不统一:顶点着色器直接传递局部空间法线,若采样使用世界坐标,法线未通过法线矩阵转换到对应空间,会导致三平面权重计算错误。
  • 纹理采样缩放缺失:直接使用顶点坐标作为UV采样,坐标尺度和纹理分辨率不匹配时会出现纹理拉伸、显示比例异常的问题,需要新增缩放系数控制纹理平铺次数。

参考修复代码

顶点着色器修改

const _VS = `#version 300 es
precision highp float;

uniform mat4 modelMatrix;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform mat3 normalMatrix;

in vec3 position;
in vec3 normal;

out vec3 vNormal;
out vec3 vPosition;

void main(){
    // 转换法线到世界空间
    vNormal = normalize(normalMatrix * normal);
    // 传递世界空间坐标用于三平面采样
    vPosition = (modelMatrix * vec4(position, 1.0)).xyz;
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
  }
`;

片元着色器修改

const _FS = `#version 300 es
precision highp float;

uniform sampler2D diffuseTexture;
// 新增纹理缩放参数,控制平铺密度
uniform float texScale;

in vec3 vNormal;
in vec3 vPosition;

out vec4 out_FragColor;

vec3 blendNormal(vec3 normal){
    vec3 blending = abs(normal);
    blending = normalize(max(blending, 0.00001));
    blending /= (blending.x + blending.y + blending.z);
    return blending;
}

vec3 triplanarMapping (sampler2D tex, vec3 normal, vec3 position, float scale) {
  vec3 scaledPos = position * scale;
  vec3 normalBlend = blendNormal(normal);
  vec3 xColor = texture(tex, scaledPos.yz).rgb;
  vec3 yColor = texture(tex, scaledPos.xz).rgb;
  vec3 zColor = texture(tex, scaledPos.xy).rgb;
  return (xColor * normalBlend.x + yColor * normalBlend.y + zColor * normalBlend.z);
}

void main(){
    vec3 color = triplanarMapping(diffuseTexture, vNormal, vPosition, texScale);
    out_FragColor = vec4(color, 1.0);
  }
`;

JS端逻辑补充

  • 材质uniform新增缩放参数,新版Three.js不需要手动声明uniform类型,会自动识别:
var uniforms1 = {
  diffuseTexture: { value: texture },
  texScale: { value: 0.02 } // 根据地形实际尺寸调整该值,直到纹理显示比例符合预期
};
  • 纹理加载完成后设置重复包裹模式,支持纹理平铺:
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
  • PlaneGeometry默认生成在XY平面、法线朝Z轴,作为地形使用时需要将网格绕X轴旋转-90度,让平面落在XZ平面、Y轴朝上,保证顶视卫星纹理正确映射到地形顶面。
  • 删除片元着色器中无用的precision mediump sampler2DArray声明,避免不必要的兼容问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 16:24:30