这篇文章解释了如何使用代码来编写一座3D立体“城市”。这个代码是由@ mrdoob最新发布的演示Demo。我发现这个演示的算法很优雅,是一个简单而有效的解决方案,所以我发了一个帖子解释它。
关于算法的一些评论
在我们将关注焦点置于问题的细节之前,把握下问题的整体和全局是很有帮助的。这个3D虚拟城市所使用的算法是完全由程序所生成的,这意味着整个城市 是动态建立,而不参考任何模板。这个算法相当优雅,且不超过100行javascript代码。这个算法的原理是怎么样的呢?简而言之,每一个建筑是一个 立方体,他们得到随机的大小和位置。足够简单吗?听起来好像不切实际,但事实就是这样的,当你从城市底部往上看时就会发现这个秘密。
从性能的角度来看,所有的建筑都合并成一个单一的几何形状,用一个单一的材料。这是做法是非常有效的,因为没有着色器切换和绘图调用命令。
为了提高真实感,通过模拟自然光使用了vertexColor
小把戏。在这个城市,在街道级别里你可以看到来自其他建筑物的阴影,所以建筑物的底部比顶部暗,我们可以采用vertexColor
来重现这样的效果。我们采取建筑物的底部顶点,并使其比顶部更暗。
让我们开始吧
我们将逐步解释那100行代码:(1)生成建筑的基础几何形状 ;(2)在城市的合适位置放置建筑物;(3)使用vertexColor技巧模拟环境光和阴影;(4)合并所有的建筑物,这样整个城市可以在一次性绘制。不多说,让我们开始吧!
生成建筑的基础几何形状
我们首先需要建立构建城市建筑的基础几何形状,它会重复使用多次,然后构建起整个城市。所以我们建立了一个简单的CubeGeometry
对象。
var geometry = new THREE.CubeGeometry( 1, 1, 1 );
我们将参考点设置在立方体的底部,而不是它的中心,以便我们进行平移操作。
geometry.applyMatrix( new THREE.Matrix4().makeTranslation( 0, 0.5, 0 ) );
然后我们去掉立方体的底面,这是一个优化小技巧。因为建筑物的底面是不可见的,因为它始终是在地面上。所以它是无用的,我们将其删除。
geometry.faces.splice( 3, 1 );
现在我们修复顶面的UV映射,我们将它们设置为单一的坐标(0,0),这样屋顶将和地板颜色相同,且建筑物的各面纹理共用,这样使得我们可以可以在单一的绘图过程中完成绘制。这也是优化绘制的小技巧。
geometry.faceVertexUvs[0][2][0].set( 0, 0 );
geometry.faceVertexUvs[0][2][1].set( 0, 0 );
geometry.faceVertexUvs[0][2][2].set( 0, 0 );
geometry.faceVertexUvs[0][2][3].set( 0, 0 );
好了,现在我们得到了一个单体建筑的几何形状,让我们绘制更多的建筑物,让它看起来更像一个城市!
在城市的合适位置放置建筑物
嗯……说实话,我们可以把他们放在任何地方。全部是随机。但是要小心,因为建筑和建筑之间会发生碰撞。但不管怎样,我们先把建筑放在随机位置。
buildingMesh.position.x = Math.floor( Math.random() * 200 - 100 ) * 10;
buildingMesh.position.z = Math.floor( Math.random() * 200 - 100 ) * 10;
然后我们在Y方向做一个随机的旋转:
buildingMesh.rotation.y = Math.random()*Math.PI*2;
然后,我们通过设置mesh.scale
属性来改变建筑的大小。首先是如何建筑的宽度和深度。
buildingMesh.scale.x = Math.random()*Math.random()*Math.random()*Math.random() * 50 + 10;
buildingMesh.scale.z = buildingMesh.scale.x
然后是建筑物的高度:
buildingMesh.scale.y = (Math.random() * Math.random() * Math.random() * buildingMesh.scale.x) * 8 + 8;
我们设置好了建筑物的位置/旋转/缩放等属性。现在让我们设置它的颜色,以及如何使用它来模拟阴影。
使用vertexColor技巧模拟环境光和阴影
在建筑丛生的大城市,建筑的底部往往比顶端更暗。这是因为太阳光线照射到建筑物顶部比底部更容易,而且在建筑物底部往往由来自其它建筑物的阴影,这 是在图形编程中称之为环境光遮蔽(Ambien