[ jimmyzhouj 翻译] Nehe iOS OpenGL ES 2.0教程
和所有的C/C++程序一样,应用程序的起点是main方法。我们执行UIApplicationMain,用InterfaceBuilder配置一个包含了EAGLView的UIWindow,并且用Lesson01AppDelegate去处理所有的事件。Window是UIApplicationMain自动创建的,可以显示我们的view。View包含了我们的OpenGL conext,可以通过这个context访问到用OpenGL ES来绘画的canvas。
注意:你之后创建你自己的OpenGL ES 项目的时候,你可以使用project wizard。生成的项目的结构有一点复杂,不过总体构成是一样的。Draw方法会在yourProjectNameViewController:drawFrame()里。我们的代码只是简单的将他们都清除了,并且将我们的OpenGL代码和window隔离开了而已。
- #import
- //standard main method, looks like that for every iOS app..
- int main(int argc, char *argv[])
- {
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- NSLog(@"Running app");
- int retVal = UIApplicationMain(argc, argv, nil, nil);
- [pool release];
- return retVal;
- }
当你control-click(或者右键)Lesson01AppDelegate的时候,你会看见2个定义好的outlets:glView和window,它们分别关联到InterfaceBuilder中的View和Window。一个Outlet可以让app delegate代码中的一个变量包含了它所关联的InterfaceBuilder中元素的引用。
- #import
- //we want to create variables of these classes, but don't need their implementation yet,
- //so we just tell the compiler that they exist - called forward declaration
- @class EAGLView;
- class Lesson;
- //This is our delegate class. It handles all messages from the device's operating system
- @interface Lesson01AppDelegate : NSObject
{ - @private
- //we store a pointer to our lesson so we can delete it at program shutdown
- Lesson *lesson;
- }
- //we configure these variables in the interface builder (IB), thus they have to be declared as IBOutlet
- //properties get accessor methods by synthesizing them in the source file (.mm)
- //in this window we will embed a view which acts as OpenGL context
- @property (nonatomic, retain) IBOutlet UIWindow *window;
- //our main window, covering the whole screen
- @property (nonatomic, retain) IBOutlet EAGLView *glView;
- @end
- #import "Lesson01AppDelegate.h"
- #import "EAGLView.h"
- #include "Lesson01.h"
- //now we implement all methods needed by our delegate
- @implementation Lesson01AppDelegate
- //
- @synthesize window;
- @synthesize glView;
- //this method tells us, that our application has started and we can set up our OpenGL things,
- //as the window is set up, and thus our glView is going to be displayed soon
- - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
- {
- //for the first lesson we don't need a depth buffer as we're not drawing any geometry yet
- [glView setDepthBufferNeeded:FALSE];
- //we create our lesson which contains the OpenGL code
- //(allocated with new -> has to be cleaned up with delete!)
- lesson = new Lesson01();
- //we tell our OpenGL view which lesson we want to use for rendering.
- [glView setLesson:lesson];
- return YES;
- }
- - (void)applicationWillResignActive:(UIApplication *)application
- {
- //the app is going to be suspended to the background,
- //so we should stop our render loop
- [glView stopRenderLoop];
- }
- - (void)applicationDidEnterBackground:(UIApplication *)application
- {
- //we could do something here when the application entered the background
- }
- - (void)applicationWillEnterForeground:(UIApplication *)application
- {
- //we could start preparing stuff for becoming active again
- }
- - (void)applicationDidBecomeActive:(UIApplication *)application
- {
- //we're on stage! so let's draw some nice stuff
- [glView startRenderLoop];
- }
- - (void)applicationWillTerminate:(UIApplication *)application
- {
- //before shutdown we stop the render loop
- [glView stopRenderLoop];
- }
- //dealloc is the destructor in Objective-C, so clean up all allocated things
- - (void)dealloc
- {
- [window release];
- [glView release];
- delete lesson;
- [super dealloc];
- }
- @end
- //forward declarations again
- @class EAGLContext;
- class Lesson;
- // This class combines our OpenGL context (which is our access to all drawing functionality)
- // with a UIView that can be displayed on the iOS device. It handles the creation and presentation
- // of our drawing surface, as well as handling the render loop which allows for seamless animations.
- @interface EAGLView : UIView {
- @private
- // The pixel dimensions of the CAEAGLLayer.
- GLint framebufferWidth;
- GLint framebufferHeight;
- // These are the buffers we render to: the colorRenderbuffer will contain the color that we will
- // finaly see on the screen, the depth renderbuffer has to be used if we want to make sure, that
- // we always see only the closest object and not just the one that has been drawn most recently.
- // The framebuffer is a collection of buffers to use together while rendering, here it is either
- // just the color buffer, or color and depth renderbuffer.
- GLuint defaultFramebuffer, colorRenderbuffer, depthRenderbuffer;
- // The display link is used to create a render loop
- CADisplayLink* displayLink;
- // Do we need a depth buffer
- BOOL useDepthBuffer;
- // The pointer to the lesson which we're rendering
- Lesson* lesson;
- // Did we already initialize our lesson?
- BOOL lessonIsInitialized;
- }
- // The OpenGL context as a property (has autogenerated getter and setter)
- @property (nonatomic, retain) EAGLContext *context;
- // Configuration setters
- - (void) setDepthBufferNeeded:(BOOL)needed;
- - (void) setLesson:(Lesson*)newLesson;
- //if we want OpenGL to repaint with the screens refresh rate, we use this render loop
- - (void) startRenderLoop;
- - (void) stopRenderLoop;
- @end
注解:并不是强制必须用EAGLView这个名字,但在iOS GL ES程序中通常都用它,因为context被称为EAGLContext。EAGL可能代表”Embedded AGL”,AGL是APPLE的OPENGL扩展。
正如我们所见,EAGLView封装了OpenGL ES context。OpenGL context被认为是允许使用OpenGL调用来绘画的许可。它跟踪记录了我们设定的所有状态,比如说当前的颜色,当前哪一幅图片被用来做纹理。Context要和canvas(我们可以绘画的地方)配合起来使用。Canvas通过一种叫framebuffer的构造来实现。framebuffer由存储不同信息的多层buffer组成。我们常用到的两个层是color renderbuffer和depth renderbuffer。Color renderbuffer里面存储了每个像素点的每个color channel,就像JPEG图像一样。这是最终显示在屏幕上的内容。The depth renderbuffer记录了color buffer中的每个像素点离屏幕的距离。如果我们画了一座距离屏幕10单位远的房子和一个距离屏幕5单位远的人,则不管先画的是房子还是人,人总是显示在房子的前面。这被称为是深度测试。Depth buffer内容不会被显示。
- #import
- #import "EAGLView.h"
- #include "Lesson.h"
- //declare private methods, so they can be used everywhere in this file
- @interface EAGLView (PrivateMethods)
- - (void)createFramebuffer;
- - (void)deleteFramebuffer;
- @end
- //start the actual implementation of our view here
- @implementation EAGLView
- //generate getter and setter for the context
- @synthesize context;
- // We have to implement this method
- + (Class)layerClass
- {
- return [CAEAGLLayer class];
- }
接下来我们开始实现EAGLView,合成(synthesize) context以自动生成getter和setter方法。然后需要重写UIView的layerClass方法,因为现在我们用的view不是标准的UI元素,而是要画到一个CAEAGL层上(CA指 CoreAnimation)。
- //our EAGLView is the view in our MainWindow which will be automatically loaded to be displayed.
- //when the EAGLView gets loaded, it will be initialized by calling this method.
- - (id)initWithCoder:(NSCoder*)coder
- {
- //call the init method of our parent view
- self = [super initWithCoder:coder];
- //now we create the core animation EAGL layer
- if (!self) {
- return nil;
- }
- CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
- //we don't want a transparent surface
- eaglLayer.opaque = TRUE;
- //here we configure the properties of our canvas, most important is the color depth RGBA8 !
- eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
- [NSNumber numberWithBool:FALSE], kEAGLDrawablePropertyRetainedBacking,
- kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat,
- nil];
- //create an OpenGL ES 2 context
- context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
- //if this failed or we cannot set the context for some reason, quit
- if (!context || ![EAGLContext setCurrentContext:context]) {
- NSLog(@"Could not create context!");
- [self release];
- return nil;
- }
- //do we want to use a depth buffer?
- //for 3D applications we usually do, so we'll set it to true by default
- useDepthBuffer = FALSE;
- //we did not initialize our lesson yet:
- lessonIsInitialized = FALSE;
- //default values for our OpenGL buffers
- defaultFramebuffer = 0;
- colorRenderbuffer = 0;
- depthRenderbuffer = 0;
- return self;
- }
- //on iOS, all rendering goes into a renderbuffer,
- //which is then copied to the window by "presenting" it.
- //here we create it!
- - (void)createFramebuffer
- {
- //this method assumes, that the context is valid and current, and that the default framebuffer has not been created yet!
- //this works, because as soon as we call glGenFramebuffers the value will be > 0
- assert(defaultFramebuffer == 0);
- NSLog(@"EAGLView: creating Framebuffer");
- // Create default framebuffer object and bind it
- glGenFramebuffers(1, &defaultFramebuffer);
- glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer);
- // Create color render buffer
- glGenRenderbuffers(1, &colorRenderbuffer);
- glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
- //get the storage from iOS so it can be displayed in the view
- [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer];
- //get the frame's width and height
- glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &framebufferWidth);
- glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &framebufferHeight);
- //attach this color buffer to our framebuffer
- glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRenderbuffer);
glFramebufferRenderbuffer是非常重要的。一个framebuffer由多个layer组成,这些layer被称为p_w_uploads。这里我们告诉OpenGL,当前绑定的framebuffer要附加一个名字为colorrenderbuffer的color buffer。GL_COLOR_ATTACHMENT_0表示一个framebuffer可以有几个color p_w_uploads,不过这个内容超过了本节课程的范围了。
- //our lesson needs to know the size of the renderbuffer so it can work with the right aspect ratio
- if(lesson != NULL)
- {
- lesson->setRenderbufferSize(framebufferWidth, framebufferHeight);
- }
- if(useDepthBuffer)
- {
- //create a depth renderbuffer
- glGenRenderbuffers(1, &depthRenderbuffer);
- glBindRenderbuffer(GL_RENDERBUFFER, depthRenderbuffer);
- //create the storage for the buffer, optimized for depth values, same size as the colorRenderbuffer
- glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, framebufferWidth, framebufferHeight);
- //attach the depth buffer to our framebuffer
- glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRenderbuffer);
- }
- //check that our configuration of the framebuffer is valid
- if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
- NSLog(@"Failed to make complete framebuffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER));
- }
- //deleting the framebuffer and all the buffers it contains
- - (void)deleteFramebuffer
- {
- //we need a valid and current context to access any OpenGL methods
- if (context) {
- [EAGLContext setCurrentContext:context];
- //if the default framebuffer has been set, delete it.
- if (defaultFramebuffer) {
- glDeleteFramebuffers(1, &defaultFramebuffer);
- defaultFramebuffer = 0;
- }
- //same for the renderbuffers, if they are set, delete them
- if (colorRenderbuffer) {
- glDeleteRenderbuffers(1, &colorRenderbuffer);
- colorRenderbuffer = 0;
- }
- if (depthRenderbuffer) {
- glDeleteRenderbuffers(1, &depthRenderbuffer);
- depthRenderbuffer = 0;
- }
- }
- }
- //this is where all the magic happens!
- - (void)drawFrame
- {
- //we need a context for rendering
- if (context != nil)
- {
- //make it the current context for rendering
- [EAGLContext setCurrentContext:context];
- //if our framebuffers have not been created yet, do that now!
- if (!defaultFramebuffer)
- [self createFramebuffer];
- glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer);
- //we need a lesson to be able to render something
- if(lesson != nil)
- {
- //check whether we have to initialize the lesson
- if(lessonIsInitialized == FALSE)
- {
- lesson->init();
- lessonIsInitialized = TRUE;
- }
- //perform the actual drawing!
- lesson->draw();
- }
- //finally, get the color buffer we rendered to, and pass it to iOS
- //so it can display our awesome results!
- glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
- [context presentRenderbuffer:GL_RENDERBUFFER];
- }
- else
- NSLog(@"Context not set!");
- }
- //our render loop just tells the iOS device that we want to keep refreshing our view all the time
- - (void)startRenderLoop
- {
- //check whether the loop is already running
- if(displayLink == nil)
- {
- //the display link specifies what to do when the screen has to be redrawn,
- //here we use the selector (method) drawFrame
- displayLink = [self.window.screen displayLinkWithTarget:self selector:@selector(drawFrame)];
- //by adding the display link to the run loop our draw method will be called 60 times per second
- [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
- NSLog(@"Starting Render Loop");
- }
- }
- //we have to be able to stop the render loop
- - (void)stopRenderLoop
- {
- if (displayLink != nil) {
- //if the display link is present, we invalidate it (so the loop stops)
- [displayLink invalidate];
- displayLink = nil;
- NSLog(@"Stopping Render Loop");
- }
- }
- //setter methods, should be straightforward
- - (void) setDepthBufferNeeded:(BOOL)needed
- {
- useDepthBuffer = needed;
- }
- - (void) setLesson:(Lesson*)newLesson
- {
- lesson = newLesson;
- //if we set a new lesson, it is not yet initialized!
- lessonIsInitialized = FALSE;
- }
- //As soon as the view is resized or new subviews are added, this method is called,
- //apparently the framebuffers are invalid in this case so we delete them
- //and have them recreated the next time we draw to them
- - (void)layoutSubviews
- {
- [self deleteFramebuffer];
- }
- //cleanup our view
- - (void)dealloc
- {
- [self deleteFramebuffer];
- [context release];
- [super dealloc];
- }
Dealloc方法清除我们创建的一切,即framebuffers和context。我们调用deleteFramebuffer来清除framebuffer。我们用release方法来释放context,因为它是通过garbage collection来管理的。最后我们调用父类的dealloc方法,在这里父类是UIView。在Objective-C里需要手动调用父类的dealloc方法,而在C++里父类的析构函数会被自动调用。
- #include
- #include
- //this is our general lesson class, providing the two most important methods init and draw
- //which will be invoked by our EAGLView
- class Lesson
- {
- public:
- //constructor
- Lesson();
- //the destructor has always to virtual!
- virtual ~Lesson();
- //abstract methods init and draw have to be defined in derived classes
- virtual void init() = 0;
- virtual void draw() = 0;
- //we need to know the size of our drawing canvas (called renderbuffer here),
- //so this method just saves the parameters in the member variables
- virtual void setRenderbufferSize(unsigned int width, unsigned int height);
- //all protected stuff will be visible within derived classes, but from nowhere else
- protected:
- //fields for the renderbuffer size
- unsigned int m_renderbufferWidth, m_renderbufferHeight;
- };
- #include "Lesson.h"
- //Lesson constructor, set default values
- Lesson::Lesson():
- m_renderbufferWidth(0),
- m_renderbufferHeight(0)
- {
- }
- //Lesson destructor
- Lesson::~Lesson()
- {
- //cleanup here
- }
- //save the renderbuffer size in the member variables
- void Lesson::setRenderbufferSize(unsigned int width, unsigned int height)
- {
- m_renderbufferWidth = width;
- m_renderbufferHeight = height;
- glViewport(0, 0, m_renderbufferWidth, m_renderbufferHeight);
- }
- //We derive our current lesson class from the general lesson class
- class Lesson01 : public Lesson
- {
- public:
- //overwrite all important methods
- Lesson01();
- virtual ~Lesson01();
- virtual void init();
- virtual void draw();
- };
- #include "Lesson01.h"
- //lesson constructor
- Lesson01::Lesson01()
- {
- //initialize values
- }
- //lesson destructor
- Lesson01::~Lesson01()
- {
- //do cleanup
- }
(图片来源: http://en.wikipedia.org/wiki/File:RGB_farbwuerfel.jpg)
- //initializing all OpenGL related things
- void Lesson01::init()
- {
- NSLog(@"Init..");
- //set the color we use for clearing our colorRenderbuffer to red
- glClearColor(1.0, 0.0, 0.0, 1.0);
- }
- //drawing a frame
- void Lesson01::draw()
- {
- //clear the color buffer
- glClear(GL_COLOR_BUFFER_BIT);
- //everything should be red now! yay :)
- }