Cocoaサンプル - OpenGLで描画

Cocoaアプリケーションプロジェクトをプロジェクト名"CocoaOpenGL"で作成しておきます。

ライブラリの追加

TARGET>Build Phases>Link Binary With Libraries>"+">OpenGL.frameworkをプロジェクトに追加します。

OpenGLViewの作成

MainMenu.xib>Object LibraryからOpenGL Viewを選択、ウインドウにドラッグドロップ>Windowと同じサイズにする

自作OpenGLViewクラスの追加

NSOpenGLViewクラスを継承したMyOpenGLViewクラスをプロジェクトに追加します。NSOpenGLViewに用意されている次のインターフェースを定義しておきます。initWithFrameが初期化、drawRectが描画処理部分、reshapeはリサイズ時の処理です。
MyOpenGLView.h

#import <AppKit/AppKit.h>
@interface MyOpenGLView : NSOpenGLView
- (id)initWithFrame:(NSRect)frameRect pixelFormat:(NSOpenGLPixelFormat*)format;
- (void)awakeFromNib;
- (void)drawRect:(NSRect)rect;
- (void)reshape;
@end
自作ViewとOpenGLViewを関連付ける

Interface BuilderでOpenGLViewを選択>Identity Inspector>Custom Class>MyOpenGLViewにする。

自作Viewの実装

NSOpenGLViewを使ってインスタンス化した場合は初期化処理でinitWithFrameではなくawakeFromNibが呼び出されますのでこちらに初期化処理を記述しておきます。(ちなみに描画処理部分はdrawRectですが、オブジェクトを描画する関数を分けて実装しています。)
MyOpenGLView.h

#import <AppKit/AppKit.h>
@interface MyOpenGLView : NSOpenGLView
- (id)initWithFrame:(NSRect)frameRect pixelFormat:(NSOpenGLPixelFormat*)format;
- (void)awakeFromNib;
- (void)drawRect:(NSRect)rect;
- (void)reshape;
-(void)drawScene;
@end

MyOpenGLView.m

#import "MyOpenGLView.h"
#import <OpenGL/gl.h>
// #import <OpenGL/glu.h>

@implementation MyOpenGLView
- (id)initWithFrame:(NSRect)frameRect pixelFormat:(NSOpenGLPixelFormat*)format
{
    self = [super initWithFrame:frameRect];
    if (self) {
        // initialize
	}
    return self;
}
// xibにViewを配置しているときはinitWithFrameが呼ばれないので注意
- (void)awakeFromNib
{
    NSLog(@"initialize");
}
- (void)drawRect:(NSRect)rect {
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // default background color
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    // View Transform
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    // Modeling Transform
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    
    [self drawScene];
    glFlush();
}
- (void)reshape
{
    int width = [self frame].size.width;
    int height = [self frame].size.height;
    // view port transform
    glViewport(0, 0, width, height);
    // projection transform
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity(); // default
}

- (void)drawScene
{
    glColor3f(0.7f, 0.2f, 0.2f);
    glBegin(GL_TRIANGLES);
    {
        glVertex3f(  0.0f,  0.0f, 0.0f);
        glVertex3f(  0.5f, 0.0f, 0.0f);
        glVertex3f(  0.0f, 0.5f ,0.0f);
    }
    glEnd();
}
@end
その他

・[self frame].sizeでフレームバッファのサイズ(CGSize型)を取得できます。さらにsize.width, size.heightで幅と高さを得られます。

[追記]

カスタムViewを使う方法

プロジェクトを作成し、OpenGL.frameworkを追加しておきます。

CustomViewの作成

MainMenu.xib>Object LibraryからCustom Viewを選択、ウインドウにドラッグドロップ>Windowと同じサイズにする

自作Viewクラスの追加

今度はNSOpenGLViewクラスを継承したMyCustomViewクラスをプロジェクトに追加します。
MyCustomView.h

@interface MyCustomView : NSOpenGLView
{
    NSOpenGLContext*  glContext;
}
- (id)initWithFrame:(NSRect)frameRect pixelFormat:(NSOpenGLPixelFormat*)format;
- (void)awakeFromNib;
- (void)prepareOpenGL;
- (void)drawRect:(NSRect)rect;
- (void)reshape;
@end
自作ViewとCustomViewを関連付ける

Interface BuilderでCustomViewを選択>Identity Inspector>Custom Class>MyCustomViewにする。
ここからMyCustomViewを実装します。

初期化

先ほどとちがってInterface BuilderでNSOpenGLViewを作成していないので、xibでNSOpenGLViewがインスタンス化されていません。今回は初期化でinitWithFrameが実行され、逆にawakeFromNibは呼ばれません。初期化処理では、ピクセルフォーマットの設定、レンダリングコンテキストの設定を行います。さらに、初期化処理を実行すると内部でセレクタが呼び出され、prepareOpenGLが自動的に実行されます。ここでOpenGLの初期設定を行います。

描画

drawRectでコンテキストのmakeCurrentContext(コンテキストの有効化)と、flushBuffer(コマンドの実行)を実行します。その間で実際の描画処理を行います。

終了処理

予め用意されているclearOpenGLメソッドを実行してコンテキストを片付けます。

MyCustomView.m

@implementation CustomOpenGLView

- (id)initWithFrame:(NSRect)frameRect pixelFormat:(NSOpenGLPixelFormat*)format
{
    NSLog(@"initWithFrame");
    //  attributeの宣言
    NSOpenGLPixelFormatAttribute attributes[] = {
        NSOpenGLPFADoubleBuffer,     // double buffering
        NSOpenGLPFAAccelerated ,       // HW accelaration
        NSOpenGLPFAStencilSize , 32,   // stencil buffer size = 32 bits
        NSOpenGLPFAColorSize , 32,     // color buffer size = 32 bits
        NSOpenGLPFADepthSize , 32,    // depth buffer size = 32bits
        0 // dummy
    };
    NSOpenGLPixelFormat* pixelFormat;
    pixelFormat = [ [ [ NSOpenGLPixelFormat alloc ] initWithAttributes : attributes ]
               autorelease ];
    //  ピクセルフォーマット設定  
    self = [ super initWithFrame : frameRect
                     pixelFormat : pixelFormat ]; 
    //  コンテキストを取得して有効にする
    glContext = [ self openGLContext ];
    [ glContext makeCurrentContext ];
  
    return self;
}
- (void)awakeFromNib
{
    NSLog(@"awakeFromNib");// xibにViewを配置していないので呼ばれない。
}
- (void)prepareOpenGL
{
   //  初期化    glEnable~など
}
- (void)drawRect:(NSRect)rect {
    [ glContext makeCurrentContext ];

    // 描画
   glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // default background color
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    // View Transform
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    // Modeling Transform
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    // draw objects
    glColor3f(0.7f, 0.2f, 0.2f);
    glBegin(GL_TRIANGLES);
    {
        glVertex3f(  0.0f,  0.0f, 0.0f);
        glVertex3f(  0.5f, 0.0f, 0.0f);
        glVertex3f(  0.0f, 0.5f ,0.0f);
    }
    glEnd();

    glFlush();
    [ glContext flushBuffer ];
}
- (void)reshape
{
    int width = [self frame].size.width;
    int height = [self frame].size.height;
    // view port transform
    glViewport(0, 0, width, height);
    // projection transform
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity(); // default
}
-(void)dealloc
{
    [glContext release];
    [self clearGLContext];
    [super dealloc];
}

アニメーション

タイマー開始と更新処理を行う2つのメソッドを追加します。

@interface CustomOpenGLView : NSOpenGLView
{
    NSOpenGLContext*  glContext;
    NSTimer*          timer;
}
//  略
- (void) startTimer;
- (void) updateTimer : (NSTimer*) aTimer;
@end

初期化の際にタイマーを開始させます。

- (id)initWithFrame:(NSRect)frameRect pixelFormat:(NSOpenGLPixelFormat*)format
{
    //  略
    //  ....
    glContext = [ self openGLContext ];
    [ glContext makeCurrentContext ];
  
    [self startTimer]; // タイマー開始
    return self;
}

タイマー開始する処理の中でセレクタを使って更新処理を直接呼び出します。

- (void) startTimer {
    timer = [ NSTimer scheduledTimerWithTimeInterval : 0.05
                                              target : self
                                            selector : @selector( updateTimer : ) //  イベントハンドラを直接実行
                                            userInfo : nil
                                            repeats : YES ];
    [timer retain];
}

あとはイベントハンドラ内で再描画処理や変数の更新処理を実装します。

- (void) updateTimer : (NSTimer*) aTimer {
    //  更新処理
    //  再描画
    [self setNeedsDisplay:YES];
    [ self display ];
}

終了時にタイマーを片付ける処理を追加して完成です。

-(void)dealloc
{
    // タイマー片付け
    [timer invalidate];
    [timer release];

    [glContext release];
    [self clearGLContext];
    [super dealloc];
}

その他

初期化のコツ

drawRect内で一度だけ初期化処理を呼び出す手もあります。

- (void)drawRect:(NSRect)rect {
if(!isInit) {
        glMatrixMode(GL_PROJECTION);// など
        isInit = YES;
    }
  //  描画
}