Three.js 使用纹理贴图创建 Minecraft 草地方块及常见问题解决

作者:桌面漫游

时间:2026-10-30

浏览:383

本文介绍如何使用 Three.js 的 TextureLoader 加载静态图像,为立方体网格(Mesh)应用草地纹理以模拟 Minecraft 风格的地形。重点解析了贴图后显示为黑色方块的两个主要原因:异步加载时序问题与光源缺失,并提供了基于 Promise 或 onload 事件的处理方案及光源设置建议。

文章目录

Three.js 纹理贴图创建 Minecraft 草地方块

在开发类似《我的世界》风格的射击游戏时,地形构建需要大量纹理贴图。本文以制作草地方块为例,详细说明如何在 Three.js 中添加纹理,并解决因纹理未生效导致方块显示为黑色的常见问题。

Three.js 纹理贴图创建 Minecraft 草地方块

为网格(Mesh)添加纹理实现草地方块效果

实现一个带有纹理的草地方块主要包含三个步骤。

第一步:初始化一个立方体几何体(Geometry)。

第二步:初始化纹理加载器并加载纹理资源。

第三步:将纹理应用于立方体并进行渲染。

step one 初始化一个111 的立方体

关于立方体的初始化,前文已有介绍,此处不再赘述,仅需一行代码即可完成。

const geometry = new THREE.BoxGeometry();

第二步:初始化纹理加载器并加载纹理

在操作前,简要回顾 Three.js 支持的纹理加载器及其类型:TextureLoader 用于加载静态图像;CubeTextureLoader 用于加载立方体贴图(如天空盒),需加载 6 张图像;CompressedTextureLoader 用于加载压缩纹理;DataTextureLoader 用于加载像素数据,常用于动态生成或特定算法生成的纹理。此外还有用于加载文件、视频、音频等的通用加载器。

本文选用 TextureLoader 加载 3 张静态图片,分别对应草地方块不同方向的纹理。草地方块共 6 个面:顶部为草坪纹理,侧面 4 个面共用一张纹理,底部为泥土纹理(具体图片资源见文末链接,请勿使用截图作为资源)。

准备所需的静态图片资源:

底部纹理:

Three.js 纹理贴图创建 Minecraft 草地方块

侧面纹理:

Three.js 纹理贴图创建 Minecraft 草地方块

顶部纹理:

Three.js 纹理贴图创建 Minecraft 草地方块

将所有资源加载逻辑封装在 loader.ts 文件中处理。

import  * as THREE from 'three';
// 导入静态的图片资源,位置注意是自己项目中存放静态资源的地址。
import grassBlockTextureSideImg from '../../assets/textures/blocks-clipped/grassBlockSide.png';
import grassBlockTextureTopImg from '../../assets/textures/blocks-clipped/grassBlockTop.png';
import dirtTextureImg from '../../assets/textures/blocks-clipped/dirt.png';
// 创建一个 THREE 加载器
const loader = new THREE.TextureLoader();
// 使用 loader.load 将静态图片加载到 ThreeJS 中
const grassBlockTextureSide = loader.load(grassBlockTextureSideImg);
const grassBlockTextureTop = loader.load(grassBlockTextureTopImg);
const dirtTexture = loader.load(dirtTextureImg);
// 定义清楚草地方块纹理顺序
export const grassBlock = {
    name: 'grassBlock',
    // 注意顺序
    textureImg: [
        grassBlockTextureSide,
        grassBlockTextureSide,
        grassBlockTextureTop,
        dirtTexture,
        grassBlockTextureSide,
        grassBlockTextureSide,
      ],
      material: [],
};
// 使用 THREE.MeshStandardMaterial 将纹理创建成材质 存入 grassBlock.material 上
grassBlock.material = grassBlock.textureImg.map((img, i) => {
    return new THREE.MeshStandardMaterial({
        map: img,
        // side: THREE.DoubleSide
    })
});
export default { grassBlock }

第二步完成。接下来只需将 grassBlock.material 应用于新建的立方体,并完成渲染即可。

第三步:使用纹理材质创建立方体并渲染

此步骤涉及代码封装以实现职责分离。创建立方体的代码放入 generateFrag.ts;场景(scene)、相机(camera)及渲染器(renderer)的初始化封装在 Core 类中。

// generateFrag.ts
import Terrain from '.';
import * as THREE from 'three';
// 导入草地格子的配置数据
import { grassBlock } from '../controller/loader';
export default class GenerateFrags {
  private terrain: Terrain;
  constructor(terrain: Terrain) {
    this.terrain = terrain;
  }
  generateAll() {}
  // 主要关注这里
  generateOneFrag() {
    const geometry = new THREE.BoxGeometry(1, 1, 1);
    // 使用纹理创建的材质来作为 mesh 的材质
    const material = grassBlock.material;
    const mesh = new THREE.Mesh(geometry, material);
    mesh.position.set(0, 0, 1);
    return mesh;
  }
}

core.ts 负责场景、相机、渲染器的初始化以及草地格子的渲染逻辑。

// core.ts
import * as THREE from 'three';
import Terrain from '../terrain';
import GenerateFrags from '../terrain/generateFrag';
export default class Core {
  // scene
  public scene: THREE.Scene;
  // 透视相机
  public camera: THREE.PerspectiveCamera;
  // renderer 渲染器
  public renderer: THREE.WebglRenderer;
  // 地形对象
  public terrain: Terrain;
  constructor() {
    this.scene = new THREE.Scene();
    this.camera = new THREE.PerspectiveCamera(
      75,
      window.innerWidth / window.innerHeight,
      0.1,
      1000,
    );
    this.renderer = new THREE.WebGLRenderer();
    // 地形
    this.terrain = new Terrain(this);
    // 其他初始化操作一并处理
    this.#init();
  }
  /**
   * 1, 监听页面窗口大小改变,改变时需要个更新坐标系(相机位置)
   */
  #init() {
    window.addEventListener('resize', () => {
      this.camera.aspect = window.innerHeight / window.innerWidth;
      this.camera.updateProjectionMatrix();
      this.renderer.setSize(window.innerWidth, window.innerHeight);
    });
    // 初始化设置相机
    // this.camera.fov = 80;
    // this.camera.aspect = window.innerWidth / window.innerWidth;
    // this.camera.far = 500;
    // this.camera.updateProjectionMatrix();
    this.camera.position.set(0, 0, 10);
    // 初始化场景scene 背景
    const backgroundColor = 0x87ceeb;
    this.scene.fog = new THREE.FogExp2(0.02);
    this.scene.background = new THREE.Color(backgroundColor);
    // 初始化场景的灯光
    const sunLight = new THREE.PointLight(0xffffff, 0.5);
    sunLight.position.set(500, 500, 500);
    this.scene.add(sunLight);
    const sunLight2 = new THREE.PointLight(0xffffff, 0.2);
    sunLight2.position.set(-500, 500, -500);
    this.scene.add(sunLight2);
    const reflectionLight = new THREE.AmbientLight(0x404040);
    this.scene.add(reflectionLight);
    this.renderer.setSize(window.innerWidth, window.innerHeight);
    document
      .getElementById('game-container')
      .appendChild(this.renderer.domElement);
    // 这里是调用入口
    this.testRenderOneGrassBlock();
  }
  // 测试生成一个草地格子
  testRenderOneGrassBlock() {
    // 初始化一个 GenerateFrags 对象来创建一个草地格子
    const generateOneFrag = new GenerateFrags(this.terrain);
    const cube = generateOneFrag.generateOneFrag();
    // 将草地格子加到 scene 中
    this.scene.add(cube);
    const animate = () => {
      requestAnimationFrame(animate);
      // 使用 mesh 的 rotation 来让草地格子旋转起来
      cube.rotation.x += 0.01;
      cube.rotation.y += 0.01;
      // 调用渲染器进行渲染
      this.renderer.render(this.scene, this.camera);
    };
    animate();
  }
}

至此,一个旋转的草地方块生成完毕,效果如下:

Three.js 纹理贴图创建 Minecraft 草地方块

分析添加纹理后不生效(显示黑色方块)的原因

若图片创建逻辑正确但渲染结果为黑色方块,通常有两个原因:一是纹理异步加载,在渲染时纹理尚未加载完成;二是场景中缺少光源。

Three.js 纹理贴图创建 Minecraft 草地方块

解决思路

确保渲染操作在纹理加载完成后进行。

若仅渲染一次,需保证渲染时纹理已就绪。对于多张图片材质的盒子,使用 Promise.all 处理最佳;若为单张图片,可直接监听 onload 事件 loader.load(imgpath, onload)。

在 core.js 中使用 requestAnimationFrame 进行重复渲染。由于第二次渲染时纹理已加载完成,从而避免了因纹理未加载导致的黑色方块问题。后续游戏中若纹理集中加载,可在每个材质加载完成后触发新的渲染。

为场景添加合适的光源

如同在黑暗房间中无法看见彩色物体,若无光照或处于逆光位置,也无法看到物体背面。因此,需将光源设置在相机前方(或设置多组光源),确保相机与物体连线方向上有光照分量。

以上为 Three.js 使用纹理贴图创建 Minecraft 草地方块的详细内容,更多 Three.js 纹理贴图资料请查阅本站相关文章。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章