カテゴリー別アーカイブ: CG

Squeak から OpenGL を利用する


 最近は携帯機器でも 3D グラフィクスが楽しめるようになってきました。そうなるとやはり自分の手でも作ってみたくなる物です。そして出来れば自分の好きな開発環境で使う事が出来ればなお嬉しいものです。ということで、Squeak からでも OpenGL を簡単に扱う事が出来るので方法を紹介したいと思います。なお、環境としてはまっさらな Squeak 4.4 を MacOS X で動かしています。

OpenGL を使えるように拡張する

 実は拡張方法は Squeak 上に情報があります。 “Help” メニューから “Extending the system” を選択します。
Extending the system
 そうすると “How to extend the system” というウィンドウが表示されます。ここには様々なプログラムが書かれているので、(必要に応じて編集するなどして) “Do it” すればシステムを拡張することが出来ます。まずは FFI(Foreign function interface) の拡張を導入します。私は Mac を使っているので、Windows と Unix 向けの記述を削除して、下記のような状態にして “Do it” します。
FFI
 インストールが終わったら続けて OpenGL 拡張を導入します。下記の部分を選択して “Do it” します。
OpenGL
 これで Squeak から、OpenGL が使えるようになります。

サンプルを動かしてみる

 それでは、サンプルを動かして動作を確認してみましょう。 Workspace で 下記のコードを “Do it” してみましょう。

OpenGL example

 下のように赤い四角形が回転する Morph が表示されるはずです(速くて良く分からないかもしれません)。クリックすると終了します。
OpenGL example
 これで、Squeak でも 3D グラフィクスプログラムを楽しむことが出来ます。

ポリゴンのプリミティブタイプの違いと使いどころ


OpenGL などの描画 API を使ってポリゴンを描画する時にプリミティブタイプを選択することになります。一般的には下図に示したようなプリミティブから選択することになります。左から「トライアングルリスト(triangle list)」、「トライアングルストリップ(triangle strip)」、「トライアングルファン(triangle fan)」です。

一番直感的なのはトライアングルリストだと思います。トライアングルストリップ、トライアングルファンに至ってはわざわざ選択する必要があるのかどうかもすぐには感じられません。とはいえ、描画手段が用意されているのですから何かしら使いどころはあるはずです。一体、それぞれの使いどころはどのようなところなのでしょうか?


トライアングルリスト

トライアングルリストはとにかく直感的で扱いやすいところが特徴です。その反面、「ポリゴン数 x 3」(x Byte 数)というその他のプリミティブに比べるとデータ量が多くなってしまうことが難点です。

トライアングルストリップ

トライアングルストリップは最初のポリゴンは3頂点分のデータが必要ですが、その後は1頂点分のデータ量で1ポリゴンを描画し続けることが出来ます。トライアングルリストと比べるとポリゴン数が増えれば増えるほど経済的に有利になります。また、同じポリゴン数であればトライアングルリストよりも描画効率が良くなります。そのため可能であればトライアングルストリップとして描画すると良いでしょう。

しかし、マテリアルなどの関係上どうしてもつなげられないポリゴンも出来てしまいます。そうした場合は、

  • 複数のトライアングルリストとしてまとめて描画する
  • 三角形一つのトライアングルストリップとして一つずつ描画する

とすることになります。

トライアングルファン

一番使いどころがなさそうなトライアングルファンですが、次のような場合には有用です。

  • 多角形ポリゴンを三角形ポリゴンに分割する
  • 三角形ポリゴンをある矩形領域に収まるように分割する

まずは「多角形ポリゴンを三角形ポリゴンに分割する」場合を考えてみます。なぜモデルデータにポリゴンを使うのかでも書きましたが、一般的には三角形ポリゴンでないと描画が出来ません。しかし、モデルデータとして多角形のデータが含まれている場合は三角形に分割して描画 API に渡すことになります。その際はトライアングルファンとして描画すれば、トライアングルリストなどで描画するよりも簡単に出来ます。

次に「三角形ポリゴンをある矩形領域に収まるように分割する」場合を考えます。下図の様な矩形領域にポリゴンを収めたいとします。ポリゴンと矩形の各辺との交点を結ぶと三角形から多角形になります。ここからは先ほどと同じでこの多角形を三角形に分割し、トライアングルファンとすれば良いです。

このようにプリミティブタイプには複数の種類が存在しますが、それぞれの特性を理解して場合によって使い分けると、効率的に処理することが出来ます。

Three.js でレンズフレア(THREE.LensFlare)を実装してみる


Three.js にはいろいろと役に立つクラスが用意されています。今回はそのうちの一つである THREE.LensFlare クラスについて紹介したいと思います。写真や映像に太陽等を写した時に印象的な光の模様が写っていることがあります。これをレンズフレアと言いますが、これを簡単に実現するためのクラスが THREE.LensFlare クラスです。

レンズフレアの素材を準備する

レンズフレア一つ一つの素になる画像を用意します。今回は以下のような 3 種類の画像を Gimp を使って作成しました。


それっぽい素材を作るコツとしては、同一形状で統一することです。今回のように丸なら丸だけ、六角形なら六角形だけというようにします。これは元々レンズフレアがレンズの性質によって発生するもので、同じレンズなら同じ形状のレンズフレアが発生するという仕組みがあるためです。

THREE.LensFlare

さっそく実装してみたコードを見てみましょう。 67 行目からがレンズフレアを作成している部分です。
69 行目から 71 行目で先ほど作成した画像ファイルをテクスチャとしてロードしています。
次に 73 行目で THREE.LensFlare クラスを使ってレンズフレアになる大元のオブジェクトを作成しています。引数は順番に

  • テクスチャ
  • 描画サイズ(-1 とすることでテクスチャのサイズになる)
  • 光源からの距離(0 から 1)
  • ブレンディング方法(光なので加算ブレンドが有効でしょう)

となっています。

次から作成した大元のオブジェクトに子のフレアを add メソッドで追加していきます。引数の最後に透明度の指定が追加されているだけで他は先ほどと同じです。

最後に 80 行目でシーンに追加しています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
<div id="div_canvas_1194" style="text-align:center;">
<script>
var camera, scene, renderer;
 
init();
initScene();
initLight();
initLensFlare();
animate();
 
function init() {
    scene = new THREE.Scene();
 
    camera = new THREE.PerspectiveCamera( 75, 1, 1, 10000 );
    camera.position.z = 1000;
    scene.add( camera );
 
    div_canvas = document.getElementById( 'div_canvas_1194' );
    renderer = new THREE.WebGLRenderer();
    renderer.setSize( 500, 500 );
	renderer.setClearColorHex( 0x000000, 1 );
 
    div_canvas.appendChild( renderer.domElement );
 
    trackball = new THREE.TrackballControls( camera, renderer.domElement );
}
 
function initScene() {
    material_green = new THREE.MeshLambertMaterial( { color: 0x00ff00, ambient: 0x00ff00, shading: THREE.FlatShading } );
    material_red = new THREE.MeshLambertMaterial( { color: 0xff0000, ambient: 0xff0000, shading: THREE.FlatShading } );
    material_blue = new THREE.MeshLambertMaterial( { color: 0x0000ff, ambient: 0x0000ff, shading: THREE.FlatShading } );
    material_white = new THREE.MeshLambertMaterial( { color: 0xffffff, ambient: 0xffffff, shading: THREE.FlatShading } );
 
    cubeGeometry = new THREE.CubeGeometry( 100, 100, 100 );
    cubeMesh = new THREE.Mesh( cubeGeometry, material_green );
    cubeMesh.position.y = 200;
    scene.add( cubeMesh );
 
    sphereGeometry = new THREE.SphereGeometry( 100, 16, 16 );
    sphereMesh = new THREE.Mesh( sphereGeometry, material_red );
    sphereMesh.position.x = -200;
    sphereMesh.position.y = 200;
    scene.add( sphereMesh );
 
    octahedronGeometry = new THREE.OctahedronGeometry( 100, 2 );
    octahedronMesh = new THREE.Mesh( octahedronGeometry, material_blue );
    octahedronMesh.position.x = 200;
    octahedronMesh.position.y = 200;
    scene.add( octahedronMesh );
 
    planeGeometry = new THREE.PlaneGeometry( 1000, 1000, 15, 15 );
    planeMesh = new THREE.Mesh( planeGeometry, material_white );
    planeMesh.position.y = -100;
    planeMesh.rotation.x = -90 * 2 * Math.PI / 360;
    scene.add( planeMesh );
}
 
function initLight()
{
	point_light = new THREE.PointLight( 0xffffff, 1, 0 );
	scene.add( point_light );
 
	ambient_light = new THREE.AmbientLight( 0x444444 );
	scene.add( ambient_light );
}
 
function initLensFlare()
{
    texture_00 = THREE.ImageUtils.loadTexture( "http://www.soft-syokunin.com/wp-content/uploads/lens_flare00.png" );
    texture_01 = THREE.ImageUtils.loadTexture( "http://www.soft-syokunin.com/wp-content/uploads/lens_flare01.png" );
    texture_02 = THREE.ImageUtils.loadTexture( "http://www.soft-syokunin.com/wp-content/uploads/lens_flare02.png" );
 
    lens_flare = new THREE.LensFlare( texture_02, 16, 0, THREE.AdditiveBlending, new THREE.Color( 0xffffff ) );
    lens_flare.add( texture_02, 32, 0.2, THREE.AdditiveBlending, new THREE.Color( 0xffffff ), 0.3 );
    lens_flare.add( texture_01, 32, 0.4, THREE.AdditiveBlending, new THREE.Color( 0xffffff ), 0.3 );
    lens_flare.add( texture_02, 48, 0.5, THREE.AdditiveBlending, new THREE.Color( 0xffffff ), 0.2 );
    lens_flare.add( texture_00, 64, 0.7, THREE.AdditiveBlending, new THREE.Color( 0xffffff ), 0.3 );
    lens_flare.add( texture_00, 96, 1, THREE.AdditiveBlending, new THREE.Color( 0xffffff ), 0.4 );
 
    scene.add( lens_flare );
}
 
function animate() {
    // note: three.js includes requestAnimationFrame shim
    requestAnimationFrame( animate );
    render();
}
 
function render() {
    trackball.update();
    renderer.render( scene, camera );
}
</script>
</div>

動作と実装

上記のソースコードの動作例が以下です。右ドラックでカメラを移動させたあとにカメラを動かすとそれと連動してレンズフレアが動きます。これだけ簡単に実装出来るだけでも驚きですが、けっこう動きも凝っています。例えば光源を遮るようにオブジェクトが重なるとレンズフレアも薄くなります。

この辺の実装を見る場合は

  • three.js/src/extras/plugins/LensFlarePlugin.js
  • three.js/src/extras/shaders/ShaderFlares.js

を参考にすると良いでしょう。シェーダを上手に使って実現しているのが分かります。