ThreeJS 纹理贴图创建一个我的世界草地方块

开始准备使用ThreeJS写一个类似《我的世界》场景的射击类游戏,地形和我的世界很相似。场景中需要进行很多的纹理贴图,本篇文章主要以给一个立方体贴图成草地为例子介绍 ThreeJS 中如何添加纹理?如何解决纹理贴图后方块不展示(纹理未生效,效果是黑色方块)问题?

给 mesh 增加纹理,实现草地方块

把大象装进冰箱需要三步,这里实现一个草地方块也需要三步。

**step one :初始化一个 geometry 立方体形状。

step two: 初始化纹理加载器,加载纹理。

step three: 将纹理贴到立方体上,渲染出来。
**

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

前边两篇文章中也有介绍,尤其是第一篇 【渲染第一个ThreeJS立方体】。不做详细介绍呀,一行代码

const geometry = new THREE.BoxGeometry();

Step two 初始化纹理加载器,加载纹理

开始介绍之前我们先简单减少一下 ThreeJS 支持的纹理加载器以及其加载的纹理类型,ThreeJS 提供 TextureLoader 来加载静态图像纹理;CubeTextureLoader 用于加载立方体贴图纹理,它通过加载 6 个图像来作为立方体的六个面,常用来创建天空盒天空球效果;CompressedTextureLoader 用于加载压缩过后的纹理; DataTextureLoader 用于加载像素数据组成的纹理,常用于动态生成纹理或者使用特定的纹理生成算饭来创建纹理。还有一些其他通用的加载器用于加载文件、视频、音频等资源。

本文选择使用 TextureLoader 来加载3张静态图片分别作为不同方向的纹理。开始前先准备3 张图片用于纹理资源分别如下。草地方块 6 个面,顶部是草坪,侧边4个面共用一个图,底部是一个图。(图片资源文末链接自取, 别使用一下截图来作为图片资源)。

准备几张静态图片:

底部:

侧边:

顶部:

把资源加载都放到 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 }

step two 完成,到目前已经完成大部分了,接下来只要将 grassBlock.material用于新建的立方体上,然后将立方体渲染出来即可。

Step three 使用纹理材质创建立方体并渲染

这一步我们需要进行一些封装,目的是将职责进行隔离。将创建立方体的代码放到 generateFrag.ts 中;我们将 scene 的初始化抽离到一个固定的类 Core 中进行封装,Core 类主要处理几件事情:初始化 scene、初始化 camera、初始化渲染器 renderer。

// 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 {// scenepublic 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();}
}

至此一个旋转的草地格子生成出来了,效果如下:

添加纹理不生效的原因分析

对一个立方体添加纹理最后可能渲染成这个样子,在保证图片正常创建格子的方式也是正确的情况下,可能有两个原因。会导致渲染出黑色的格子来,第一:纹理是异步加载渲染之前纹理还未加载好,第二:没有光源。

解决思路

确保渲染在纹理加载之后

如果只渲染一次,那么需要保证渲染时纹理已经加载完成,最好的方式就是使用 Promise 来处理一个盒子需要多张图片来作为材质时 Promise.all 会很好用。如果是单张图片可直接监听onload 事件 loader.load(imgpath, onload)

我们在 core.js 中使用 requestAnimationFrame 来重复渲染草地格子第二次渲染开始纹理已经加载完成了由此避开了纹理未加载就渲染导致形状黑色问题。后续游戏中使用的纹理大部分都集中加载,因此可以检测每个材质回来后就进行新的渲染触发。

为场景添加合适的光源

想象一下在漆黑的屋子里面有一个彩色的球,一点光都没有啥都看不见。另外就是逆光时我们看不见光源背后的东西。因此我们需要将光源设置到相机的顺方向(或者多设置几组光源),保证相机与物体的连线上能存在光的分量。

本文由mdnice多平台发布

ThreeJS 纹理贴图创建一个我的世界草地方块相关推荐

  1. 关于ThreeJs纹理贴图动画的实现

    效果图:实际效果,图中的贴图是可以动的 一.准备工作 1.静态文件 2.首先你要引入相关依赖: threejs核心依赖: http://www.yanhuangxueyuan.com/versions ...

  2. 【ThreeJS】基础教学 创建一个立方体

    因为看到ThingJS中的文章ThingJS和threejs的区别在哪?ThingJS是免费的么?-场景搭建-ThingJS 开发者社区 讲THREE很难学,这是误导,其实是很简单的: 就拿他文章里的 ...

  3. 用AI从零开始创建一个宫崎骏的世界

    前言 学习人工智能有段日子了,一直感觉问题定义难,模型设计难,算力不足难,部署落地更难.期间掉坑无数,出坑不易.结合这段时间的心路历程,完整的记录一个AI应用,从实际问题出发到模型选型.数据收集.数据 ...

  4. OpenGL:纹理贴图

    纹理贴图是在栅格化的模型表面上覆盖图像的技术.它是为渲染场景添加真实感的最基本和最重要的方法之一.硬件也为纹理贴图提供了硬件支持,使得它具备实现实时的照片级真实感的超高性能.纹理单元是专为纹理设计的硬 ...

  5. OpenGL 纹理贴图

    纹理贴图允许把一幅砖墙图像映射到一个多边形的表面上,并把正面墙画成单个多边形.纹理贴图能够保证当这个多边形变形或渲染时,映射到多边形表面的图像也能够表现出正确的行为. 纹理贴图是一个相当大的主题,并且 ...

  6. OPenGL笔记--创建一个3D场景

    文章目录 一.前言 二.效果展示 三.详细流程 3.1.World.txt文件规则 3.2.加载World.txt 3.3.绘制场景 3.4.交互 四.详细代码 五.举一反三 一.前言 通过前面的学习 ...

  7. Threejs系列--14游戏开发--沙漠赛车游戏【纹理贴图之loading加载】

    Threejs系列--14游戏开发--沙漠赛车游戏[纹理贴图之loading加载] 序言 目录结构 代码一览 world/index.js代码 Application.js代码 代码解读 运行结果 序 ...

  8. threejs 绘制球体_实战:用 threejs 创建一个地球

    原标题:实战:用 threejs 创建一个地球 提示: 讲座 前端大型免费公开课讲座 教程 从零基础学前端教程,都在这~ 上个月底,在朋友圈看到一个号称"这可能是地球上最美的h5" ...

  9. WebGL/ThreeJS几何体、材质、纹理贴图,给几何体披上好看的外衣

    1.ThreeJS的常见几何体 BufferGeometry和Geometry有什么不同? 如果你想简单理解BufferGeometry和Geometry有什么不同,就是两者的数据结构不同,缓冲类型几 ...

最新文章

  1. 5.8fork父子进程
  2. 消除危害 让BYOD策略更安全的几个秘诀
  3. symfony 2 app.php,Symfony2安装的方法(2种方法)
  4. 马斯克入选美国工程院院士,张宏江博士入选外籍院士
  5. ASP.NET小知识
  6. Android应用连接代理服务器状况监测解决
  7. Spring Boot和JSP
  8. 5分绩点转4分_工作复盘|因为这5点,4月份目标没完成
  9. [leetcode]242. Valid Anagram判断两个字符串是不是包含相同字符的重排列
  10. VMWare关闭beep声
  11. 面向对象和面向过程思想 oc
  12. 从SAP BPC中Entity维设计的理念考虑Web程序中类似文档库之类的设计该考虑的东西...
  13. EAS后台事务的超时时长如何设置?
  14. pc端移动端布局有什么区别
  15. 同时安装Office2016和Visio2016
  16. 有线路由器加无线路由器WAN接LAN和LAN接LAN的连线方法
  17. 2021-09-30 cannot locate default realm
  18. Docker 常用命令收录 -- 持续更新
  19. 高效记录任务和提醒的极简ToDo待办事项便签应用
  20. Gitea 的简单介绍

热门文章

  1. GD32VF103_DAC
  2. 山东大学软件学院项目实训-创新实训-网络安全靶场实验平台(五)
  3. 用SEGGER Embedded Studio(SES)开发蓝牙nRF52840
  4. 设计原则之【单一职责原则】
  5. 视频里的声音怎么转换成音频?
  6. Hexo 建立你的博客
  7. app build file记录
  8. 前端工程师需要学习ps 吗_【百度前端工程师面试】前端开发技术要会PS-看准网...
  9. hanlp的基本使用--python(自然语言处理)
  10. 理解变分自编码器,GAN的近亲