てなわけでレイトレーシングの説明だ。
 レイトレーシングの基本原理は簡単で、
3次元空間上に視点を置き、その前に透明なスクリーンを置く場合、スクリーンの各画素は、視点と画素を結んだ直線上に存在する物体の色になる

 というもの。

$テン*シー*シー-1

 で、その3次元空間上のスクリーンを画像と考え、iPhoneやMacの画面上に表示させてやれば視点の先にスクリーンを通して広がる3次元空間が再現できるわけです。

$テン*シー*シー-2

 そのためには画像中の任意の画素の色を設定できる機能が必要になってくるんですな。
 というわけでレイトレーシングの実装前に、この表示システムを作ってしまいます。

 ファイル>新規プロジェクト…メニューを選んで新規プロジェクトを作るところは同じ。
 iPhoneアプリで使うフレームワークCocoa touchはMacのアプリで使うフレームワークCocoaのサブセットなので、かなりデジャビュな感じなりよ。
 違うのはiOS ApplicationじゃなくMac OS X Applicationを選ぶ点くらい。

$テン*シー*シー-3

 プロジェクト名はsphereとしました。
 作られたプロジェクトはこんな感じでiOSのwindow-based applicationみたいな構成になっとります。

$テン*シー*シー-4

 ちなみに上で表示してるプロジェクトウィンドウのレイアウトは私の好みである「コンデンス」。こいつはXcode>環境設定…メニューで表示されるウィンドウの全般タグで選択します。

$テン*シー*シー-5
プロジェクトが一つでも開かれている時は選択不能

 続いて、ファイル>新規ファイル…メニューを選んで、このプロジェクトに表示用のカスタムビューを用意するわけですが、ここはiOSと違いUIViewではなくNSViewてのを親クラスにします。
 名前はimageView。
 
$テン*シー*シー-6
iOS側のCocoa Touch ClassじゃなくMac OS X側のCocoa Classね

 ここまでできたらMainMenu.xib(のEnglish)をダブルクリックしてInterface Builderを起動。

$テン*シー*シー-7

 MainMenu.xibにはWindowの中にひとつNSViewが配置されてます。

$テン*シー*シー-8

 ここらへんはiOSのwindow-based applicationよりview-based applicationっぽいんだけど、iOSみたいにこいつを直接imageViewクラスに変更しても駄目でした。
 このContent Viewの中に別のNSViewを配置して、こいつをimageViewクラスにしないと駄目みたい。
 なので、まずはライブラリウィンドウからContent ViewにCustom Viewをドロップ。ライブラリウィンドウが表示されていない人はTool>Libraryメニューね。

$テン*シー*シー-9

 ま、位置は後で調整するとして、とりあえずMainMenu.xibウィンドウのContent View中に埋め込まれたCustom Viewを選んでおいてInspectorボタンをクリック。

$テン*シー*シー-12

 Inspectorウィンドウが表示されるので、Identityタブを選んでClassをNSViewからimageViewに変更します。

$テン*シー*シー-10

 これで準備ができたのでInterface Builderは終了してXcodeにもどります。

$テン*シー*シー-11
終了時に保存するか聞いてくるのでSaveを選択

 Xcodeに戻ったらimageView.hでレイトレーシング計算結果を格納するコンテキスト変数を定義。
@interface imageView : NSView {
CGContextRef offscreenContext;
}
@end


 でもってimageView.mでinitWithFrameメソッドに自分の処理を追加します。
 追加するのはレイトレーシング計算結果を格納するコンテキストの作成と、レイトレーシング計算処理。コンテキストは1000x1000ピクセルの画像とし、これが3次元空間上のスクリーンになるわけです。
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
offscreenContext = newImageBuffer(1000, 1000);
rendering(offscreenContext);

}
return self;
}

 あとはdrawRectメソッドでレイトレーシング計算結果を画面に表示。iOSと違ってUIKitがないんで、現在のCGContextRefを取り出す方法がちと違いますが、CGBitmapContextCreateImageやCGContextDrawImageはiPhoneアプリ開発、その(175) 空に消えいるアゲハ蝶なんかでも使ったおなじみの関数ですな。
- (void)drawRect:(NSRect)dirtyRect {
CGContextRef context
= (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
CGImageRef image = CGBitmapContextCreateImage(offscreenContext);
CGContextDrawImage(context, self.bounds, image);
CGImageRelease(image);
}

 最後はdeallocメソッドにレイトレーシング計算結果を格納するコンテキストの破棄を加えておしまい。
-(void)dealloc {
disposeImageBuffer(offscreenContext);
[super dealloc];
}

 newImageBuffer関数は同じくその(175)で使ったCGBitmapContextCreateの応用です。
static CGContextRef newImageBuffer(int width, int height)
{
#define COMPONENT_COUNT 4 // R G B α の順で各1バイト
// 合計4バイトで1画素を構成。
size_t bitsPerComponent = 8; // 各要素は1バイト=8ビット
size_t bytesPerRow = width * COMPONENT_COUNT;
unsigned char* baseAddress = (unsigned char*)malloc(
bytesPerRow * height * sizeof(unsigned char));
// 全画素を 黒 不透明度=100%で初期化
for (int v = 0; v < height; v++) {
for (int h = 0; h < width; h++) {
unsigned char* p = baseAddress + (v * bytesPerRow) + h * 4;
*p++ = 0; // RGBをすべて0:黒
*p++ = 0;
*p++ = 0;
*p++ = 255; // α値を最大:不透明度=100%
}
}
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(baseAddress,
width, height, bitsPerComponent, bytesPerRow, colorSpace,
kCGImageAlphaPremultipliedLast);
CGColorSpaceRelease(colorSpace);
return context;
}

static void disposeImageBuffer(CGContextRef context)
{
unsigned char* baseAddress = CGBitmapContextGetData(context);
CGContextRelease(context);
free(baseAddress);
}

 違うのはCGBitmapContextCreateの第一引数に自前のバッファを渡してる点。こうやってると
unsigned char* baseAddress = CGBitmapContextGetData(context);

 てふうに画像バッファの先頭アドレスを取り出せるみたい。
 画像バッファと画素は以下のような関係で、先頭アドレスから4バイトごとに横一列分の画素情報が並び、横の画素数分繰り返したら、縦の次のラインに移動して、また繰り返すという関係になってます。

$テン*シー*シー-13

 で、この1つの画素の色やアルファ値を変更してるのが、newImageBuffer関数のforループ内での
*p++ = 0; // RGBをすべて0:黒
*p++ = 0;
*p++ = 0;
*p++ = 255; // α値を最大:不透明度=100%

 って部分で、これは赤、緑、青の三原色の照度およびアルファ値、各0.0~1.0を1バイトで表現できる数値0~255(この値を取れるのがunsigned char型)として書き込んで画素を設定してる作業なわけです。
 ちなみに赤、緑、青の各256諧調の組み合わせが、よく言われるフルカラー1,600万色(= 256 x 256 x 256)なわけですね。

$テン*シー*シー-15

 なので、newImageBuffer関数のforループ内で
for (int h = 0; h < width; h++) {
unsigned char* p = baseAddress + (v * bytesPerRow) + h * 4;
*p++ = h;
*p++ = v;
*p++ = 0;
*p++ = 255; // α値を最大:不透明度=100%
}

 な~んてすると

$テン*シー*シー-14

 てな具合にチェック模様が出現するんですな。
 つまり、ここでやってる
unsigned char* p = baseAddress + (v * bytesPerRow) + h * 4;

 という計算と、そのpで示されるアドレスへの値の書き込みが
任意の画素を任意の色で塗り分ける機能

 になるわけです。

 ということで準備できたので、次回はいよいよrendering関数内でレイトレーシング。

------------
サンプルプロジェクト:opencl01.zip