three.jsでカメラとの距離を無視して描画順序を制御する方法

f:id:shogonir:20170723013117p:plain

 

目次

  1. この記事の目的
  2. 描画順序を制御する方法
  3. 描画処理のソースコード
  4. ソースコード
  5. まとめ

 

1. この記事の目的

この記事では、カメラからの距離を無視して描画順序を制御する方法を紹介します。
今回は例として、二つのオブジェクトを空間に配置し、描画順序を切り替えます。
この際、オブジェクトのポジションは変更しないものとします。

サンプルコードをGitHubにあげました。
※flow typeを導入していますのでソースコードはピュアなJSではありません

github.com

 

2. 描画順序を制御する方法

描画順序を制御するには、シーンを複数使用します。
シーンを複数準備し、深度を無視して順番に重ねて描画していきます。

 

3. 描画処理のソースコード

描画に関する重要なポイントを抽出したソースコードを下記に記します。
 

let renderer = new THREE.WebGLRenderer({ canvas: this.canvas });
renderer.setClearColor(0x000000);
renderer.autoClear = false;

let camera = new THREE.PerspectiveCamera(45, this.canvasWidth / this.canvasHeight, 0.1, 1000);

let enemyScene = new THREE.Scene();
let wallScene = new THREE.Scene();
let scenes = [
  this.enemyScene,
  this.wallScene
];

let self: CanvasController = this;
scenes.forEach((scene) => {
  renderer.clearDepth();
  renderer.render(scene, camera);
});

 
まずrenderer.autoClearをfalseにすることで、renderer.render()を実行した際に、画面のクリアをさせないようにします。
こうすることで、1フレームに複数のシーンを重ねて描画することができます。

 

もう1点は、renderer.render()の直前にrenderer.clearDepth()することです。
clearDepth()することで、それまでの描画物より手前に描画を行うことができます。

 

4. 全ソースコード

ソースコード書いてみて動かない場合などは下記のコードを参照してください。
flow typeありで実装したので注意してください。

 

// @flow

import * as THREE from 'three';

export default class CanvasController {

  canvas: HTMLCanvasElement;
  canvasWidth: number;
  canvasHeight: number;

  renderer: THREE.WebGLRenderer;
  camera: THREE.PerspectiveCamera;
  frameCount: number;

  enemyScene: THREE.Scene;
  wallScene: THREE.Scene;
  scenes: Array<THREE.Scene>;

  constructor(canvasId: string) {
    let mayBeCanvas: ?HTMLCanvasElement = document.getElementById(canvasId);
    if (mayBeCanvas == null || mayBeCanvas.getContext == null) {
      throw new Errpr('not canvas');
    }
    this.canvas = mayBeCanvas;
    this.canvasWidth = this.canvas.clientWidth;
    this.canvasHeight = this.canvas.clientHeight;

    this.renderer = new THREE.WebGLRenderer({ canvas: this.canvas });
    this.renderer.setClearColor(0x000000);
    this.renderer.autoClear = false;

    this.camera = new THREE.PerspectiveCamera(45, this.canvasWidth / this.canvasHeight, 0.1, 1000);
    this.camera.position.set(0, 0, 10);
    this.frameCount = 0;

    this.enemyScene = new THREE.Scene();
    this.wallScene = new THREE.Scene();
    this.scenes = [
      this.enemyScene,
      this.wallScene
    ];

    let cubeGeometry: THREE.Geometry = new THREE.CubeGeometry(2, 2, 2);
    let cubeMaterial: THREE.Material = new THREE.MeshBasicMaterial({ color: 0xFF0000 });
    let cube: THREE.Mesh = new THREE.Mesh(cubeGeometry, cubeMaterial);
    this.enemyScene.add(cube);

    let wallGeometry: THREE.Geometry = new THREE.PlaneGeometry(2, 2);
    let wallMaterial: THREE.Material = new THREE.MeshBasicMaterial({ color: 0x0000FF });
    let wall: THREE.Mesh = new THREE.Mesh(wallGeometry, wallMaterial);
    wall.position.set(-1, 0, 2);
    this.wallScene.add(wall);

    requestAnimationFrame(this.update.bind(this));
  }

  update() {
    this.frameCount++;
    if (this.frameCount % 60 === 0) {
      this.scenes = [
        this.wallScene,
        this.enemyScene
      ];
    }
    else if (this.frameCount % 30 === 0) {
      this.scenes = [
        this.enemyScene,
        this.wallScene
      ];
    }

    this.renderer.clear();

    let self: CanvasController = this;
    this.scenes.forEach((scene: THREE.Scene) => {
      self.renderer.clearDepth();
      self.renderer.render(scene, self.camera);
    });

    requestAnimationFrame(this.update.bind(this));
  }
}

 

5. まとめ

カメラとの距離を無視して描画順序を制御する方法を紹介しました。
使いどころとしては、ゲームで壁のむこうの敵を透視したいときなどでしょうか。

自分が描画順序で検索した際に、renderDepthがよくひっかかったため記事を書きました。
Object3d.renderDepthはnumber型のパラメータで、この値が大きいものから描画されるそうです。
もしかしたら小さいものから描画だったかもしれません。

renderDepthはカメラとの距離よりも優先される感じではないので、今回ご紹介した方法がいい時もあります。