2013年4月9日 星期二

OpenGL基本實作(二)

延續前一的專案的部分,加入三角形的繪製。

在此需要GLSL的協助,來制作Vertex及Fragment 的shader,而載入shader則由GLESUtils Class來呼叫。

另外要注意 glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, 0, vertices );
定義頂點的屬性資料(之後便可設定 vertex shader 的 attributes 變數之值:
    //     Position, SourceColor)

    //
    // a. 當在繪製時, OpenGL 會從儲存在緩衝區物件裡的陣列取得頂點資料.
    // b. 我們需要告訴 OpenGL 儲存在緩衝區物件裡的頂點陣列資料是什麼格式.
    // c. 亦即, 我們需要告訴 OpenGL 如何去理解儲存在緩衝區裡的陣列資料.
    // d. 當 glVertexAttribPointer 函式被呼叫時, 它會參照到此時任何綁定到
    //    GL_ARRAY_BUFFER 的緩衝區,
在此處為 setupVBOs 函式中的 vertexBuffer.
    //
    // void glVertexAttribPointer(GLuint  index
    //                            GLint  size
    //                            GLenum  type
    //                            GLboolean  normalized
    //                            GLsizei  stride
    //                            const GLvoid *  pointer);
    //
    // 參數說明
    // index: 即將被設定的 vertex shader 的 attribute 之索引位置,
    //            之前已在 compileShaders 函式中呼叫 glGetAttribLocation 取得.
    //
    // size: 每個頂點屬性的元素數量, 在此為:位置 3 個(x,y,z); 顏色 4 個(r,g,b,a). 
    //
    // type: 每個元素的資料型別, 在此, 位置與顏色皆為 GL_FLOAT.
    //
    // normalized: 是否要對頂點進行正規化處理, 在此為否: GL_FALSE
    //
    // stride(跨度): 二個連續頂點屬性之間的位元偏移量
    //              亦即, 包含個別頂點資料的資料結構之大小, 在此皆為: sizeof(Vertex)  
    //
    // pointer: 在儲存該屬性的資料結構中, 此屬性資料的起始偏移量
    //
    // typedef struct {
    //     float Position[3];
    //     float Color[4];
    // } Vertex;
    //
    // 說明:
    // a. Position 資料是位於 struct 的起始處, 因此偏移量為: 0.
    // b. Color 資料是位於 Position 資料之後, 因此偏移量為: sizeof(float) * 3.

原始參考實例  http://blog.csdn.net/kesalin/article/details/8223649

接續前一個專案。
1. 首先會先加入一個GLESUtils的Class檔,繼承自NSObject,主要目的在載入shader的檔案。
GLESUtils.h檔如下
#import <Foundation/Foundation.h>
#include <OpenGLES/ES2/gl.h>

@interface GLESUtils : NSObject

// Create a shader object, load the shader source string, and compile the shader.
//
+(GLuint)loadShader:(GLenum)type withString:(NSString *)shaderString;

+(GLuint)loadShader:(GLenum)type withFilepath:(NSString *)shaderFilepath;

//
///
/// Load a vertex and fragment shader, create a program object, link program.
/// Errors output to log.
/// vertexShaderFilepath Vertex shader source file path.
/// fragmentShaderFilepath Fragment shader source file path
/// return A new program object linked with the vertex/fragment shader pair, 0 on failure
//
+(GLuint)loadProgram:(NSString *)vertexShaderFilepath withFragmentShaderFilepath:(NSString *)fragmentShaderFilepath;

@end


2.  GLESUtils.m檔如下
#import "GLESUtils.h"

@implementation GLESUtils

+(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;
}

+(GLuint)loadProgram:(NSString *)vertexShaderFilepath withFragmentShaderFilepath:(NSString *)fragmentShaderFilepath
{
    // Load the vertex/fragment shaders
    GLuint vertexShader = [self loadShader:GL_VERTEX_SHADER
                              withFilepath:vertexShaderFilepath];
    if (vertexShader == 0)
        return 0;
   
    // pixel 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;
}


@end

3. 對應的vertex shader檔-->VertexShader.glsl
attribute vec4 vPosition;

void  main(void)
{
    gl_Position = vPosition;
}

4. 對應的Fragment shader檔 --> FragmentShader.glsl
precision mediump float;

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

5. 在OpenGLView.h加入兩個變數
#import <UIKit/UIKit.h>

#import <QuartzCore/QuartzCore.h>
#include <OpenGLES/ES2/gl.h>
#include <OpenGLES/ES2/glext.h>


@interface OpenGLView : UIView {
    CAEAGLLayer* _eaglLayer;
    EAGLContext* _context;
    GLuint _colorRenderBuffer;
    GLuint _frameBuffer;
   
    GLuint _programHandle;
    GLuint _positionSlot;


}

- (void)setupLayer;
- (void)setupContext;
- (void)setupRenderBuffer;
- (void)destoryRenderAndFrameBuffer;
- (void)render;


@end



6. 在OpenGLView.m加入所需程式碼
#import "OpenGLView.h"
#import "GLESUtils.h"

@implementation OpenGLView

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
        [self setupLayer];
        [self setupContext];
        [self setupProgram]; //  load shader and run
    }
    return self;
}

/*
 // Only override drawRect: if you perform custom drawing.
 // An empty implementation adversely affects performance during animation.
 - (void)drawRect:(CGRect)rect
 {
 // Drawing code
 }
 */

+ (Class)layerClass {
    // 只有 [CAEAGLLayer class] 类型的 layer 才支持在其上描绘 OpenGL 内容。
    return [CAEAGLLayer class];
}

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

- (void)setupContext {
    // 指定 OpenGL 渲染 API 的版本,在这里我们使用 OpenGL ES 2.0
    EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2;
    _context = [[EAGLContext alloc] initWithAPI:api];
    if (!_context) {
        NSLog(@"Failed to initialize OpenGLES 2.0 context");
        exit(1);
    }
   
    // 设置为当前上下文
    if (![EAGLContext setCurrentContext:_context]) {
        _context = nil;
       
        NSLog(@"Failed to set current OpenGL context");
        exit(1);
    }
}

- (void)setupRenderBuffer {
    glGenRenderbuffers(1, &_colorRenderBuffer);
    // 设置为当前 renderbuffer
    glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderBuffer);
    // 为 color renderbuffer 分配存储空间
    [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_eaglLayer];
}

- (void)setupFrameBuffer {
    glGenFramebuffers(1, &_frameBuffer);
    // 设置为当前 framebuffer
    glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
    // 将 _colorRenderBuffer 装配到 GL_COLOR_ATTACHMENT0 这个装配点上
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                              GL_RENDERBUFFER, _colorRenderBuffer);
}

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


- (void)render
{
    glClearColor(0, 1.0, 0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
   
    // Setup viewport
    //
    glViewport(0, 0, self.frame.size.width, self.frame.size.height);
   
    GLfloat vertices[] = {   // 三角形的點
        0.0f,  0.5f, 0.0f,  // 三角形起始點
        -0.5f, -0.5f, 0.0f,
        0.5f,  -0.5f, 0.0f  // 三角形的結束點

   };
   
    // Load the vertex data
    //
    glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, 0, vertices );
    

    // Enable use of these arrays (they are disabled by default).   
    glEnableVertexAttribArray(_positionSlot);  
   
 // Draw triangle
    //
    glDrawArrays(GL_TRIANGLES, 0, 3);
   
    [_context presentRenderbuffer:GL_RENDERBUFFER];

}


- (void)layoutSubviews {
    [EAGLContext setCurrentContext:_context];
   
    [self destoryRenderAndFrameBuffer]; 
   
    [self setupRenderBuffer];
    [self setupFrameBuffer];
   
    [self render];
}

//---------------------------------------------------
- (void)setupProgram
{
    // Load shaders
    //
    NSString * vertexShaderPath = [[NSBundle mainBundle] pathForResource:@"VertexShader"
                                                                  ofType:@"glsl"];
    NSString * fragmentShaderPath = [[NSBundle mainBundle] pathForResource:@"FragmentShader"
                                                                    ofType:@"glsl"];
   
    // Create program, attach shaders, compile and link program
    //
    _programHandle = [GLESUtils loadProgram:vertexShaderPath
                 withFragmentShaderFilepath:fragmentShaderPath];
    if (_programHandle == 0) {
        NSLog(@" >> Error: Failed to setup program.");
        return;
    }
   
    glUseProgram(_programHandle);  // 執行 shader   
    // Get attribute slot from program
    //
    _positionSlot = glGetAttribLocation(_programHandle, "vPosition");
}


@end



7. 整個例子的檔案列表


8. 結果

沒有留言: