【问题标题】:Three.js: Cannot display mesh created with texture arrayThree.js:无法显示使用纹理数组创建的网格
【发布时间】:2021-08-18 04:49:09
【问题描述】:

我正在构建一个非常原始的游戏,它基于你放置在沙盒世界中的立方体(一个完全独特的概念,它将彻底改变我们所知道的游戏),并且我正在处理块生成。这是我目前所拥有的:

我的块是在对象字面量中定义的:

import * as THREE from 'three';

const loader = new THREE.TextureLoader();

interface BlockAttrs
{
    breakable: boolean;
    empty:     boolean;
}

export interface Block
{
    attrs:       BlockAttrs;
    mat_bottom?: THREE.MeshBasicMaterial;
    mat_side?:   THREE.MeshBasicMaterial;
    mat_top?:    THREE.MeshBasicMaterial;
}

interface BlockList
{
    [key: string]: Block;
}

export const Blocks: BlockList = {
    air:
    {
        attrs:
        {
            breakable: false,
            empty: true,
        },
    },
    grass:
    {
        attrs:
        {
            breakable: true,
            empty: false,
        },
        mat_bottom: new THREE.MeshBasicMaterial({map: loader.load("/tex/dirt.png")}),
        mat_side: new THREE.MeshBasicMaterial({map: loader.load("/tex/grass-side.png")}),
        mat_top: new THREE.MeshBasicMaterial({map: loader.load("/tex/grass-top.png")}),
    },
};

这是我的Chunk 课程:

import * as THREE from 'three';
import { BufferGeometryUtils } from 'three/examples/jsm/utils/BufferGeometryUtils';

import { Block, Blocks } from './blocks';

const px = 0; const nx = 1; const py = 2;
const ny = 3; const pz = 4; const nz = 5;

export default class Chunk
{
    private static readonly faces = [
        new THREE.PlaneGeometry(1,1)
            .rotateY(Math.PI / 2)
            .translate(0.5, 0, 0),

        new THREE.PlaneGeometry(1,1)
            .rotateY(-Math.PI / 2)
            .translate(-0.5, 0, 0),

        new THREE.PlaneGeometry(1,1)
            .rotateX(-Math.PI / 2)
            .translate(0, 0.5, 0),

        new THREE.PlaneGeometry(1,1)
            .rotateX(Math.PI / 2)
            .translate(0, -0.5, 0),

        new THREE.PlaneGeometry(1,1)
            .translate(0, 0, 0.5),

        new THREE.PlaneGeometry(1,1)
            .rotateY(Math.PI)
            .translate(0, 0, -0.5)
    ];
    private structure: Array<Array<Array<Block>>>;
    public static readonly size = 16;
    private materials = Array<THREE.MeshBasicMaterial>();
    private terrain = Array<THREE.BufferGeometry>();

    constructor ()
    {
        this.structure = new Array<Array<Array<Block>>>(Chunk.size);

        for (let x = 0; x < Chunk.size; x++)
        {
            this.structure[x] = new Array<Array<Block>>(Chunk.size);

            for (let y = 0; y < Chunk.size; y++)
            {
                this.structure[x][y] = new Array<Block>(Chunk.size);

                for (let z = 0; z < Chunk.size; z++)
                    if ((x+y+z) % 2)
                        this.structure[x][y][z] = Blocks.grass;
                    else
                        this.structure[x][y][z] = Blocks.air;
            }
        }
    }

    private blockEmpty (x: number, y: number, z: number): boolean
    {
        let empty = true;

        if (
            x >= 0 && x < Chunk.size &&
            y >= 0 && y < Chunk.size &&
            z >= 0 && z < Chunk.size
        ) {
            empty = this.structure[x][y][z].attrs.empty;
        }

        return empty;
    }

    private generateBlockFaces (x: number, y: number, z: number): void
    {
        if (this.blockEmpty(x+1, y, z))
        {
            this.terrain.push(Chunk.faces[px].clone().translate(x, y, z));
            this.materials.push(this.structure[x][y][z].mat_side);
        }

        if (this.blockEmpty(x, y, z+1))
        {
            this.terrain.push(Chunk.faces[nx].clone().translate(x, y, z));
            this.materials.push(this.structure[x][y][z].mat_side);
        }

        if (this.blockEmpty(x, y-1, z))
        {
            this.terrain.push(Chunk.faces[py].clone().translate(x, y, z));
            this.materials.push(this.structure[x][y][z].mat_bottom);
        }

        if (this.blockEmpty(x, y+1, z))
        {
            this.terrain.push(Chunk.faces[ny].clone().translate(x, y, z));
            this.materials.push(this.structure[x][y][z].mat_top);
        }

        if (this.blockEmpty(x, y, z-1))
        {
            this.terrain.push(Chunk.faces[pz].clone().translate(x, y, z));
            this.materials.push(this.structure[x][y][z].mat_side);
        }

        if (this.blockEmpty(x-1, y, z))
        {
            this.terrain.push(Chunk.faces[nz].clone().translate(x, y, z));
            this.materials.push(this.structure[x][y][z].mat_side);
        }
    }

    public generateTerrain (): THREE.Mesh
    {
        this.terrain   = new Array<THREE.BufferGeometry>();
        this.materials = new Array<THREE.MeshBasicMaterial>();

        for (let x = 0; x < Chunk.size; x++)
            for (let y = 0; y < Chunk.size; y++)
                for (let z = 0; z < Chunk.size; z++)
                    if (!this.structure[x][y][z].attrs.empty)
                        this.generateBlockFaces(x, y, z);

        return new THREE.Mesh(
            BufferGeometryUtils.mergeBufferGeometries(this.terrain),
            this.materials
        );
    }
}

我知道网格创建者应该与模型分离,但现在我正在试验。该类的工作方式如下:

首先,constructor() 创建一个 Block 的 3D 矩阵。我已将其设置为以airgrass 的棋盘模式创建它,因此每隔一个块都是空的。

接下来,我从我的场景中调用generateTerrain()

this.chunk = new Chunk();
this.add(this.chunk.generateTerrain());

调用此方法时,它会为每个非空块输入generateBlockFaces,并将适当的PlaneGeometrys 推入terrain 数组以及适当的THREE.MeshBasicMaterial 推入materials 数组。然后我使用BufferGeometryUtils.mergeBufferGeometries 合并几何,并创建通过合并几何和materials 数组的网格。

我遇到的问题是在传递new THREE.MeshNormalMaterial 或任何其他材料时创建网格效果很好,但当我传递materials 数组时却不行。传递数组会创建对象(console.loging 它表明它是在没有错误的情况下创建的),但它没有与场景一起绘制。

我是否误以为materials 数组将为每个面分配材质?我做错了什么?

【问题讨论】:

标签: javascript typescript three.js textures


【解决方案1】:

对于想要使用材质数组的人:

const materials = [
  new THREE.MeshBasicMaterial( { color: 'red' } ),
  new THREE.MeshBasicMaterial( { color: 'blue' } )
];

const geometries = [
  new THREE.PlaneGeometry( 1, 1 ),
  new THREE.PlaneGeometry( 1, 1 )
];
geometries[ 1 ].rotateX( Math.PI * -0.5 );

// Add groups that set the materialIndex of each vertex in a group
// For this code all the vertices in each geometry have the same material. 
// If you load in a model that already has multiple textures you don't need to do this.
geometries[ 0 ].addGroup( 0, geometries[0].attributes.position.count, 0 ); 
geometries[ 1 ].addGroup( 0, geometries[1].attributes.position.count, 1 );

// Setting true on the second argument enables groups for the merged geometry.
const mergedGeometry = BufferGeometryUtils.mergeBufferGeometries( geometries, true );

const multiMaterialMesh = new THREE.Mesh( mergedGeometry, materials );
scene.add( multiMaterialMesh );

【讨论】:

    【解决方案2】:

    我在文档中找到对THREE.UVMapping 的引用后解决了这个问题。将几何图形发送到 GPU 时,纹理坐标需要是顶点坐标的双向。为此,我在我的块中定义了以下三个属性:

    uv_bottom: [
        stone_row     / Textures.rows, (stone_col+1) / Textures.cols,
        (stone_row+1) / Textures.rows, (stone_col+1) / Textures.cols,
        stone_row     / Textures.rows, stone_col     / Textures.cols,
        (stone_row+1) / Textures.rows, stone_col     / Textures.cols,
    ],
    uv_side: [
        stone_row     / Textures.rows, (stone_col+1) / Textures.cols,
        (stone_row+1) / Textures.rows, (stone_col+1) / Textures.cols,
        stone_row     / Textures.rows, stone_col     / Textures.cols,
        (stone_row+1) / Textures.rows, stone_col     / Textures.cols,
    ],
    uv_top: [
        stone_row     / Textures.rows, (stone_col+1) / Textures.cols,
        (stone_row+1) / Textures.rows, (stone_col+1) / Textures.cols,
        stone_row     / Textures.rows, stone_col     / Textures.cols,
        (stone_row+1) / Textures.rows, stone_col     / Textures.cols,
    ],
    

    Textures.rowsTextures.cols 引用了我的纹理图集(所有纹理存储在 png 网格中的文件)具有的列数和行数,每个块都有自己的 rowcol 文件参考它的位置。然后,我在 Chunk 类中创建了一个 private uv = Array&lt;Array&lt;number&gt;&gt;();,并修改了地形生成器以将块的 uv 数组推送到它。例如,这是对正 z 面的处理方式(请注意,为了提高效率,我已经交换了 yz):

    if (this.blockEmpty(x, z+1, y))
    {
        this.terrain.push(Chunk.faces[pz].clone().translate(x, y, z));
        this.uv.push(this.structure[x][z][y].uv_side);
    }
    

    现在,BufferGeometry 只接受 'uv' 数组作为类型(在本例中为 Float32Array),因此我必须从 this.uv 的扁平版本构造一个。这是地形生成器函数现在的样子:

    public generateTerrain (): THREE.Mesh
    {
        this.terrain = new Array<THREE.BufferGeometry>();
    
        for (let x = 0; x < Chunk.base; x++)
            for (let z = 0; z < Chunk.base; z++)
                for (let y = 0; y < Chunk.build_height; y++)
                    if (!this.structure[x][z][y].attrs.empty)
                        this.generateBlockFaces(x, z, y);
    
        const geometry = BufferGeometryUtils.mergeBufferGeometries(this.terrain);
        geometry.setAttribute('uv', new THREE.BufferAttribute(new Float32Array(this.uv.flat()), 2));
    
        return new THREE.Mesh(geometry, Textures.material);
    }
    

    如您所见,我使用的材料来自Textures 类。这是整个导入的文件:

    import * as THREE from 'three';
    
    export default class Textures
    {
        private static readonly loader = new THREE.TextureLoader();
    
        public static readonly rows = 3;
        public static readonly cols = 2;
    
        public static readonly atlas    = Textures.loader.load("/tex/atlas.png");
        public static readonly material = new THREE.MeshBasicMaterial({map: Textures.atlas});
    }
    
    Textures.atlas.magFilter = THREE.NearestFilter;
    Textures.atlas.minFilter = THREE.NearestFilter;
    

    就是这样!地形现在生成渲染每个块的纹理,我对此非常高兴:D

    【讨论】:

    • 很好的解决方案。放弃材料阵列并寻找纹理图集。那款流行的体素游戏也是如此。它叫什么……采矿工艺什么的?
    • 我不知道你在说什么游戏,这很奇怪,因为我绝对没有生活在岩石之下!
    猜你喜欢
    • 1970-01-01
    • 2013-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-22
    • 2011-11-02
    • 1970-01-01
    • 2016-04-04
    相关资源
    最近更新 更多