ThreeJS使用紋理貼圖創(chuàng)建一個我的世界草地方塊
ThreeJS 紋理貼圖創(chuàng)建一個我的世界草地方塊
開始準備使用ThreeJS寫一個類似《我的世界》場景的射擊類游戲,地形和我的世界很相似。場景中需要進行很多的紋理貼圖,本篇文章主要以給一個立方體貼圖成草地為例子介紹 ThreeJS 中如何添加紋理?如何解決紋理貼圖后方塊不展示(紋理未生效,效果是黑色方塊)問題?
給 mesh 增加紋理,實現(xiàn)草地方塊
把大象裝進冰箱需要三步,這里實現(xiàn)一個草地方塊也需要三步。
**step one :初始化一個 geometry 立方體形狀。
step two: 初始化紋理加載器,加載紋理。
step three: 將紋理貼到立方體上,渲染出來。
**
step one 初始化一個111 的立方體
前邊兩篇文章中也有介紹,尤其是第一篇 【渲染第一個ThreeJS立方體】。不做詳細介紹呀,一行代碼
const geometry = new THREE.BoxGeometry();
Step two 初始化紋理加載器,加載紋理
開始介紹之前我們先簡單減少一下 ThreeJS 支持的紋理加載器以及其加載的紋理類型,ThreeJS 提供 TextureLoader 來加載靜態(tài)圖像紋理;CubeTextureLoader 用于加載立方體貼圖紋理,它通過加載 6 個圖像來作為立方體的六個面,常用來創(chuàng)建天空盒天空球效果;CompressedTextureLoader 用于加載壓縮過后的紋理; DataTextureLoader 用于加載像素數(shù)據(jù)組成的紋理,常用于動態(tài)生成紋理或者使用特定的紋理生成算飯來創(chuàng)建紋理。還有一些其他通用的加載器用于加載文件、視頻、音頻等資源。
本文選擇使用 TextureLoader 來加載3張靜態(tài)圖片分別作為不同方向的紋理。開始前先準備3 張圖片用于紋理資源分別如下。草地方塊 6 個面,頂部是草坪,側(cè)邊4個面共用一個圖,底部是一個圖。(圖片資源文末鏈接自取, 別使用一下截圖來作為圖片資源)。
準備幾張靜態(tài)圖片:
底部:
側(cè)邊:
頂部:
把資源加載都放到 loader.ts 文件中處理
import * as THREE from 'three'; // 導(dǎo)入靜態(tài)的圖片資源,位置注意是自己項目中存放靜態(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)建一個 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)完成大部分了,接下來只要將 grassBlock.material
用于新建的立方體上,然后將立方體渲染出來即可。
Step three 使用紋理材質(zhì)創(chuàng)建立方體并渲染
這一步我們需要進行一些封裝,目的是將職責進行隔離。將創(chuàng)建立方體的代碼放到 generateFrag.ts 中;我們將 scene 的初始化抽離到一個固定的類 Core 中進行封裝,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ì)來作為 mesh 的材質(zhì) 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, 監(jiān)聽頁面窗口大小改變,改變時需要個更新坐標系(相機位置) */ #init() { window.addEventListener('resize', () => { this.camera.aspect = window.innerHeight / window.innerWidth; this.camera.updateProjectionMatrix(); this.renderer.setSize(window.innerWidth, window.innerHeight); }); // 初始化設(shè)置相機 // 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); // 這里是調(diào)用入口 this.testRenderOneGrassBlock(); } // 測試生成一個草地格子 testRenderOneGrassBlock() { // 初始化一個 GenerateFrags 對象來創(chuàng)建一個草地格子 const generateOneFrag = new GenerateFrags(this.terrain); const cube = generateOneFrag.generateOneFrag(); // 將草地格子加到 scene 中 this.scene.add(cube); const animate = () => { requestAnimationFrame(animate); // 使用 mesh 的 rotation 來讓草地格子旋轉(zhuǎn)起來 cube.rotation.x += 0.01; cube.rotation.y += 0.01; // 調(diào)用渲染器進行渲染 this.renderer.render(this.scene, this.camera); }; animate(); } }
至此一個旋轉(zhuǎn)的草地格子生成出來了,效果如下:
添加紋理不生效的原因分析
對一個立方體添加紋理最后可能渲染成這個樣子,在保證圖片正常創(chuàng)建格子的方式也是正確的情況下,可能有兩個原因。會導(dǎo)致渲染出黑色的格子來,第一:紋理是異步加載渲染之前紋理還未加載好,第二:沒有光源。
解決思路
確保渲染在紋理加載之后
如果只渲染一次,那么需要保證渲染時紋理已經(jīng)加載完成,最好的方式就是使用 Promise 來處理一個盒子需要多張圖片來作為材質(zhì)時 Promise.all 會很好用。如果是單張圖片可直接監(jiān)聽onload 事件 loader.load(imgpath, onload)
我們在 core.js 中使用 requestAnimationFrame 來重復(fù)渲染草地格子第二次渲染開始紋理已經(jīng)加載完成了由此避開了紋理未加載就渲染導(dǎo)致形狀黑色問題。后續(xù)游戲中使用的紋理大部分都集中加載,因此可以檢測每個材質(zhì)回來后就進行新的渲染觸發(fā)。
為場景添加合適的光源
想象一下在漆黑的屋子里面有一個彩色的球,一點光都沒有啥都看不見。另外就是逆光時我們看不見光源背后的東西。因此我們需要將光源設(shè)置到相機的順方向(或者多設(shè)置幾組光源),保證相機與物體的連線上能存在光的分量。
以上就是ThreeJS使用紋理貼圖創(chuàng)建一個我的世界草地方塊的詳細內(nèi)容,更多關(guān)于ThreeJS 紋理貼圖的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
TypeScript逆變之條件推斷和泛型的應(yīng)用示例詳解
這篇文章主要為大家介紹了TypeScript逆變之條件推斷和泛型的應(yīng)用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-09-09TypeScript數(shù)據(jù)結(jié)構(gòu)之隊列結(jié)構(gòu)Queue教程示例
這篇文章主要為大家介紹了TypeScript數(shù)據(jù)結(jié)構(gòu)之隊列結(jié)構(gòu)Queue教程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-02-02layui.layer彈出層(子頁面)改變父頁面內(nèi)容(訪問元素和函數(shù))
當前頁面(父框架或父頁面)使用layer以iframe層的方式彈出新的窗口(子框架或子頁面)時,如何在子頁面中訪問父頁面的元素和函數(shù),從而改變父元素的頁面顯示,給用戶合理舒適的體驗。2023-02-02TypeScript實現(xiàn)類型安全的EventEmitter
這篇文章主要為大家介紹了TypeScript實現(xiàn)類型安全的EventEmitter示例詳解有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-03-03