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) 完全沒有更動,請自行參考之前的範例。最後應該會看到一個桃紅色的正方形上有個三角形。
沒有留言:
張貼留言