实现效果
思路
实现天空盒子的思路,其实就是将四张图片拼接成一个盒子如下图,这种形式在游戏当中应用最为广泛
案例源码分析
CCViewController.m
在使用OpenGL ES 实现代码时,必须设置实现上下文,并获取当前GLKView设置当前的格式例如:GLKViewDrawableColorFormatRGBA8888和GLKViewDrawableDepthFormat24,然后将设置的上下文为当前上下文
-(void)setUpRC
{
//1.新建OpenGL ES 上下文
self.cContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
//获取GLKView
GLKView *view = (GLKView *)self.view;
view.context = self.cContext;
view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
//设置self.cContext作为当前上下文
[EAGLContext setCurrentContext:self.cContext];
在天空盒子这个案例中,我们使用到了一个比较特殊的函数GLKMatrix4MakeLookAt(float eyeX, float eyeY, float eyeZ, float centerX, float centerY, float centerZ,float upX, float upY, float upZ)
这个函数在案例中的注释做了详细说明,所以这里就详细解释了,它的作用其实就是获取一个世界坐标系坐标,那么我们需要设置几个属性,首先是相机(观察者)在世界坐标系的位置
、观察者观察的物体在世界坐标系的位置
、观察者向上的方向在世界坐标系的位置
,三个顶点位置的设置,同时还要设置光照效果
//视图矩阵 定义3个position
//相机(观察者)在世界坐标系的位置 第一组:就是脑袋的位置
self.eyePosition = GLKVector3Make(0.0f, 10.0f, 10.0f);
//观察者观察的物体在世界坐标系的位置 第二组:就是眼睛所看物体的位置
self.lookAtPosition = GLKVector3Make(0.0f, 0.0f, 0.0f);
//观察者向上的方向的世界坐标系的方向。第三组:就是头顶朝向大的方向(因为你可以头歪着的状态)
self.upVector = GLKVector3Make(0.0f, 1.0f, 0.0f);
//灯光
self.baseEffect = [[GLKBaseEffect alloc] init];
//是否使用光照
self.baseEffect.light0.enabled = GL_TRUE;
//光照的位置
self.baseEffect.light0.position = GLKVector4Make(0.0f, 0.0f, 2.0f, 1.0f);
//反射光的颜色
self.baseEffect.light0.specularColor = GLKVector4Make(0.25f, 0.25f, 0.25f, 1.0f);
//漫反射光的颜色
self.baseEffect.light0.diffuseColor = GLKVector4Make(0.75f, 0.75f, 0.75f, 1.0f);
//计算光照的策略
//GLKLightingTypePerVertex:表示在三角形的每个顶点执行照明计算,然后在三角形中插值。
//GLKLightingTypePerPixel指示对照明计算的输入在三角形内进行插值,并在每个片段上执行照明计算。
self.baseEffect.lightingType = GLKLightingTypePerPixel;
在OpenGL ES中我们最常用到的数据就是顶点数据,接下来需要设置顶点缓存,通过OES (顶点缓存区对象 Vertex Array Buffer) 拓展类来实现,同时开始绘制飞机模式的顶点数据
//顶点缓存
GLuint buffer;
//OES 拓展类
//设置顶点属性指针
//为vertexArrayID 申请一个标记
glGenVertexArraysOES(1, &_cPositionBuffer);
//绑定一块区域到vertexArrayID上
glBindVertexArrayOES(_cPositionBuffer);
//创建VBO的3个步骤
//1.生成新缓存对象glGenBuffers
//2.绑定缓存对象glBindBuffer
//3.将顶点数据拷贝到缓存对象中glBufferData
//创建缓存对象并返回缓存对象的标识符
glGenBuffers(1, &buffer);
//创建缓存对象对应到相应的缓存上
/*
glBindBuffer (GLenum target, GLuint buffer);
target:告诉VBO缓存对象时保存顶点数组数据还是索引数组数据 :GL_ARRAY_BUFFER\GL_ELEMENT_ARRAY_BUFFER
任何顶点属性,如顶点坐标、纹理坐标、法线与颜色分量数组都使用GL_ARRAY_BUFFER。用于glDraw[Range]Elements()的索引数据需要使用GL_ELEMENT_ARRAY绑定。注意,target标志帮助VBO确定缓存对象最有效的位置,如有些系统将索引保存AGP或系统内存中,将顶点保存在显卡内存中。
buffer: 缓存区对象
*/
glBindBuffer(GL_ARRAY_BUFFER, buffer);
/*
数据拷贝到缓存对象
void glBufferData(GLenum target,GLsizeiptr size, const GLvoid* data, GLenum usage);
target:可以为GL_ARRAY_BUFFER或GL_ELEMENT_ARRAY
size:待传递数据字节数量
data:源数据数组指针
usage:
GL_STATIC_DRAW
GL_STATIC_READ
GL_STATIC_COPY
GL_DYNAMIC_DRAW
GL_DYNAMIC_READ
GL_DYNAMIC_COPY
GL_STREAM_DRAW
GL_STREAM_READ
GL_STREAM_COPY
”static“表示VBO中的数据将不会被改动(一次指定多次使用),
”dynamic“表示数据将会被频繁改动(反复指定与使用),
”stream“表示每帧数据都要改变(一次指定一次使用)。
”draw“表示数据将被发送到GPU以待绘制(应用程序到GL),
”read“表示数据将被客户端程序读取(GL到应用程序),”
*/
//starshipPositions 飞机模型的顶点数据
glBufferData(GL_ARRAY_BUFFER, sizeof(starshipPositions), starshipPositions, GL_STATIC_DRAW);
//出于性能考虑,所有顶点着色器的属性(Attribute)变量都是关闭的,意味着数据在着色器端是不可见的,哪怕数据已经上传到GPU,由glEnableVertexAttribArray启用指定属性,才可在顶点着色器中访问逐顶点的属性数据.
//VBO只是建立CPU和GPU之间的逻辑连接,从而实现了CPU数据上传至GPU。但是,数据在GPU端是否可见,即,着色器能否读取到数据,由是否启用了对应的属性决定,这就是glEnableVertexAttribArray的功能,允许顶点着色器读取GPU(服务器端)数据。
glEnableVertexAttribArray(GLKVertexAttribPosition);
//顶点数据传入GPU之后,还需要通知OpenGL如何解释这些顶点数据,这个工作由函数glVertexAttribPointer完成
/*
glVertexAttribPointer (GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr)
indx:参数指定顶点属性位置
size:指定顶点属性大小
type:指定数据类型
normalized:数据被标准化
stride:步长
ptr:偏移量
*/
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 0, NULL);
//给buffer重新绑定
//1.创建缓存对象并返回缓存对象的标示符
glGenBuffers(1, &buffer);
//将缓存对象对应到相应的缓存上
glBindBuffer(GL_ARRAY_BUFFER, buffer);
//数据拷贝到缓存对象
//starshipNormals 飞机模型光照法线
glBufferData(GL_ARRAY_BUFFER, sizeof(starshipNormals), starshipNormals, GL_STATIC_DRAW);
//glEnableVertexAttribArray启用指定属性
glEnableVertexAttribArray(GLKVertexAttribNormal);
//顶点数据传入GPU之后,还需要通知OpenGL如何解释这些顶点数据,这个工作由函数glVertexAttribPointer完成
glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_FALSE, 0, NULL);
接下来准备天空盒子的属性,首先开启背面剔除,同时开启深度测试,然后获取天空盒子的纹理,同时设置天空盒子的特效,将天空盒子的长宽高进行初始化,设置默认值。
//开启背面剔除
glEnable(GL_CULL_FACE);
//开启深度测试
glEnable(GL_DEPTH_TEST);
//加载纹理图片
NSString *path = [[NSBundle bundleForClass:[self class]]pathForResource:@"skybox3" ofType:@"png"];
NSError *error = nil;
//获取纹理信息
GLKTextureInfo *textureInfo = [GLKTextureLoader cubeMapWithContentsOfFile:path options:nil error:&error];
if (error) {
NSLog(@"error %@", error);
}
//配置天空盒特效
self.skyboxEffect = [[CCSkyBoxEffect alloc] init];
//纹理贴图的名字
self.skyboxEffect.textureCubeMap.name = textureInfo.name;
//纹理贴图的标记
self.skyboxEffect.textureCubeMap.target = textureInfo.target;
//天空盒子的长宽高
self.skyboxEffect.xSize = 6.0f;
self.skyboxEffect.ySize = 6.0f;
self.skyboxEffect.zSize = 6.0f;
渲染场景代码
/**
* 渲染场景代码
*/
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
//设置清屏颜色
glClearColor(0.5f, 0.1f, 0.1f, 1.0f);
//清理颜色/深度缓冲区
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//非暂停状态
if (!self.cPauseSwitch.on) {
//更新变换矩阵
[self setMatrices];
}
//更新天空盒的眼睛/投影矩阵/模型视图矩阵
self.skyboxEffect.center = self.eyePosition;
self.skyboxEffect.transform.projectionMatrix = self.baseEffect.transform.projectionMatrix;
self.skyboxEffect.transform.modelviewMatrix = self.baseEffect.transform.modelviewMatrix;
//准备绘制天空盒子
[self.skyboxEffect prepareToDraw];
/*
1. 深度缓冲区
深度缓冲区原理就是把一个距离观察平面(近裁剪面)的深度值(或距离)与窗口中的每个像素相关联。
1> 首先使用glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH)来打开DEPTH_BUFFER
void glutInitDisplayMode(unsigned int mode);
2> 每帧开始绘制时,须清空深度缓存 glClear(GL_DEPTH_BUFFER_BIT); 该函数把所有像素的深度值设置为最大值(一般是远裁剪面)
3> 必要时调用glDepthMask(GL_FALSE)来禁止对深度缓冲区的写入。绘制完后在调用glDepthMask(GL_TRUE)打开DEPTH_BUFFER的读写(否则物体有可能显示不出来)
注意:只要存在深度缓冲区,无论是否启用深度测试(GL_DEPTH_TEST),OpenGL在像素被绘制时都会尝试将深度数据写入到缓冲区内,除非调用了glDepthMask(GL_FALSE)来禁止写入。
在绘制天空盒子的时候,禁止深度缓冲区
*/
glDepthMask(false);
//绘制天空盒子
[self.skyboxEffect draw];
//开启深度缓冲区
glDepthMask(true);
//将缓存区/纹理都清空
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
// 需要重新设置顶点数据,不需要缓存
/*
很多应用会在同一个渲染帧调用多次glBindBuffer()、glEnableVertexAttribArray()和glVertexAttribPointer()函数(用不同的顶点属性来渲染多个对象)
新的顶点数据对象(VAO) 扩展会几率当前上下文中的与顶点属性相关的状态,并存储这些信息到一个小的缓存中。之后可以通过单次调用glBindVertexArrayOES() 函数来恢复,不需要在调用glBindBuffer()、glEnableVertexAttribArray()和glVertexAttribPointer()。
*/
glBindVertexArrayOES(self.cPositionBuffer);
//绘制飞船
//starshipMaterials 飞船材料
for (int i = 0; i < starshipMaterials; i++) {
//设置材质的漫反射颜色
self.baseEffect.material.diffuseColor = GLKVector4Make(starshipDiffuses[i][0], starshipDiffuses[i][1], starshipDiffuses[i][2], 1.0f);
self.baseEffect.material.specularColor = GLKVector4Make(starshipSpeculars[i][0], starshipSpeculars[i][1], starshipSpeculars[i][2], 1.0f);
//飞船准备绘制
[self.baseEffect prepareToDraw];
//绘制
/*
glDrawArrays (GLenum mode, GLint first, GLsizei count);提供绘制功能。当采用顶点数组方式绘制图形时,使用该函数。该函数根据顶点数组中的坐标数据和指定的模式,进行绘制。
参数列表:
mode,绘制方式,OpenGL2.0以后提供以下参数:GL_POINTS、GL_LINES、GL_LINE_LOOP、GL_LINE_STRIP、GL_TRIANGLES、GL_TRIANGLE_STRIP、GL_TRIANGLE_FAN。
first,从数组缓存中的哪一位开始绘制,一般为0。
count,数组中顶点的数量。
*/
glDrawArrays(GL_TRIANGLES, starshipFirsts[i], starshipCounts[i]);
}
}
在天空盒子当中,视角是会自动移动的,那么就需要更改矩阵视图的位置,从而达到观察者眼睛移动的效果,而当我们修改视图变换矩阵之后,需要重新设置一个4x4矩阵变换的世界坐标系坐标
//更新变换矩阵
- (void)setMatrices
{
//获取纵横比
const GLfloat aspectRatio = (GLfloat)(self.view.bounds.size.width)/(GLfloat)(self.view.bounds.size.height);
//修改视图变换矩阵
self.baseEffect.transform.projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(85.0f), aspectRatio, 0.1f, 20.0f);
//获取世界坐标系去模型矩阵中.
/*
LKMatrix4 GLKMatrix4MakeLookAt(float eyeX, float eyeY, float eyeZ,
float centerX, float centerY, float centerZ,
float upX, float upY, float upZ)
等价于 OpenGL 中
void gluLookAt(GLdouble eyex,GLdouble eyey,GLdouble eyez,GLdouble centerx,GLdouble centery,GLdouble centerz,GLdouble upx,GLdouble upy,GLdouble upz);
目的:根据你的设置返回一个4x4矩阵变换的世界坐标系坐标。
参数1:眼睛位置的x坐标
参数2:眼睛位置的y坐标
参数3:眼睛位置的z坐标
第一组:就是脑袋的位置
参数4:正在观察的点的X坐标
参数5:正在观察的点的Y坐标
参数6:正在观察的点的Z坐标
第二组:就是眼睛所看物体的位置
参数7:摄像机上向量的x坐标
参数8:摄像机上向量的y坐标
参数9:摄像机上向量的z坐标
第三组:就是头顶朝向的方向(因为你可以头歪着的状态看物体)
*/
self.baseEffect.transform.modelviewMatrix =
GLKMatrix4MakeLookAt(self.eyePosition.x,
self.eyePosition.y,
self.eyePosition.z,
self.lookAtPosition.x,
self.lookAtPosition.y,
self.lookAtPosition.z,
self.upVector.x,
self.upVector.y,
self.upVector.z
);
//增加角度
self.angle += 0.01;
//调整眼睛的位置 sinf:求正弦值
self.eyePosition = GLKVector3Make(-5.0f * sinf(self.angle),
-5.0f,
-5.0f * cosf(self.angle));
//调整观察的位置
self.lookAtPosition = GLKVector3Make(0.0,
1.5 + -5.0f * sinf(0.3 * self.angle),
0.0);
}
从上面的代码到现在,我们一直都在准备绘制当中,并且一直没有设置天空盒子的属性和数据。为了方便管理,我们将天空盒子的内容已经抽取出一个类(CCSkyBoxEffect),作用是为了更方便管理盒子的属性和数据。
在CCSkyBoxEffect 大致分为初始化、准备绘制、绘制三种步骤,加载着色器和验证等等也在该类当中,此处就不一一细说,在代码当中已经详细注释了。
CCSkyBoxEffect.m
步骤一:
初始化纹理并且设置使用的纹理类型,并且设置盒子的立方体的8个位置数据,然后将数据放入到缓存区等待使用,为立方体设置索引数据,通过索引数据来设置三角形的位置也塞入缓存区。
-(id)init
{
self = [super init];
if (self != nil) {
//初始化纹理
_textureCubeMap = [[GLKEffectPropertyTexture alloc]init];
//是否使用原始纹理
_textureCubeMap.enabled = YES;
//该纹理阶段采样的纹理的OpenGL 名称
_textureCubeMap.name = 0;
//设置使用的纹理类型
/*
GLKTextureTarget2D --2D纹理 等价于OpenGL 中的GL_TEXTURE_2D
GLKTextureTargetCubeMa --立方体贴图 等价于OpenGL 中的GL_TEXTURE_CUBE_MAP
*/
_textureCubeMap.target = GLKTextureTargetCubeMap;
//纹理用于计算其输出片段颜色的模式,看看GLKTextureEnvMode
/*
GLKTextureEnvModeReplace, 输出颜色由从纹理获取的颜色.忽略输入的颜色
GLKTextureEnvModeModulate, 输出颜色是通过将纹理颜色与输入颜色来计算所得
GLKTextureEnvModeDecal,输出颜色是通过使用纹理的alpha组件来混合纹理颜色和输入颜色来计算的。
*/
_textureCubeMap.envMode = GLKTextureEnvModeReplace;
//变换
_transform = [[GLKEffectPropertyTransform alloc] init];
self.center = GLKVector3Make(0, 0, 0);
self.xSize = 1.0f;
self.ySize = 1.0f;
self.zSize = 1.0f;
//立方体的8个角
const float vertices[CCSkyboxNumCoords] = {
-0.5, -0.5, 0.5,
0.5, -0.5, 0.5,
-0.5, 0.5, 0.5,
0.5, 0.5, 0.5,
-0.5, -0.5, -0.5,
0.5, -0.5, -0.5,
-0.5, 0.5, -0.5,
0.5, 0.5, -0.5,
};
//创建缓存对象,并返回缓存标示-顶点
glGenBuffers(1, &vertexBufferID);
//将缓存区绑定到相应的缓存区上-数据缓存区
glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID);
//将数据拷贝到缓冲区上
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//绘制立方体的三角形带索引
const GLubyte indices[CCSkyboxNumVertexIndices] = {
1, 2, 3, 7, 1, 5, 4, 7, 6, 2, 4, 0, 1, 2
};
//创建缓存对象,并返回缓存标示符-索引
glGenBuffers(1, &indexBufferID);
//将缓存区绑定到相应的缓存区上-索引缓存区
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferID);
//将数据拷贝到缓冲区上
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
}
return self;
}
步骤二
准备绘制, 首先判断是否已经加载了顶点/片元着色器程序,然后使用program ,然后将设置的顶点数据和纹理数据绑定到然后使用program当中的指定数据位置
//准备绘制
-(void)prepareToDraw
{
if (program == 0) {
//加载你写的顶点/片元着色器程序
[self loadShaders];
}
if (program != 0) {
//1.使用program
glUseProgram(program);
//移动天空盒子的模型视图矩阵
GLKMatrix4 skyboxModelView = GLKMatrix4Translate(self.transform.modelviewMatrix, self.center.x, self.center.y, self.center.z);
//放大天空盒子模型视图矩阵
skyboxModelView = GLKMatrix4Scale(skyboxModelView, self.xSize, self.ySize, self.zSize);
//将模型视图矩阵与投影矩阵结合-矩阵相乘
GLKMatrix4 modelViewProjectionMatrix = GLKMatrix4Multiply(self.transform.projectionMatrix, skyboxModelView);
//为当前程序对象指定Uniform变量的值
/*
什么叫MVPMatrix?
MVPMatrix,本质上就是一个变换矩阵.用来把一个世界坐标系的点转换成裁剪空间的位置.在前面我
说过,学过OpenGL 的人都知道.3D物体从建模到最终显示到屏幕上需要经历以下几个阶段:
1.对象空间(Object space)
2.世界空间(World Space)
3.照相机空间(Camera Space/Eye Space)
4.裁剪空间(Clipping Space)
5.设备空间(normalized device space)
6.视口空间(Viewport)
从对象空间到世界空间的变换叫做Model-To-World变换,
从世界空间到照相机空的变换叫做Worl-To-View变换,
从照相机空间到裁剪空间变换叫做View-TO-Pojection变换.
合起来,从对象空间-裁剪空间的这个过程就是我们所说的MVP变换.
这里的每一个变换都是乘以一个矩阵,3个矩阵相乘最后还是一个矩阵.
这就传递到顶点着色器中的MVPMatrix矩阵.
gl_Position = u_mvpMatrix * vec4(a_position, 1.0);
glUniformMatrix4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat* value)
参数1:location,要更改的uniforms变量的位置
参数2:cout ,更改矩阵的个数
参数3:transpose,指是否要转置矩阵,并将它作为uniform变量的值,必须为GL_FALSE
参数4:value ,指向count个数的元素指针.用来更新uniform变量的值.
为当前程序对象指定Uniform变量的值
*/
glUniformMatrix4fv(uniforms[CCMVPMatrix], 1, 0, modelViewProjectionMatrix.m);
//一个纹理采样均匀变量
/*
void glUniform1f(GLint location, GLfloat v0);
为当前程序对象指定Uniform变量的值
location:指明要更改的uniform变量的位置
v0:在指定的uniform变量中要使用的新值
*/
glUniform1i(uniforms[CCSamplersCube], 0);
//顶点数组ID 如果等于0
if (vertexArrayID == 0) {
//OES 拓展类
//设置顶点属性指针
//为vertexArrayID 申请一个标记
glGenVertexArraysOES(1, &vertexArrayID);
//绑定一块区域到vertexArrayID上
glBindVertexArrayOES(vertexArrayID);
//glEnableVertexAttribArray启用指定属性,才可在顶点着色器中访问逐顶点的属性数据。
//着色器能否读取到数据,由是否启用了对应的属性决定,这就是glEnableVertexAttribArray的功能,允许顶点着色器读取GPU(服务器端)数据。
glEnableVertexAttribArray(GLKVertexAttribPosition);
//将VertexArrayID 绑定是数组缓存区
glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID);
/*
读取数据到顶点着色器
参数1:读取到顶点中
参数2:读取个数
参数3:类型
参数4:是否归一化
参数5:从哪个位置开始读取
*/
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 0, NULL);
}else {
//调用恢复所有先前编写的顶点属性指针与vertexarrayID
glBindVertexArrayOES(vertexArrayID);
}
//绑定索引id
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferID);
//如果绑定的纹理可用
if (self.textureCubeMap.enabled) {
//绑定纹理
//参数1:纹理类型
//参数2:纹理名称
glBindTexture(GL_TEXTURE_CUBE_MAP, self.textureCubeMap.name);
}else {
//绑定一个空的
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
}
}
}
步骤三
绘制, 在该案例当中,我们用的方法是索引绘制法,通过制定的索引位置,来绘制出盒子的顶点数据。
//绘制
-(void)draw
{
/*
索引绘制方法
glDrawElements (GLenum mode, GLsizei count, GLenum type, const GLvoid* indices);
参数列表:
mode:指定绘制图元的类型,但是如果GL_VERTEX_ARRAY 没有被激活的话,不能生成任何图元。它应该是下列值之一: GL_POINTS, GL_LINE_STRIP,GL_LINE_LOOP,GL_LINES,GL_TRIANGLE_STRIP,GL_TRIANGLE_FAN,GL_TRIANGLES,GL_QUAD_STRIP,GL_QUADS,GL_POLYGON
count:绘制图元的数量
type 为索引数组(indices)中元素的类型,只能是下列值之一:GL_UNSIGNED_BYTE,GL_UNSIGNED_SHORT,GL_UNSIGNED_INT
indices:指向索引数组的指针。
*/
glDrawElements(GL_TRIANGLE_STRIP, CCSkyboxNumVertexIndices, GL_UNSIGNED_BYTE, NULL);
}
在OpenGL ES 可编程管线当中我们是需要自己实现顶点着色器和片元着色器的
顶点着色器, 设置了一个mvp矩阵和一个纹理贴图,同时设置v_texCoord 用于传递数据给片元着色器
//CCSkyboxShader.vsh
//Vertex Shader
//顶点
attribute vec3 a_position;
//mvp矩阵
uniform highp mat4 u_mvpMatrix;
//纹理贴图
uniform samplerCube u_unitCube[1];
//纹理坐标
varying lowp vec3 v_texCoord[1];
void main()
{
//获取纹理的位置
v_texCoord[0] = a_position;
//修改顶点位置 = MVP矩阵 * 顶点
//vec4(a_position, 1.0);表示将3维向量修改为4维向量
gl_Position = u_mvpMatrix * vec4(a_position, 1.0);
}
片元着色,将矩阵的采样纹理以及纹理坐标一起交给gl_FragColor,进行渲染
CCSkyboxShader.fsh
//CCSkyboxShader.fsh
//fragment Shader
//MVP矩阵变化
uniform highp mat4 u_mvpMatrix;
// 立方体贴图纹理采样器
uniform samplerCube u_unitCube[1];
//纹理坐标
varying lowp vec3 v_texCoord[1];
void main()
{
//textureCube(sampler, p)
//sampler:指定采样的纹理 p:指定纹理将被采样的纹理坐标。
gl_FragColor = textureCube(u_unitCube[0], v_texCoord[0]);
}
结语
到这里,天空盒子的实现就基本都实现了,主要是通过将图片裁剪成四个面,并通过索引绘制法将顶点数据组合成盒子立方体,并且设置盒子的光照效果和世界坐标系的位置,同时还实现了飞机模型。在盒子当中会不停地移动,是因为添加变换矩阵,不停地更改角度达到了飞机在移动的效果。
GitHub代码处CCSkyBox.git