在前兩天的進度中,我們完成了 50x50 隨機地圖的資料鋪設,並架構了仰角約 28 度的 2.5D 長焦視角。然而,當我們滿懷期待地將繪製好的 16×16 像素貼圖匯入 Three.js 時,往往會迎來第一個嚴重的視覺打擊:原本銳利俐落的像素顆粒,被引擎渲染得像蒙上一層毛玻璃般模糊不堪;而隨手套用在地圖方塊上的貼圖,也讓方塊的「頂面」與「立面」看起來毫無結構區分。
現代 3D 渲染引擎預設的材質與取樣機制是為了平滑寫實的高多邊形模型設計的,但這恰恰是 2D 像素藝術(Pixel Art)的天敵。今天我們將從底層的紋理取樣著手,封裝專屬的像素貼圖載入器,並透過 Three.js 的 BoxGeometry 六面材質陣列機制,將方塊的「頂面壓頂石」與「立面石磚」徹底分離。
【Step 1:封裝像素銳利化載入器 loadPixelTexture】
在預設情況下,Three.js 的 TextureLoader 在載入圖片時,會自動啟用雙線性插值(Linear Filter)與 Mipmaps(多級漸遠紋理技術)。這項技術會在物體遠離鏡頭時自動調用低解析度的模糊版本以抑制摩爾紋,但對於尺寸僅有 16×16 的像素圖塊而言,這只會導致像素邊界徹底融化成一團色塊。
我們必須在全域封裝一個專屬於像素藝術的載入函式 loadPixelTexture,強制鎖定紋理的取樣行為,並一次性載入地城所需的地面石磚貼圖
// 載入 4 張石頭圖片並保持像素銳利化
// ==========================================
// 2. 貼圖載入與像素銳利化設定
// ==========================================
const textureLoader = new THREE.TextureLoader(); //
function loadPixelTexture(url) {
const tex = textureLoader.load(url); //
tex.magFilter = THREE.NearestFilter; //
tex.minFilter = THREE.NearestFilter; //
tex.generateMipmaps = false; //
return tex; //
}
// 地面石磚
const stoneTex1 = loadPixelTexture('/stone1.png'); //
const stoneTex2 = loadPixelTexture('/stone2.png'); //
const stoneTex3 = loadPixelTexture('/stone3.png'); //
const stoneTex4 = loadPixelTexture('/stone4.png'); //
【Step 2:地城牆壁專屬材質抽離與 PBR 地板字典】
除了地板貼圖,一座具備厚重歷史感的古堡遺跡還需要完整的立體建材。我們不能拿地面的鵝卵石直接貼在垂直的牆壁上,那樣會完全缺乏建築的受力感與結構感。我們繼續使用封裝好的函式,載入專屬於牆面的立面、壓頂石與裝飾件:
// 專屬地城牆壁材質
const wallFaceTex = loadPixelTexture('/wall_face.png'); // 暗藍古石磚立面
const wallFaceMossTex = loadPixelTexture('/wall_face_moss.png'); // 青苔石磚立面
const wallTopTex = loadPixelTexture('/wall_top.png'); // 牆頂平整壓頂石
const wallTopCrackedTex = loadPixelTexture('/wall_top_cracked.png'); // 裂紋壓頂石
const wallCarvedTex = loadPixelTexture('/wall_carved.png'); // 石雕飾帶
const lanternTex = loadPixelTexture('/lantern.png'); // 鍛鐵壁燈
// 地板材質
const materials = {
0: new THREE.MeshStandardMaterial({ map: stoneTex1, roughness: 0.9, metalness: 0.1 }),
3: new THREE.MeshStandardMaterial({ map: stoneTex2, roughness: 0.9, metalness: 0.1 }),
4: new THREE.MeshStandardMaterial({ map: stoneTex3, roughness: 0.9, metalness: 0.1 }),
1: new THREE.MeshStandardMaterial({ map: stoneTex4, roughness: 0.85, metalness: 0.15 }),
2: new THREE.MeshStandardMaterial({ map: stoneTex4, roughness: 0.85, metalness: 0.15 }),
};
【Step 3:BoxGeometry 6 面多材質陣列(Multi-Material)空間解構】
在真實的 2.5D 遊戲中,視角是從斜上方俯視的。這意味著玩家會同時看到牆壁的「頂部」與「側面」。如果整個方塊六個面都貼上同一張垂直石磚圖,從上方看下去就會出現嚴重的違和感。
Three.js 的 BoxGeometry 支援傳入一個包含 6 個材質的陣列,分別依序對應方塊的 6 個幾何面 :
$$\text{[+X (右), -X (左), +Y (頂), -Y (底), +Z (前), -Z (後)]}$$
我們利用這個空間映射規則,先定義五種不同面相的標準材質,再組合出專屬於牆體頂層的複合材質陣列
// 牆體材質組合 (Box 6 面:+X, -X, +Y, -Y, +Z, -Z)
const wallFaceMat = new THREE.MeshStandardMaterial({ map: wallFaceTex, roughness: 0.85, metalness: 0.15 });
const wallMossMat = new THREE.MeshStandardMaterial({ map: wallFaceMossTex, roughness: 0.9, metalness: 0.1 });
const wallTopMat = new THREE.MeshStandardMaterial({ map: wallTopTex, roughness: 0.8, metalness: 0.1 });
const wallTopCrackedMat = new THREE.MeshStandardMaterial({ map: wallTopCrackedTex, roughness: 0.8, metalness: 0.1 });
const wallCarvedMat = new THREE.MeshStandardMaterial({ map: wallCarvedTex, roughness: 0.75, metalness: 0.2 });
// 頂層方塊:頂面為壓頂石,四面為古石磚
const wallCapMaterials = [
wallFaceMat, wallFaceMat, wallTopMat, wallFaceMat, wallFaceMat, wallFaceMat ];
const wallCapMossMaterials = [
wallMossMat, wallMossMat, wallTopCrackedMat, wallMossMat, wallMossMat, wallMossMat ];
const wallPillarMaterials = [
wallCarvedMat, wallCarvedMat, wallTopMat, wallCarvedMat, wallCarvedMat, wallCarvedMat ];
【本日結語】
透過今天的設定,我們徹底封死了像素貼圖被雙線性插值模糊的可能性,讓每一顆像素都精準鎖定在螢幕像素格子上。同時,多材質陣列的幾何解構,讓一個小小的立方體同時具備了「水平壓頂石」與「垂直砌磚立面」的雙重視覺層次。
明天,我們將針對「拉伸變形」展開致命一擊——徹底摒棄生硬的 scale.y = 3,改採「1x1 模組化立體堆疊」工法,讓每一塊城牆磚石的長寬比維持絕對完美的 1:1 正方形!