2013年5月20日 星期一

OpenGLES on IOS (二)

整理一下整個OPENGLES在IOS上實做過程的資料

參考網頁
http://blog.csdn.net/kesalin/article/details/8223649
http://blog.csdn.net/kesalin/article/details/7168967


實作紀錄網頁
http://kirenenko-tw.blogspot.tw/2013/04/opengl_11.html


1. 首先使用 initWithCoder 初始化 Storyboard 上的元件(OpenGLView.m)

initWithCoder 是物件既有的一個初始化方法函式,正常來說,你不會在使用程式碼動態產生物件時去呼叫它產生新的物件,而是在使用 Storyboard 設計介面時,直接將 Storyboard 上的元件與類別中的 initWithCoder 方法做連結,使用類似靜態的方式,讓應用程式在一開始執行時就將介面上的元件直接定義成該類別。

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self setupLayer];     // 初始設定 一個 CAEAGLLayer


        [self setupContext];    // 初始設定一個 rendering context

        [self setupProgram];  // 載入shader的執行程式碼 ,最重要的主程式

        [self setupProjection];  //設定投射矩陣
     
        [self resetTransform];  // 回歸初始值
       
    }
   
    return self;
}





OpenGL ES是Core Animation的客户,要使用OpenGL ES需要创建一个UIView,这个UIView由一个特殊的core animation layer支持,这个特殊的layer是一个CAEAGLLayer对象。CAEAGLLayer是OpenGLES和core animation联系的桥梁。当应用程序渲染完一帧后,CAEAGLLayer的内容被呈现并且和其他view的数据组合。


參考網頁 http://hi.baidu.com/wwssttt/item/e161f725e2ba9941469962d3

- (void)setupLayer
{
    _eaglLayer = (CAEAGLLayer*) self.layer;
   
    // CALayer 默认是透明的,必须将它设为不透明才能让其可见
    _eaglLayer.opaque = YES;
   
    // 设置描绘属性,在这里设置不维持渲染内容以及颜色格式为 RGBA8
    _eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
                                     [NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
}




EAGLContext : 定义了rendering context。应用程序创建和初始化一个EAGLContext,设定他为当前的OpenGL命令的目标。OpenGL命令通常存放在一个 context所维护的队列里面,并在之后被执行。EAGLContext也提供了方法将图像呈现给Core Animation。

- (void)setupContext {
    // 指定 OpenGL 渲染 API 的版本,在这里我们使用 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);
    }
}




在前面提到可编程管线通过用 shader 语言编写脚本文件实现的,这些脚本文件相当于 C 源码,有源码就需要编译链接,因此需要对应的编译器与链接器,shader 对象与 program 对象就相当于编译器与链接器。shader 对象载入源码,然后编译成 object 形式(就像C源码编译成 .obj文件)。经过编译的 shader 就可以装配到 program 对象中,每个 program对象必须装配两个 shader 对象:一个顶点 shader,一个片元 shader,然后 program 对象被连接成“可执行文件”,这样就可以在 render 中是由该“可执行文件”了。 

參考網頁 http://content.gpwiki.org/index.php/OpenGL:Tutorials:Loading_and_using_GLSL_shaders#Creating_the_Program



- (void)setupProgram
{
    // Load shaders
    //
   
    NSString * vertexShaderPath;
    NSString * fragmentShaderPath;
   
    vertexShaderPath = [[NSBundle mainBundle] pathForResource:@"VertexShader"
                                                       ofType:@"glsl"];
    fragmentShaderPath = [[NSBundle mainBundle] pathForResource:@"FragmentShader"
                                                         ofType:@"glsl"];
 
   
   
    //從 加入 shader,此處的
loadProgramg需自己處理   
_programHandle = [GLESUtils loadProgram:vertexShaderPath
                 withFragmentShaderFilepath:fragmentShaderPath];
    if (_programHandle == 0) {
        NSLog(@" >> Error: Failed to setup program.");
        return;
    }
   
    glUseProgram(_programHandle);
   
    // Get the attribute position slot from program
    // 通过调用 glGetAttribLocation 我们获取到 shader 中定义的变量 vPosition 在 program 的槽位,通过该槽位我们就可以对 vPosition 进行操作。
    // Returns the location of an attribute variable
    _positionSlot = glGetAttribLocation(_programHandle, "vPosition");
   
    // Get the uniform model-view matrix slot from program
    //  Returns the location of a uniform variable
    _modelViewSlot = glGetUniformLocation(_programHandle, "modelView");

   
    // Get the uniform projection matrix slot from program
    //
    _projectionSlot = glGetUniformLocation(_programHandle, "projection");
   
}




 



其中會用到兩隻檔案
VertexShader.glsl  及  FragmentShader.glsl





VertexShader.glsl
uniform mat4 projection;
uniform mat4 modelView;
attribute vec4 vPosition;


void  main(void)
{
    gl_Position = projection * modelView * vPosition;
}


FragmentShader.glsl

precision mediump float;

void main()
{
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}


此處要說明一下


Uniforms and Attributes

Uniforms 是一个program 中统一分配的,vertext 和fragment中同名的Uniform必须同类型。对应于不经常变化的变量。

Attributes 变化率高的变量。主要用来定义输入的每次点属性。

Uniforms and Attributes 在shader中通过location 和 name 来对应的。

通过GLint glGetUniformLocation(GLuint program,const char* name).根据一个Uniform的名称获取其location. 通过 glUniform***系列函数可以给一个location 设置一个Uniform的值。

參考網頁  http://blog.csdn.net/beelike/article/details/5774288


對於 loadProgram,可將其放在外部檔案,此例放在GLESUtils.m
+(GLuint)loadProgram:(NSString *)vertexShaderFilepath withFragmentShaderFilepath:(NSString *)fragmentShaderFilepath
{
    // Load the vertex/fragment shaders, and compile,此處的
loadShader也需要自己處理  
// shader type => GL_VERTEX_SHADER
    GLuint vertexShader = [self loadShader:GL_VERTEX_SHADER
                              withFilepath:vertexShaderFilepath];
    if (vertexShader == 0)
        return 0;
   
    // shader type => GL_FRAGMENT_SHADER
    GLuint fragmentShader = [self loadShader:GL_FRAGMENT_SHADER
                                withFilepath:fragmentShaderFilepath];
    if (fragmentShader == 0) {
        glDeleteShader(vertexShader);
        return 0;
    }
   
    // Create the program object
    GLuint programHandle = glCreateProgram();
    if (programHandle == 0)
        return 0;
   
    glAttachShader(programHandle, vertexShader);
    glAttachShader(programHandle, fragmentShader);
   
    // Link the program
    glLinkProgram(programHandle);
   
    // Check the link status
    GLint linked;
    glGetProgramiv(programHandle, GL_LINK_STATUS, &linked);
   
    if (!linked) {
        GLint infoLen = 0;
        glGetProgramiv(programHandle, GL_INFO_LOG_LENGTH, &infoLen);
       
        if (infoLen > 1){
            char * infoLog = malloc(sizeof(char) * infoLen);
            glGetProgramInfoLog(programHandle, infoLen, NULL, infoLog);
           
            NSLog(@"Error linking program:\n%s\n", infoLog);
           
            free(infoLog);
        }
       
        glDeleteProgram(programHandle );
        return 0;
    }
   
    // Free up no longer needed shader resources
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
   
    return programHandle;
}
 


+(GLuint)loadShader:(GLenum)type withFilepath:(NSString *)shaderFilepath
{
    NSError* error;
    NSString* shaderString = [NSString stringWithContentsOfFile:shaderFilepath
                                                       encoding:NSUTF8StringEncoding
                                                          error:&error];
    if (!shaderString) {
        NSLog(@"Error: loading shader file: %@ %@", shaderFilepath, error.localizedDescription);
        return 0;
    }
   
    return [self loadShader:type withString:shaderString];
}

+(GLuint)loadShader:(GLenum)type withString:(NSString *)shaderString
{
    // Create the shader object
    GLuint shader = glCreateShader(type);  //
    if (shader == 0) {
        NSLog(@"Error: failed to create shader.");
        return 0;
    }
   
    // Load the shader source
    const char * shaderStringUTF8 = [shaderString UTF8String];
    glShaderSource(shader, 1, &shaderStringUTF8, NULL);
   
    // Compile the shader
    glCompileShader(shader);
   
    // Check the compile status
    GLint compiled = 0;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
   
    if (!compiled) {
        GLint infoLen = 0;
        glGetShaderiv ( shader, GL_INFO_LOG_LENGTH, &infoLen );
       
        if (infoLen > 1) {
            char * infoLog = malloc(sizeof(char) * infoLen);
            glGetShaderInfoLog (shader, infoLen, NULL, infoLog);
            NSLog(@"Error compiling shader:\n%s\n", infoLog );
           
            free(infoLog);
        }
       
        glDeleteShader(shader);
        return 0;
    }
   
    return shader;
}
 


基本shader創建的步驟,但是省略了檢查錯誤的步驟,實作上卻是必要的。
1. glCreateShader创建着色器对象

2. glShaderSource为着色器加载源代码,讀入檔案

3. glCompileShader编译每个着色器

4. glCreateProgram创建程序对象



5. glAttachShader 把着色器对象连接到程序对象

6. glLinkProgram 链接程序对象,生成可执行程序

7. glUseProgram安装可执行程序替换OpengGL固定功能流水线处理模块








設定投影矩陣
此處可參考網頁 http://kirenenko-tw.blogspot.tw/2013/05/opengl_16.html,瞭解整個投影的需求。 
-(void)setupProjection
{
    // Generate a perspective matrix with a 60 degree FOV
    //
    float aspect = self.frame.size.width / self.frame.size.height;  // 目前的畫面是投射近距離的畫面,要觀察遠距離的畫面。
    ksMatrixLoadIdentity(&_projectionMatrix);
   
    // 使用一個單元矩陣來產生一個投射的矩陣
    ksPerspective(&_projectionMatrix, 60.0, aspect, 1.0f, 20.0f);

   
    // Load projection matrix
    glUniformMatrix4fv(_projectionSlot, 1, GL_FALSE, (GLfloat*)&_projectionMatrix.m[0][0]);
    ///glUniformMatrix4fv函数用我们得到的变换矩阵来更新着色器程序里的uniform变量矩阵modelview。第一个参数是要修改的uniform变量的索引位置,第二个参数是要修改的矩阵数据,这里是1个。第三个参数是第四个参数是按行主序还是列主序指定的,这里我们用GL_FALSE(0),表明是列主序指定的,矩陣不需要轉置。最后一个参数是我们前面设定的变换矩阵。  
}



- (void)resetTransform
{
    if (_displayLink != nil) {
        [_displayLink removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        _displayLink = nil;
    }

    self.posX = 0.0;
    self.posY = 0.0;
    self.posZ = -5.5;
   
    self.rotateX = 0.0;
    self.rotateY = 0.0;
    self.scaleZ = 1.0;
   
    [self updateTransform];
}
 


// 重新計算所有modelView的對應矩陣
- (void)updateTransform
{
    // Generate a model view matrix to rotate/translate/scale
    //
    ksMatrixLoadIdentity(&_modelViewMatrix);
   
    // Translate away from the viewer
    //
    ksMatrixTranslate(&_modelViewMatrix, self.posX, self.posY, self.posZ);
   
    // Rotate the triangle
    //
    ksMatrixRotate(&_modelViewMatrix, self.rotateX, 1.0, 0.0, 0.0);  // rotate X
   
    ksMatrixRotate(&_modelViewMatrix, self.rotateY, 0.0, 1.0, 0.0);  // rotate Y
   
   
    // Scale the triangle
    ksMatrixScale(&_modelViewMatrix, 1.0, 1.0, self.scaleZ);
   
    // Load the model-view matrix,將
modelViewMatrix載入到_modelViewSlot中
     glUniformMatrix4fv(_modelViewSlot, 1, GL_FALSE, (GLfloat*)&_modelViewMatrix.m[0][0]);
}
 


2. 一旦有所需要,就開始繪圖,此處包含在render內


- (void)render
{
    if (_context == nil)
        return;
   
    glClearColor(0, 1.0, 0, 1.0);  // specify clear values for the color buffers
    glClear(GL_COLOR_BUFFER_BIT);  //  clear buffers to preset values, The three masks are GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT, and GL_STENCIL_BUFFER_BIT.
   
    // Setup viewport
    //
    glViewport(0, 0, self.frame.size.width, self.frame.size.height); // glViewport 表示渲染 surface 将在屏幕上的哪个区域呈现出来
   
    [self drawCube];  // 劃出方形磚塊
   
    [_context presentRenderbuffer:GL_RENDERBUFFER];  // Displays a renderbuffer’s contents on screen.
}

3. 使用layoutSubviews來繪製畫面,這是一個類似重畫的機制,

可參考 http://kirenenko-tw.blogspot.tw/2013/04/layoutsubviews.html


- (void)layoutSubviews
{
    [EAGLContext setCurrentContext:_context];  // The graphics-context object to set as the current one. This must be an instance of a concrete subclass of NSGraphicsContext.
   
    glUseProgram(_programHandle);  // Installs a program object as part of current rendering state
   
    [self destoryBuffers];
   
    [self setupBuffers];
        


    // 任何目標的轉移與縮放,基本上都只用到下面兩個函數呼叫  
    [self updateTransform];
    [self render];

}



- (void)destoryBuffers
{
    glDeleteRenderbuffers(1, &_colorRenderBuffer);
    _colorRenderBuffer = 0;
   
    glDeleteFramebuffers(1, &_frameBuffer);
    _frameBuffer = 0;
}



- (void)setupBuffers {
    glGenRenderbuffers(1, &_colorRenderBuffer); // Specifies an array in which the generated renderbuffer object names are stored.
   
   
    // 设置为当前 renderbuffer
    glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderBuffer);
    // bind a renderbuffer to a renderbuffer target
   
   
    // 为 color renderbuffer 分配存储空间
    [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_eaglLayer];
    // Binds a drawable object’s storage to an OpenGL ES renderbuffer object.
   
   
    glGenFramebuffers(1, &_frameBuffer); //
    //  Specifies an array in which the generated framebuffer object names are stored.
   
    // 设置为当前 framebuffer
    glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
    //bind a framebuffer to a framebuffer target
   
   
    // 将 _colorRenderBuffer 装配到 GL_COLOR_ATTACHMENT0 这个装配点上
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                              GL_RENDERBUFFER, _colorRenderBuffer);
    //attach a renderbuffer as a logical buffer to the currently bound framebuffer object
   
   
}




整個程序到此,可以繪出一個3D的Model。如果有任何Model上的變化,只需要用到下面兩行就可以了。

// 任何目標的轉移與縮放,基本上都只用到下面兩個函數呼叫  
    [self updateTransform];
    [self render];