OS X下使用OpenGL做离屏渲染
有时,我们想通过GPU做一些视频、图像处理,而处理的结果不需要显示在显示器上,而是直接交给主存,这时候我们可以通过OpenGL的离屏渲染来实现。
由于我们不需要将渲染好的像素显示到屏幕上,因此我们可以使用framebuffer object,将像素放到fbo上,然后通过glReadPixels来最终获取渲染好的像素。
在Mac OS X Lion中做离屏渲染非常简单,基本步骤如下:
1、创建OpenGL绘制上下文,可使用离屏渲染属性,然后可以设置上其它属性。
2、创建framebuffer object
3、绑定创建好的fbo
4、编译、连接、加载着色器并初始化一些基本的OpenGL的上下文
5、生成纹理,并将它绑定到framebuffer中
6、取消framebuffer的绑定,然后就可以做渲染了。
在使用glDrawArrays等绘制接口之后,可以调用glFlush来确保像素都已经到了指定的framebuffer,然后通过调用glReadPixels将像素读取出来。
下面将给出具体的代码:
//// MyGLView.h// OpenGLShaderBasic//// Created by Zenny Chen on 10/4/10.// Copyright 2010 GreenGames Studio. All rights reserved.//#import <Cocoa/Cocoa.h>@class NSOpenGLContext;@interface MyGLView : NSObject{ GLuint program; GLuint framebufferID; GLuint texName; NSOpenGLContext *glContext;}- (void)prepareOpenGL;- (void)compute;@end |
这里尽管给出的Objective-C类叫MyGLView,但它其实不是一个UIView的子类。由于我们不需要将结果显示到屏幕上,因此这里也就不需要用UIView作为父类。
//// MyGLView.m// OpenGLShaderBasic//// Created by Zenny Chen on 10/4/10.// Copyright 2010 GreenGames Studio. All rights reserved.//#import "MyGLView.h"#include <OpenGL/gl.h>#include <OpenGL/OpenGL.h>#include <OpenGL/CGLRenderers.h>#define MY_PIXEL_WIDTH 128#define MY_PIXEL_HEIGHT 128@implementation MyGLView// uniform indexenum{ UNIFORM_SAMPLER, NUM_UNIFORMS};static GLint uniforms[NUM_UNIFORMS];// attribute indexenum { ATTRIB_VERTEX, ATTRIB_TEXCOORD, NUM_ATTRIBUTES};- (BOOL)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file{ GLint status; const GLchar *source; source = (GLchar *)[[NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil] UTF8String]; if (!source) { NSLog(@"Failed to load vertex shader"); return FALSE; } *shader = glCreateShader(type); glShaderSource(*shader, 1, &source, NULL); glCompileShader(*shader); GLint logLength; glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength); if (logLength > 0) { GLchar *log = (GLchar *)malloc(logLength); glGetShaderInfoLog(*shader, logLength, &logLength, log); NSLog(@"Shader compile log:\n%s", log); free(log); } glGetShaderiv(*shader, GL_COMPILE_STATUS, &status); if (status == 0) { glDeleteShader(*shader); return FALSE; } return TRUE;}- (BOOL)linkProgram:(GLuint)prog{ GLint status; glLinkProgram(prog); GLint logLength; glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength); if (logLength > 0) { GLchar *log = (GLchar *)malloc(logLength); glGetProgramInfoLog(prog, logLength, &logLength, log); NSLog(@"Program link log:\n%s", log); free(log); } glGetProgramiv(prog, GL_LINK_STATUS, &status); if (status == 0) return FALSE; return TRUE;}- (BOOL)validateProgram:(GLuint)prog{ GLint logLength, status; glValidateProgram(prog); glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength); if (logLength > 0) { GLchar *log = (GLchar *)malloc(logLength); glGetProgramInfoLog(prog, logLength, &logLength, log); NSLog(@"Program validate log:\n%s", log); free(log); } glGetProgramiv(prog, GL_VALIDATE_STATUS, &status); if (status == 0) return FALSE; return TRUE;}- (BOOL)loadShaders{ GLuint vertShader, fragShader; NSString *vertShaderPathname, *fragShaderPathname; // create shader program program = glCreateProgram(); // create and compile vertex shader vertShaderPathname = [[NSBundle mainBundle] pathForResource:@"Shader" ofType:@"vsh"]; if (![self compileShader:&vertShader type:GL_VERTEX_SHADER file:vertShaderPathname]) { NSLog(@"Failed to compile vertex shader"); return FALSE; } // create and compile fragment shader fragShaderPathname = [[NSBundle mainBundle] pathForResource:@"Shader" ofType:@"fsh"]; if (![self compileShader:&fragShader type:GL_FRAGMENT_SHADER file:fragShaderPathname]) { NSLog(@"Failed to compile fragment shader"); return FALSE; } // attach vertex shader to program glAttachShader(program, vertShader); // attach fragment shader to program glAttachShader(program, fragShader); // bind attribute locations // this needs to be done prior to linking glBindAttribLocation(program, ATTRIB_VERTEX, "position"); glBindAttribLocation(program, ATTRIB_TEXCOORD, "texCoords"); //glBindFragDataLocationEXT(program, 0, "myFragColor"); // link program if (![self linkProgram:program]) { NSLog(@"Failed to link program: %d", program); return FALSE; } // get uniform locations uniforms[UNIFORM_SAMPLER] = glGetUniformLocation(program, "sampler"); // release vertex and fragment shaders if (vertShader) glDeleteShader(vertShader); if (fragShader) glDeleteShader(fragShader); return TRUE;}- (void)dealloc{ if(texName != 0) glDeleteTextures(1, &texName); if(framebufferID != 0) glDeleteFramebuffers(1, &framebufferID); if(glContext != nil) { [glContext release]; glContext = nil; } [super dealloc];}static GLfloat __attribute__((aligned(16))) my_buffer[MY_PIXEL_WIDTH * MY_PIXEL_HEIGHT * 4];static GLfloat __attribute__((aligned(16))) checkImage[MY_PIXEL_WIDTH * MY_PIXEL_HEIGHT * 4];static void initCheckImage(void){ for(int row = 0; row < MY_PIXEL_HEIGHT; row++) { for(int col = 0; col < MY_PIXEL_WIDTH; col++) { checkImage[row * MY_PIXEL_WIDTH * 4 + col * 4 + 0] = 0.1f; checkImage[row * MY_PIXEL_WIDTH * 4 + col * 4 + 1] = 0.2f; checkImage[row * MY_PIXEL_WIDTH * 4 + col * 4 + 2] = 0.3f; checkImage[row * MY_PIXEL_WIDTH * 4 + col * 4 + 3] = 0.4f; } }}- (void)prepareOpenGL{ if(glContext != nil) return; #if 0 CGLPixelFormatObj pixObj = NULL; GLint nPix = 0; CGLChoosePixelFormat((const CGLPixelFormatAttribute[]){kCGLPFAOpenGLProfile, kCGLOGLPVersion_3_2_Core, 0}, &pixObj, &nPix); NSOpenGLPixelFormat *pixelFormat = [[NSOpenGLPixelFormat alloc] initWithCGLPixelFormatObj:pixObj]; CGLReleasePixelFormat(pixObj); #else // 创建pixel format,设置离屏渲染 NSOpenGLPixelFormat *pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:(const NSOpenGLPixelFormatAttribute[]){NSOpenGLPFAAllRenderers, NSOpenGLPFAOffScreen, NSOpenGLPFAAllowOfflineRenderers, 0}];#endif // 创建OpenGL上下文 glContext = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:nil]; [pixelFormat release];<br> // 将此上下文作为当前上下文 [glContext makeCurrentContext]; if(![self loadShaders]) return; glGenFramebuffers(1, &framebufferID); glBindFramebuffer(GL_FRAMEBUFFER, framebufferID); glViewport(0, 0, MY_PIXEL_WIDTH, MY_PIXEL_HEIGHT); glClearColor(0.4f, 0.4f, 0.4f, 1.0f); // Use shader program glUseProgram(program); // Drawing code here. static const GLfloat squareVertices[] = { -1.0f, -1.0f, 0.0f, 1.0f, -1.0f, 0.0f, -1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f }; static const GLfloat texCoords[] = { 0.0f, 0.0f, // left lower 1.0f, 0.0f, // right lower 0.0f, 1.0f, // left upper 1.0f, 1.0f // right upper }; glEnable(GL_CULL_FACE); // 初始化纹理数据 initCheckImage(); glClampColorARB(GL_RGBA_FLOAT_MODE_ARB, GL_TRUE); glClampColorARB(GL_CLAMP_FRAGMENT_COLOR_ARB, GL_FALSE); glClampColorARB(GL_CLAMP_READ_COLOR_ARB, GL_FALSE); // 初始化纹理 glPixelStorei(GL_UNPACK_ALIGNMENT, 8); glActiveTexture(GL_TEXTURE0); glGenTextures(1, &texName); glBindTexture(GL_TEXTURE_2D, texName); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, MY_PIXEL_WIDTH, MY_PIXEL_HEIGHT, 0, GL_RGBA, GL_FLOAT, checkImage); // 将纹理绑定到帧缓存 glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, texName, 0); glUniform1i(uniforms[UNIFORM_SAMPLER], 0); // Set the sampler value // Update attribute values glVertexAttribPointer(ATTRIB_VERTEX, 3, GL_FLOAT, 0, 0, squareVertices); glEnableVertexAttribArray(ATTRIB_VERTEX); glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, 0, 0, texCoords); glEnableVertexAttribArray(ATTRIB_TEXCOORD);}- (void)compute{ glClear(GL_COLOR_BUFFER_BIT); // Draw glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glFlush(); glReadPixels(0, 0, MY_PIXEL_WIDTH, MY_PIXEL_HEIGHT, GL_RGBA, GL_FLOAT, my_buffer); NSLog(@"R:%f, G:%f, B:%f, A:%f", my_buffer[0], my_buffer[1], my_buffer[2], my_buffer[3]);}@end |
下面给出Vertex shader和Fragment shader,这两个Shader的代码很简单:
//// Shader.vsh// GLSLTest//// Created by Zenny Chen on 4/11/10.// Copyright GreenGames Studio 2010. All rights reserved.//#version 120#extension GL_EXT_gpu_shader4 : enableattribute vec4 position;attribute vec4 texCoords;uniform mat4 translate;uniform mat4 projection;void main(){ gl_Position = position; gl_TexCoord[0] = texCoords;} |
//// Shader.fsh// GLSLTest//// Created by Zenny Chen on 4/11/10.// Copyright GreenGames Studio 2010. All rights reserved.//#version 120uniform sampler2D sampler;void main(){ // gl_FragColor gl_FragColor = texture2D(sampler, gl_TexCoord[0].st) + vec4(0.4, 0.3, 0.2, 1.1);} |
最后给出OS X的AppDelegate中的实现。各位可以拿这代码自己尝试一下,看看输出结果~
//// AppDelegate.h// OpenGL4Compute//// Created by zenny_chen on 12-12-9.// Copyright (c) 2012年 zenny_chen. All rights reserved.//#import <Cocoa/Cocoa.h>@class MyGLView;@interface AppDelegate : NSObject <NSApplicationDelegate>{ MyGLView *glComputeObj;}@property (assign) IBOutlet NSWindow *window;@end |
//// AppDelegate.m// OpenGL4Compute//// Created by zenny_chen on 12-12-9.// Copyright (c) 2012年 zenny_chen. All rights reserved.//#import "AppDelegate.h"#import "MyGLView.h"@implementation AppDelegate- (void)dealloc{ [super dealloc];}- (void)applicationDidFinishLaunching:(NSNotification *)aNotification{ // Insert code here to initialize your application NSView *baseView = self.window.contentView; CGSize viewSize = baseView.frame.size; NSButton *button = [[NSButton alloc] initWithFrame:CGRectMake(20.0f, viewSize.height - 60.0f, 100.0f, 30.0f)]; [button setButtonType:NSMomentaryPushInButton]; [button setBezelStyle:NSRoundedBezelStyle]; [button setTitle:@"Compute"]; [button setTarget:self]; [button setAction:@selector(computeButtonTouched:)]; [baseView addSubview:button]; [button release];}- (void)computeButtonTouched:(id)sender{ if(glComputeObj == nil) { glComputeObj = [[MyGLView alloc] init]; [glComputeObj prepareOpenGL]; } [glComputeObj compute];}@end |
浙公网安备 33010602011771号