-
:转载时请以超链接形式标明文章原始出处和作者信息及
OpenGL ES 11 – 单纹理,多视图,纹理渲染,以及数学灵感
我在高中的{zh1}一年,我正在做一个先进的数学题目,是因为有趣,看着油漆干燥。有不少次我都没有谈到,因为我的那些先进的教学理念在课堂的环境根本没有激励我。
有{yt},我继续坐在教室的后面发呆,突然我发现白板上的这样一行:
[ a b c ] . [ d e f ]
我坐在笔直。我知道这是矩阵数学,这是我一直暴露在阅读有关图形程序,但我还没有xx理解。嗯,我有兴趣再次,鼓舞注意,甚至喜欢自己。
兴趣是一个有趣的事情。我提到这一点,因为无论我如何努力,我不能xx获得该教程混合权利。所以,我刚刚张贴,并决定回来后,我所有的“灵感” 。
现实将是我永远也不会得到启发寻找在立方体和金字塔,试图处理日益复杂的OpenGL ES的概念。其上市时间开始(缓慢)增加的复杂性场面在我们正在尝试英寸这样,灵感不应枯竭。
增加了现场的复杂性将沿着放缓,但我们不能作出任何巨大的跳跃在任何时间,以免造成人民后面。但是,增加的复杂性,我们将和您的结果将学习现场管理,以及在OpenGL ES的概念。
今天的教程将刚才我们在隧道。但是,在我们达到现场,我们有一些其他位做家务。首先,我们需要支付{zh1}一次专题载入纹理中的iPhone ,但直到去年我离开,因为它是最重要的(阅读:我忘记了它) 。然后我们要引起我们的隧道使用4种不同的材质,但加载从一个单一的纹理文件。{zh1},我们会做一些基本呈现的纹理,触摸基地再次与融合。
iPhone’s Y != OpenGL’s Y
OpenGL’s Y不适合很多的平台,包括iPhone的。所以当我们使用渲染的时候,y是从上往下的。
为了解决这个问题,不要调用 glRotatef() ,虽然它可以解决这个问题,但是会浪费CPU/GPU的时间。
这是仅于的解决办法。
0. 1. 改变纹理之前捆绑在一个图像编辑器,以便在加载的正确方式。这一工程,但有可能减少跨平台的兼容性。还需要更多的工作。我不会做这件事。
0. 2.改变纹理的坐标数组为右坐标。也就是说,左下角将是(0,1).OpenGL并不关系你的纹理数据,它只需要知道每个顶点对应的纹理坐标就可以了。
0.
3。改变 loadTexture[] 函数,当它加载图片的时候自行翻转。但是这样一来,你每次加载图片都要浪费时间。并且增加CPU/GPU的消耗。
“{zj0}方案”是使用第3点。
我将改变 loadTexture[] 函数。在本教程里,一切还很好,还没有OpenGL不能做的事。
{wy}的改变事修改下面的函数。
CGContextRef textureContext = CGBitmapContextCreate(textureData,
texWidth, texHeight,
8, texWidth * 4,
CGImageGetColorSpace(textureImage),
kCGImageAlphaPremultipliedLast);
// Rotate the image -- These two lines are new
CGContextTranslateCTM(textureContext, 0, texHeight);
CGContextScaleCTM(textureContext, 1.0, -1.0);
这为我们的图像指定了正确的方向。
从单个纹理中出来的多个纹理
做为今天的乐趣,我们将使用单一的纹理来生成4个纹理对象:两个墙的纹理,地板的纹理和天花板的纹理。
在上个教程里,我们为立方体的每个面都加载了一个纹理。我们每一次加载纹理并传送给OpenGL,它都会在OpenGL的控制空间里增加一个副本。因此,我们可以使用free()去释放内存,在loadTexture里使用malloc()为纹理图片开启内存。显然,我们每次改变纹理都需要时间,每次使用新的纹理都要通知OpenGL绑定新的纹理。
这个方法给了你一些变革,但它不是真正的性能。虽然是一个方便的技巧,我只是想告诉你可以使用纹理的一个部分。
{dy}件事就是创建一个空白的图片,大小是512*512,足够放下4张256*256的图片。然后我们复制每个图片到这个大图片里,保存为png文件。
至少有些工作你可以自己完成了,我将分离出每个纹理通过控制我的纹理坐标。
在我们定义纹理坐标前,我们需要定位我们使用的顶点坐标。我们需要一个矩形如下:
const GLfloat elementVerticies[] = {
-1.0, 1.0, 0.0, // Top left
-1.0, -1.0, 0.0, // Bottom left
1.0, -1.0, 0.0, // Bottom right
1.0, 1.0, 0.0 // Top right
};
ok,如果我们想绘制木材纹理(左下角的),我们需要指定下面的纹理坐标。
const GLfloat combinedTextureCoordinate[] = {
// The wood wall texture
0.0, 1.0, // Vertex[0~2] top left of square
0.0, 0.5, // Vertex[3~5] bottom left of square
0.5, 0.5, // Vertex[6~8] bottom right of square
0.5, 1.0, // Vertex[9~11] top right of square
和一起一样,他们就是坐标。每个纹理坐标对应一个顶点坐标。
从底部到木材纹理的顶部,正好是全部大小的一半, y坐标将是0.5而不是以前的0.0。以前,我们是使用的GLshort,现在我们使用GLfloats。
同样,x方向也是到达0.5就截至了。
这里是四个纹理的坐标:
const GLfloat combinedTextureCoordinate[] = {
// The wood wall texture
0.0, 1.0, // Vertex[0~2] top left of square
0.0, 0.5, // Vertex[3~5] bottom left of square
0.5, 0.5, // Vertex[6~8] bottom right of square
0.5, 1.0, // Vertex[9~11] top right of square
// The brick texture
0.5, 1.0,
0.5, 0.5,
1.0, 0.5,
1.0, 1.0,
// Floor texture
0.0, 0.5,
0.0, 0.0,
0.5, 0.0,
0.5, 0.5,
// Ceiling texture
0.5, 0.5,
0.5, 0.0,
1.0, 0.0,
1.0, 0.5
};
{zh1}一件事,我定义了一些宏来让代码阅读的更加容易,为获得每个纹理坐标的正确偏移值。
#define WOOD_TC_OFFSET 0
#define BRICK_TC_OFFSET 8
#define FLOOR_TC_OFFSET 16
#define CEILING_TC_OFFSET 24
绘制隧道
完整的隧道是这样的:
需要注意的{dy}件事是我们只使用了一个对象。绘制隧道的代码没有创建更多的对象,我们只使用了单独一个,只是我们操纵它,让它看起来像多重的。事实上,左边墙有5个,右边有5个,上下各有10个。
所以我猜,我可以说本教程介绍了最全的代码重用。
在我们绘制任何事情前,我们需要设置OpenGL绘制纹理映射的状态。下面的代码,你应该非常的熟悉了。
glBindTexture(GL_TEXTURE_2D, textures[0]);
glVertexPointer(3, GL_FLOAT, 0, elementVerticies);
glEnableClientState(GL_VERTEX_ARRAY);
glEnable(GL_TEXTURE_2D);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
我们要告诉OpenGL我们的顶点数组,告诉它去使用它。改变为纹理映射并且告诉它去使用它。我们不能告诉OpenGL关于纹理坐标数组,因为我们将取决于构成的矩形,我们正在绘制的。
首先,我们绘制地板。这个地板由两块我们之前定义的矩形组成。移动到屏幕的下半部分,旋转90度,让它看起来像个地板。然后并排的绘制。这个代码如下:
// Draw the Floor
// First, point the texture co-ordinate engine at the right offset
glTexCoordPointer(2, GL_FLOAT, 0, &combinedTextureCoordinate[FLOOR_TC_OFFSET]);
for (int i = 0; i < 5; i++) {
glPushMatrix();
{
glTranslatef(-1.0, -1.0, -2.0+(i*-2.0));
glRotatef(-90.0, 1.0, 0.0, 0.0);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}
glPopMatrix();
glPushMatrix();
{
glTranslatef(1.0, -1.0, -2.0+(i*-2.0));
glRotatef(-90.0, 1.0, 0.0, 0.0);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}
glPopMatrix();
}
我们用一个for循环来循环5次。每次循环中,都有两个地板的矩形被绘制出来。这里需要重视的改变是调用了 glTexCoordPointer():
glTexCoordPointer(2, GL_FLOAT, 0,
&combinedTextureCoordinate[FLOOR_TC_OFFSET]);
数组的参数指针需要通过开始坐标的正确地址。看下开始定义的宏。
点击 “Build & Go”:
现在,轮到墙面了。过程是相同的,但是这一次,我们把它们在x轴上移动并且在y轴上旋转。
// Draw the walls
// This time we'll change the texture coordinate array during the drawing
for (int i = 0; i < 5; i++) {
glPushMatrix();
{
glTexCoordPointer(2, GL_FLOAT, 0, &combinedTextureCoordinate[BRICK_TC_OFFSET]);
glTranslatef(-1.0, 0.0, -2.0+(i*-2.0));
glRotatef(-90.0, 0.0, 1.0, 0.0);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}
glPopMatrix();
glPushMatrix();
{
glTexCoordPointer(2, GL_FLOAT, 0, &combinedTextureCoordinate[WOOD_TC_OFFSET]);
glTranslatef(1.0, 0.0, -2.0+(i*-2.0));
glRotatef(-90.0, 0.0, 1.0, 0.0);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}
glPopMatrix();
}
请注意,我已经修改了纹理坐标的偏移值。因为我们要使用不用的纹理去绘制左边及右边。
{zh1},我们绘制天空。和地板相同,只是增加y轴而不是减少。
// Draw the ceiling
// Start by setting the texture coordinate pointer
glTexCoordPointer(2, GL_FLOAT, 0, &combinedTextureCoordinate[CEILING_TC_OFFSET]);
for (int i = 0; i < 5; i++) {
glPushMatrix();
{
glTranslatef(-1.0, 1.0, -2.0+(i*-2.0));
glRotatef(90.0, 1.0, 0.0, 0.0);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}
glPopMatrix();
glPushMatrix();
{
glTranslatef(1.0, 1.0, -2.0+(i*-2.0));
glRotatef(90.0, 1.0, 0.0, 0.0);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}
glPopMatrix();
}
这就是我们完成的隧道.
当然,这个没有什么特别的。这只是为给我们一个合适的场景来表示各种绘制效果。在这里我们开始处理,比如屏幕管理,从世界底图里渲染地板和墙壁。
纹理渲染:轻松
纹理渲染的过程就是由已经被绘制到屏幕上的来创建一个新的纹理。所以不是从我们的应用程序包里加载纹理,我们让OpenGL渲染屏幕,这时候我们复制全部或部分的场景到我们的一个缓冲区,然后在提交它完成屏幕之前重新添加它。
哦!深呼吸。这不是很复杂,并且这很有用。
一个简单的例子,纹理渲染是复制你渲染缓冲中的一部分。你应该这样想,当我们渲染缓冲中的光线效果,透明度和混合都已经完成了。所以你只需要复制它就可以使用这些效果了,而不需要重新计算灯光,阴影等。
I wasn’t going to cover render to texture yet as it’s probably a bit further on than where I’m up to so far, but there’s been a few requests so I will cover the easy way to achieve this effect now. We won’t see the best yet because we have not yet covered details such as lighting but you’ll get the idea.
在我们隧道的世界里,我们要添加一个图片在隧道的尽头,并且使用提供的纹理来支持反射效果。
首先一个事,我们添加一个对象。
这不是光; 在我隧道的尽头是Romo
{dy}件事,我们加载两张新的纹理。一个是背景效果,然后我们绘制第二张纹理(Romo),我的英雄,在前面。这样我就可以展示混合的纹理效果了。
增加两行新的函数在 initWithCoder[]:
[self loadTexture:@"bluetex.png" intoLocation:textures[1]];
[self loadTexture:@"romo.png" intoLocation:textures[2]];
在绘制天花板之后,加载我们的纹理,然后我们将产生隧道尽头。首先我们需要设置标准的纹理坐标数组从我们纹理中,然后我们绘制不透明的纹理矩形,然后是部分透明的矩形。
const GLfloat standardTextureCoordinates[] = {
0.0, 1.0,
0.0, 0.0,
1.0, 0.0,
1.0, 1.0
};
// Draw the Blue texture
glBindTexture(GL_TEXTURE_2D, textures[1]);
glTexCoordPointer(2, GL_FLOAT, 0, standardTextureCoordinates);
glPushMatrix();
{
glTranslatef(0.0, 0.0, -6.0);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}
glPopMatrix();
glBindTexture(GL_TEXTURE_2D, textures[2]);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glPushMatrix();
{
glTranslatef(0.0, 0.0, -5.9);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}
glPopMatrix();
glDisable(GL_BLEND);
有了对混合教程的失败例子来比较,这是一个好的多的例子。记不记得,在混合教程里面,你需要首先绘制不透明的物体,然后在绘制半透明的物体?这就是我们要做的。Romo的图片只是其中一个,还有一个相同尺寸的蓝色纹理。
绘制结束以后就是这样:
通过这个混合的例子。注销掉 glEnable(GL_BLEND), 你只需要注销掉这行。你就可以得到以下的内容:
romo的纹理图片掩盖了后面的蓝色纹理,注意到了没?这是非混合。我这里给你看到的是,仅仅是绘制一个纹理,包罗所有效果。
反注释 glEnable(GL_BLEND). 我们将使用的纹理渲染将不需要这个效果。现在我们设置。我将告诉你纹理渲染有那些过程。
纹理渲染:过程
纹理渲染的过程是很简单。只有四步:
1。创建渲染纹理是从那个从我们渲染图片复制出内容而创建的目标纹理。使用的方法xx同其他的纹理加载,你可以使用loadTexture[].
2。渲染到你的屏幕。我们是这样做的;我们有romo在隧道里面的一部分混合效果以证明我们是从渲染缓冲中复制出来的。
3。从我们{dy}步设置的纹理中复制一部分(或全部)出来。
4。渲染新的纹理。
现在你知道四个步骤是什么了,只有第3步是全新的,其他的将有新的内容,但是你会非常熟悉。
创建纹理渲染
通常情况下,我们想用做纹理的图片,都是加载它,格式化它,然后调用glTexImage2D()发送它到OpenGL。如果一个渲染纹理,我们步需要用了,但是我们依然需要获得OpenGL开启的空间,这是是从渲染屏幕中复制出的当前的存储纹理的空间。这是很简单的。到initWithCoder[],然后在加载纹理之后,为了渲染纹理添加下面的代码
// Render to Texture texture buffer setup
glBindTexture(GL_TEXTURE_2D, textures[3]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 128, 128, 0, GL_RGBA,
GL_UNSIGNED_BYTE, nil);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
{dy}件我们要做的事,告诉OpenGL我们纹理在工作,并且选择下一个可用的纹理id,然后,下面一行,我们创建了一个和我们加载文件一样的内存空间。
这里是两者的不同。首先,我们在纹理尺寸里写死了为128x128,你可以选择你喜欢的尺寸,但是必须是2的幂。
{zh1}的参数我已经设置为nil(或NULL如果您愿意) 。{zh1}的参数,通常包含一个缓冲区包含我们的图象数据。由于我们没有图像数据,我们只是让OpenGL的知道,这将创建纹理的本身,而是没有填写任何数据到分配的内存。
{zh1},我们设置过滤参数。
渲染到屏幕上
这部分是自我解释。我们要做的就是写入数据到我们的纹理缓冲,然后到第3步。
复制部分纹理缓冲区域到我们的渲染纹理
有多种方式可以做到这点。在这个教程种我们只介绍一种最快的,但是支持了所有的功能。
我们要做的就是复制我们渲染缓冲中的一部分,同时包含了所有效果,如混合,或者其他我们做的效果。
在完成屏幕绘制的函数之后,我们增加下面几行。
glBindTexture(GL_TEXTURE_2D, textures[3]);
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 100, 150, 128, 128);
不管你相不相信,你已经做好了纹理渲染。{dy}行我们都知道,我们告诉了OpenGL使用那个纹理。调用 glCopyTexSubImage2D()是一个复杂的工作。这个函数获得了当前渲染缓冲中,复制其中的一部分并且存储到当前xx纹理中。
我们复制了(100,150)这点上,尺寸为128x128的像素到我们的纹理。这些参数如下:
glCopyTexSubImage2D(
target 在 OpenGL ES 一般是GL_TEXTURE_2D
level 详细级别,将包括投影图
xoffset &yoffset 这是生成纹理目标的x,y的偏移值。
x & y 这是源(纹理缓冲)的x,y偏移值。
width & height 将复制的图片尺寸
);
需要注意的是,你是从2d空间里复制出的数据,不包含z坐标,所以这里是不支持3d空间的坐标的。你需要重新设定视窗空间。
好了,纹理已经从渲染空间成功的创建了,现在我们需要使用它。
渲染我们新的纹理
{zh1}部分的代码如下:
glPushMatrix();
{
glTranslatef(0.0, -1.0, -2.0);
glRotatef(-75.0, 1.0, 0.0, 0.0);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}
glPopMatrix();
我们移动了一些,并旋转了下,所以就如下了: