bufferedimage生成的图片模糊_OpenGL游戏引擎开发[7]生成南京地形图

获取南京地形高度图数据

上节我们自己生成另一个平面地形,这节我们生成一个带有真实地形高度的【南京玄武区】的地形图。

如果大家玩过《城市天际线》的画,都知道游戏里面可以导入现实世界真实的地图的。其官网提供了一个工具:

http://terrain.party/

dea9b62f73f62276dcf549ceeec87fde.png

这是一个使用openlayers地图库制作的一个在线生成真实世界地形高度图的工具,我们在上面找到想要生成高度图的区域,就可以使用右侧的工具栏下载按钮进行下载。

下载下来一个压缩包:

9ab0863ee1ceb29aaae62569f30ed1f5.png

里面有文件的说明信息,请自行查看。这里我们使用融合过的高度图【nanjing Height Map (Merged).png】

接着,将之前生成平面网格时赋予每个顶点高度为0的代码,改成从高度图中读取高度数据。

97f624005d618bde9a63b39489295143.png

因为高度图是一个灰度图,里面的像素值的范围是(0,255)。这里需要根据自己的需求来使用这个高度值。比如:有的区域比较平坦,一张高度图中的像素值可能都集中在125左右,那么你绘制出来地形的高度起伏并不是很明显,特别是地形图很大的时候(也就是上节说的生成地形时SIZE设置很大时)。如果有的区域,地形的起伏刚好较为均匀,地形图的像素值在(0,255)分布较为均匀,直接使用像素值作为OpenGL中顶点的y值,那么效果可能也会不错。

总之,数据是死的, 人是活的。怎么使用数据,取决于你最终想要的效果。

"计算机图形学中,只要你看起来是对的,那就是对的。" --忘了哪本书上讲的。

处理高度数据

首先,我们先直接使用像素值来作为要绘制的高度来看看效果怎么样。

fa7c3b8e56b3c055829cc3b931400511.png

我们这里使用的是PNG图片,有RGBA四个通道组成。

因为是灰度图,所以 R=G=B (常识),这里我们没有使用到透明度通道,因为这里没有物体的混合嘛。

我们随便取R通道作为来代表该点实际的高度值。

Java中使用 BufferedImage.getRGB() 返回一个int

Java中int占4个字节,刚好每个字节(占8bit)存储一个像素值,看下图:

f623eb01b29b09da1496fb6befae9bc6.png

那么如果要获取R的像素值,就需要对返回的int进行右移16位,再使用0xFF(1个字节=8bit)进行与运算,这样就能得到R的像素值。这个不难理解吧。

A通道、G通道和B通道的值获取以此类推,只不过这里没用到。

那么我们直接将R的值返回,看看绘制的地形图是什么样的。

f90e414d64a66af9715801ccb6c5aaf7.png
00be7c0a344c2e9dc3bc4001a625ece6.png

好像还不错, 我们使用归一化的方法来看看效果。

f0ec4f266a5d972b2e7058be83f48397.png

BufferedImage.getRGB()返回的int的范围是[-256*256*256,+256*256*256],也就是我们这里定义的 MAX_PIXCEL_COLOR

e5206105d2e44a8af898ec8c6d966eed.png

因为int我们只用到了后面3个字节(RGB),第一个字节(表达A通道),这里我们没用到。后3个字节全为1时(也就是 MAX_PIXCEL_COLOR )该int绝对值值最大。

b9d20046aa719783096a2c270933162c.png

那么任取 h 属于 [-MAX_PIXCEL_COLOR,MAX_PIXCEL_COLOR]

h += MAX_PIXCEL_COLOR

h的范围变成 [ 0, 2 * MAX_PIXCEL_COLOR]

然后, h /= 2 * MAX_PIXCEL_COLOR

那么,h的范围变成 [ 0, 1],这样h就归一化为 [0,1]范围内了。

最后,乘以 我们定义的地形的最大高度 MAX_HEIGHT 来进行绘制。

cea4c626590df13383f8499d1569351f.png

好像也差不多,大家可以根据实际情况,不一定要将高度值归一化到[0,1]之间,归一化到 [1/4, 3/4]范围内也是可以的嘛。

OK,本节结束。

欢迎大家关注我的公众号【OpenGL编程】。

每天进步一点点,探索3D编程背后的技术细节,不再做只会“调包”的傻小白。以幽默风趣的行文风格,近乎白话文的专业知识讲解,分享3D编程的心得、教程、算法,带你走进3D编程的世界。

daab012becab28f810801f5da1a96ba0.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
作者对游戏的说明: 首先,您应当以一种批判的眼光来看待本程序。这个游戏是我制作 的第一部RPG游戏,无任何经验可谈,完全按照自己对游戏的理解进 行设计的。当我参照了《圣剑英雄2》的源码之后,才体会到专业游 戏引擎的博大精深。 该程序的内核大约有2000余行,能够处理人物的行走、对话、战斗, 等等。由于该程序的结构并不适于这种规模的程序,故不推荐您详 细研究该程序。所附地图编辑器的源程序我已经添加了详细的注释, 其程序结构也比较合理,可以作为初学VC的例子。 该程序在VC的程序向导所生成的SDI框架的基础上修改而成。它没有 使用任何关于VC底层的东西。程序的绝大部分都是在CgameView类中 制作的,只有修改窗口特征的一段代码在CMainFrm类中。其他的类 统统没有用到。另外添加的一个类是CEnemy类。 整个游戏的故事情节分成8段,分别由Para1.h ~ Para8.h八个文件 实现。由于程序仅仅能够被动的处理各种各样的消息,所以情节的 实现也只能根据系统的一些参数来判断当前应当做什么。在程序中 使用了冗长的if……else if……结构来实现这种判断。 当然,在我的记录本上,详细的记录了每个事件的判断条件。这种 笨拙的设计当然是不可取的。成都金点所作《圣剑英雄II》采用了 剧本解读的方式,这才是正统的做法。但这也需要更多的编程经验 和熟练的code功夫。 下面列举的是程序编制过程中总结出来的经验和教训。 第一,对话方式应该采用《圣剑英雄II》的剧本方式。 现在的方式把一个段落中所有的对话都混在一个文件中,然后给每 句话一个号码相对应。这样做虽然降低了引擎的难度,却导致剧情的 编写极其繁琐。 第二,运动和显示应当完全分开。 现在的程序中,运动和显示是完全同步的。即:在定时器中调用所有 敌人的运动函数,然后将主角的动画向前推一帧,接着绘制地图,调 用所有敌人的显示函数、重绘主角。这样的好处是不会掉帧,但带来 的问题是,如果要提高敌人的运动速度,那么帧数也跟着上去了。所 以当DEMO版反馈说速度太慢的时候,我修改起来非常困难。而这个问 题到最后也仅仅是将4步一格该成了2步一格。 第三,VC中数组存在上限。如果用“int aaa[1000000000]”定义一个 数组,编译器肯定不会给分配那么大的内存空间。而在这个程序中, 地图矩阵、NPC矩阵都超过了VC中数组的上限。但这一点知道的太晚了。 在1.0版本中已经发现地图最右端缺少了几行,但不知道是什么原因 造成的。(地图编辑器中未出现此问题,因为地图编辑器是用“序列 化”的方式存盘读盘的。)解决这个问题的方法是用“new”来分配 内存空间。 第四,由于不知道应该如何使用“new”和“delete”,几乎所有的DC 都使用了全局变量。这是完全没有必要的。程序运行期大约会耗用20 多M的内存空间,相当于一个大型游戏所使用的内存空间了。 另外,在游戏的剧情、美工方面也有许多问题,总之一个词“业余”。 我就不总结了。下一部作品,我将争取在程序上有一个质的飞跃。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值