本系列翻譯自 Nehe Gamedev 的 iOS OpenGL ES 2.0 教程,旨在幫助開發者從零建立現代 OpenGL ES 2.0 渲染管線。第 1 課雖無華麗圖形,但詳細解釋了 iOS 應用啟動流程、EAGLView 架構、渲染循環與 Lesson 基類設計,是後續所有課程的基石。
專案包含三類核心物件:
Lesson01AppDelegate、EAGLView,負責 iOS 生命週期與 OpenGL Context 管理。Lesson 基類及其派生類 Lesson01,封裝跨平台渲染邏輯。main.m → UIApplicationMain → 載入 MainWindow.xib。
// Lesson01AppDelegate.h
@class EAGLView;
class Lesson;
@interface Lesson01AppDelegate : NSObject <UIApplicationDelegate> {
@private
Lesson* lesson; // 持有渲染邏輯實例
}
@property (nonatomic, retain) IBOutlet UIWindow* window;
@property (nonatomic, retain) IBOutlet EAGLView* glView;
@end
application:didFinishLaunchingWithOptions: 中建立 Lesson01 實例並注入 EAGLView,後續的 applicationDidBecomeActive/applicationWillResignActive 控制渲染循環啟停。
繼承 UIView,重寫 layerClass 回傳 CAEAGLLayer,使其成為可繪製的 OpenGL 畫布。
// initWithCoder
context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
[EAGLContext setCurrentContext:context];
// createFramebuffer
glGenFramebuffers(1, &defaultFramebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer);
glGenRenderbuffers(1, &colorRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
[context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &framebufferWidth);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &framebufferHeight);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRenderbuffer);
// 可選深度緩衝
if (useDepthBuffer) {
glGenRenderbuffers(1, &depthRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, depthRenderbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, framebufferWidth, framebufferHeight);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRenderbuffer);
}
glCheckFramebufferStatus(GL_FRAMEBUFFER); // 驗證完整性
- (void)startRenderLoop {
if (!displayLink) {
displayLink = [self.window.screen displayLinkWithTarget:self selector:@selector(drawFrame)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
}
- (void)stopRenderLoop {
if (displayLink) {
[displayLink invalidate];
displayLink = nil;
}
}
CADisplayLink 同步螢幕刷新率(約 60 FPS),將 drawFrame 加入 RunLoop,實現流暢動畫。
- (void)drawFrame {
if (context) {
[EAGLContext setCurrentContext:context];
if (!defaultFramebuffer) [self createFramebuffer];
glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer);
if (lesson) {
if (!lessonIsInitialized) {
lesson->init();
lessonIsInitialized = YES;
}
lesson->draw(); // 委派給 C++ 渲染邏輯
}
glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER]; // 送顯示
}
}
// Lesson.h
class Lesson {
public:
Lesson();
virtual ~Lesson();
virtual void init() = 0; // 資源載入、Shader 編譯等
virtual void draw() = 0; // 每幀渲染指令
virtual void setRenderbufferSize(unsigned int w, unsigned int h);
protected:
unsigned int m_renderbufferWidth, m_renderbufferHeight;
};
純虛函數強制派生類實作 init/draw,setRenderbufferSize 內呼叫 glViewport 設定繪製區域。
// Lesson01.mm
void Lesson01::init() {
glClearColor(1.0f, 0.0f, 0.0f, 1.0f); // RGBA 紅色
}
void Lesson01::draw() {
glClear(GL_COLOR_BUFFER_BIT); // 清除色彩緩衝
}
執行結果:全螢幕純紅,證明 OpenGL ES 2.0 管線已正常運作。
第 2 課將引入 Shader 程式物件、頂點屬性陣列,繪製第一個三角形,正式進入可程式化管線開發。