浏览器中的三维空间「Three.js场景搭建」

在第一章

《Three.js初体验》

中,大致介绍了在网页中渲染三维场景的简要流程。距离上一章已经过去了两周,如果你充满好奇心,并且有足够的执行力的话,相信你已经能读懂简单的JavaScript和HTML了,因此,这一章会在这个假设上做继续介绍。

既然是搭建场景,我们就来做一个大家都熟悉,但又具有挑战性的场景—城市。城市是一个复杂的系统,要解决一个复杂的问题,首先要把它进行抽象,将主要特征表达出来,然后次要特征也就可有可无了。

城市中有各种建筑物,并且有区域地标集中的特性,我们可以将这个“简要城市特征”进行几何化—在一个圆形区域内,随机放置方形柱,距离圆形区域圆心越近的方柱高度越高。听起来蛮复杂的,其实效果很简单:

这效果图指引我打开 [外卖广告位招租] ,然后点了一份 [薯条广告位招租],囧

也许你已经知道接下来我们要做什么了:

使用Three.js创建城市的边界实现随机生成建筑算法总结经验与方法

一、使用Three.js创建城市的边界

城市的边界,我们假定为圆形,所以我们可以创建一个高度很小的圆柱来作为城市的地面。但是首先,在创建物体前,我们需要创建场景和相机:

var scene, camera, renderer; scene = new THREE.Scene(); camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight); camera.position.set(3,4,5); camera.lookAt(new THREE.Vector3(0,0,0)); scene.add(camera);

上面这段代码,我们创建了一个场景并在场景中添加了一个相机,接下来,我们开始向场景中添加圆形区域,我们设置这个圆形区域的半径为3:

var regionR = 3; var cylinderMesh = new THREE.CylinderGeometry(regionR, regionR, 0.05, 1024); var material = new THREE.MeshNormalMaterial(); var cylinder = new THREE.Mesh( cylinderMesh, material ); cylinder.position.set(0,0,0); scene.add( cylinder );

上面这段代码在场景中添加了一个圆盘,其实是高度极小的圆柱,如果你急切想看到效果的话,添加下面一段代码,就可以看到圆盘了:

renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); renderer.render(scene, camera);

这时候在浏览器中运行一下,可以看到如下效果

在这里需要注意一下,在创建渲染器的时候,增加了{ antialias: true },这表示渲染时将具有抗锯齿效果,如果你运行的时候出现了任何问题,可以在这里

检查代码哪里出了问题。

二、实现随机生成建筑算法

随机放置“薯条”似乎有点麻烦,我们如何保证”薯条”放在圆形区域内,并且能够控制它们的高度呢?

这时候需要引入极坐标系的知识,我们最常用的坐标系是笛卡尔坐标系(直角坐标系),在平面中确定(x, y)就能确定一个点的位置,给定x、y的值确定的区间值,就能确保这个点位于一个方形或矩形区域内。在极坐标系中,确定点到圆心的距离r,和一个角度θ就能确定这个点的位置,给定r一个区间值,就能确保这个点位于圆形或环形区域内。

我再画一张图,让你更容易理解一些:

有了这些知识,我们在一个圆形区域内生成随机点就很容易:
function pointsGenerator(amount) { var polarPointsArr = []; for (var i = 0; i < amount; i ++) { var r = Math.random() * regionR; var θ = (2 * Math.PI / 360) * (Math.random() * 360); polarPointsArr.push({r: r, θ: θ}); } return polarPointsArr; } var points = pointsGenerator(100);

我们定义了pointsGenerator函数来为我们生成极坐标随机点,并在最后使用这个函数获得了100个随机点,但是Three.js中的坐标系是笛卡尔坐标系,所以我们需要将这些极坐标点转换为笛卡尔坐标点,这个转换非常简单,只需把pointsGenerator改造一下:

function pointsGenerator(amount) { var polarPointsArr = []; for (var i = 0; i < amount; i ++) { var r = Math.random() * regionR; var θ = (2 * Math.PI / 360) * (Math.random() * 360); polarPointsArr.push({r: r, θ: θ}); } var cartesianPointsArr = []; for (var j = 0; j < polarPointsArr.length; j ++) { var r = polarPointsArr[j].r; var θ = polarPointsArr[j].θ; var x = r * Math.cos(θ); var y = r * Math.sin(θ); cartesianPointsArr.push({x: x, y: y}); } return {polarPointsArr: polarPointsArr, cartesianPointsArr: cartesianPointsArr}; }

在改造的pointsGenerator中,我们也同时返回了极坐标点,因为我们在后面有可能会用到。

有了这些随机点,我们就可以在100次循环里创建方形柱,然后将它们放到这些随机生成的坐标上!我们仍然可以写一个函数,来实现刚才的想法:

function addRadomCubes(amount) { var cubesArr = []; var randomPosition = pointsGenerator(amount); var polarPointsArr = randomPosition.polarPointsArr; var cartesianPointsArr = randomPosition.cartesianPointsArr; for (var i = 0; i < cartesianPointsArr.length; i ++) { var position = cartesianPointsArr[i]; var r = polarPointsArr[i].r; var cubeMesh = new THREE.BoxGeometry(0.2, regionR r, 0.2); var cube = new THREE.Mesh(cubeMesh, material); cube.position.set(position.x, (regionR r) / 2 + 0.025, position.y); scene.add(cube); cubesArr.push(cube); } return {cubesArr: cubesArr, randomPosition: randomPosition}; }

可以注意到,代码中,我用 (regionR – r) 作为“薯条”的高度。使用addRadomCubes函数,我们可以轻松地在场景中添加任意数量的随机“薯条”了!

var cubesArrInfo = addRadomCubes(100);

让我们看一下

效果

三、总结经验与方法

上面的“抽象城市”看起来还不错,我们已经实现了文章一开始抽象出来的“简要城市特征”。

在Three.js中创建场景如此简单,仅仅需要“创建”和“置入”这两步操作,场景的空间效果,完全取决于,你创建的是什么物体和物体置入的位置。在你想要搭建一个场景前,可以先花一些时间来思考,是否能够做一个有效的规划,然后其余的工作,就是填充和丰富场景的空间了。

我们的“抽象城市”还有很多可能性,可以添加更多规则,你可以自己想一些规则来丰富它。看看我做了什么,你是否能够实现呢?

上一章:《Three.js初体验》

下一章:《Three.js相机漫谈》

    THE END
    喜欢就支持一下吧
    点赞15 分享
    评论 抢沙发
    头像
    欢迎您留下宝贵的见解!
    提交
    头像

    昵称

    取消
    昵称表情代码图片

      暂无评论内容