OpenGL ES学习之路(9.0) 天空盒子的实现

实现效果

OpenGL ES学习之路(9.0) 天空盒子的实现_第1张图片
image.png

思路

实现天空盒子的思路,其实就是将四张图片拼接成一个盒子如下图,这种形式在游戏当中应用最为广泛


OpenGL ES学习之路(9.0) 天空盒子的实现_第2张图片
image.png

案例源码分析

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

你可能感兴趣的:(OpenGL ES学习之路(9.0) 天空盒子的实现)