OPENGL三维场景搭建、漫游、交互

时间:2024-04-04 08:26:09

OPENGL三维场景搭建、漫游、交互

标签(空格分隔): OPENGL


这是博主的一次实验,实验截止日期还没有到。等deadline过后,博主附上源码。
源码地址:更新:OPENGL三维场景搭建、漫游、交互


程序在读取OBJ模型基础上完成,传送门:OPENGL读取OBJ模型

天空盒

读取bmp图像像素信息(这里以24位BMP图像为例)

  这里大家需要自行复习一下BMP图像的组成。在这里我就不详述了。给大家一个传送门:
BMP图像组成
  BMP图像的组成:BITMAPFILEHEADER、BITMAPINFOHEADER、RGBTRIPLE(16位和256色特有)、位图数据(即像素信息)。
  读取BMP跳过BITMAPFILEHEADER与BITMAPINFOHEADER结构,得到图像大小数据,然后分配数据空间读取像素信息,将该空间的数据作为纹理数据。
  注意:读24位BMP时需要注意,24位BMP像素信息储存顺序位BGR,而不是RGB,读完数据后,需要把数据信息“处理”一下,否则最终贴图颜色不正确。
  下图中,图一为错误颜色,图二为正确颜色。
OPENGL三维场景搭建、漫游、交互
OPENGL三维场景搭建、漫游、交互
天空盒结果:
OPENGL三维场景搭建、漫游、交互

天空盒随着视点移动

视点移动

  借助glLookAt函数和一些数学知识实现。
  我们都知道glLookAt()函数前三个参数是视点的坐标x,y,z,中间三个参数是视点看向的坐标xd、yd、zd。可以根据圆的极坐标方程来求得视点看向的方向(坐标),从而知道视点移动的方向。
  OPENGL三维场景搭建、漫游、交互
  如上图,只要设定一个旋转角,通过改变旋转角,可以改变视点看向的坐标,确定视点移动方向。从而视点可以向360度全方向移动。

天空盒移动

  根据视点移动求出的增量,直接赋给天空盒,通过glTransf()可以让天空盒随视点移动。

凹凸地形纹理

普通地形

即一张贴图:
OPENGL三维场景搭建、漫游、交互

凹凸地形

需要额外的记录地形高度的BMP图片。同时借助顶点数组来实现。
程序中记录地形高度的BMP图片大小是32*32,在XOZ平面上放大, 顶点数组中记录(放大后的X、根据高度BMP获取的高度Y、放大后的Z)。纹理数组中记录(放大前X,放大前Z)。这样在三维空间中,点(X放大,Y, Z放大)点的纹理坐标为普通地形纹理的(X,Z)点。
根据顶点数组和纹理数组绘制地形。运用了glDrawElements()。
OPENGL三维场景搭建、漫游、交互
OPENGL三维场景搭建、漫游、交互
在视点移动时,需要实时获取当前视点所在点(X,Z)所对应的地形高度Y,然后把Y加到glLookAt()函数上。

树木与火焰

读取tag

BMP图片没有ALPHA通道,关于树木和火焰的贴图,都是读取的tga格式图片。

树木绕着总是垂直于视点向量

这里我有两个想法,一个是旋转,另一个是把承载树木纹理的长方形就绘制在垂直于视点看向方向向量上。
i. 旋转
OPENGL三维场景搭建、漫游、交互
ii. 本身垂直
OPENGL三维场景搭建、漫游、交互
这里我采用的第二种方法,两种方法应该是一样的,虽然两种方法中的变量在视点变换中已经求出来了,不用二次计算。第一种方法只需要传一个参数,但是要调用一次旋转函数。第二中方法传两个参数,但是参数只要进行少数次乘法就可以得到想要的效果。
同理,树木也要根据凹凸地形来确定自身在Y轴的高度。
OPENGL三维场景搭建、漫游、交互

透明纹理的遮挡顺序

透明纹理之间会相互遮挡,这里精力有限,没有解决,只是把树木放到display最后绘制。


更新:想起来老师介绍过一种比较简单的方法,来正确绘制透明物体。主要是根据绘制顺序来实现。
绘制顺序就是:首先绘制所有不透明的物体。如果两个物体都是不透明的,则谁先谁后都没有关系。然后,将深度缓冲区设置为只读。接下来,绘制所有半透明的物体。如果两个物体都是半透明的,则谁先谁后只需要根据自己的意愿(注意了,先绘制的将成为“目标颜色”,后绘制的将成为“源颜色”,所以绘制的顺序将会对结果造成一些影响)。最后,将深度缓冲区设置为可读可写形式。
如下图是十字树木面片的效果:
OPENGL三维场景搭建、漫游、交互

OPENGL三维场景搭建、漫游、交互

火焰序列(opengl闲时调用)

i.现成的火焰tga纹理真的难找,我是通过一段火焰视频,通过AE导出成tga序列,然后选取部分序列,对每个火焰添加alpha通道。

ii. 火焰纹理实现
读取火焰纹理序列,设定时间阈值,每隔这一段时间阈值,更换火焰纹理。从而实现火焰效果。
注意:
火焰纹理更换,不能放在display()函数中,因为display(),只有在窗口需要重绘时才会调用。举个例子,当视点站在火焰前不动时,火焰纹理不会更换。
需要使用glutIdleFunc()函数,但是又不能往里传display,因为display中有很多其他的物体绘制,闲时不停调用display()会让程序运行时不流畅。
我在glutIdleFunc()函数中传入一个f()函数,里面每隔特定时间更改火焰纹理序列索引,并通过glutPostRedisplay(),让display重绘。这样一来,既可以让火焰纹理不停更换,又能让程序运行流畅。
OPENGL三维场景搭建、漫游、交互

鼠标操控

根据鼠标位置旋转视角

视点可以根据鼠标的位置进行旋转。主要根据glutPassiveMotionFunc()实现。这里没有什么技术含量。

鼠标拾取与选择

这里查了很多资料,OPENGL中选择机制不是发出一条射线,而是通过裁剪框来实现的,设定一个裁剪框,在该区域内的物体全部被选中。这里给出我找到比较好的博文分享给大家:
OpenGL学习笔记:拾取与选择
OpenGL-选择与拾取
同时为了便于大家理解,我编写了一个小小的拾取与选择程序,供大家参考。传送门:鼠标拾取与选择
为了避免这种多重选中,我在程序中只读入三个OBJ模型,想按住鼠标左键移动可以移动选定的OBJ模型。
i. 步骤:
1. 按住鼠标左键选定OBJ模型:
主要需要以下几步进入选择模式、分配名字栈、得到命中记录。
2. 分析名字栈中数据信息,得到命中物体的名字。
由于物体可以有多个名字,甚至可以木有名字,因此元素的结构不是固定大小的,以下为例:
OPENGL三维场景搭建、漫游、交互
OPENGL三维场景搭建、漫游、交互
3. 得到名字,知道鼠标选择的具体OBJ模型,也就知道要操作的对象。
第三步只要在glutMotionFunc(motion); motion方法中实现对OBJ模型的操作即可。我在程序中实现的是obj模型随着鼠标移动可绕着视点旋转。
OPENGL三维场景搭建、漫游、交互