【3D 描画 Step3】 - Infty Project

【3D 描画
Step3】元素名を 2D 描画する
OpenGL には文字描画の概念がないこと、元素名は回転してほしくないことの 2 点の理由から、元素
名は 2D 描画する。そのために、原子の 3 次元空間の座標から、文字を描画するウィンドウ上の座標を求
める。
<座標変換について>
※座標変換について、まとまった詳しい解説は「OpenGL による 3D 描画の基礎知識」の 3 ページ以
降に記述してあるので、参照のこと。
y軸
104.45°
θ
z軸
x軸
ワールド座標系の軸
◆オブジェクト座標系とワールド座標系
O 原子も H 原子も描画するときは
glut.glutSolidSphere(0.2f, div, div); ………(*)
と記述する。引数は順に半径、Z 軸まわりの分割数、Z 軸に沿った分割数で、球の中心座標を設定する引
数はない。これは、常にこの関数が球の中心を原点として描画するからである。この場合の座標系が「オ
ブジェクト座標系」となる。
しかし、図のように実際にワールド座標系で考えれば O 原子の中心座標は (0, 0.4, 0) だし、右側の
H 原子は (cosθ, 0.4-sinθ, 0) [※θ=(180°-104.45°)/2] である。このような位置に描画するため
には、描画したい球の中心が原点になるよう事前に glTranslatef や glRotatef で座標変換しておく。
例えば、O 原子の場合、
gl.glTranslatef(0.0f, 0.4f, 0.0f);
// y 軸正方向に 0.4 平行移動
θに相当する
を実行した後で(*)の球の描画命令を書けばよい。また、右側の H 原子は
上記の 0.4 の平行移動が利いた状態でさらに以下を実行する
gl.glRotatef(-degree, 0.0f, 0.0f, 1.0f); // 座標軸を z 軸周りに-degree 度回転
gl.glTranslatef(1.0f, 0.0f, 0.0f);
// x 軸正方向に 1 だけ平行移動
-1-
このような、回転や平行移動による座標変換の情報は
「ModelView 行列」
が持っている。ModelView 行列は、gl.glPushMatrix()でいったん保存しておき、回転や平行移動を重ね
て描画した後、gl.glPopMatrix()で保存した状態に戻すことができる。
◆ワールド座標系とウィンドウ座標系
3 次元空間であるワールド座標から、2 次元の表示領域に対する座標をもとめるには、
「Projectioin 行列」
を使う。この行列は、3D からどのように 2D に投影するかを決めるものである。
また、パネルに対する表示領域を「ビューポート」といい、4 次元の配列で表現する。
ビューポート=[左下の点の x 座標, y 座標, 幅, 高さ]
◆オブジェクト座標系からウィンドウ座標系への変換
OpenGL には便利な関数があって、
glu.gluProject(objx, objy, objz, modelMatrix, 0, projMatrix, 0, viewport, 0, winPos, 0);
objx, objy, objz:3D 空間の x 座標、y 座標、z 座標(オブジェクト座標系)
modelMatrix:ModelView 行列
projMatrix:Projection 行列
viewport:ビューポート
を呼び出すと winPos(double の配列)にビューポートの左下を原点とする、右・上が正方向の 2 次元
ピクセルの単位での座標がはいる。つまりオブジェクト座標系からウィンドウ座標系に変換してくれる。
しかし、描画するにはパネルの左上を原点とした、下向きが正の座標でなければならないので、今回
は上記の関数で求めた座標をさらに変換する。下記のソースコードはこの変換部分が空欄になっている
ので、下図を参考に考えて埋めなさい。
描画で必要な
座標の原点
viewport[2]
viewport[0]
ビューポート
viewport[3]
glu.gluProject で求まる
座標の原点
viewport[1]
<作成手順>
1.WaterPanel.java を編集する。
2.実行して元素名 O,H が正常に描画されることを確認する
-2-
getHeight()
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
WaterPanel.java
package chemical.water;
import
import
import
import
import
import
import
java.awt.Color;
java.awt.Font;
java.awt.Graphics;
java.awt.Point;
java.awt.event.MouseEvent;
java.awt.event.MouseListener;
java.awt.event.MouseMotionListener;
import
import
import
import
import
javax.media.opengl.GL;
javax.media.opengl.GLAutoDrawable;
javax.media.opengl.GLEventListener;
javax.media.opengl.GLJPanel;
javax.media.opengl.glu.GLU;
import com.sun.opengl.util.GLUT;
public class WaterPanel extends GLJPanel implements GLEventListener,
MouseListener, MouseMotionListener {
private
private
private
private
GL gl;
// OpenGL の関数群をもつオブジェクトその1
GLU glu;
// OpenGL の関数群をもつオブジェクトその2
GLUT glut;
// OpenGL の関数群をもつオブジェクトその3
int div = 20;
// 球、円柱の分割数
// 軸と O-H のなす角(※水の O-H がなす角は 104.45 度)
private float degree = (180-104.45f)/2.0f;
private
private
private
private
float rotx = 0.0f, roty = 0.0f;
float sRotx, sRoty;
Point startPoint, endPoint;
boolean defaultFlg = true;
//
//
//
//
回転量
ドラッグ開始時の回転量
ドラッグの開始点と終了点
true だったら回転量を 0 にする
private
private
private
private
private
private
double[] model = new double[16];
// ModelView 行列
double[] proj = new double[16];
// Projection 行列
int[] view = new int[4];
// ビューポート
double[] pointO = new double[3];
// O 原子のウィンドウ座標
double[] pointH1 = new double[3]; // H 原子 1 のウィンドウ座標
double[] pointH2 = new double[3]; // H 原子 2 のウィンドウ座標
/* 光 ----------------------------------------------------*/
// 光の位置 {x 座標, y 座標, z 座標, 光源までの距離(0 は無限円)}
private float[] lPosition = { -10.0f, 10.0f, 10.0f, 0.0f };
// 光の色 {R, G, B, アルファ(透明度)} ※数値は 0 から 1 まで
private float[] lSpecular = { 0.8f, 0.8f, 0.8f, 1.0f };
// 鏡面
private float[] lDiffuse = { 0.8f, 0.8f, 0.8f, 1.0f };
// 拡散
private float[] lAmbient = { 0.4f, 0.4f, 0.4f, 1.0f };
// 環境
/* 物体の反射率 -------------------------------------------*/
// {R, G, B, アルファ(透明度)} ※数値は 0 から 1 まで
private float[] mSpecular = { 0.3f, 0.3f, 0.3f, 1.0f };
// 鏡面
private float[] mDiffuse = { 0.2f, 0.2f, 0.2f, 1.0f };
// 拡散
// 鏡面係数:きらめきの度合い(0~128)
private float mShininess = 10.0f;
/* 色の定義 -----------------------------------------------*/
// {R, G, B, アルファ(透明度)} ※数値は 0 から 1 まで
private float[] blue = { 0.0f, 0.0f, 1.0f, 1.0f };
// 青
private float[] red = { 1.0f, 0.0f, 0.0f, 1.0f };
// 赤
private float[] green = {0.0f, 1.0f, 0.5f, 1.0f };
// 緑
/**
* コンストラクタ
-3-
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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
WaterPanel.java
*/
public WaterPanel () {
// MouseEvent を受け取れるようにする
addMouseListener(this);
// MouseMotionEvent を受け取れるようにする
addMouseMotionListener(this);
// 3D 描画できるようにリスナ登録
addGLEventListener(this);
}
/* 2D 描画 ************************************************************/
@Override
public void paintComponent(Graphics g) {
// 親クラスの paintComponent 呼び出し
super.paintComponent(g);
// フォント設定 ※Dialog は Windows では MS ゴシック
g.setFont(new Font("Dialog", Font.BOLD, 24));
// 白色設定
g.setColor(Color.WHITE);
// 文字位置の微調整用
int tune = 8;
// 元素名を描画
g.drawString("O", (int)pointO[0]-tune, (int)pointO[1]+tune);
g.drawString("H", (int)pointH1[0]-tune, (int)pointH1[1]+tune);
g.drawString("H", (int)pointH2[0]-tune, (int)pointH2[1]+tune);
}
/* 3D 描画 ************************************************************/
/**
* x 軸周りに円柱を描画
* @param r 円柱の半径
* @param length 円柱の長さ
* @param offset offset ≦x≦offset+length に描かれる
*/
public void drawCylinder(float r, float length, float offset) {
double t;
// 要素として連続する四角形を使う
gl.glBegin(GL.GL_QUAD_STRIP);
for (int i=0; i<=div; i++) {
// 角度を計算(ラジアン)
t = 2.0 * Math.PI * i / div;
// 光の反射の法線ベクトル
gl.glNormal3f(0, (float)Math.cos(t), (float)Math.sin(t));
// 頂点を設定
gl.glVertex3f(offset, (float)(r*Math.cos(t)), (float)(r*Math.sin(t)));
gl.glVertex3f(offset+length, (float)(r*Math.cos(t)), (float)(r*Math.sin(t)));
}
gl.glEnd();
}
/**
* オブジェクト座標から、ウィンドウ座標を求める
*/
public boolean gluProject(double objx,
double objy,
double objz,
double[] modelMatrix,
double[] projMatrix,
int[] viewport,
double[] winPos ) {
/* gluProject:3D空間のx座標,y座標,z座標,ModelView行列,Projection行列,ビューポートを使って
* ウィンドウ座標を求める。
* ただし、ビューポート(描画領域)の左下が原点、y 座標上向き */
-4-
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
WaterPanel.java
boolean bl = glu.gluProject(objx, objy, objz,
modelMatrix, 0, projMatrix, 0, viewport, 0, winPos, 0);
winPos[0] += viewport[0];
// パネルの左上を原点とする y 座標下向きに変換
// ※viewport の中身は[左下の x 座標, y 座標, ビューポート(描画領域)の幅, 高さ]
winPos[1] = getHeight() - viewport[1] - winPos[1];
return bl;
}
/**
* GLEventListener のメソッド:3D 描画メソッド
*/
@Override
public void display(GLAutoDrawable drawable) {
/* deaultFlg=true なら最初かリセットボタンが押された状態
* なので、回転量を 0 にするs */
if (defaultFlg) {
rotx = 0.0f;
roty = 0.0f;
defaultFlg = false;
}
// 背景色で塗りつぶし
gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
// 現時点のマトリクス(Modelview 行列)を保存 ※必ず glPopMatrix()と対で使用する
/* glPushMatrix と glPopMatrix()は、座標を回転したり平行移動したり
* したものを、する前の状態を覚えておくために使う。
* これを使わないと、回転などがどんどん重なっていってしまう。 */
gl.glPushMatrix();
// z軸上 15 の位置から原点を見る,上方向は y 軸
// glulookat(視点の位置 x,y,z, 視点から見る座標 x,y,z, 上方向ベクトル x,y,z)
glu.gluLookAt(0.0, 0.0, 15.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
// マウスの移動量に応じて回転
gl.glRotatef(rotx, 1.0f, 0.0f, 0.0f);
gl.glRotatef(roty, 0.0f, 1.0f, 0.0f);
// y 軸正方向に少しずらす
gl.glTranslatef(0.0f, 0.4f, 0.0f);
/* 棒 1 を描画(z 軸周りに-degree 度回転)----------*/
// ModelView 行列保存
gl.glPushMatrix();
// 色指定
// ※環境光の反射率とは、つまり物体の色になる
gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT, green, 0);
// 座標軸を z 軸周りに-degree 度回転
// ※2,3,4 番目の引数は回転軸を表す(順に x, y, z)
gl.glRotatef(-degree, 0.0f, 0.0f, 1.0f);
// 半径 0.07 の円柱を 0≦x≦1 に描画
drawCylinder(0.07f, 1.0f, 0.0f);
// 行列を保存した時の状態に戻す
gl.glPopMatrix();
/* 棒 2 を描画(z 軸周りに degree 度回転)----------*/
gl.glPushMatrix();
gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT, green, 0);
gl.glRotatef(degree, 0.0f, 0.0f, 1.0f);
drawCylinder(0.07f, 1.0f, -1.0f); // -1≦x≦0
gl.glPopMatrix();
/* O 原子を原点に描画 -------------------------*/
gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT, red, 0);
// 塗りつぶしの球を描画
// glutSolidSphere(半径, Z 軸まわりの分割数, Z 軸に沿った分割数)
glut.glutSolidSphere(0.3, div, div);
-5-
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
WaterPanel.java
// ModelView 行列を取得
gl.glGetDoublev(GL.GL_MODELVIEW_MATRIX, model, 0);
// O 原子のウィンドウ座標を取得
gluProject(0.0, 0.0, 0.0, model, proj, view, pointO);
// H 原子 1 を描画 -----------------------------*/
gl.glPushMatrix();
gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT, blue, 0);
gl.glRotatef(-degree, 0.0f, 0.0f, 1.0f);
gl.glTranslatef(1.0f, 0.0f, 0.0f);
// x 方向に 1 だけ平行移動
glut.glutSolidSphere(0.2, div, div);
gl.glGetDoublev(GL.GL_MODELVIEW_MATRIX, model, 0); // ModelView 行列を取得
gluProject(0.0, 0.0, 0.0, model, proj, view, pointH1);
gl.glPopMatrix();
// H 原子 2 を描画 -----------------------------*/
gl.glPushMatrix();
gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT, blue, 0);
gl.glRotatef(degree, 0.0f, 0.0f, 1.0f);
gl.glTranslatef(-1.0f, 0.0f, 0.0f);
// x 方向に-1 だけ平行移動
glut.glutSolidSphere(0.2, div, div);
gl.glGetDoublev(GL.GL_MODELVIEW_MATRIX, model, 0); // ModelView 行列を取得
gluProject(0.0, 0.0, 0.0, model, proj, view, pointH2);
gl.glPopMatrix();
// glPushMatrix()を呼ぶ前の行列の状態に戻す
gl.glPopMatrix();
}
/**
* GLEventListener のメソッド:表示の切り替えが発生した場合に呼ばれる
* 今回は特に記述することはない
*/
@Override
public void displayChanged(GLAutoDrawable drawable, boolean modeChanged,
boolean deviceChanged) {
}
/**
* GLEventListener のメソッド:初期化時に呼ばれる
*/
@Override
public void init(GLAutoDrawable drawable) {
// OpenGL の関数群をもつオブジェクトを取得
gl = drawable.getGL();
glu = new GLU();
glut = new GLUT();
// 背景色を黒に設定
gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
// 影に隠れて見えないものは表示しない
gl.glEnable(GL.GL_DEPTH_TEST);
// 光を有効にする
gl.glEnable(GL.GL_LIGHTING);
gl.glEnable(GL.GL_LIGHT0);
// 光の反射の法線ベクトルを正規化
gl.glEnable(GL.GL_NORMALIZE);
// 光の位置を設定
/* 4 番目の数字はオフセット。
* JOGL は c++のソースを JAVA に自動変換している。
* c++では配列はポインタなので、オフセットの概念がある
* JAVA にポインタはないので引数が増やされているのだが、
* オフセットの数字は 0 でよい。 */
gl.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, lPosition, 0);
// 光の色を設定
-6-
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
WaterPanel.java
gl.glLightfv(GL.GL_LIGHT0, GL.GL_SPECULAR, lSpecular, 0); //
gl.glLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, lDiffuse, 0);
gl.glLightfv(GL.GL_LIGHT0, GL.GL_AMBIENT, lAmbient, 0);
// 物体の反射率を設定
gl.glMaterialfv(GL.GL_FRONT, GL.GL_SPECULAR, mSpecular, 0); //
gl.glMaterialfv(GL.GL_FRONT, GL.GL_DIFFUSE, mDiffuse, 0); //
gl.glMaterialf(GL.GL_FRONT, GL.GL_SHININESS, mShininess); //
鏡面
// 拡散
// 環境
鏡面
拡散
鏡面係数
}
/**
* GLEventListener のメソッド:表示領域が変更されたときに呼ばれる
*/
@Override
public void reshape(GLAutoDrawable drawable, int x, int y, int width,
int height) {
// ビューポート(描画領域)を設定
gl.glViewport(0, 0, width, height);
// マトリクスとして Projection 行列を対象にする
gl.glMatrixMode(GL.GL_PROJECTION);
// マトリクスを単位行列で初期化
gl.glLoadIdentity();
// 視点の設定(参考資料を参照)
// gluPerspactive(視野角, 縦横比, near, far);
glu.gluPerspective(10.0, (double)width/(double)height, 1.0, 100.0);
// マトリクスとして ModelView 行列を対象にする
gl.glMatrixMode(GL.GL_MODELVIEW);
// Projection 行列取得
gl.glGetDoublev(GL.GL_PROJECTION_MATRIX, proj, 0);
// ビューポート取得
gl.glGetIntegerv(GL.GL_VIEWPORT, view, 0);
}
/* MouseListener のメソッド **************************************************/
@Override
public void mouseClicked(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public void mouseEntered(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public void mouseExited(MouseEvent e) {
// TODO Auto-generated method stub
}
/**
* マウスを押したらスタートポイントに設定
*/
@Override
public void mousePressed(MouseEvent e) {
// マウスを押した場所を取得
startPoint = e.getPoint();
// 現時点での回転量を保存
sRotx = rotx;
sRoty = roty;
}
-7-
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
WaterPanel.java
@Override
public void mouseReleased(MouseEvent e) {
// TODO Auto-generated method stub
}
/* MouseMotionListener のメソッド *************************************************/
/**
* ドラッグで回転量を計算
*/
@Override
public void mouseDragged(MouseEvent e) {
// マウスの場所を取得
endPoint = e.getPoint();
// 回転量を計算(パネルの端から端で一回転)
rotx = sRotx + 360.0f*(float)(endPoint.y - startPoint.y)/(float)getWidth();
roty = sRoty + 360.0f*(float)(endPoint.x - startPoint.x)/(float)getHeight();
// 再描画
repaint();
}
@Override
public void mouseMoved(MouseEvent e) {
// TODO Auto-generated method stub
}
/* defaultFlg の setter ***************************************************************/
public void setDefaultFlg(boolean defaultFlg) {
this.defaultFlg = defaultFlg;
}
}
-8-