OpenGL ES 多个模型导入呈现的公共部分

OpenGL ES 多个模型导入呈现的公共部分

太阳火神的美丽人生 (http://blog.csdn.net/opengl_es)

本文遵循“署名-非商业用途-保持一致”创作公用协议

转载请保留此句:太阳火神的美丽人生 -  本博客专注于 敏捷开发及移动和物联设备研究:iOS、Android、Html5、Arduino、pcDuino否则,出自本博客的文章拒绝转载或再转载,谢谢合作。


为什么要提这个话题呢,是因为多个模型文件obj导入到OpenGL ES中,会有多个纹理库,多个几何体,灯光暂时还不知道咋弄估计也会有多个吧,再有多个......还没理太清还我有多少个多个!

不过至少,就这些是要根据每个Obj中承载的内容不同来呈现的,那么其它的工作就应该是可以共用,统一进行设置,不需要再牵扯到每一个obj,不用因为obj的个数和各自内部来考虑了。

另外,也可以随时根据obj的动态加载,来进行重绘,反复地重启应用,不光我闲麻烦,估计用户也觉得开发这应用的人真二......一系列。

总之,我得先把这些公共的部分拆出来,针对Obj的部分,也得做成能重复使用的,要不然有几个obj我要做几次,代码多得自已都乱了,那估计遇到问题,就不甭想找出来解决掉了。

提纲挈领估计就是这意思吧,得有纲有目,思路才清楚,清晰的思路,不用多费脑筋,有些问题的解决办法自然就出来了,何况,我这后续一大堆事儿,还没头绪呢。


那么接下来就看看总共有哪几部分:此处声明,以下代码均来自罗朝辉的博文示例,再次感谢,虽然你的博文写的有点粗犷,但至少花些时间,还得获得了大部分框架!


第一部分 公共部分

1、层对象要改成支持 OpenGL ES 的;

+ (Class)layerClass {
    
    // Support for OpenGL ES
    return [CAEAGLLayer class];
}

2、设置 OpenGL ES 层对象实例的属性:默认的属性是透明的,画上去也看不到,不就白辛苦了吗

- (void)setupLayer {
    
    _eaglLayer = (CAEAGLLayer*) self.layer;
    
    // 使 CALayer 可见
    _eaglLayer.opaque = YES;
    
    // 设置绘制属性:
    _eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
                                     [NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking,
                                            kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
}

层对象的属性  drawableProperties 是个NSDictionary 类型,以上代码中给出了两个在绘制时要使用的属性值,估计象 OpenGL ES 这类状态机的玩意,肯定会有默认值,你不提供,也照样工作,只不过不是按你预期工作而已。这里的两个属性声明与描述附后,简单解释如下:

kEAGLDrawablePropertyRetainedBacking:


kEAGLDrawablePropertyColorFormat:


3、创建并设置当前图形上下文为支持 OpenGL ES 的版本

- (void)setupContext {
    
    // 指定 OpenGL 的上下文的版本为 OpenGL ES 2.0
    EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2;
    // 创建上下文对象
    _context = [[EAGLContext alloc] initWithAPI:api];
    if (!_context) {
        
        NSLog(@" >> Error: Failed to initialize OpenGLES 2.0 context");
        exit(1);
    }
    
    // 设置当前上下文为刚创建的上下文对象,并判断是否设计成功
    if (![EAGLContext setCurrentContext:_context]) {
        
        _context = nil;
        NSLog(@" >> Error: Failed to set current OpenGL context");
        exit(1);
    }
}
层对象由UIView父类调用自动创建,并且层对象的生命周期随UIView不求同生,但求同死,真乃好兄弟啊!

除以上层对象,接着往下的过程都需要销毁,或者说有些基本量,可以省去销毁或重置的步骤而已。

并且基本都要有一个记录的宿主语言变量来暂存初始值,在相应事件影响导致其更新时,也能暂存相应值,仅在马上要去渲染时才将这些值更新给着色器程序变量,这样可以避勉中间思维和操作抖动产生的影响,借句术语,可以叫做滤波作用。因为从CPU到GPU的值传递是很耗费资源的,对性能有很大影响,所以每一小步都得去考虑,才不致于在模型和贴图、灯光等数量大的时侯,把一个很小的变化,放大得很大。最后不用时,还得记得销毁这些对象,不过状态机机制,始终要有一个状态,所以始终有一个对象是在用的,这样每一次建立新的对象时,记得先销毁前一版本就可以了。

这样由此可总结出如下简单规则:两量,三步

两量是指宿主语言的暂存量和着色器语言在GPU中开辟的槽位量,其句柄需要在宿主语言中建立,并从着色器程序中获得槽位,以便将宿主语言中的暂存量更新到GPU的着色器语言程序中去。

三步即上面分析的:

a、宿主语言暂存量的初始化,这样即使没有更新,也有有个初始的状态值,这可能也是OpenGL ES状态概念的一部分或影响而生;

b、宿主语言暂存量的更新,有些可能由事件触发只更新一次,包括:

        I、投影更新:触摸更改拉伸、旋转、缩放参数的事件;  

        II、顶点更新:增加或删除几何体而导致的模型内部清理,或移动、缩放、旋转几何体于坐标空间,需要更改几何顶点,这个确实有点难度,有知道的兄弟,不妨回复指点,谢了先;

        III、纹理更新:纹理贴图的更改,需要重建纹理缓冲区;

        IV、灯光更新:灯光的位置或环境光、反射光、高光、亮度等发生变化的事件触发更新;

        IIV、好像没有别的更新了吧,暂时没想到!


上下文对象作为一个类对象,在不用时需要销毁,上面创建过程对应的销毁方法如下:

- (void)destoryContext {
    
    if (_context && [EAGLContext currentContext] == _context) {
        
        [EAGLContext setCurrentContext:nil];
        _context = nil;
    }
}
4、着色器及程序构建
初始化部分:

- (void)setupProgram {
    
    // 加载顶点着色器和片元着色器源码
    NSString * vertexShaderPath = [[NSBundle mainBundle] pathForResource:@"VertexShader" ofType:@"glsl"];
    NSString * fragmentShaderPath = [[NSBundle mainBundle] pathForResource:@"FragmentShader" ofType:@"glsl"];
    
    // 创建着色器程序,并附着顶点着色器对象和片元着色器对象
    _programHandle = [GLESUtils loadProgram:vertexShaderPath withFragmentShaderFilepath:fragmentShaderPath];
    
    // 检查着色器程序是否创建成功
    if (_programHandle == 0) {
        
        NSLog(@" >> Error: Failed to setup program.");
        return;
    }
    
    // 使用刚创建的着色器程序
    glUseProgram(_programHandle);
    
    // 从着色器程序获取着色器变量槽位
    [self getSlotsFromProgram];
}

- (void)getSlotsFromProgram {
    
    // 从着色器程序获取局部变量 attribute 和全局变量 uniform 的槽位
    
    // 投影变换矩阵
    _projectionSlot = glGetUniformLocation(_programHandle, "projection");
    // 视图模型变换矩阵
    _modelViewSlot = glGetUniformLocation(_programHandle, "modelView");
    // 法线矩阵
    _normalMatrixSlot = glGetUniformLocation(_programHandle, "normalMatrix");
    
    // 灯光位置
    _lightPositionSlot = glGetUniformLocation(_programHandle, "vLightPosition");
    // 环境光
    _ambientSlot = glGetUniformLocation(_programHandle, "vAmbientMaterial");
    // 反射光
    _diffuseSlot = glGetAttribLocation(_programHandle, "vDiffuseMaterial");
    // 镜面高光
    _specularSlot = glGetUniformLocation(_programHandle, "vSpecularMaterial");
    // 亮度
    _shininessSlot = glGetUniformLocation(_programHandle, "shininess");
    
    // 顶点属性:几何顶点
    _positionSlot = glGetAttribLocation(_programHandle, "vPosition");
    // 顶点属性:纹理坐标
    _textureCoordSlot = glGetAttribLocation(_programHandle, "vTextureCoord");
    // 顶点属性:法线坐标
    _normalSlot = glGetAttribLocation(_programHandle, "vNormal");
    // 是否法线翻转标识
    _revertNormalSlot = glGetUniformLocation(_programHandle, "vRevertNormalSlot");
    
    // 纹理单元0
    _sampler0Slot = glGetUniformLocation(_programHandle, "Sampler0");
    // 纹理单元1
    _sampler1Slot = glGetUniformLocation(_programHandle, "Sampler1");
    // 多纹理混合模式
    _blendModeSlot = glGetUniformLocation(_programHandle, "BlendMode");
    // 透明度,还没研究,希望永远也用不到
    _alphaSlot = glGetUniformLocation(_programHandle, "Alpha");
    
}

更新部分发生在各部分对槽位变量的调用处。

销毁部分:

- (void)destoryProgram {
    
    if (_programHandle != 0) {
        
        glDeleteProgram(_programHandle);
        _programHandle = 0;
    }
}

5、创建渲染缓冲区和桢缓冲区

- (void)setupBuffers {
    
    // 生成渲染缓冲区:用作颜色缓冲区
    glGenRenderbuffers(1, &_colorRenderBuffer);
    // 绑定到当前渲染缓冲区附着点
    glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderBuffer);
    // 当前渲染缓冲区附着点上绑定的缓冲区分布存储空间,此处使用上下文方法替换glRenderbufferStorage为颜色缓冲区分别层为存储空间
    [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_eaglLayer];
    
    // 获取当前渲染缓冲区附着点上绑定的缓冲区的宽和高
    int width, height;
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &width);
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &height);
    
    // 生成渲染缓冲区:用作深度缓冲区
    glGenRenderbuffers(1, &_depthRenderBuffer);
    // 绑定到当前渲染缓冲区附着点
    glBindRenderbuffer(GL_RENDERBUFFER, _depthRenderBuffer);
    // 当前渲染缓冲区附着点上绑定的缓冲区分布存储空间,
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height);
    
    
    // 生成桢缓冲区
    glGenFramebuffers(1, &_frameBuffer);
    // 绑定到当前桢缓冲区附着点
    glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
    
    // 将颜色渲染缓冲区附着到桢缓冲区的GL_COLOR_ATTACHMENT0附着点上
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                              GL_RENDERBUFFER, _colorRenderBuffer);
    // 将深度渲染缓冲区附着到桢缓冲区的GL_DEPTH_ATTACHMENT附着点上
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
                              GL_RENDERBUFFER, _depthRenderBuffer);
    
    
    // 重新将颜色渲染缓冲区对象绑定到当前渲染缓冲区附着点上
    glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderBuffer);
}
渲染缓冲区和桢缓冲区的销毁如下,渲染缓冲区包括颜色缓冲区和深度缓冲区,模板缓冲区也属于渲染缓冲区,但它不需要建立,在建立桢缓冲区时,其内部自动分配创建了:

- (void)destoryBuffer:(GLuint *)buffer {
    
    // 缓冲区对象有引用,且引用的存储区有内容
    if (buffer && *buffer != 0) {
        
        // 删除渲染缓冲区
        glDeleteRenderbuffers(1, buffer);
        // 置空缓冲区引用
        *buffer = 0;
    }
}

- (void)destoryBuffers {
    
    [self destoryBuffer: &_depthRenderBuffer];
    [self destoryBuffer: &_colorRenderBuffer];
    [self destoryBuffer: &_frameBuffer];
}
这里,老罗同志直接用删除渲染缓冲区的API来删除桢缓冲区,一直想找到解释、证实可行的证据,无耐没找到,后续还是改成用桢缓冲区删除方法为好,确保没有内存泄潜漏发生,虽然我也不知道能不能发生。

5、视角变换,包括投影变换和视图模型变换

初始化部分:

- (void)setupProjection {
    
    // 初始化坐标转换值:旋转、平移、拉伸
    _scale = 0.3f;
    _scalOffset = 0.01f;
    _translateX = 3.0f;
    _translateY = 3.0f;
    _translateZ = -8.0f;
    [self resetRotationMatrix];
    
    // 计算宽、高比
    float width = self.frame.size.width;
    float height = self.frame.size.height;
    float aspect = width / height;
    // 生成透视矩阵 Generate a perspective matrix with a 60 degree FOV
    ksMatrixLoadIdentity(&_projectionMatrix);
    ksPerspective(&_projectionMatrix, 90.0, aspect, 4.0f, 12.0f);
    //ksOrtho(&_projectionMatrix, -20, 20, -20, -20, 4.0f, 12.0f);
    
    // 加载投影矩阵 Load projection matrix
    glUniformMatrix4fv(_projectionSlot, 1, GL_FALSE, (GLfloat*)&_projectionMatrix.m[0][0]);
    
    // 允许深度测试
    glEnable(GL_DEPTH_TEST);
    
}

ksPerspective

透视投影

ksOrtho

正交投影

这两位,前者好用,后者不显示内容,还不知道是不行啊,还是位置太偏了看不到。

总之,理论上,正交投影是会看上去有变形,而透视投影更符合人眼观看的结果,但实际呈现时,球面旋转,发现左边比右边高出好多,这个效果真是让人无法忍受,但还不知道如何解决,简单讲,就是不知道是啥问题,提不出问题来,想解决太难了。

这里先挖个坑,后续解决了,再来填坑。哈哈,估计好多坑都不会填的了,为啥?忘了呗,也许在新的贴子中可以找得到。


更新部分:

- (void)updateProjection {
    
    // 初始化视图模型矩阵结构
    ksMatrixLoadIdentity(&_modelViewMatrix);
    // 用平移变换暂存量来更新视图模型矩阵
    ksTranslate(&_modelViewMatrix, _translateX-8, _translateY, _translateZ);
    // 用拉伸变换暂存量来更新视图模型矩阵
    ksScale(&_modelViewMatrix, _scale, _scale, _scale);
    // 用旋转变换暂存量来更新视图模型矩阵
    ksMatrixMultiply(&_modelViewMatrix, &_rotationMatrix, &_modelViewMatrix);
    
    // 以上部分可以实时计算更新,不过还是在这里计算一次的好
    
    // 将视图模型矩阵更新到着色器程序中 _modelViewSlot 槽位对应的着色器变量
    glUniformMatrix4fv(_modelViewSlot, 1, GL_FALSE, (GLfloat*)&_modelViewMatrix.m[0][0]);
    
    // Load the normal matrix.
    // It's orthogonal, so its Inverse-Transpose is itself!
    
    // 创建法线矩阵变量:这个矩阵具体干啥的,真的没搞明白,实际和 _modelViewSlot 就是两种矩阵,少一维而已
    KSMatrix3 normalMatrix3;
    // 矩阵转换,这里好像函数名命反了吧,应该 ksMatrix3ToMatrix4 或 ksMatrix4FromMatrix3 才对
    ksMatrix4ToMatrix3(&normalMatrix3, &_modelViewMatrix);
    
    // 将法线矩阵更新到着色器程序中 _normalMatrixSlot 槽位对应的着色器变量
    glUniformMatrix3fv(_normalMatrixSlot, 1, GL_FALSE, (GLfloat*)&normalMatrix3.m[0][0]);
}
这些矩阵或参数均是基本量或结构,相应的 API 大都在OpenGL ES 2.0版本废除了,而转由着色器中计算,增加了麻烦的同时,也增加了灵活性,对于我等小白只能是前者。


第二部分 

6、灯光

    挖个坑,不埋土,后面有数要数一数;

7、纹理

    挖个坑,不埋土,后面有数要数一数;

8、顶点属性

    挖个坑,不埋土,后面有数要数一数;


能查清吗,挖了几个坑?哈哈,这些就是这两天的工作成果,知道哪些是变的了,其实解决问题很重要,更重要的是提问和发现问题。不知道问题是啥,那就是最惨的事情,永远出路!


接下来,就针对导入的模型文件obj进行解析、整理,争取构建出不用这一层关心的架构模型,每一个场景自绘完成预期任务,说白了,就是把 OpenGL ES 的连续功能肢解了,之前偿试过复加是可以的,那么肢解也应该问题不大,这就是我提问的问题,问题往往就是下一步的指路标,没问题,就永远不知往哪走,连贫路口都看不清。

完成后,再补充,期待吧。



附录A、OpenGL ES 层对象属性

Drawable Property Keys

Keys to specify in the drawableProperties dictionary.

EAGL_EXTERN NSString * const kEAGLDrawablePropertyColorFormat;

EAGL_EXTERN NSString * const kEAGLDrawablePropertyRetainedBacking;

Constants

kEAGLDrawablePropertyColorFormat

The key specifying the internal color buffer format for the drawable surface. The value for this key is anNSString object that specifies a specific color buffer format. This color buffer format is used by theEAGLContext object to create the storage for a renderbuffer. The default value iskEAGLColorFormatRGBA8.

Available in iOS 2.0 and later.

Declared in EAGLDrawable.h.

kEAGLDrawablePropertyRetainedBacking

The key specifying whether the drawable surface retains its contents after displaying them. The value for this key is anNSNumber object containing aBOOL data type. IfNO, you may not rely on the contents being the same after the contents are displayed. IfYES, then the contents will not change after being displayed. Setting the value toYES is recommended only when you need the content to remain unchanged, as using it can result in both reduced performance and additional memory usage. The default value isNO.

Available in iOS 2.0 and later.

Declared in EAGLDrawable.h.

Color Formats

Color formats that can be specified under the kEAGLDrawablePropertyColorFormat key.

EAGL_EXTERN NSString * const kEAGLColorFormatRGB565;

EAGL_EXTERN NSString * const kEAGLColorFormatRGBA8;

Constants

kEAGLColorFormatRGB565

Specifies a 16-bit RGB format that corresponds to the OpenGL ES GL_RGB565 format.

Available in iOS 2.0 and later.

Declared in EAGLDrawable.h.

kEAGLColorFormatRGBA8

Specifies a 32-bit RGBA format that corresponds to the OpenGL ES GL_RGBA8888 format.

Available in iOS 2.0 and later.

Declared in EAGLDrawable.h.


你可能感兴趣的:(OpenGL ES 多个模型导入呈现的公共部分)