講義プリント

CG プログラミング論 平成 26 年 6 月 30 日
第11章.隠面消去
【学習のねらい】
① 前回はワイヤーフレーム図形の描画を学習しました。これは図形の稜線を全て描画す
るため、当該視点からは本来は見えない部分も描画してしまいます。そこで今回は、
本来は見えない線(隠線)や面(隠面)を判定し、それを描かない処理を学習します。
これを隠線・隠面消去と言います。これにより、よりリアルな 3 次元図形を描くこと
ができます。
11-1
ワイヤーフレーム図形の欠点
3次元図形を2次元平面上に投影して図を描く場合に、データとして与えられた線分を
全て描くと、例えば立方体の場合は下の左図のようになります。この場合、
(該当する視点
から見える)本来の図形が右上なのか右下なのか識別できません。
?
さらに、線分の数が増えてくると形状の把握自体が困難となって来ます。実際、皆も【基
礎課題 10-1】で視点を色々と変えて表示させた時に、
(簡単な三角錐でも)その形状を判断
する事が困難になる事に気づいたと思います。このように、立体図形を表示するのにはワ
イヤーフレーム表現だけでは不十分なのです。そこで、隠れた面(隠面)を消去する(表
示しない)操作、すなわち隠面消去が必要になって来ます。
11-2
隠面消去処理の方法
ある視点から一つの図形を見た場合、その面が見えるかどうかを判定する簡便な方法と
して、面の法線ベクトルと視線ベクトルの間の角度を用いる方法があります。ベクトルと
言うと難しく感ずる人がいるかもしれませんが、原理は極めて簡単なことです。まず、あ
115
CG プログラミング論 平成 26 年 6 月 30 日
る面に垂直なベクトルを法線ベクトルと言います。ベクトルとは、大きさと向きを持った量
と考えて下さい。具体的には、下の様に矢印で表されます。この矢印が各面の法線ベクト
ルです。
<面の法線ベクトル>
この法線ベクトルを用いると、ある視点からその面が見えるかどうかを判定する事ができ
ます。次の図を見て下さい。
視線ベクトル
<可視判定>
面A
面C
α
α
見える
面B
見える・見えないの境界
α
見えない
この図より、ある面の法線ベクトルと視線ベクトルとの間の角度αが 90°以上かどうかに
よって、その面が見えるかどうかを判定できることが分かります。一般に、次のように判
定できます。
cosα ≧0 その面は見える
cosα <0 その面は見えない
これは直感的に理解可能でしょう。ここでは、
(詳細はともかく)この原理を理解してお
けば十分です。
次ページの図は球面をポリゴン(四角形)に分割し、各ポリゴンの辺をそのまま描いた
図(左側)と、上述の可視判定を行って、「法線ベクトルが手前(視点側)を向いている面」
のみを描いた図(右側)です。
以下本章では、この球面を例に採り上げて、隠面処理を行うプログラムを作成する事に
します。
116
CG プログラミング論 平成 26 年 6 月 30 日
ポリゴンで表した球面
11-3
隠面処理を行った後の球面
ポリゴンによる球面の表現
今、半径rの球を考えましょう。この球面上の任意の点 P(x,y,z)の座標は、球の中心を原
点とすると次の式で与えられます。
x  r sin  sin 
y  r cos
z  r sin  cos
y
0  
P
0    2
r
z
x
ここに、角度θおよびφは下図で定義されるものです。
Y
r
Z
θ
X
φ
117
CG プログラミング論 平成 26 年 6 月 30 日
さて、球面を描くためには、θ方向およびφ方向を適当な間隔Δθ、Δφで刻んで行き、
任意の[(θ,φ)-(θ+Δθ,φ+Δφ)
]の領域で囲まれた四角形をポリゴンとして連
ねて行き多面体として描けば良いのです。
y
一つのポリゴン
(θ,φ)に対
(θ,φ+Δφ) に
応した座標
対応した座標
(θ+Δθ,φ)
に対応した
z
(θ+Δθ,φ+Δφ)
に対応した座標
座標
x
【応用課題 11-A】
上の考えに従って、次のように球面をポリゴンに分割して描くプログラムを作成しまし
ょう。隠面消去はこの次の課題で行います。
基本となるプログラムは【基礎課題 10-1】です。このプログラムをコピーして本課題用
プロジェクトとし、プログラムを開いた状態にして下さい。
118
CG プログラミング論 平成 26 年 6 月 30 日
<プログラム作成手順>
①
この課題用アプリケーションを開いた状態で、プロジェクトの中に新規クラスを
「Hidden3D」という名前で作成(追加)して下さい(要領は【基礎課題 10-1】と同じ
です)
。
② すでに、NewJFrame と MyPanel 及び WireFrame クラスは作成しているので、次の
ような状態になるはずです。この状態で、新規クラス「Hidden3D」を記述します。
package 名はそれぞれ各自のプロジェクトによって異なって結構です。適宜、自分の
パッケージ名に読み替えて下さい。
③ Hidden3D クラスを次ページのように記述します。
119
CG プログラミング論 平成 26 年 6 月 30 日
package ouyou8_7_a;
import java.awt.*;
パッケージ名は各自のプログラムに応じて
異なる事に注意。
public class Hidden3D {
double Vx,Vy,Vz;
double[][][] faceX;
double[][][] faceY;
double[][][] faceZ;
double[] x; //ポリゴンのx座標(ワールド座標系)
double[] y; //ポリゴンのy座標(ワールド座標系)
double[] z; //ポリゴンのz座標(ワールド座標系)
double[] Xv; //ポリゴンのx座標(視点座標系)
double[] Yv; //ポリゴンのy座標(視点座標系)
double[] Zv; //ポリゴンのz座標(視点座標系)
int Np,Np1,Np2; //ポリゴンの頂点数、角度θ、φ方向のメッシュ数
public Hidden3D() {
}
public void setData(double Vx,double Vy,double Vz,int Np
,int Np1,int Np2,double[][][] faceX
,double[][][] faceY,double[][][] faceZ) {
・・・
}
public void MyPaint(Graphics g) {
・・・
}
void zahyo_henkan() {
・・・
}
}
メソッド「setData」、
「MyPaint」そして「zahyo_henkan」の詳細は次ページ以降に示
しています。
※ 上の faceX、faceY そして faceZ という変数は、球面を構成する各ポリゴンの x 座標、
y座標そしてz座標を格納する 3 次元配列です。今、θ、φ方向をそれぞれ
{θ1,θ2,
・・・,θi,
・・・,θNp1}、{φ1,φ2,
・・・,φj,
・・・,φNp2}
という刻み(メッシュと言います)に区切ります。そして(θi,φj)で指定されるポリ
ゴンのk番目の頂点のx座標を
0
1
faceX[i][j][k], k=0~3
ポリゴン
3
に格納する様にします。
120
2
頂点の番号
CG プログラミング論 平成 26 年 6 月 30 日
<setData メソッド>
視点の座標(Vx,Vy,Vz)、各ポリゴンの頂点の数 Np(今の場合 4)、θ方向のメッシュ数(刻
み数)Np1、φ方向のメッシュ数 Np2、各(θ,φ)で指定されるポリゴンの頂点の座標を
格納した 3 次元配列 faceX、faceY、faceZ をメインプログラムから受け取るメソッドです。
public void setData(double Vx,double Vy,double Vz,int Np
,int Np1,int Np2,double[][][] faceX
,double[][][] faceY,double[][][] faceZ) {
this.Vx=Vx;
this.Vy=Vy;
this.Vz=Vz;
this.Np=Np;
this.Np1=Np1;
this.Np2=Np2;
this.faceX=faceX;
this.faceY=faceY;
this.faceZ=faceZ;
}
<MyPaint メソッド>
ここで、画像を描画します。θ方向、φ方向のメッシュ毎に各ポリゴンを描いています。
public void MyPaint(Graphics g) {
int[] xp=new int[Np];
int[] yp=new int[Np];
x=new double[Np]; //ポリゴンの各頂点のx座標
y=new double[Np]; //ポリゴンの各頂点のy座標
z=new double[Np]; //ポリゴンの各頂点のz座標
int xc=125; //描画中心のx座標
int yc=125; //描画中心のy座標
g.setColor(Color.blue);
for(int i=0;i<Np1;i++) {
for(int j=0;j<Np2;j++) {
for(int k=0;k<Np;k++) {
x[k]=faceX[i][j][k]; //ポリゴンの各頂点のx座標
y[k]=faceY[i][j][k]; //ポリゴンの各頂点のy座標
z[k]=faceZ[i][j][k]; //ポリゴンの各頂点のz座標
}
zahyo_henkan(); //ポリゴンの各頂点の座標を視点座標系へ変換
for(int k=0;k<Np;k++) {
xp[k]=xc+(int) Xv[k];
yp[k]=yc-(int) Yv[k];
}
g.drawPolygon(xp,yp,Np);
}
}
}
121
CG プログラミング論 平成 26 年 6 月 30 日
<zahyo_henkan メソッド>
ここで、座標変換を行い、ワールド座標系の座標から、視点座標系の座標(Xv,Yv,Zv)を
求めています。これは、10 章で作成したものと全く同じです。
void zahyo_henkan() {
double root_val, cos_alfa, sin_alfa, cos_beta, sin_beta;
double x0,y0,z0,x1,y1,z1,x2,y2,z2;
Xv=new double[Np]; //座標変換後のx座標(視点座標系でのx座標)
Yv=new double[Np]; //座標変換後のy座標(視点座標系でのy座標)
Zv=new double[Np]; //座標変換後のz座標(視点座標系でのz座標)
//回転移動に必要な諸量の計算
root_val = Math.sqrt(Vx*Vx+Vz*Vz);
if(Vx!=0 || Vz!=0) {
cos_alfa = Vz/root_val;
sin_alfa = Vx/root_val;
}
else { //(Vx=0 かつ Vz=0)の場合α回転をしない(α=0 とおく)
cos_alfa = 1;
sin_alfa = 0;
}
root_val = Math.sqrt(Vx*Vx+Vy*Vy+Vz*Vz);
cos_beta = Math.sqrt(Vx*Vx+Vz*Vz) / root_val;
sin_beta = Vy / root_val;
for ( int i=0; i<Np; i++ ) {
// 原点を視点に平行移動
x0 = x[i] - Vx;
y0 = y[i] - Vy;
z0 = z[i] - Vz;
// y 軸の回りにαだけ回転
x1 = x0*cos_alfa - z0*sin_alfa;
y1 = y0;
z1 = x0*sin_alfa + z0*cos_alfa;
// x 軸の回りにβだけ回転
x2 = x1;
y2 = y1*cos_beta-z1*sin_beta;
z2 = y1*sin_beta + z1*cos_beta;
// z 軸の反転(向きを逆にする)
Xv[i]=x2;
Yv[i]=y2;
Zv[i]=-z2;
}
}
122
CG プログラミング論 平成 26 年 6 月 30 日
④ NewJFrame.java に戻って、DrawGraphics メソッドを次のように記述します。コメ
ントからプログラムの意味は分かるでしょう。なお、ここでは球を構成するポリゴンの座
標を求める処理を、Sphere()という(新たに定義する)メソッドで行うようにしていま
す。現時点ではこのメソッドは未定義のため作成時にエラー(警告)が出ますが、それは
気にせずひとまずこの DrawGraphics の記述を完成させて下さい。そして、このプログ
ラム記述後、次の⑤でメソッド Sphere()を記述する事にします。
void DrawGraphics(Graphics g) {
// 視点の座標の定義
double Vx=Double.parseDouble(jTextFieldVx.getText());
double Vy=Double.parseDouble(jTextFieldVy.getText());
double Vz=Double.parseDouble(jTextFieldVz.getText());
double[][][] faceX=new double[100][100][4];
double[][][] faceY=new double[100][100][4];
double[][][] faceZ=new double[100][100][4];
int Np1=20 //θ方向のメッシュ数(刻み数)
int Np2=20 //φ方向のメッシュ数(刻み数)
int Np=4 //ポリゴンの頂点数
int r=100; //球の半径
Sphere(Np1,Np2,faceX,faceY,faceZ,r); //球を構成するポリゴンの座標
の計算
Hidden3D h3D= new Hidden3D();
h3D.setData(Vx,Vy,Vz,Np,Np1,Np2,faceX,faceY,faceZ); //描画に必
要なデータを設定する
h3D.MyPaint(g); //画像の描画
}
④
上の DrawGraphics()の定義の後に、次ページの様にメソッド Sphere()の記述を加
えて下さい。ここでは、各ポリゴン頂点の(x,y,z)座標を p.117 の式にしたがって計算し
ています。
123
CG プログラミング論 平成 26 年 6 月 30 日
void Sphere(int N_theta,int N_fai,double faceX[][][]
,double faceY[][][],double faceZ[][][],int r) {
double d_theta=Math.PI/N_theta; //角度θの増分(刻み幅)
double d_fai=2*Math.PI/N_fai; //角度φの増分(刻み幅)
double theta1,fai1,theta2,fai2;
double x[]=new double[4]; //四角形ポリゴンの x 座標
double y[]=new double[4]; //四角形ポリゴンの y 座標
double z[]=new double[4]; //四角形ポリゴンの z 座標
for(int i=0;i<N_theta;i++){
theta1=i*d_theta;
//角度θ1
theta2=(i+1)*d_theta; //角度θ2
y[0]=r*Math.cos(theta1);
y[1]=r*Math.cos(theta1);
y[2]=r*Math.cos(theta2);
y[3]=r*Math.cos(theta2);
for(int j=0;j<N_fai;j++) {
fai1=j*d_fai;
//角度φ1
fai2=(j+1)*d_fai;
//角度φ2
z[0]=r*Math.sin(theta1)*Math.cos(fai1);
z[1]=r*Math.sin(theta1)*Math.cos(fai2);
z[2]=r*Math.sin(theta2)*Math.cos(fai2);
z[3]=r*Math.sin(theta2)*Math.cos(fai1);
x[0]=r*Math.sin(theta1)*Math.sin(fai1);
x[1]=r*Math.sin(theta1)*Math.sin(fai2);
x[2]=r*Math.sin(theta2)*Math.sin(fai2);
x[3]=r*Math.sin(theta2)*Math.sin(fai1);
for (int k=0;k<4;k++) {
faceX[i][j][k]=x[k]; //(i,j)番目のポリゴンの k 番目の頂点のx座標
faceY[i][j][k]=y[k]; //(i,j)番目のポリゴンの k 番目の頂点のy座標
faceZ[i][j][k]=z[k]; //(i,j)番目のポリゴンの k 番目の頂点のz座標
}
}
}
}
完成したら、プログラムを実行し、p.118 の様に表示される事を確認してみて下さい。こ
こでは、視点座標(Vx,Vy,Vz)として(1,1,1)を指定しています。
※ この課題は、実行結果を確認後「実行結果を確認しました」と書いて送って下さい。
124
CG プログラミング論 平成 26 年 6 月 30 日
11-4
隠面消去
【応用課題 11-B】
-
「隠面消去」処理の追加
上の課題(
【応用課題 11-A】
)のプログラムを改良して、次のように隠面消去を行って描
画する様にしましょう。
ポイントは、Hiden3D クラス内に隠面消去処理を行うメソッドを追加する事です。隠面
消去の考え方は 11-2 節で説明した通りですが、それを実現する具体的な方法の説明は後回
しにして、まずプログラムを作成してみましょう(具体的な説明は p.128 の【解説】参照)
。
<プログラム作成手順>
① Hidden3D クラスの定義部分に戻って下さい。このクラス内に、各ポリゴンが見えるか
どうかの判定(可視判定)を行うメソッド Kashi_hantei()を追加します。プログラ
ムは次ページの通りです。場所は Hidden3D クラス内であればどこでも良いのですが、
例えば MyPaint()メソッドの後に記述しましょう。
125
CG プログラミング論 平成 26 年 6 月 30 日
boolean Kashi_hantei() {
double HousenV_x,HousenV_y,HousenV_z; //面の法線ベクトルの成分
double HousenV; //法線ベクトルの大きさ
double ViewV; //視線ベクトルの大きさ
double V1_x,V1_y,V1_z,V2_x,V2_y,V2_z;//面上のベクトル V1,V2 の成分
double cos_alfa; //cosα
// V1 ベクトルの定義
V1_x=x[3]-x[1];
V1_y=y[3]-y[1];
V1_z=z[3]-z[1];
// V2 ベクトルの定義
V2_x=x[2]-x[0];
V2_y=y[2]-y[0];
V2_z=z[2]-z[0];
// 面(ポリゴン)の法線ベクトルの定義
HousenV_x=V1_y*V2_z-V1_z*V2_y;
HousenV_y=V1_z*V2_x-V1_x*V2_z;
HousenV_z=V1_x*V2_y-V1_y*V2_x;
//法線ベクトルの大きさの定義
HousenV=Math.sqrt( HousenV_x*HousenV_x+HousenV_y*HousenV_y
+HousenV_z*HousenV_z);
//視線ベクトルの大きさの定義
ViewV=Math.sqrt(Vx*Vx+Vy*Vy+Vz*Vz);
// cosαの計算
cos_alfa=(HousenV_x*Vx+HousenV_y*Vy+HousenV_z*Vz)
/HousenV/ViewV;
if(cos_alfa>=0) {
return true;
}
else {
return false;
}
}
※
ソースから分かるとおり、このメソッドは論理型の値を持っています。そして、も
し、該当する面(ポリゴン)が見えるなら true、見えない場合は false という値を返し
ます。
126
CG プログラミング論 平成 26 年 6 月 30 日
② 続いて MyPaint()メソッドを次のように修正します。点線枠が修正部分です。つまり、
ある視点から当該ポリゴンが見える場合にのみそれを描く様にするのです。
public void MyPaint(Graphics g) {
int[] xp=new int[Np];
int[] yp=new int[Np];
x=new double[Np]; //ポリゴンの各頂点のx座標
y=new double[Np]; //ポリゴンの各頂点のy座標
z=new double[Np]; //ポリゴンの各頂点のz座標
int xc=125; //描画中心のx座標
int yc=125; //描画中心のy座標
g.setColor(Color.blue);
for(int i=0;i<Np1;i++) {
for(int j=0;j<Np2;j++) {
for(int k=0;k<Np;k++) {
x[k]=faceX[i][j][k]; //ポリゴンの各頂点のx座標
y[k]=faceY[i][j][k]; //ポリゴンの各頂点のy座標
z[k]=faceZ[i][j][k]; //ポリゴンの各頂点のz座標
}
if(Kashi_hantei()) { //面(ポリゴン)が見える場合
zahyo_henkan(); //ポリゴンの各頂点の座標を視点座標系へ変換
for(int k=0;k<Np;k++) {
xp[k]=xc+(int) Xv[k];
yp[k]=yc-(int) Yv[k];
}
g.drawPolygon(xp,yp,Np);
}
}
}
}
プログラムを完成したら実行し、p.125 の図の様に隠面消去がなされる事を確認して下さ
い。
※ この課題も、実行結果を確認後「実行結果を確認しました」と書いて送って下さい。
127
CG プログラミング論 平成 26 年 6 月 30 日
【解説】
可視判定処理の実現
ある面が特定の視点から見えるかどうかの可視判定は p.116 で述べた要領で行えます。こ
れをもう少し具体的に記述すると、次の 4 つのステップからなると言えます。

1.面の法線ベクトル V N を求める(英語名の Normal Vector から、N をつけました)。

2.視線ベクトル V E を求める(Eye から E をつけました)
。
3.法線ベクトルと視線ベクトルの間の角度αから cosαを求める。
4.cosα ≧0
→
その面は見える、
cosα <0
→
その面は見えない
により可視
判定を行う。
新たに付加したメソッド Kashi_hantei()で行っているのはこの処理に他なりません。
ただ、実際に cosαを求めるためには、ベクトルに関する基礎知識が必要になります。そ
こで、以下に、上の処理に必要になる必要最小限度の基礎知識を整理しておきましょう。
<整理-ベクトルの基礎知識>
1.ベクトルとは、向きと大きさを持った量である、と端的に定義しておきましょう。で
すからベクトルは矢印で表現できます。

2.あるベクトル V は、矢印の始点を原点にとったときの終点(矢印の先)の座標(Vx,Vy,Vz)
で指定できます。
Z

V (Vx ,V y ,Vz )
終点
始点
X
Y
3.一般に、ある平面に垂直な方向、つまり法線ベクトルの方向はその面上の任意の2つ


のベクトル( V1 , V 2 )で指定できます。具体的には法線ベクトルの各成分は次のように
表されます。
(VN ) x  (V1 ) y (V2 ) z  (V1 ) z (V2 ) y

VN
(VN ) y  (V1 ) z (V2 ) x  (V1 ) x (V2 ) z

V1
(VN ) z  (V1 ) x (V2 ) y  (V1 ) y (V2 ) x
128
法線ベクトル

V2
CG プログラミング論 平成 26 年 6 月 30 日
4.ここで、次の式で定義される、二つのベクトルの内積を導入します。
 
 
V1 V2 | V1 || V2 | cos

V1
(1)

2
2
2
| Vi | (Vi ) x  (Vi ) y  (Vi ) z , i  1,2

V2
(2)
α
また、内積は各成分を用いると次のように表すこともできます。
 
V1 V2  (V1 ) x (V2 ) x  (V1 ) y (V2 ) y  (V1 ) z (V2 ) z
(3)
すると、(1)と(3)より cosαが次のように求まります。
 
 
cos  V1 V2 / | V1 || V2 |
(4)
メソッド Kashi_hantei()では、

* 上の3の手続きによって面(ポリゴン)の法線ベクトル V N を求め、

* それと視線ベクトル V E との内積を計算し(4)式を用いて cosαを求め、
* 可視判定を行う。
という処理を行っているのです。
129