原文地址:http://www.playfuljs.com/realistic-terrain-in-130-lines/

写于2014年5月5日

程序员们都喜欢创造一些东西,但是,还有什么会比创建一个世界更让人感到惊喜?想想Minecraft, Terragen, Skyrim,以及以前的每一个都会使用一些生成分形地形的飞行模拟器。今天,我们要来探索如何使用漂亮而又简单的QPSO算法 (diamond-square algorithm),到时,你也可以扮演上帝![Demo] [Source]

程序员往往是懒惰的(从经验来说的话),而懒惰的一个很好地“副作用”就是这真的是可以避免一些(重复)工作的很不错的方式。既然这样, 与其花上乏味的几个小时来创建可能是很蹩脚的岩石表面,不如我们在思想上教会电脑岩石到底是什么。为了达到我们的目的,我们会生成分形或者形状,而这些形状会以越来越小的变化不断重复。

我并不能以某种方式来证明地形确实是分形的,但是这种方法看起来真的很不错,因此你可以信任这种方法。

立体地图

我们会将我们的地形存储为一个简单地有高度的地图:一个由地形在任意给定的x,y坐标上的高度值所组成的二维数组。这是一个比较简单的数据结构,用我们喜欢的canvas,webgl,interpretive dance等技术都可以来渲染这些高度值。最大的限制是我们不能在地形中表示有垂直的洞的形状,比如洞穴,隧道或者桥梁。

function Terrain(detail) {this.size = Math.pow(2, detail) + 1;this.max = this.size - 1;this.map = new Float32Array(this.size * this.size);
}

对任何尺寸的网格你都可以应用上面的算法来生成地图,但是对于一个由2的整数幂加1的网格组成方形来说它是最简单的。我们将使用x,y和z轴相同大小的值,在一个多维数据集中实现我们的地形。我们把相关的细节(detail)(即网格的数量)转化成了2的整数幂加1,因此更多的网格数量需要有更大的数据集。

对应的算法

想法是这样的:取一个平面的方形。把它分成4个子方形,然后把这4个子方形的中心向上或向下随机的偏移一定量。把这些子方形再分成更多的子方形并且重复上面的步骤,每一次都将偏移的量减少,这样第一次的偏移会有最大的效果而后面的偏移都会提供更小的细节(起伏程度)。

这就是中点置换算法(Midpoint displacement algorithm)。我们的菱形算法基于类似的原则,但是生成了看起来会更自然的结果。与其只是把方格分成更多的子方格,不如在分成子方格与分成子的菱形方格之间做个替换。

1.设置各个角的坐标

首先,我们要设置各个角的坐标值来作为“种子”值,它会影响后面的呈现。我们会将所有的角落从数据集的一半的位置开始:

this.set(0, 0, self.max / 2);
this.set(this.max, 0, self.max / 2);
this.set(this.max, this.max, self.max / 2);
this.set(0, this.max, self.max / 2);

2.将地图分块

现在,我们将会递归的来看立体地图的越来越小的分块。在每一个分块的过程中,我们会把地图分成方块,并在方形阶段更新它们的中心点。然后,我们会把地图分成菱形,并在菱形阶段再次更新它们的中心点。

divide(this.max);function divide(size) {var x, y, half = size / 2;var scale = roughness * size;if (half < 1) return;for (y = half; y < self.max; y += size) {for (x = half; x < self.max; x += size) {square(x, y, half, Math.random() * scale * 2 - scale);}}for (y = 0; y <= self.max; y += half) {for (x = (y + half) % size; x <= self.max; x += size) {diamond(x, y, half, Math.random() * scale * 2 - scale);}}divide(size / 2);
}

saele变量保证了随着我们分块的次数的增多,偏移量是不断减小的。对于每一次分块,我们将当前的size变量与roughness相乘,roughness决定了我们的地形是平滑的(该变量值趋近于0时)还是起伏的(该变量值趋近于1时)。

3.形状

两种形状(方形和菱形)的工作机制是类似的,但是要从不同的点来绘制数据。在方形阶段,要在应用随机偏移之前获取四个角的坐标的平均值,在菱形阶段要在执行随机偏移之前获取四个边缘点的坐标的平均值。

function diamond(x, y, size, offset) {var ave = average([self.get(x, y - size),      // topself.get(x + size, y),      // rightself.get(x, y + size),      // bottomself.get(x - size, y)       // left]);self.set(x, y, ave + offset);
}

渲染

算法只是给了我们数据,我们可以用很多种方式来渲染数据。我们将整合一连串的渲染技巧来渲染一个位于canvas元素上的栅格化的,等距的,3d形式的地形图上。

从back到front

首先,我们将创建嵌套的循环从我们地图的“back”(y = 0) 到“front”(y=this.size)来绘制矩形。如果你要渲染一个简单地,平的,自顶向下的方形,那么要执行的循环是一样的。

for (var y = 0; y < this.size; y++) {for (var x = 0; x < this.size; x++) {var val = this.get(x, y);var top = project(x, y, val);var bottom = project(x + 1, y, 0);var water = project(x, y, waterVal);var style = brightness(x, y, this.get(x + 1, y) - val);rect(top, bottom, style);rect(water, bottom, 'rgba(50, 150, 200, 0.15)');}
}

光亮和阴影

我们对于集合映射的原始方法提供了一个很好的视觉文理。通过比较当前的高度值和下一个点的高度值,我们会找到一个坡度。坡度高的一侧我们用较亮的矩形来填充,另一侧则用较暗的矩形来填充。

var b = ~~(slope * 50) + 128;
return ['rgba(', b, ',', b, ',', b, ',1)'].join('');

等轴投影

我们可以从正面来绘制每一样东西,但是,在将方块转为3d之前,先将它转为菱形看起来会更有趣。等轴投影将左上角和右下角在视图的中间对齐。

function iso(x, y) {return {x: 0.5 * (self.size + x - y),y: 0.5 * (x + y)};
}

透视投影

我们将使用一个同样简单的3d投影转换我们的x,y,z值为在二维视角屏幕上的平面图像。

所有的透视投影的基本想法都是用水平和垂直的位置除以深度,那样的话更高的深度的渲染就会更接近于原点(例如,越远的物体就会看起来越小)。

function project(flatX, flatY, flatZ) {var point = iso(flatX, flatY);var x0 = width * 0.5;var y0 = height * 0.2;var z = self.size * 0.5 - flatZ + point.y * 0.75;var x = (point.x - self.size * 0.5) * 6;var y = (self.size - point.y) * 0.005 + 1;return {x: x0 + x / y,y: y0 + z / y};}
};

把所有的内容都整合起来

首先,我们用我们所期望的细小平面创建了一个地形实例。然后我们生成了它的立体地图,提供了一个位于0和1之间的roughness值。最后,我们把地形绘制到了canvas上。

var terrain = new Terrain(9);
terrain.generate(0.7);
terrain.draw(canvasContext, width, height);

试试看

请点击下面链接查看最终效果:来自另一个世界的地形(otherworldly terrain)

接下来

如果你跟我一样,这个简单的算法会让你渴望去创造一个在线的自制梦幻风景,一个基于飞行器的第一人称射击游戏,模拟钓鱼或者一个大型多人在线角色扮演游戏等等。这个单一的立方体式的,基于canvas的demo非常需要扩展。

下面的几项我希望你能去尝试:

1.用WebGl渲染;

2.跟随高度的变化,高度越小的地形越平滑(像沙子),高度越大的越崎岖;

3.投射暗影,而不是简单地基于斜坡来产生阴影;

4.添加一个功能,生成洞穴和隧道。

按照惯例,你还可以在这里看到这个想法。

相关的工作

现在有很多人在研究这个算法,并且他们创建了很多很多很酷的东西。而且,黑客新闻讨论也展示了很多相关的真的非常不可思议的例子。这里有几个比较突出的:

  • WebGL rendering implementation by callum
  • Objective C implementation by Chris Cieslak
  • Processing implementation by Jerome Herr
  • Heightmap-based raycaster by namuol
  • Procedural demo entry explanation by Inigo Quilez
  • Fractional Brownian Motion by rbaravaelle
  • Polygonal game map generation by Red Blob Games

讨论

可以在黑客新闻参与讨论。

133行代码实现质感地形(js,基于canvas)相关推荐

  1. 转载: 133 行代码实现质感地形

    133 行代码实现质感地形 本文由  伯乐在线  -  abell123  翻译自  Hunter Lofti .欢迎加入 技术翻译小组 .转载请参见文章末尾处的要求. 程序员们都喜欢创造一些东西,但 ...

  2. 133 行代码实现质感地形

    本文由  伯乐在线  -  abell123  翻译自  Hunter Lofti .欢迎加入 技术翻译小组 .转载请参见文章末尾处的要求. 程序员们都喜欢创造一些东西,但是,还有什么会比创建一个世界 ...

  3. 100行代码实现最简单的基于FFMPEG+SDL的视频播放器(SDL1.x)

    ===================================================== 最简单的基于FFmpeg的视频播放器系列文章列表: 100行代码实现最简单的基于FFMPEG ...

  4. skycons.js 基于canvas的天气动态js插件

    skycons.js 基于canvas的天气动态js插件 skycons.js是一个开源的javascript天气动画图标渲染器.相当于gif动图一样. skycons CDN地址:https://w ...

  5. Three.js基于Canvas的文字贴图

    Three.js基于Canvas的文字贴图 居中对齐,对中文文字做了长度兼容处理,可根据开发需求自行构造Canvas贴图 const createLabel = () => {const nam ...

  6. 100行代码实现最简单的基于FFMPEG+SDL的视频播放器

    简介 FFMPEG工程浩大,可以参考的书籍又不是很多,因此很多刚学习FFMPEG的人常常感觉到无从下手.我刚接触FFMPEG的时候也感觉不知从何学起. 因此我把自己做项目过程中实现的一个非常简单的视频 ...

  7. html5 canvas图表,Chart.js基于Canvas画布的HTML5统计图表库 - 资源分享

    Chart.js 是一个简单.面向对象.为设计者和开发者准备的图表绘制工具库.可以绘制柱状图.热点图.曲线图等,使用HTML5中的Canvas画布,支持原生的和jQuery的调用方法. 特点 6种图表 ...

  8. 不到50行代码,HTML+CSS+JS创建一个简单的色轮

    简单,其实就是调库! 文档在这里 Get Started | iro.js 我也写了一个简单的样例在下面可以参考一下

  9. skycons.js 基于canvas的天气动态图标小插件

    skycons是什么 skycons.js是一个开源的javascript天气动画图标渲染器.(特点就是动态的图标,敲黑板) skycons CDN地址:https://www.bootcdn.cn/ ...

最新文章

  1. 51CTO路由技术电子书
  2. 配合使用自制的PE3.0启动盘和Windows部署服务,实现Ghost网克
  3. Docker常用操作命令
  4. Lucene查看分析器的分词效果
  5. FFmpeg--命令详解
  6. 手把手教你做一套 UTM 广告投放!| 原力计划
  7. 十条jQuery代码片段助力Web开发效率提升
  8. TensorFlow共享变量
  9. 通过玩游戏从计算机小白到黑客的进阶之路!
  10. spss20安装许可证代码_SPSS23安装教程
  11. PHP接收云之家审批结果,首页云之家开放平台文档
  12. 万兆局域网方案_局域网组建实施方案.doc
  13. SNF快速开发平台MVC-集成了百度开源项目echars
  14. zyb的面试 Hdu6468
  15. SpringBoot项目怎么重命名
  16. 学习html的心得总结
  17. 项目管理sod_Microsoft Visual SourceSafe(项目文件管理) V6.0 最新中文版(图文)
  18. matlab 1g等于多少byte,1G大还是1GB大???它们和MB,KB,字节又怎么换算??
  19. 2022年高压电工判断题及答案
  20. 纵横字谜的答案 (UVa232)

热门文章

  1. Unity之UGUI的学习(七):Slider(滑动条)
  2. 2021高考蚌埠四中成绩查询,2021年蚌埠高考状元是谁分数多少分,历年蚌埠高考状元名单...
  3. vue对比html,Vue与React比较
  4. ElasticSearch是什么?Lucene是什么?
  5. 浮动提示title更改字体大小
  6. 论文阅读Patient-specific reconstruction of volumetric computed tomography images from a single projectio
  7. 电脑开机后显示服务器没有声音,我的电脑音频服务启动不了,没有声音,小扬声器那里是一个红色的小叉叉...
  8. PIE-Engine利用modis计算ndvi
  9. 安卓开发-Gps定位获取位置信息
  10. SQL小试:每天的领导和合伙人