您所在的位置: &
天空盒与天空穹的使用技巧
天空盒与天空穹的使用技巧
人民邮电出版社
《Android 3D游戏开发技术宝典——OpenGL ES 2.0》第11章常用3D开发技巧,本章主要介绍了一些常用的3D开发技巧,包括标志版、灰度图地形生成、天空盒与天空穹、镜像技术、动态文本输出以及非真实感绘制等。本节为大家介绍天空盒与天空穹的使用技巧。
11.3.3& 天空盒与天空穹的使用技巧
前面两个小节给出的两个案例,第一个仅包含天空盒本身,第二个用天空穹罩住了不太大的山地地形。但在很多实际应用中场景非常大,若使用尺寸足以包含整个场景的天空盒或天空穹效果就不是很好。因此实际开发时经常会仅让天空盒或天空穹罩住场景的一部分,随着摄像机的移动天空盒或天空穹也一起移动,具体情况如图11-18所示。
采用这种天空盒或天空穹使用技巧的游戏有很多,如大名鼎鼎的极品飞车、都市赛车等,读者可以在玩这些游戏进行娱乐的同时细致观察一下就能感觉到。同时本书最后的大案例BN赛艇也是采用此技术的,有兴趣的读者可以重点参考BN赛艇中使用天空穹的代码。
▲图11-18& 天空盒/穹伴随摄像机移动示意图
【责任编辑: TEL:(010)】&&&&&&
关于&&&&的更多文章
全书共22章,其中第1章与第2章为android平台相关的一些基础知识
本书描述了黑客用默默无闻的行动为数字世界照亮了一条道路的故事。
本书全面讲解WPF的实际工作原理,是一本WPF权威著作。
SQL Server 2012附带了强大的Analysis Services新功能
本书通过对目前中国企业在风险管理和内部控制工作中的
《游戏开发核心技术--剧本和角色创造》分“剧本”、“角色”和“游戏玩法”三部分,第一部分着重说明故事的历史、一般故事元素、
51CTO旗下网站OpenGLES入门教程1-Tutorial01-GLKitOpenGLES入门教程2-Tutorial02-shader入门OpenGLES入门教程3-Tutorial03-三维变换OpenGLES入门教程4-Tutorial04-GLKit进阶OpenGLES进阶教程1-Tutorial05-地球月亮OpenGLES进阶教程2-Tutorial06-光线OpenGLES进阶教程3-Tutorial07-粒子效果OpenGLES进阶教程4-Tutorial08-帧缓存OpenGLES进阶教程5-Tutorial09-碰碰车OpenGLES进阶教程6-Tutorial10-平截体优化这一次的内容是精心准备的天空盒特效,为了节约大家时间,这次在教程里面不贴代码,demo部分的内容都是干货。写这个demo的过程中遇到了一些坎,最后会提到。特别留意天空盒纹理坐标推导和顶点数据对象切换。
天空盒特效:OpenGL ES提供了一个立方体贴图(cube mapping)的专门用于产生天空盒效果的纹理贴图模式。举例:一个人,站在立方体的中间,上下左右前后看到的都是立方体的图片。
为节省流量,gif比较模糊,清晰效果可以看demo。
天空盒的核心就是通过方向来取样纹理,纹理坐标被当作方向向量,建立适合的正方体后,位置坐标就是纹理坐标。
1、尺寸大小
天空盒的尺寸可以随意,但是需要足够大以容纳渲染的场景。同时天空盒的中心要尽可能贴近视点的眼睛位置,避免太近产生纹理拉伸。
2、纹理坐标到纹素推导(核心)
纹理坐标(s, t, r)被当作方向向量看待,每个纹理单元都表示从原点所看到的纹理立方体上的图像。如果是texture2D的情况,纹理坐标(s, t)会直接返回相应位置的纹素;textureCube的情况,首先读取cube纹理,然后以正方体中心为原点,(s,t,r)为方向,求出正方体和方向向量的交点位置(a, b),按照位置(a, b)在纹理上面选择相对应的纹素。举个例子:对于(s, t, r) , 假设 S=fabs(s) ,同理有T
R。如果S & T
并且 S & R。 那么可以确定交点在面+x 和 -x根据s的±可以确定±x面。直线过原点和点(s, t, r) ,那么也会过点(1, t/s, r/s)。(t/s) 和(r/s)就是对应的纹素位置。考虑到立方体的面为[-1, 1],那么可以把 (t/s + 1) / 2,这样就得到真正的纹素坐标( (t/s + 1) / 2, (r/s + 1) / 2)
3、顶点数据对象切换(核心)
glBindVertexArrayOES很多应用会在同一个渲染帧调用多次glBindBuffer()、glEnableVertexAttribArray()和glVertexAttribPointer()函数(用不同的顶点属性来渲染多个对象)新的顶点数据对象(VAO) 扩展会几率当前上下文中的与顶点属性相关的状态,并存储这些信息到一个小的缓存中。之后可以通过单次调用glBindVertexArrayOES() 函数来恢复,不需要在调用glBindBuffer()、glEnableVertexAttribArray()和glVertexAttribPointer()。
VAO和VBOVBO:顶点缓冲区对象(buffer-object),用于存储顶点坐标、纹理坐标、顶点法线、顶点颜色等。VAO:顶点数据对象,记录顶点数据存储状态信息的状态对象(status-object)。
This extension introduces vertex array objects which encapsulate vertex array states on the server side (vertex buffer objects). These objects aim to keep pointers to vertex data and to provide names for different sets of vertex data. Therefore applications are allowed to rapidly switch between different sets of vertex array state, and to easily return to the default vertex array state.Q:Should vertex array objects be sharable across multiple OpenGL ES
contexts?A: No.
demo实现过程遇到最大的一个坑,如下:
暂停的适合,天空盒的效果会消失!然后开始寻找问题所在,最后发现问题代码出现在这里
// 增加角度
if (!self.mPauseSwitch.on) {
self.angle += 0.01;
实在无法理解为什么角度的改变会影响天空盒的显示。回顾了一下OpenGL ES的绘制过程,从顶点缓存到变换、着色到帧缓存,发现天空盒的绘制都没有问题。接着开始思考,会不会是飞机的绘制影响了天空盒的绘制?因为这是两个着色器,存在不同的顶点数据和纹理。于是尝试在绘制完天空盒后调用下面,防止天空盒绑定的数据缓存被飞机的影响。
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
在绘制完飞机后调用下面,防止飞机的顶点数据去影响到天空盒。
glDisableVertexAttribArray(GLKVertexAttribPosition);
glDisableVertexAttribArray(GLKVertexAttribNormal);
glBindTexture(GL_TEXTURE_2D, 0);
然而并没有什么用 ==!
问题搁在心头好几天,每天都会尝试,也在google查GLKSkyboxEffect相关的问题,可惜GLKSkyboxEffect是苹果官方自己实现。本来天空盒是上周就已经实现好,为了写这篇文章实现了一个暂停的功能,就出现这个bug。虽然去掉暂停功能很正常,但是demo里面存在问题。经过很多天尝试后,已经可以确定的是,是飞机的绘制影响了天空盒的位置,角度的旋转只是隐藏了bug。开始寻找非OpenGL ES的文章,看看OpenGL的天空盒实现,同时查看苹果官方的文档。最后偶然在苹果的文档中看到一个关键词OES,我似乎明白了什么。OES是OpenGL ES的一个非标准扩展,天空盒里面有用到,而我并没有处理。尝试用OES来管理飞机的顶点模型。
glGenVertexArraysOES(1, &_mPositionBuffer);
glBindVertexArrayOES(_mPositionBuffer);
问题果然迎仍而解:暂停的时候天空盒是正常的。接下来开始尝试不用OES,寻找问题的根源。最后的结论:天空盒的绘制调用了glBindVertexArrayOES(),可是在绘制结束后没有glBindVertexArrayOES(0);导致飞机的顶点数据影响了天空盒的顶点数据。解决方案:在绘制完天空盒后调用glBindVertexArrayOES(0);,问题完美解决。
天空盒还有两部分内容:一个是切图,这个比较简单,用CoreGraphics即可;另一个是用Shader来实现天空盒,而非GLKSkyboxEffect,这部分加进来篇幅就过长了。这两部分都在github上面放了源码,都已经编译调试没问题,下载完可以直接运行。附上代码
为什么视点距离天空盒太近会产生纹理拉伸?
热点阅读:
小主,按键盘右方向键 → 翻页可以跳过片头呢
本文标题:
原文链接:
和本文相似的内容:
日,佛山市发生了一起特殊凶杀案:犯罪嫌疑人李善周***两名,将受害者张东成控制后,李善周亲自手持一柄尖刀,对着张东成的下体猛刺,活生生地将张东成的***和OpenGL中线框图的消隐_百度文库
两大类热门资源免费畅读
续费一年阅读会员,立省24元!
OpenGL中线框图的消隐
上传于||暂无简介
阅读已结束,如果下载本文需要使用2下载券
想免费下载本文?
定制HR最喜欢的简历
你可能喜欢[OpenGL] 立方体贴图(Cubemap) - 简书
下载简书移动应用
写了63862字,被56人关注,获得了44个喜欢
[OpenGL] 立方体贴图(Cubemap)
之前我们一直使用的是2D纹理,下面来讨论一种将多个纹理组合起来映射到一个单一纹理的类型,它就是cubemap。
基本上说cubemap包含6个2D纹理,每个2D纹理是立方体的一个面。你可能会奇怪费事地把6个纹理结合为一个这样的立方体有什么用?这是因为cubemap有自己特有的属性,可以使用一个方向向量对它们索引和采样。想象一下,我们有一个1×1×1的单位立方体,在它内部有个以原点为起点的方向向量。
从cubemap上使用橘***向量采样一个纹理值看起来和下图有点像:
方向向量的大小不重要。一旦提供了方向,OpenGL就会获取方向向量触碰到立方体表面上的相应的纹理像素(texel),这样就返回了正确的纹理采样值。
方向向量触碰到立方体表面的一点也就是cubemap的纹理位置,这意味着只要立方体的中心位于原点上,我们就可以使用立方体的位置向量来对cubemap进行采样。然后我们就可以获取所有顶点的纹理坐标,就和立方体上的顶点位置一样。所获得的结果是一个纹理坐标,通过这个纹理坐标就能获取到cubemap上正确的纹理。
创建一个Cubemap:
Cubemap和其他纹理一样,所以要创建一个cubemap,在进行任何纹理操作之前,需要生成一个纹理,激活相应纹理单元然后绑定到合适的纹理目标上。这次要绑定到 GL_TEXTURE_CUBE_MAP纹理类型:
GLuint textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
由于cubemap包含6个纹理,我们必须调用glTexImage2D函数6次,还需要把纹理目标(target)参数设置为cubemap特定的面,来告诉OpenGL创建的纹理是对应立方体哪个面的。OpenGL就提供了6个不同的纹理目标,来应对cubemap的各个面:
根据枚举特效,可以每次加1进行循环操作
for(GLuint i = 0; i & textures_paths.size(); i++)
image = SOIL_load_image(textures_paths[i], &width, &height, 0, SOIL_LOAD_RGB);
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
我们也要定义它的环绕方式和过滤方式:
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
在片段着色器中,我们使用一个不同的采样器 -- samplerCube,用它来从texture函数中采样,但是这次使用的是一个vec3方向向量,取代vec2。下面是一个片段着色器使用了cubemap的例子:
in vec3 textureD // 用一个三维方向向量来表示Cubemap纹理的坐标
uniform samplerC
// Cubemap纹理采样器
void main() {
color = texture(cubemap, textureDir);
天空盒(Skybox):
cubemap可以简单的实现很多有意思的技术。其中之一便是天空盒(Skybox)。天空盒是一个包裹整个场景的立方体,它由6个图像构成一个环绕的环境,给玩家一种他所在的场景比实际的要大得多的幻觉。如果把这6个面折叠到一个立方体中,就会获得模拟了一个巨大风景的立方体。
加载天空盒:先按上面方法加载6张纹理。然后创建一个立方体来绘制天空盒。当一个立方体的中心位于原点(0,0,0)的时候,它的每一个位置向量也就是以原点为起点的方向向量。这个方向向量就是我们要得到的立方体某个位置的相应纹理值。所以我们只需要提供位置向量,而无需纹理坐标。它的顶点着色器如下:
#version 330 core
layout (location = 0) in vec3
out vec3 TexC
uniform mat4
uniform mat4
void main()
gl_Position =
projection * view * vec4(position, 1.0);
TexCoords =
顶点着色器把输入的位置向量作为输出给片段着色器的纹理坐标。片段着色器就会把它们作为输入去采样samplerCube:
#version 330 core
in vec3 TexC
uniform samplerC
void main()
color = texture(skybox, TexCoords);
我们希望天空盒以玩家为中心。移除视图矩阵的平移部分,这样移动就影响不到天空盒的位置向量了。我们可以只用4X4矩阵的3×3部分去除平移。简单地将矩阵转为33矩阵再转回来,就能达到目标:
glm::mat4 view = glm::mat4(glm::mat3(camera.GetViewMatrix()));
为了绘制天空盒,我们可以把它作为场景中第一个绘制的物体并且关闭深度写入。这样天空盒才能成为所有其他物体的背景来绘制出来。
但这样并不高效,如果先渲染天空盒,那么就会为屏幕上每一个像素运行片段着色器,即使前面有物体遮挡。但如果我们直接打开深度测试,因为天空盒是个1×1×1的立方体,极有可能会通不过深度测试。办法是,我们需要让深度缓冲相信天空盒的深度缓冲有着最大深度值1.0,如此只要有个物体存在深度测试就会失败,看似物体就在它前面了。
前面坐标系统我们说过,透视除法(perspective division)是在顶点着色器运行之后执行的,它把gl_Position的xyz坐标除以w元素。而除法结果的z就等于顶点的深度值。因此,我们可以把输出位置的z元素设置为它的w元素,这样它的z就会转换为w/w = 1.0。修改顶点着色器:
void main()
vec4 pos = projection * view * vec4(position, 1.0);
gl_Position = pos.
TexCoords =
1.0就是深度值的最大值,只有在没有任何物体可见的情况下天空盒才会被渲染(只有通过深度测试才渲染,否则假如有任何物体存在,就不会被渲染,只去渲染物体)。
我们必须改变一下深度方程,把它设置为GL_LEQUAL,原来默认的是GL_LESS。深度缓冲会为天空盒用1.0这个值填充深度缓冲,所以我们需要保证天空盒是使用小于等于深度缓冲来通过深度测试的,而不是小于。
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
选择支付方式: