我们已经使用2D纹理很长时间了,但除此之外仍有更多的纹理类型等着我们探索。在本节中,我们将讨论的是将多个纹理组合起来映射到一张纹理上的一种纹理类型:立方体贴图(Cube Map)。
简单来说,立方体贴图就是一个包含了6个2D纹理的纹理,每个2D纹理都组成了立方体的一个面:一个有纹理的立方体。
你可能会奇怪,这样一个立方体有什么用途呢?为什么要把6张纹理合并到一张纹理中,而不是直接使用6个单独的纹理呢?立方体贴图有一个非常有用的特性
,它可以通过一个方向向量来进行索引/采样
。假设我们有一个1x1x1的单位立方体,方向向量的原点位于它的中心。使用一个橘黄色的方向向量来从立方体贴图上采样一个纹理值会像是这样:
方向向量的大小并不重要,只要提供了方向,OpenGL就会获取方向向量(最终)所击中的纹素,并返回对应的采样纹理值。
如果我们假设将这样的立方体贴图应用到一个立方体上,采样立方体贴图所使用的方向向量将和立方体(插值的)顶点位置非常相像。这样子,只要立方体的中心位于原点,我们就能使用立方体的实际位置向量来对立方体贴图进行采样了。接下来,我们可以将所有顶点的纹理坐标当做是立方体的顶点位置。最终得到的结果就是可以访问立方体贴图上正确面(Face)纹理的一个纹理坐标。
创建立方体贴图
立方体贴图是和其它纹理一样的,所以如果想创建一个立方体贴图的话,我们需要生成一个纹理,并将其绑定到纹理目标上,之后再做其它的纹理操作。这次要绑定到GL_TEXTURE_CUBE_MAP
:
unsigned int textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
因为立方体贴图包含有6个纹理,每个面一个,我们需要调用glTexImage2D
函数6次,参数和之前教程中很类似。但这一次我们将纹理目标(target)参数设置为立方体贴图的一个特定的面,告诉OpenGL我们在对立方体贴图的哪一个面创建纹理。这就意味着我们需要对立方体贴图的每一个面都调用一次glTexImage2D
。
由于我们有6个面,OpenGL给我们提供了6个特殊的纹理目标,专门对应立方体贴图的一个面。
纹理目标 | 方位 |
---|---|
GL_TEXTURE_CUBE_MAP_POSITIVE_X | 右 |
GL_TEXTURE_CUBE_MAP_NEGATIVE_X | 左 |
GL_TEXTURE_CUBE_MAP_POSITIVE_Y | 上 |
GL_TEXTURE_CUBE_MAP_NEGATIVE_Y | 下 |
GL_TEXTURE_CUBE_MAP_POSITIVE_Z | 后 |
GL_TEXTURE_CUBE_MAP_NEGATIVE_Z | 前 |
和OpenGL的很多枚举(Enum)一样,它们背后的int值是线性递增的,所以如果我们有一个纹理位置的数组或者vector,我们就可以从GL_TEXTURE_CUBE_MAP_POSITIVE_X开始遍历它们,在每个迭代中对枚举值加1,遍历了整个纹理目标:
if (imageArr.count!=6) {
NSLog(@"[cubeTexture]: image 数量错误");
return;
}
[self bindCubeTexture];
for (unsigned int i = 0; i < imageArr.count; I++)
{
UIImage * image = imageArr[i];
GLubyte *imageData = [self _getImageData:imageArr[I]];
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, 0, GL_RGBA , image.size.width, image.size.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);
}
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_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);
在片段着色器中,我们使用了一个不同类型的采样器,samplerCube,我们将使用texture函数使用它进行采样,但这次我们将使用一个vec3的方向向量而不是vec2。使用立方体贴图的片段着色器会像是这样的:
precision mediump float;
varying vec3 vTexCoords;
uniform samplerCube cubemap; // 立方体贴图的纹理采样器
void main()
{
// textureCube(envMap, reflectVec)
vec4 textureColor = textureCube(cubemap, vTexCoords);
gl_FragColor = textureColor;
}
天空盒
立方体贴图最主要的应用实例就是天空盒了
天空盒是一个包含了整个场景的(大)立方体,它包含周围环境的6个图像,让玩家以为他处在一个比实际大得多的环境当中。游戏中使用天空盒的例子有群山、白云或星空。下面这张截图中展示的是星空的天空盒,它来自于『上古卷轴3』:
你可能现在已经猜到了,立方体贴图能完美满足天空盒的需求:我们有一个6面的立方体,每个面都需要一个纹理。在上面的图片中,他们使用了夜空的几张图片,让玩家产生其位于广袤宇宙中的错觉,但实际上他只是在一个小小的盒子当中。
你可以在网上找到很多像这样的天空盒资源。比如说这个网站就提供了很多天空盒。天空盒图像通常有以下的形式:
如果你将这六个面折成一个立方体,你就会得到一个完全贴图的立方体,模拟一个巨大的场景。一些资源可能会提供了这样格式的天空盒,你必须手动提取六个面的图像,但在大部分情况下它们都是6张单独的纹理图像。
这里贴上shader顶点编码
precision mediump float;
attribute vec3 aPos;
//attribute vec3 aNormal;
varying vec3 vTexCoords;
uniform mat4 view;
uniform mat4 projection;
void main()
{
vTexCoords = aPos;
mat4 tempview= mat4(mat3(view));
vec4 pos = projection * tempview * vec4(aPos, 1.0);
gl_Position = pos.xyww;//重点,去除位移
}
渲染最终结果如图
主要源码
#import "DefaultRenderViewController.h"
#import "DefaultRenderBindObject.h"
#import "CubeManager.h"
#import "SkyBindObject.h"
float DR_planeVertices[] = {
// positions // texture Coords (note we set these higher than 1 (together with GL_REPEAT as texture wrapping mode). this will cause the floor texture to repeat)
5.0f, -0.5f, 5.0f, 2.0f, 0.0f,
-5.0f, -0.5f, 5.0f, 0.0f, 0.0f,
-5.0f, -0.5f, -5.0f, 0.0f, 2.0f,
5.0f, -0.5f, 5.0f, 2.0f, 0.0f,
-5.0f, -0.5f, -5.0f, 0.0f, 2.0f,
5.0f, -0.5f, -5.0f, 2.0f, 2.0f
};
float skyboxVertices[] = {
//right
1.0f, -1.0f, -1.0f,
1.0f, -1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
1.0f, 1.0f, -1.0f,
1.0f, -1.0f, -1.0f,
// left
-1.0f, -1.0f, 1.0f,
-1.0f, -1.0f, -1.0f,
-1.0f, 1.0f, -1.0f,
-1.0f, 1.0f, -1.0f,
-1.0f, 1.0f, 1.0f,
-1.0f, -1.0f, 1.0f,
//top
-1.0f, 1.0f, -1.0f,
1.0f, 1.0f, -1.0f,
1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
-1.0f, 1.0f, 1.0f,
-1.0f, 1.0f, -1.0f,
//bottom
-1.0f, -1.0f, -1.0f,
-1.0f, -1.0f, 1.0f,
1.0f, -1.0f, -1.0f,
1.0f, -1.0f, -1.0f,
-1.0f, -1.0f, 1.0f,
1.0f, -1.0f, 1.0f,
//front
-1.0f, -1.0f, 1.0f,
-1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
1.0f, -1.0f, 1.0f,
-1.0f, -1.0f, 1.0f,
// positions back
-1.0f, 1.0f, -1.0f,
-1.0f, -1.0f, -1.0f,
1.0f, -1.0f, -1.0f,
1.0f, -1.0f, -1.0f,
1.0f, 1.0f, -1.0f,
-1.0f, 1.0f, -1.0f,
};
@interface DefaultRenderViewController ()
@property (nonatomic ,strong) Vertex * cubeVBO;
@property (nonatomic ,strong) Vertex * skyboxVBO ;
@property (nonatomic ,strong) TextureUnit * cubeUnit ;
@property (nonatomic ,strong) CubeTextureUnit * cubemapTexture ;
@property (nonatomic ,strong) Shader * skyboxShader ;
@property (nonatomic ,strong) GLBaseBindObject * skyBindObject ;
@end
@implementation DefaultRenderViewController
-(void)initSubObject{
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
self.bindObject = [DefaultRenderBindObject new];
self.skyBindObject = [SkyBindObject new];
}
-(void)loadVertex{
self.cubeVBO= [Vertex new];
int vertexNum =[CubeManager getTextureNormalVertexNum];
[self.cubeVBO allocVertexNum:vertexNum andEachVertexNum:5];
for (int i=0; iuniforms[DR_uniform_view], 1, 0,viewMatrix.m);
}
-(void)_bindProjectionMatrix4:(GLBaseBindObject*)bindObject{
GLfloat aspectRatio= CGRectGetWidth([UIScreen mainScreen].bounds) / CGRectGetHeight([UIScreen mainScreen].bounds);
GLKMatrix4 projectionMatrix =
GLKMatrix4MakePerspective(
GLKMathDegreesToRadians(85),
aspectRatio,
0.1f,
100.0f);
glUniformMatrix4fv(bindObject->uniforms[DR_uniform_projection], 1, 0,projectionMatrix.m);
}
-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect{
glClearColor(1,0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
GLKMatrix4 cubeMode1 =[self _getModeMatrix4Location:GLKVector3Make(0.0f, 0.0f, 0.0f)];
static int mm = 30;
// mm++;
cubeMode1 = GLKMatrix4Rotate(cubeMode1, mm*M_PI/180.0, 0, 1, 0);
// static float tran = 0;
// tran +=0.1;
// cubeMode1 = GLKMatrix4Translate(cubeMode1, 0, 0,tran );
//
[self.shader use];
[self _bindViewMatrix4:self.bindObject];
[self _bindProjectionMatrix4:self.bindObject];
[self _drawCubeMode:cubeMode1 bindObject:self.bindObject];
glDepthFunc(GL_LEQUAL); // change depth function so depth test passes when values are equal to depth buffer's content
glDepthMask(GL_FALSE);
[self.skyboxShader use];
GLfloat aspectRatio=1;
GLKMatrix4 projectionMatrix =
GLKMatrix4MakePerspective(
GLKMathDegreesToRadians(85),
aspectRatio,
0.1f,
100.0f);
glUniformMatrix4fv(self.skyBindObject->uniforms[SKB_uniform_projection], 1, 0,projectionMatrix.m);
GLKMatrix4 viewMatrix =
GLKMatrix4MakeLookAt(
0.0, 0.0, 3.0, // Eye position
0.0, 0.0, 0.0, // Look-at position
0.0, 1.0, 0.0); // Up direction
// remove translation from the view matrix
glUniformMatrix4fv(self.skyBindObject->uniforms[SKB_uniform_view], 1, 0,viewMatrix.m);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_CUBE_MAP, self.cubemapTexture.textureID);
glUniform1i(self.skyBindObject->uniforms[SKB_uniform_Texture], 1);
[self.skyboxVBO enableVertexInVertexAttrib:SKB_aPos numberOfCoordinates:3 attribOffset:0];
[self.skyboxVBO drawVertexWithMode:GL_TRIANGLES startVertexIndex:0 numberOfVertices:36];
glDepthFunc(GL_LESS);
glDepthMask(GL_TRUE);
}
-(GLKMatrix4)_getModeMatrix4Location:(GLKVector3)location{
GLKMatrix4 mode = GLKMatrix4Identity;
mode = GLKMatrix4TranslateWithVector3(mode, location);
// mode = GLKMatrix4Rotate(mode, 30*M_PI/180.0, 0, 1, 0);
return mode;
}
-(void)_drawCubeMode:(GLKMatrix4)mode bindObject:(GLBaseBindObject*)bindObject{
glUniformMatrix4fv(bindObject->uniforms[DR_uniform_model], 1, 0,mode.m);
[self.cubeUnit bindtextureUnitLocationAndShaderUniformSamplerLocation:bindObject->uniforms[DR_uniform_Texture]];
[self.cubeVBO enableVertexInVertexAttrib:DR_aPos numberOfCoordinates:3 attribOffset:0];
[self.cubeVBO enableVertexInVertexAttrib:DR_aTexCoords numberOfCoordinates:2 attribOffset:sizeof(float)*3];
[self.cubeVBO drawVertexWithMode:GL_TRIANGLES startVertexIndex:0 numberOfVertices:[CubeManager getTextureNormalVertexNum]];
}
@end
#import "CubeTextureUnit.h"
@interface CubeTextureUnit ()
@end
@implementation CubeTextureUnit
- (instancetype)init
{
self = [super init];
if (self) {
[self _customInit];
}
return self;
}
-(void)_customInit{
[self _createCubeTextureBuffer];
}
-(void)bindCubeTexture{
glBindTexture(GL_TEXTURE_CUBE_MAP, self.textureID);
}
-(void)bindCubeTextureInImageArray:(NSArray *)imageArr{
if (imageArr.count!=6) {
NSLog(@"[cubeTexture]: image 数量错误");
return;
}
[self bindCubeTexture];
for (unsigned int i = 0; i < imageArr.count; i++)
{
UIImage * image = imageArr[i];
GLubyte *imageData = [self _getImageData:imageArr[i]];
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, 0, GL_RGBA , image.size.width, image.size.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);
}
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_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);
}
#pragma mark - private
-(void)_createCubeTextureBuffer{
glGenTextures(1, &_textureID);
}
-(void)_deleteCubeTextureBuffer{
glDeleteTextures(1, &_textureID);
}
- (void*)_getImageData:(UIImage*)image{
CGImageRef imageRef = [image CGImage];
size_t imageWidth = CGImageGetWidth(imageRef);
size_t imageHeight = CGImageGetHeight(imageRef);
GLubyte *imageData = (GLubyte *)malloc(imageWidth*imageHeight*4);
memset(imageData, 0,imageWidth *imageHeight*4);
CGContextRef imageContextRef = CGBitmapContextCreate(imageData, imageWidth, imageHeight, 8, imageWidth*4, CGImageGetColorSpace(imageRef), kCGImageAlphaPremultipliedLast);
// CGContextTranslateCTM(imageContextRef, 0, imageHeight);
// CGContextScaleCTM(imageContextRef, 1.0, -1.0);
CGContextDrawImage(imageContextRef, CGRectMake(0.0, 0.0, (CGFloat)imageWidth, (CGFloat)imageHeight), imageRef);
CGContextRelease(imageContextRef);
return imageData;
}
- (void)dealloc
{
[self _deleteCubeTextureBuffer];
}
@end
其他博客地址
立方体贴图
OpenGLZeroStudyDemo(14)-高级Opengl-立方体贴图