SCNRenderer では SCNScene 内で SKScene が描画されない

あるいは SceneKit 内で動画を再生する方法について

## 背景

大変暑い日が続きますね。我が家のパソコン部屋では、夜な夜な PUBG をやるために2台の PC がフル稼働しており、GPU から発される熱がとてつもない事になっています。そしてこの部屋にはクーラーが付いておらず、地獄の様相を呈しています。仕方なくクーラーがついている部屋に移動し、狭いながらも快適な環境になりました。
今回は、GLKViewController で GLKView に直接 OpenGL の命令で描画している既存コードに動画再生機能を追加したかったけどできなかったので仕方ない感じの対応をしたという内容です。

## 概要

SceneKit 内にテクスチャとして動画を貼り付けたい時、SpriteKit の SKVideoNode を使う方法があります。しかし、SceneKit を SCNView ではなく SCNSceneRenderer を使って描画していると SpriteKit が描画されませんでした。
色々試した結果、原因がわからなかったので仕方なく SCNView を GLKView の subview として追加することでこれを解決しました。

## SCNScene に追加できる動画の板ポリを作るコード

SKVideoNode をテクスチャとして貼り付けた板ポリの SCNNode を作成します。

```objc
- (void)viewDidLoad {
    ...

    SCNNode *videoNode = [self.class videoNodeWithURL:@"https://example.com/video.mp4"];
    [_scene.rootNode addChildNode:videoNode];
}

+ (SCNNode *)videoNodeWithURL:(NSURL *)url {
    CGSize videoSize = (CGSize) { 640, 480 };

    // 動画を再生する SKSScene を作成する
    SKScene *scene = [VideoScene sceneWithSize:videoSize];
    {
        SKVideoNode *videoNode = [SKVideoNode videoNodeWithURL:url];
        videoNode.position = CGPointMake(scene.size.width / 2.0, scene.size.height / 2.0);
        videoNode.yScale = -1.f;
        videoNode.size = videoSize;

        [scene addChild:videoNode];
        [videoNode play];
    }

    // 板ポリを作る
    SCNNode *node = [SCNNode node];
    SCNPlane *plane = [SCNPlane planeWithWidth:videoSize.width height:videoSize.height];
    plane.firstMaterial.diffuse.contents = scene; // scene をテクスチャとして追加する
    plane.firstMaterial.doubleSided = YES;
    node.geometry = plane;

    return node;
}
```

## SCNRenderer で既存の GLKView に描画するコード

3D が簡単に扱えてとても便利です。
しかし、上記で作った videoNode を scene に追加しても描画されません。

```objc
- (void)viewDidLoad {
    _context = [EAGLContext.alloc initWithAPI:kEAGLRenderingAPIOpenGLES2];
    self.view.context = _context;
    self.view.drawableDepthFormat = GLKViewDrawableDepthFormat24;

    _renderer = [SCNRenderer rendererWithContext:context options:nil];

    _scene = [SCNScene scene];
    _scene.paused = NO;

    _cameraNode = [SCNNode.alloc init];
    _cameraNode.camera = [SCNCamera.alloc init];
    [_scene.rootNode addChildNode:_cameraNode];

    SCNNode *ambientLightNode = [SCNNode node];
    ambientLightNode.light = [SCNLight light];
    ambientLightNode.light.type = SCNLightTypeAmbient;
    ambientLightNode.light.color = [UIColor colorWithWhite:0.67 alpha:1.0];
    [_scene.rootNode addChildNode:ambientLightNode];

    SCNNode *omniLightNode = [SCNNode node];
    omniLightNode.light = [SCNLight light];
    omniLightNode.light.type = SCNLightTypeOmni;
    omniLightNode.light.color = [UIColor colorWithWhite:0.75 alpha:1.0];
    omniLightNode.position = SCNVector3Make(0, 50, 50);
    [_scene.rootNode addChildNode:omniLightNode];

    _renderer.scene = _scene;
    _renderer.delegate = self;
    _renderer.pointOfView = _cameraNode;
}

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
    /* 既存の描画処理が色々 */

    [_renderer renderAtTime:0];
}
```

## 解決法: SCNView に描画する

SCNView を GLKView に追加します。サイズ・位置が同じになるようにします。

![スクリーンショット 2017-07-13 12.17.34.png](https://qiita-image-store.s3.amazonaws.com/0/31477/ac408425-d08f-8fba-cb75-85ea873b0ff4.png)

SCNRenderer を SCNView に入れ替えます。(同じ SCNSceneRenderer protocol を実装しているので置換するだけです)

```objc
- (void)viewDidLoad {
    _context = [EAGLContext.alloc initWithAPI:kEAGLRenderingAPIOpenGLES2];
    self.view.context = _context;
    self.view.drawableDepthFormat = GLKViewDrawableDepthFormat24;

    _scene = [SCNScene scene];
    _scene.paused = NO;

    _cameraNode = [SCNNode.alloc init];
    _cameraNode.camera = [SCNCamera.alloc init];
    [_scene.rootNode addChildNode:_cameraNode];

    SCNNode *ambientLightNode = [SCNNode node];
    ambientLightNode.light = [SCNLight light];
    ambientLightNode.light.type = SCNLightTypeAmbient;
    ambientLightNode.light.color = [UIColor colorWithWhite:0.67 alpha:1.0];
    [_scene.rootNode addChildNode:ambientLightNode];

    SCNNode *omniLightNode = [SCNNode node];
    omniLightNode.light = [SCNLight light];
    omniLightNode.light.type = SCNLightTypeOmni;
    omniLightNode.light.color = [UIColor colorWithWhite:0.75 alpha:1.0];
    omniLightNode.position = SCNVector3Make(0, 50, 50);
    [_scene.rootNode addChildNode:omniLightNode];

    _scnView.scene = _scene;
    _scnView.delegate = self;
    _scnView.pointOfView = _cameraNode;

    SCNNode *videoNode = [self.class videoNodeWithURL:@"https://example.com/video.mp4"];
    [_scene.rootNode addChildNode:videoNode];
}

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
    /* 既存の描画処理が色々 */

    // [_renderer renderAtTime:0]; これはもう必要ないです
}
```

これにて一件落着