ThreeJS使用紋理貼圖創(chuàng)建一個(gè)我的世界草地方塊
ThreeJS 紋理貼圖創(chuàng)建一個(gè)我的世界草地方塊
開(kāi)始準(zhǔn)備使用ThreeJS寫一個(gè)類似《我的世界》場(chǎng)景的射擊類游戲,地形和我的世界很相似。場(chǎng)景中需要進(jìn)行很多的紋理貼圖,本篇文章主要以給一個(gè)立方體貼圖成草地為例子介紹 ThreeJS 中如何添加紋理?如何解決紋理貼圖后方塊不展示(紋理未生效,效果是黑色方塊)問(wèn)題?

給 mesh 增加紋理,實(shí)現(xiàn)草地方塊
把大象裝進(jìn)冰箱需要三步,這里實(shí)現(xiàn)一個(gè)草地方塊也需要三步。
**step one :初始化一個(gè) geometry 立方體形狀。
step two: 初始化紋理加載器,加載紋理。
step three: 將紋理貼到立方體上,渲染出來(lái)。
**
step one 初始化一個(gè)111 的立方體
前邊兩篇文章中也有介紹,尤其是第一篇 【渲染第一個(gè)ThreeJS立方體】。不做詳細(xì)介紹呀,一行代碼
const geometry = new THREE.BoxGeometry();
Step two 初始化紋理加載器,加載紋理
開(kāi)始介紹之前我們先簡(jiǎn)單減少一下 ThreeJS 支持的紋理加載器以及其加載的紋理類型,ThreeJS 提供 TextureLoader 來(lái)加載靜態(tài)圖像紋理;CubeTextureLoader 用于加載立方體貼圖紋理,它通過(guò)加載 6 個(gè)圖像來(lái)作為立方體的六個(gè)面,常用來(lái)創(chuàng)建天空盒天空球效果;CompressedTextureLoader 用于加載壓縮過(guò)后的紋理; DataTextureLoader 用于加載像素?cái)?shù)據(jù)組成的紋理,常用于動(dòng)態(tài)生成紋理或者使用特定的紋理生成算飯來(lái)創(chuàng)建紋理。還有一些其他通用的加載器用于加載文件、視頻、音頻等資源。
本文選擇使用 TextureLoader 來(lái)加載3張靜態(tài)圖片分別作為不同方向的紋理。開(kāi)始前先準(zhǔn)備3 張圖片用于紋理資源分別如下。草地方塊 6 個(gè)面,頂部是草坪,側(cè)邊4個(gè)面共用一個(gè)圖,底部是一個(gè)圖。(圖片資源文末鏈接自取, 別使用一下截圖來(lái)作為圖片資源)。
準(zhǔn)備幾張靜態(tài)圖片:
底部:

側(cè)邊:

頂部:

把資源加載都放到 loader.ts 文件中處理
import * as THREE from 'three';
// 導(dǎo)入靜態(tài)的圖片資源,位置注意是自己項(xiàng)目中存放靜態(tài)資源的地址。
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';
// 創(chuàng)建一個(gè) THREE 加載器
const loader = new THREE.TextureLoader();
// 使用 loader.load 將靜態(tài)圖片加載到 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 將紋理創(chuàng)建成材質(zhì) 存入 grassBlock.material 上
grassBlock.material = grassBlock.textureImg.map((img, i) => {
return new THREE.MeshStandardMaterial({
map: img,
// side: THREE.DoubleSide
})
});
export default { grassBlock }step two 完成,到目前已經(jīng)完成大部分了,接下來(lái)只要將 grassBlock.material用于新建的立方體上,然后將立方體渲染出來(lái)即可。
Step three 使用紋理材質(zhì)創(chuàng)建立方體并渲染
這一步我們需要進(jìn)行一些封裝,目的是將職責(zé)進(jìn)行隔離。將創(chuàng)建立方體的代碼放到 generateFrag.ts 中;我們將 scene 的初始化抽離到一個(gè)固定的類 Core 中進(jìn)行封裝,Core 類主要處理幾件事情:初始化 scene、初始化 camera、初始化渲染器 renderer。
// generateFrag.ts
import Terrain from '.';
import * as THREE from 'three';
// 導(dǎo)入草地格子的配置數(shù)據(jù)
import { grassBlock } from '../controller/loader';
export default class GenerateFrags {
private terrain: Terrain;
constructor(terrain: Terrain) {
this.terrain = terrain;
}
generateAll() {}
// 主要關(guān)注這里
generateOneFrag() {
const geometry = new THREE.BoxGeometry(1, 1, 1);
// 使用紋理創(chuàng)建的材質(zhì)來(lái)作為 mesh 的材質(zhì)
const material = grassBlock.material;
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(0, 0, 1);
return mesh;
}
}core.ts 部分負(fù)責(zé)場(chǎng)景、相機(jī)、渲染器的初始化以及渲染草地格子。
// 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;
// 透視相機(jī)
public camera: THREE.PerspectiveCamera;
// renderer 渲染器
public renderer: THREE.WebglRenderer;
// 地形對(duì)象
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, 監(jiān)聽(tīng)頁(yè)面窗口大小改變,改變時(shí)需要個(gè)更新坐標(biāo)系(相機(jī)位置)
*/
#init() {
window.addEventListener('resize', () => {
this.camera.aspect = window.innerHeight / window.innerWidth;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
});
// 初始化設(shè)置相機(jī)
// 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);
// 初始化場(chǎng)景scene 背景
const backgroundColor = 0x87ceeb;
this.scene.fog = new THREE.FogExp2(0.02);
this.scene.background = new THREE.Color(backgroundColor);
// 初始化場(chǎng)景的燈光
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);
// 這里是調(diào)用入口
this.testRenderOneGrassBlock();
}
// 測(cè)試生成一個(gè)草地格子
testRenderOneGrassBlock() {
// 初始化一個(gè) GenerateFrags 對(duì)象來(lái)創(chuàng)建一個(gè)草地格子
const generateOneFrag = new GenerateFrags(this.terrain);
const cube = generateOneFrag.generateOneFrag();
// 將草地格子加到 scene 中
this.scene.add(cube);
const animate = () => {
requestAnimationFrame(animate);
// 使用 mesh 的 rotation 來(lái)讓草地格子旋轉(zhuǎn)起來(lái)
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
// 調(diào)用渲染器進(jìn)行渲染
this.renderer.render(this.scene, this.camera);
};
animate();
}
}至此一個(gè)旋轉(zhuǎn)的草地格子生成出來(lái)了,效果如下:

添加紋理不生效的原因分析
對(duì)一個(gè)立方體添加紋理最后可能渲染成這個(gè)樣子,在保證圖片正常創(chuàng)建格子的方式也是正確的情況下,可能有兩個(gè)原因。會(huì)導(dǎo)致渲染出黑色的格子來(lái),第一:紋理是異步加載渲染之前紋理還未加載好,第二:沒(méi)有光源。

解決思路
確保渲染在紋理加載之后
如果只渲染一次,那么需要保證渲染時(shí)紋理已經(jīng)加載完成,最好的方式就是使用 Promise 來(lái)處理一個(gè)盒子需要多張圖片來(lái)作為材質(zhì)時(shí) Promise.all 會(huì)很好用。如果是單張圖片可直接監(jiān)聽(tīng)onload 事件 loader.load(imgpath, onload)
我們?cè)?core.js 中使用 requestAnimationFrame 來(lái)重復(fù)渲染草地格子第二次渲染開(kāi)始紋理已經(jīng)加載完成了由此避開(kāi)了紋理未加載就渲染導(dǎo)致形狀黑色問(wèn)題。后續(xù)游戲中使用的紋理大部分都集中加載,因此可以檢測(cè)每個(gè)材質(zhì)回來(lái)后就進(jìn)行新的渲染觸發(fā)。
為場(chǎng)景添加合適的光源
想象一下在漆黑的屋子里面有一個(gè)彩色的球,一點(diǎn)光都沒(méi)有啥都看不見(jiàn)。另外就是逆光時(shí)我們看不見(jiàn)光源背后的東西。因此我們需要將光源設(shè)置到相機(jī)的順?lè)较颍ɑ蛘叨嘣O(shè)置幾組光源),保證相機(jī)與物體的連線上能存在光的分量。
以上就是ThreeJS使用紋理貼圖創(chuàng)建一個(gè)我的世界草地方塊的詳細(xì)內(nèi)容,更多關(guān)于ThreeJS 紋理貼圖的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
DS-SDK封裝ThreeJS的三維場(chǎng)景核心庫(kù)Viewer
這篇文章主要為大家介紹了基于DS-SDK封裝ThreeJS的三維場(chǎng)景核心庫(kù)Viewer封裝示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
TypeScript逆變之條件推斷和泛型的應(yīng)用示例詳解
這篇文章主要為大家介紹了TypeScript逆變之條件推斷和泛型的應(yīng)用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09
TypeScript數(shù)據(jù)結(jié)構(gòu)之隊(duì)列結(jié)構(gòu)Queue教程示例
這篇文章主要為大家介紹了TypeScript數(shù)據(jù)結(jié)構(gòu)之隊(duì)列結(jié)構(gòu)Queue教程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02
基于tsup打包TypeScript實(shí)現(xiàn)過(guò)程
這篇文章主要為大家介紹了基于tsup打包TypeScript實(shí)現(xiàn)過(guò)程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12
layui.layer彈出層(子頁(yè)面)改變父頁(yè)面內(nèi)容(訪問(wèn)元素和函數(shù))
當(dāng)前頁(yè)面(父框架或父頁(yè)面)使用layer以iframe層的方式彈出新的窗口(子框架或子頁(yè)面)時(shí),如何在子頁(yè)面中訪問(wèn)父頁(yè)面的元素和函數(shù),從而改變父元素的頁(yè)面顯示,給用戶合理舒適的體驗(yàn)。2023-02-02
TypeScript實(shí)現(xiàn)類型安全的EventEmitter
這篇文章主要為大家介紹了TypeScript實(shí)現(xiàn)類型安全的EventEmitter示例詳解有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03

