ThreeJS 纹理贴图创立一个我的世界草地方块
开始筹备应用 ThreeJS 写一个相似《我的世界》场景的射击类游戏,地形和我的世界很类似。场景中须要进行很多的纹理贴图,本篇文章次要以给一个立方体贴图成草地为例子介绍 ThreeJS 中如何增加纹理?如何解决纹理贴图后方块不展现(纹理未失效,成果是彩色方块)问题?
给 mesh 减少纹理,实现草地方块
把大象装进冰箱须要三步,这里实现一个草地方块也须要三步。
**step one:初始化一个 geometry 立方体形态。
step two: 初始化纹理加载器,加载纹理。
step three:将纹理贴到立方体上,渲染进去。
**
step one 初始化一个 1 11 的立方体
前边两篇文章中也有介绍,尤其是第一篇【渲染第一个 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 {
// 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();}
}
至此一个旋转的草地格子生成进去了,成果如下:
增加纹理不失效的起因剖析
对一个立方体增加纹理最初可能渲染成这个样子,在保障图片失常创立格子的形式也是正确的状况下,可能有两个起因。会导致渲染出彩色的格子来,第一:纹理是异步加载渲染之前纹理还未加载好,第二:没有光源。
解决思路
确保渲染在纹理加载之后
如果只渲染一次,那么须要保障渲染时纹理曾经加载实现,最好的形式就是应用 Promise 来解决一个盒子须要多张图片来作为材质时 Promise.all 会很好用。如果是单张图片可间接监听 onload 事件 loader.load(imgpath, onload)
咱们在 core.js 中应用 requestAnimationFrame 来反复渲染草地格子第二次渲染开始纹理曾经加载实现了由此避开了纹理未加载就渲染导致形态彩色问题。后续游戏中应用的纹理大部分都集中加载,因而能够检测每个材质回来后就进行新的渲染触发。
为场景增加适合的光源
设想一下在乌黑的屋子外面有一个黑白的球,一点光都没有啥都看不见。另外就是逆光时咱们看不见光源背地的货色。因而咱们须要将光源设置到相机的顺方向(或者多设置几组光源),保障相机与物体的连线上能存在光的重量。
本文由 mdnice 多平台公布