2014年8月8日 星期五

[紀錄] GLES Sample 2 - A Simple Tutorial to Set Up OpenGL Shader Programming


OpenGL Shading Language是以C語言為基礎的高階著色語言,讓開發者可以直接對繪圖管線做直接的控制。
目前繪圖管線可編程的部分有處理頂點和處理像素的Vertex Shader和Pixel Shader(又稱Fragment Shader)

Vertex Shader:模型是由點構成,因此改變點即改變模型的外型。
Pixel Shader:像素點可以變化不同的顏色,因此要對模型填充顏色即改變螢幕上的每個像素。

更詳細的介紹可以參考 著色器(Shader)-逍遙文工作室 或者 自行在網上Google。
此次的範例練習是延續前一篇文章:[紀錄] GLES Sample 1 - A Simple Tutorial to Use OpenGL ES 2.0 on iOS7 並參考 基廉列克雜記本的OpenGL基本實作(二)

由於要在OpenGL中使用GLSL,必需單獨Compile Shader,然後將編譯好的Shader Link 成一個 Program。





整個設置過程就如同上圖,Vertex Shader和Fragment Shader都需各自經過Compile的步驟,接著將compile好的shader attach到Program中。一個Program中可以attach多個Vertex Shader和Fragment Shader。一個Vertext Shader也可以attach到多個不同的Program中,Fragment Shader也是一樣。

1. 新增vertext shader
    // New File -> Other -> Empty -> 輸入檔名(在這個範例中的檔名為:Sample.vsh)

Sample.vsh

attribute vec4 position;

void main()
{
    gl_Position = position;
}


2. 新增fragment shader
   // New File -> Other -> Empty -> 輸入檔名(在這個範例中的檔名為:Sample.fsh)

Sample.fsh

precision mediump float;

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


3. 接著新增class為NSObject的GLESUtils.h和GLESUtils.m檔,負責做GLSL的配置。

GLESUtils.h

#import <Foundation/Foundation.h>
#import <OpenGLES/ES2/gl.h>

enum {
    ATTRIB_POSITION
};

@interface GLESUtils : NSObject

+ (BOOL)loadShader:(NSString *)vertexShaderName fragmentShader:(NSString *)fragmentShaderName forProgram:(GLuint *)programHandle;
+ (BOOL)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file;
+ (BOOL)linkProgram:(GLuint)program;

@end

GLESUtils.m

#import "GLESUtils.h"

@implementation GLESUtils

+ (BOOL)loadShader:(NSString *)vertexShaderName fragmentShader:(NSString *)fragmentShaderName forProgram:(GLuint *)programHandle
{
    // create and compile shader
    // need addes all shaders on Build Phases -> Copy Bundle Resources
    GLuint vertextShader, fragShader;
    NSString *vertexShaderPath = [[NSBundle mainBundle] pathForResource:vertexShaderName ofType:@"vsh"];
    if (![self compileShader:&vertextShader type:GL_VERTEX_SHADER file:vertexShaderPath]) {
        NSLog(@"failed to compile vertext shader");
        return FALSE;
    }
    
    NSString *fragmentShaderPath = [[NSBundle mainBundle] pathForResource:fragmentShaderName ofType:@"fsh"];
    if (![self compileShader:&fragShader type:GL_FRAGMENT_SHADER file:fragmentShaderPath]) {
        NSLog(@"failed to compile fragment shader");
        return FALSE;
    }
    
    // create shader program
    *programHandle = glCreateProgram();
    
    // attach vertex and fragment shader to program
    glAttachShader(*programHandle, vertextShader);
    glAttachShader(*programHandle, fragShader);
    
    // bind attribute locations
    // this needs to be done prior to linking program
    glBindAttribLocation(*programHandle, ATTRIB_POSITION, "position");
    
    // link program
    if (![self linkProgram:*programHandle]) {
        NSLog(@"failed to link program: %d", *programHandle);
        
        if (vertextShader) {
            glDeleteShader(vertextShader);
            vertextShader = 0;
        }
        
        if (fragShader) {
            glDeleteShader(fragShader);
            fragShader = 0;
        }
        
        if (*programHandle) {
            glDeleteProgram(*programHandle);
            *programHandle = 0;
        }
        return FALSE;
    }
    return TRUE;
}

+ (BOOL)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file
{
    const GLchar *source;
    source = (GLchar *)[[NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil] UTF8String];
    if (!source) {
        NSLog(@"failed to load shader file:%@",file);
        return FALSE;
    }
    
    // createShader => shaderSource => compileShader
    *shader = glCreateShader(type);
    glShaderSource(*shader, 1, &source, NULL);
    glCompileShader(*shader);
    
    // check the compile status
    GLint compiledStatus = 0;
    glGetShaderiv(*shader, GL_COMPILE_STATUS, &compiledStatus);
    if (!compiledStatus) {
        
#if defined(DEBUG)
        GLint infoLogLength;
        glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &infoLogLength);
        if (infoLogLength > 0) {
            
            GLchar *infoLog = (GLchar *)malloc(infoLogLength);
            glGetShaderInfoLog(*shader, infoLogLength, &infoLogLength, infoLog);
            NSLog(@"failed to compile shader log:\n%s\n",infoLog);
            
            free(infoLog);
            
        }
#endif
        glDeleteShader(*shader);
        return FALSE;
    }
    return TRUE;
}

+ (BOOL)linkProgram:(GLuint)program
{
    glLinkProgram(program);
    
    // check the link status
    GLint linkedStatus = 0;
    glGetProgramiv(program, GL_LINK_STATUS, &linkedStatus);
    if (!linkedStatus) {
        
#if defined(DEBUG)
        GLint infoLogLength;
        glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength);
        
        if (infoLogLength > 0) {
            
            GLchar *infoLog = (GLchar *)malloc(infoLogLength);
            glGetProgramInfoLog(program, infoLogLength, &infoLogLength, infoLog);
            NSLog(@"failed to link program log:\n%s\n", infoLog);
            
            free(infoLog);
        }
#endif
        glDeleteProgram(program);
        return FALSE;
    }
    return TRUE;
}

@end

另外要特別注意的是若在Link Binary With Libraries中看到shader files,請把它移除。若在Copy Bundle Resources中沒有看到shader files請把檔案加入。 最後的呈現應該會如下圖:



4. 修改OpenGLSample.h
    // 主要修改的部分為增加了GLES Shader使用到的變數宣告,還有setupProgram function。

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

@interface OpenGLSample : UIView {
    CAEAGLLayer *eaglLayer;
    EAGLContext *context;
    
    GLuint renderBuffer, frameBuffer;
    
    // GLES Shader
    GLuint sampleProgram;
    GLuint positionSlot, colorSlot;
}

// OpenGL initialize
- (void)setupLayer;
- (void)setupContext;
- (void)setupProgram;

@end

5. 修改OpenGLSample.m
    // 在initWithFrame中加入[self setupProgram];
    // 加上setupProgram function
    // 修改render function並傳入參數給vertex shader

#import "OpenGLSample.h"
#import "GLESUtils.h"

@implementation OpenGLSample

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

// Override the class method to return the OpenGL layer, as opposed to the normal CALayer
+ (Class)layerClass {
    return [CAEAGLLayer class];
}

- (void)setupLayer {
    eaglLayer = (CAEAGLLayer *) self.layer;
    // the default CALayer is transparent, so set to opaque be seen by user.
    eaglLayer.opaque = YES;
    eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:NO],kEAGLDrawablePropertyRetainedBacking,kEAGLColorFormatRGBA8,kEAGLDrawablePropertyColorFormat, nil];
}

- (void)setupContext {
    // set the api is OpenGLES 2.0
    context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    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)setupProgram {
    [GLESUtils loadShader:@"Sample" fragmentShader:@"Sample" forProgram:&sampleProgram];
    
    glUseProgram(sampleProgram);
    positionSlot = glGetAttribLocation(sampleProgram, "position");
}

// This function will be called when triger addSubview or reset the frame of this view.
- (void)layoutSubviews {
    [self destroyRenderAndFrameBuffer];
    [self setupRenderAndFrameBuffer];
    [self render];
}

- (void)destroyRenderAndFrameBuffer {
    
    if (frameBuffer) {
        glDeleteFramebuffers(1, &frameBuffer);
        frameBuffer = 0;
    }
 
    if (renderBuffer) {
        glDeleteRenderbuffers(1, &renderBuffer);
        renderBuffer = 0;
    }
}

- (void)setupRenderAndFrameBuffer {
    // setup RenderBuffer
    glGenRenderbuffers(1, &renderBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer);
    [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer];
    
    // setup FrameBuffer
    glGenFramebuffers(1, &frameBuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBuffer);
    
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
        NSLog(@"Failure with framebuffer generation");
        exit(1);
    }
}

- (void)render {
    glClearColor(1.0, 0.0, 0.5, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    
    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
    };
    
    glVertexAttribPointer(positionSlot, 3, GL_FLOAT, GL_FALSE, 0, vertices);
    glEnableVertexAttribArray(positionSlot);
    glDrawArrays(GL_TRIANGLES, 0, 3);
    
    [context presentRenderbuffer:GL_RENDERBUFFER];
}

@end

其餘4個檔案(OpenGLSampleAppDelegate.h,OpenGLSampleAppDelegate.m,OpenGLSampleViewController.h,OpenGLSampleViewController.m) 完全沒有更動,請自行參考之前的範例

最後應該會看到一個桃紅色的正方形上有個三角形。



沒有留言:

張貼留言