OS X下使用OpenGL做离屏渲染

时间:2020-12-03 18:50:04

有时,我们想通过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 index
enum
{
UNIFORM_SAMPLER,

NUM_UNIFORMS
};
static GLint uniforms[NUM_UNIFORMS];

// attribute index
enum {
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];
// 将此上下文作为当前上下文
[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 : enable

attribute 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 120

uniform 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