关于javascript:3D-沙盒游戏之地面网格设计

24次阅读

共计 12075 个字符,预计需要花费 31 分钟才能阅读完成。

背景

最近小组在摸索研发一个 3D 的沙盒小游戏 demo。对于沙盒游戏来说,高空是必不可少的元素。为了升高难度,在这个 demo 中,高空将不波及 y 轴坐标的变动,也就是应用一个与 xOz 立体平行的立体,对应到事实世界中,就是一块不带任何起伏的高山。本篇文章以 babylon.js 作为框架进行阐明。冀望的成果相似下图(截图来自于手游部落抵触):

指标

首先咱们须要在 xOz 立体上创立一块矩形作为高空。为了不让高空看起来过于枯燥,须要给高空贴上一些纹理,比方草地、鹅卵石路等等;在此基础上,纹理还须要能够部分替换,比方能够实现一条在草地地方的鹅卵石小路。同时,在高空上,须要搁置其余模型(比方人物、修建等),为了防止模型在挪动或者新增的时候,呈现重叠的状况,还得晓得以后高空上对应的地位的状态(是否已被模型占用),因而在新增或挪动模型的时候,须要获取以后模型在高空上的具体位置信息。基于以上需要,能够梳理为以下两个大指标:

  1. 实现高空初始化,且能够扭转特定地位的纹理
  2. 获取模型在高空上的地位信息

围绕这两个指标,上面通过两个实现篇,给大家展现下如何一步步实现~

实现之高空创立篇

首先要把思路捋一下:先是须要创立一个高空,其实高空的实质也是一个模型。其次要批改高空的局部纹理。有一个比较简单的办法,就是把高空给细分为一个个网格,每个网格能够独自的进行纹理贴图,每次更换纹理的时候,也不会影响其余格子。

定义好一些常量,以便前面的解说。这些常量只须要先看一下,有个根本的印象即可,前面用到的时候,会具体解释。

// 高空的长度(x 方向)const GROUND_WIDTH = 64
// 高空的宽度(z 方向
const GROUND_HEIGHT = 64

// 高空纹理的宽度 
const TEXTURE_WIDTH = 1024
// 高空纹理的高度
const TEXTURE_HEIGHT = 1024

// 一个方向上(s 和 t 坐标方向),把高空分为多少个小块
const GROUND_SUBDIVISION = 32

上面是具体的步骤。

1. 建设一块高山

查阅 babylon.js 的相干文档,间接调用 api 即可创立,代码比较简单,间接贴到上面:

const ground = MeshBuilder.CreateGround(
    name,
    {width: GROUND_WIDTH, height: GROUND_HEIGHT, subdivisions: GROUND_SUBDIVISION},
    scene,
)

下面代码中,用到了三个在这一篇刚开始就曾经定义好的常量 GROUND_WIDTH、GROUND_HEIGHT 和 GROUND_SUBDIVISION。前两个常量别离代表的是要创立的高空的宽高,都是 64。它们所属的坐标系是裁剪坐标系。因为 WebGL 中利用了很多坐标系,可能有些同学还不是很理解,举荐去看看这篇文章 WebGL 坐标系根底。至于 GROUND_SUBDIVISION 这个常量,指的是要把矩形的一条边,分为多少段,这一篇是把高空的 x、z 方向都分成了 32 段。

简略的一行代码,就能够创立出一块高山了,看看成果:


2. 给“大地”贴上纹理

简单的性能总是由一个个简略的性能演变来的。首先先做最简略的一步,先给咱们刚刚创立好的这块大地,贴上纹理,轻易找一张草地的图片,查看 babylon.js 材质与纹理这一部分文档,先给高空一个材质,而后在材质上进行贴图,代码如下:

// 创立一个规范材质
const groundMaterial = new StandardMaterial('groundMaterial', scene);
// 创立一个纹理
const groundTexture = new Texture('//storage.360buyimg.com/model-rendering-tool/files/jdLife/grass.jpg', scene)
// 把纹理赋值给材质的漫反射纹理(这里也能够是其余类型的纹理)groundMaterial.diffuseTexture = groundTexture
// 把材质赋值给高空的材质属性
ground.material = groundMaterial

当初高空曾经有了纹理贴图了:


3. 宰割高空为一个个格子

在上一步,尽管曾经实现了给高空贴纹理,然而成果必定是不能满足预约的需要的。如果对 WebGL 有相干理解的同学,应该会晓得,如果要给材质的特定地位,贴上特定的纹理,就须要获取材质上顶点数据,而后打上图钉,再在纹理贴图中,依据顶点对应的图钉点,来获取须要的图片的地位。这应该是一步比较复杂的操作,而且 babylon.js 封装的比拟深,如果间接暴力的去实现起来,会是比拟大的工程。

于是,本着 babylon.js 应该有封装好的类,能够实现(或者通过简略的改变后能够实现)这个需要的猜想,再次翻阅它的文档,终于找到一个相似的例子。为了不便浏览,间接截取效果图展现进去:

看了一下这个例子的代码,能够总结为:

  1. 应用了 babylon.js 的 AdvancedDynamicTexture.CreateForMesh 高级动静纹理为高空创立纹理。
  2. 高级动静纹理提供了 addControl 办法,能够往纹理上增加各种“容器”。
  3. “容器”是 Babylon.js 的一个类 Container。
  4. “容器”也有 addControl 办法,能够在“容器”外面持续增加“容器”,即能够“套娃”。

babylon.js 的 AdvancedDynamicTexture 的实现原理,在这里先不探讨,然而当初有了下面这些知识点,联合 demo,就能对高空进行分格了。间接上代码,把步骤写在了正文里:

// 首先调用 AdvancedDynamicTexture 的 api,创立纹理
const groundTexture = GUI.AdvancedDynamicTexture.CreateForMesh(ground, TEXTURE_WIDTH, TEXTURE_HEIGHT, false)

// 创立最外层的 Container -- panel,它的宽高和纹理的统一
const panel = new GUI.StackPanel()
panel.height = TEXTURE_HEIGHT + 'px'
panel.width = TEXTURE_WIDTH + 'px'

// 把 panel 增加到纹理上
groundTexture.addControl(panel)

// 循环的建设一列列 Row,并且加到 panel 下面
for (let i = 0; i < GROUND_SUBDIVISION; i++) {const row = new GUI.StackPanel()
  row.height = TEXTURE_HEIGHT / GROUND_SUBDIVISION + 'px'
  // 把 row 增加到 panel 上
  panel.addControl(row)
  row.isVertical = false
  // 在循环的,在每一行外面建设一个个格子
  for (let j = 0; j < GROUND_SUBDIVISION; j++) {const block = new GUI.Rectangle()
    block.width = TEXTURE_WIDTH / GROUND_SUBDIVISION + 'px'
    row.addControl(block)     
  }
}

代码里用到了 TEXTURE_WIDTH 和 TEXTURE_HEIGHT 两个常量,它们别离代表的是纹理的宽高。绝对纹理的尺寸有更多理解的同学,能够参考下 WebGL 纹理详解之三:纹理尺寸与 Mipmapping 这篇文章的解释,这里不开展细谈。

看看这时候的成果:


4. 给每个格子独自贴图,并且存储纹理 Image 对象

这里的 Image 指的是 Babylon.js 外面的一个类,为了不便下文间接称它 Image。
为什么要给每个格子独自贴图,这个无需解释了。至于为什么要存储每个格子的纹理 Image 对象,是为了不便前面去批改贴图。因为在创立这些格子的时候,是通过循环创立的,所以它们自身曾经具备肯定的程序了,因而只有把它们在创立的时候,都 push 到一个数组外面(blockImageArray),读的时候依照创立的程序传入索引就能够了。

实现的时候,还是先实现最简略的,让每个格子的纹理都一样就好了。在上一步的代码根底上增加,代码如下:

...
  // 在循环的,在每一行外面建设一个个格子
  for (let j = 0; j < GROUND_SUBDIVISION; j++) {const block = new GUI.Rectangle()
    block.width = TEXTURE_WIDTH / GROUND_SUBDIVISION + 'px'
    row.addControl(block)
    // 暗藏格子的边框
    block.thickness = 0
    // 创立 Image 对象
    const blockImage = new GUI.Image('blockTexture','//storage.360buyimg.com/model-rendering-tool/files/jdLife/grass.jpg')
    // 把图片增加到 block 上
    block.addControl(blockImage)
    // 在里面的定义域外面先创立好 blockImageArray
    blockImageArray.push(blockImage)
  }

值得注意的是,上述代码在创立 Image 对象的时候,是间接通过 url 进行动静导入的,这会造成,每次创立一个 Image,就去发一个申请,显然是存在性能问题的。
于是,再一次翻查 babylon.js 的文档,寻求优化计划。Image 有一个 domImage 属性,值的类型为 HTMLImageElement,能够通过批改这个属性,来批改图片内容。所以只有当时加载图片生成 HTMLImageElement 并且存储在 imageSource,在创立 Image 的时候,对它的 domImage 属性进行赋值即可。优化后的代码:

// 把须要的图片导入好,放到 imageSource 外面
const imageSource : {[key: string]: HTMLImageElement } = {grass: img, stone: img}

...
// 创立 image 的时候不再传递 url 参数了
const blockImage = new GUI.Image()
// 对 domImage 属性赋值
blockImage.domImage = imageSource.grass

当初来看一下成果:


5. 更改纹理

通过了上一步的操作,曾经创立进去了一块有模有样的绿地了,接下来须要做的是纹理更换的性能。先实现个最简略的:在最外层的 panel 监听点击事件,通过点击的地位,判断以后点击的是高空的第几行第几列,而后找到 blockImageArray 对应的元素,对它的 domImage 进行再赋值就好了。代码如下:

panel.onPointerClickObservable.add(e => {const { y, x} = e
    const perBlockWidth = TEXTURE_WIDTH / GROUND_SUBDIVISION
    const perBlockHeight = TEXTURE_HEIGHT / GROUND_SUBDIVISION
    const row = Math.floor(y / perBlockHeight)
    const col = Math.floor(x / perBlockWidth)
    const index = row * GROUND_SUBDIVISION + col
    blockObjArr[index].domImage = imageSource.stone
})

看看当初的成果:

到此为止,就曾经实现了 创立高空并且能够扭转纹理 这个指标了。

实现之模型占位计算篇

名词:

  • 高空:案例中的立体
  • 模型:案例中须要计算占位的物体
  • 索引编号:二维数组的下标
  • 网格坐标系:将高空宰割为均等网格而造成的坐标系
  • WebGL 坐标系:原始 WebGL 坐标系
  • 模型基点:模型原点
  • 转换:从一个值换算到另一个值
  • 纠偏:把原有的坐标数值加上偏正值(这里是半个格子的长度或宽度)
  • 突围盒:能把模型整个包起来的最小长方体 bounding

流程图:https://www.processon.com/vie…

通过上一篇的实现,当初曾经创立了一块高空,并将高空等分成了若干个网格。这时要获取模型在高空上的占位状况,就须要转换为获取模型在高空上所占格子的数据。下图展现了,一个模型(房子)在高空上所占的格子的状况(被占的格子边框显示为红色):

为了看起来直观一些,咱们将在上面的阐明中把高空宰割成 8*8 的网格体系。

以下是波及到的常量:

// 从新定义一下,让高空分为 8 * 8 的网格
const GROUND_SUBDIVISION = 8

// 每一个格子在相机裁剪坐标系中的宽度
const PER_BLOCK_VEC_X = GROUND_WIDTH / GROUND_SUBDIVISION
// 每一个格子在相机裁剪坐标系中的高度
const PER_BLOCK_VEC_Z = GROUND_HEIGHT / GROUND_SUBDIVISION

// 模型地位向量在 x 轴方向的偏移量
const CENTER_OFF_X = PER_BLOCK_VEC_X / 2
// 模型地位向量在 z 轴方向的偏移量
const CENTER_OFF_Z = PER_BLOCK_VEC_Z / 2

// 半个格子在相机裁剪坐标系中的宽度
const HALF_BLOCK_VEC_X = PER_BLOCK_VEC_X / 2
// 半一个格子在相机裁剪坐标系中的高度
const HALF_BLOCK_VEC_Z = PER_BLOCK_VEC_Z / 2

要确切地晓得高空上的模型占用了哪几个格子,首先得建设高空网格坐标系。还记得在上一篇中,生成这些格子的时候,是通过两个 for 循环生成的吗,其实在生成这些格子的时候,同时也产生了索引。为了浏览不便,我再贴一下生成网格的代码:

for (let i = 0; i < GROUND_SUBDIVISION; i++) {const row = new GUI.StackPanel()
  row.height = TEXTURE_HEIGHT / GROUND_SUBDIVISION + 'px'
  // 把 row 增加到 panel 上
  panel.addControl(row)
  row.isVertical = false
  // 在循环的,在每一行外面建设一个个格子
  for (let j = 0; j < GROUND_SUBDIVISION; j++) {const block = new GUI.Rectangle()
    block.width = TEXTURE_WIDTH / GROUND_SUBDIVISION + 'px'
    row.addControl(block)     
    // 创立贴图
    const blockImage = new GUI.Image()
    // 对 domImage 属性赋值
    blockImage.domImage = imageSource.grass
    block.addControl(blockImage)
    blockImageArray.push(blockImage)
  }
}

能够了解为,每个网格的 i 和 j 就对应着它们在 z 方向和 x 方向的坐标。

依据每个网格在网格坐标系的 x 和 z 坐标,设置索引编号(每个网格对应一个坐标),索引编号的数据结构为:

interface Coord {
  x: number,
  z: number
} 

放到 8*8 网格坐标系中即为:

下面这张图是不是比拟好了解了呢,看起来就像咱们初中学习的立体直角坐标系,原点在左上角,x 轴为程度方向从左向右,z 轴是垂直方向从上到下。

对应到代码中,咱们能够通过创立一个二维数组,来存储这个网格坐标系。这样就能够用高空网格的坐标作为索引,在二维数组中寻找对应的值,以判断该网格上是否有模型占位。

1. 创立网格坐标系坐标汇合数组:groundStatus

网格坐标系坐标汇合数组 groundStatus,咱们把它定义为一个 number 二维数组。

groundStatus 数据结构如下:

type GroundStatus = number[][]

二维数组中的每个元素与网格坐标系中的坐标一一对应。每个坐标对应的初始值为 0,代表以后坐标没有被占位,当有模型搁置在下面时,值 +1;当模型移开或删除时,值 -1。不应用 boolean 作为存储类型的起因,是因为 boolean 只有 true 和 false 两种状态,不能满足更为简单的需要,比方在挪动模型的时候,呈现模型重叠的状况的时候,groundStatus 对应的格子上,就会有两个模型。如果用 boolean 来示意的话,是没方法示意进去的,因为它只有 true 和 false 两种值。然而如果应用 number 的话,就能够在该格子对应的元素,把值批改为 2,标识单前格子上有两个模型占位了。

在设计 groundStatus 索引时,以 x 还是 z 坐标为一维索引,在性能影响上区别不大。出于调试方面的思考,倡议以 z 坐标为一维索引,便于浏览器的控制台二维数组的展现与网格坐标系一一对应。


2. 模型基点向量与网格坐标系坐标的换算

模型基点向量指的是模型数据中的 position 属性,定义了模型在 WebGL 坐标系中的地位。position 是一个三维向量,恪守的是 WebGL 坐标系,比方当 position 值为 (0, 0, 0) 的时候,呈现在高空的地位就是高空的中心点。为了不便,下文我都会把它叫做,模型的 基点

图片起源:在 InfraWorks 中编辑 3D 模型的基点或插入点

WebGL 坐标系的 (0, 0, 0) 换算成高空坐标系,就是网格坐标 (3, 3)、(3, 4)、(4, 3)、(4, 4) 这四个格子的交点。如下图所示:

黄色的方块,代表圆点在高空上所占的格子。理论占位只有 1 格的模型,在这个地位向上取整的时候,最小的占位也是 4 个格子,一旦波及到碰撞检测等性能,会呈现模型占位过大的问题。所以咱们须要对中心点进行偏移,使模型在网格坐标系中的占位尽量靠近模型实在占位,往右下角——x、z 各偏移半个网格的单位即可,这时候 (0, 0, 0) 对应的基点坐标就是 (4, 4) 格子的中心点了。偏移的准则是保障模型的基点能落在网格坐标系的某个格子的中点,以便更为精确地进行模型占位的计算。如下图:

这里值得注意的是,当咱们传入模型的地位向量为 (x, y, z) 的时候,咱们会手动的把模型的地位改为 (x + CENTER_OFF_X, 0, z – CENTER_OFF_Z)(Y 轴向量本次不波及计算,因而能够省略)。z 向量的计算为减法,因为 WebGL 坐标系的 z 轴向上为正的,而网格坐标系 z 轴向上为负。

这里咱们封装一个传入模型的地位向量、返回该点的高空坐标的函数:

function getGroundCoordByModelPos(buildPosition: Vector3): Coord {const { _x, _z} = buildPosition
  const coordX = Math.floor(GROUND_WIDTH / 2 / PER_BLOCK_VEC_X + (_x + CENTER_OFF_X) / PER_BLOCK_VEC_X)
  const coordZ = Math.floor((GROUND_HEIGHT / 2 - (_z - CENTER_OFF_Z)) / PER_BLOCK_VEC_Z)
  return {x: coordX, y: coordZ}
}


3. 获取模型占位区在 WebGL 坐标系的要害数据

这一步是为了获取模型的理论占位相干数据,为后续的网格坐标系占位转换做筹备。

模型存在最小突围盒的概念,也叫最小边界框,用于界定模型的几何图元。突围盒 / 边界框能够是矩形,也能够是更为简单的形态,为不便形容,咱们这里采纳矩形突围盒 / 边界框的形式进行阐明。下文中简称突围盒。

图片起源:3D 碰撞检测

当咱们将 WebGL 坐标系的模型投射到网格坐标系上时,能够失去一片区域:

黄色区域代表的是模型占位区域,彩色点则是模型的基点。babylon.js 提供了相干的 api,能够计算出模型突围盒的边界与基点的间隔,这里的值均基于 WebGL 坐标系。

咱们将这些间隔存储到 rawOffsetMap 的对象中,数据结构如下:

interface RawOffsetMap {
  rawOffsetTop: number
  rawOffsetBottom: number
  rawOffsetLeft: number
  rawOffsetRight: number
}

计算代码如下:

/*
 @param {AbstractMesh[] } meshes 模型导入后返回的后果
 @param {Vector3} scale 模型的缩放倍数
*/
function getRawOffsetMap(meshes: AbstractMesh[], scale: Vector3 = new Vector3(1, 1, 1)): RawOffsetMap {
  // 申明最小的向量
  let min = null
  // 申明最大的向量
  let max = null
  
  // 对模型的 meshes 数组进行遍历
  meshes.forEach(function (mesh) {
    //babylon.js 提供的 api,能够遍历该 mesh 的和 mesh 的所有子 mesh,找到它们的边界
    const boundingBox = mesh.getHierarchyBoundingVectors()

    // 如果以后的最小向量不存在,那么把以后的 mesh 的 boundingBox 的 min 属性赋值给它
    if (min === null) {min = new Vector3()
      min.copyFrom(boundingBox.min)
    }

    // 如果以后的最大向量不存在,那么把以后的 mesh 的 boundingBox 的 max 属性赋值给它
    if (max === null) {max = new Vector3()
      max.copyFrom(boundingBox.max)
    }

    // 对最小向量和以后的 boundingBox 的 min 属性,从 x,y,z 这三个重量进行比拟与再赋值
    min.x = boundingBox.min.x < min.x ? boundingBox.min.x : min.x
    min.y = boundingBox.min.y < min.y ? boundingBox.min.y : min.y
    min.z = boundingBox.min.z < min.z ? boundingBox.min.z : min.z

    // 对最大向量和以后的 boundingBox 的 max 属性,从 x,y,z 这三个重量进行比拟与再赋值
    max.x = boundingBox.max.x > max.x ? boundingBox.max.x : max.x
    max.y = boundingBox.max.y > max.y ? boundingBox.max.y : max.y
    max.z = boundingBox.max.z > max.z ? boundingBox.max.z : max.z
  })

  return {
    rawOffsetRight: max.x * scale.x,
    rawOffsetLeft: Math.abs(min.x * scale.x),
    rawOffsetBottom: max.z * scale.z,
    rawOffsetTop: Math.abs(min.z * scale.z)
  }
}


4. 获取模型占位区在网格坐标系上的要害数据:offsetMap

这一步是将模型 WebGL 坐标系的占位要害数据转换为网格坐标系中的数据。

如上图所示,黄色的格子,代表的是模型基点所在的格子。红色是模型在网格坐标系转化之后的占位——当模型边界占位不满一格的时候(比方只占了格子的一半),按占满一格来算。这四个数据,咱们应用 offsetMap 对象来存储:

interface OffsetMap {
  offsetLeft: number,
  offsetRight: number,
  offsetTop: number,
  offsetBottom: number
} 

在上一节中,曾经计算出模型的 rawOffsetTop,rawOffsetBottom,rawOffsetLeft,rawOffsetRight。当初只有把这几个要害值一一转化为 offsetMap 对应的要害值即可。

上图中黄色区域是模型在 WebGL 坐标系中的占位,红色区域是将模型占位向上取整后,在网格坐标系中所占网格的汇合。rawOffsetMap 与 offsetMap 中字段的转化关系为:rawOffsetLeft 对应 offsetLeft;rawOffsetRight 对应 offsetRight;rawOffsetTop 对应 offsetTop;rawOffsetBottom 对应 offsetBottom。以 rawOffsetLeft 转化为 offsetLeft 为例,将 rawOffsetLeft 减去半个格子的宽度(HALF_BLOCK_VEC_X),而后除以一个格子的宽度(PER_BLOCK_VEC_X),再向上取整。上面为具体代码:

function getModelOffsetMap(rawOffsetMap: RawOffsetMap): OffsetMap {const { rawOffsetMapLeft, rawOffsetRight, rawOffsetBottom, rawOffsetTop} = rawOffsetMap
  const offsetLeft = Math.ceil((rawOffsetLeft - HALF_BLOCK_VEC_X) / PER_BLOCK_VEC_X)
  const offsetRight = Math.ceil((rawOffsetRight - HALF_BLOCK_VEC_X) / PER_BLOCK_VEC_X)
  const offsetTop = Math.ceil((rawOffsetTop - HALF_BLOCK_VEC_Z) / PER_BLOCK_VEC_Z)
  const offsetBottom = Math.ceil((rawOffsetBottom - HALF_BLOCK_VEC_Z) / PER_BLOCK_VEC_Z)
  return {
    offsetBottom,
    offsetLeft,
    offsetRight,
    offsetTop
  }
}


5. 计算出模型在网格坐标系的突围盒索引:bounding

这一步咱们将计算出模型突围盒在 groundStatus 中的索引下标,以便通过 groundStatus 来判断对应网格是否已被占位。bounding 即为占位模型在 groundStatus 数据中的几个边界索引值。

bounding 的数据结构如下:

interface Bounding {
  minX: number,
  maxX: number,
  minZ: number,
  maxZ: number
} 

还是先通过一张图,解释一下 bounding 对象中的四个值指的什么:

上图中,红色区域是模型在网格坐标系中所占网格。bounding 数据中的四个值,代表了模型突围盒边界网格在 groundStatus 中的索引数组下标,作为更新 groundStatus 中的占位数值的根据。

基于第 4 步中失去的 offsetMap 数据,联合第 2 步中的基点坐标,即可算出最终的 bounding:

function getModelBounding(buildPosition: Vector3, offsetMap: OffsetMap): IBounding {const modelGroundPosCoord = getGroundCoordByModelPos(buildPosition)
  const {x, y} = modelGroundPosCoord
  const {offsetBottom, offsetLeft, offsetRight, offsetTop} = offsetMap
  
  const minX = x - offLeft
  const maxX = x + offRight
  const minZ = y - offTop
  const maxZ = y + offBottom
  
  return {
    minX,
    maxX,
    minZ,
    maxZ
  }
}

至此,对于模型的 bounding 的计算就实现了。

6. 更新占位数据

在上一步,曾经获取到了模型在高空坐标系的 bounding,这时候只需利用 bounding 的值,对 groundstatus 进行赋值就好了,代码如下:

// 索引边界判断
function isValidIndex(x: number, z: number): boolean {if (x >= 0 && x < GROUND_SUBDIVISION && z >= 0 && z < GROUND_SUBDIVISION) return true
  return false
}

function setModlePosition(groundStatus: GroundStatus, bounding: Bounding) {const { minX, maxX, minZ, maxZ} = bounding

  for (let i = minZ; i <= maxZ; i++) {for (let j = minX; j <= maxX; j++) {if (isValidIndex(j, i))
        groundStatus[i][j]++
    }
  }
}

后续的待优化项

该项目标高空时一块高山,没有思考深度方面的信息。如果是在高空有起伏的场景下,当初的数据结构是不足以应酬的。如果是那种阶梯式高度的场景(高空由 n 片高度不同的高山形成),那么至要把 groundStatus 数组的元素的数据结构进行革新,退出高空高度标识的属性即能够满足需要。然而如果是那种高下起伏并且带有坡度的地形,那么很难进行革新。

成品展现

参考链接

  • WebGL 纹理详解之三:纹理尺寸与 Mipmapping — http://www.jiazhengblog.com/b…
  • WebGL 坐标系根底 — https://juejin.cn/post/689079…
  • babylon.js 官网 — https://www.babylonjs.com/

    欢送关注凹凸实验室博客:aotu.io

或者关注凹凸实验室公众号(AOTULabs),不定时推送文章。

正文完
 0