用CSS构建生成式3D地形世界
等轴投影总能唤起一种温馨怀旧的感觉,这很可能源于90年代像素艺术经典游戏(从《Populous》到《Transport Tycoon》)将这种美学深深烙印在我们集体记忆中的浪潮。
在本文中,我们将探索如何用现代CSS重现同样的魅力。具体来说,我们将深入了解新发布的Layoutit地形生成器的内部机制,学习如何结合堆叠网格和3D变换在浏览器中创建完全可寻址的3D空间。
搭建场景
.scene元素充当我们的摄像机支架:这是通过perspective属性开始定义深度的起点。通过设置较大的值(8000px),我们获得了近乎等轴测的视角,并带有轻微的自然失真。这个父容器的每个子元素都继承transform-style: preserve-3d,这基本上确保了3D变换按预期工作。
.floor元素定义了世界的倾斜度。通过应用transform: rotateX(65deg) rotate(45deg),我们将整个空间倾斜进入视图,建立摄像机的方向。在这个基础上,多个.z元素通过translateZ(25px * level)垂直堆叠。这样,每个层在特定高度(唯一的Z级别)充当网格切片,而行和列定义了X和Y坐标。
这些元素共同创建了我们将放置形状的3D网格。从这个基础开始,我们的地形可以开始上升了!
|
|
|
|
扩展形状语法
除了简单的立方体,我们的世界需要新的图元:我们称它们为平面、斜坡、楔形和尖峰,它们是地形生成的最小单位。
每个形状倾斜一个或两个平面来定义其形式。它们遵循2:1的二测投影系统,其中每个高度单位等于两个深度单位。实际上,这产生了测量为50×50×25px的单元格。常见的面倾斜角度arctan(0.5) ≈ 26.565°保持了跨图块的几何一致性,确保了干净的阴影过渡和相邻单元格之间的无缝斜坡。
让我们仔细看看每个形状是如何组合的:
平面形状
平面保持水平;它只是一个在Z维度上平移25px并旋转以匹配其基本方向的平面。
|
|
斜坡形状
斜坡重用相同的平面容器,但添加了一个矩形面伪元素,倾斜26.565°以创建斜坡。
|
|
楔形形状
楔形结合了斜坡的倾斜面和一个旋转90度的镜像面,在它们之间创建凹形连接。
|
|
尖峰形状
尖峰镜像斜坡以形成峰顶。它结合了两个相对的斜坡:前斜坡向内倾斜,镜像的一个上升,直到它们在凸脊处相遇。
|
|
纹理和光照
由于我们的形状只是普通的DOM元素,我们可以轻松地用CSS样式化它们。在这种情况下,使用background-image或background-color是最佳选择,因为它不会添加新节点(如<img>或<svg>会)。作为折衷方案,在需要动画或交互时,向选定形状添加内联SVG可能是有意义的。
此引擎中的光照是方向性的并烘焙到纹理中。我们将光源固定在西侧(180°),并根据每个可见面与该光源的角度将其分类为四个亮度带之一。每个形状根据其相对于光源的方向接收一个光照级别类(.l-1到.l-4)。结果是可信的阴影,即使在场景旋转时也保持一致。
生成噪声
地形是一个高度图:一组由噪声构建并塑造成粗略陆地的二维高程值数组。初始原始场来自像simplex-noise这样的库,随后进行许多细化处理。这平滑了斑点,将陡峭区域台阶化,并限制了陡峭度的变化程度。这个世界的黄金法则之一是图块的高度差不能超过一个级别,这保持了斜坡的一致性并防止形成悬崖。
在用户侧,暴露了两个主要旋钮:陆地覆盖率,控制填充地图的水的百分比;和地形类型,设置场景中高程的上限。
一旦高度图构建完成,分类器决定哪个形状适合每个单元格。图块最多可以有八个可能的邻居,每个有四个旋转状态,这很快增加了数百种组合。为了处理所有这些复杂性,规则手册定义了形状应如何在每个基本点相遇。当这些规则仍然不足时(如在尖锐交叉点或极端斜坡上),一组手动策划的覆盖步骤介入清理并使地形保持稳定。
性能注意事项
堆叠网格的主要瓶颈之一是它们可以容纳的DOM元素数量。每个图块、面和层都会累加,当我们渲染大型地形时,浏览器已经在处理数千个节点。32×32×12网格大致是大多数现代系统的安全限制;超出此范围,渲染变得不可预测,帧速率下降,图块可能闪烁或完全消失。
使用clip-path绘制楔形和尖峰的三角形面带来的真正痛点。它看起来干净且纯CSS,但它迫使浏览器在每次场景旋转时重新绘制,拖累性能。修复方法是切换到具有透明背景的预切割PNG精灵。在浏览器适当优化3D上下文中的clip-path之前,精灵仍然是最可靠的选择。
下一步
除了作为一个伟大的技术挑战外,这个项目证明了堆叠网格技术可以远远超出立方体。添加斜坡和角度开启了一种新的深度:实际上由光和形状塑造的3D体积,即使一切仍然只是CSS。
从这里开始,有许多路径可以探索。等轴测网页游戏是一个明显的方向,还有生活在浏览器中的轻量级交互体验。目标不是取代WebGL,而是探索一种不同的构建3D项目的方式,保持简单、可读和可检查。
至于我的下一个3D网格项目,可能涉及将地形内外翻转:镜像两个垂直网格,使用重复的高度图形成单个连续体积。也许这就是我们实现真正的CSS球体的方式。