CG プログラミング論 平成 26 年 7 月 28 日 第13章.補間曲線-ベジェ曲線 【学習のねらい】 ① 前回までの学習では、球を題材にして、それに隠面消去さらにはシェーディングを施 すことによって立体的に描く方法を学習しました。これら隠面消去およびシェーディ ングについては、一般に任意の形状の図形についても適用できます。しかし、(関数で 表現できない様な)任意の形状を描くためには、表面上の各点の座標(x,y,z)を一つ ずつ指定しなければなりません。複雑な図形の場合は、膨大な点の座標を与えなけれ ばならず、大変な労力がかかります。そこで、その労力を減らすため、幾つかの代表 的な点を与え、それらを適当な関数で結ぶ(補間する)事で任意の形状(曲線)を表 現する方法があります。今回は、CG の世界で最もよく用いられるベジェ曲線による一 般の曲線の近似方法を学習しましょう。 13-1 曲線の近似 これまで、円や楕円を描く方法は学習してきました。また、下の(c)の様に、円と楕円を 組み合わせて色々な曲線を描くことも可能です。 (a) (b) (c) しかし、これらの2次曲線(楕円や放物線、双曲線の総称です)を組み合わせて複雑な曲 線を描こうとする場合には、かなり細かく分解しなければうまく近似できなくなります。 実際、下図のような場合には、曲線を分解することがやっかいになります。 143 CG プログラミング論 平成 26 年 7 月 28 日 そこで、このような複雑な曲線をできる限り少ないデータで記述することができる方法 が色々と工夫されました。その中の一つがここで学習するベジェ曲線です。これは、もと もとフランスの自動車メーカー「ルノー社」の技術者ピエール・E・ベジェ(Pierre E.Bezier) が、自動車の設計のために考え出したものと言われています。その後、自動車のボディに 止まらず、様々な形状を表現するのに威力を発揮したため、CG の世界でも頻繁に用いられ るようになったのです。 13-2 ベジェ曲線による補間① ベジェ曲線では、制御点と呼ばれる複数の点によって、曲線の形が決まります。ここで は、制御点の個数が4である場合のベジェ曲線について学習します。 今、下のように P1~P4 の 4 つの点が与えられた時に、これを順次直線で結ぶと下のよう な図が描けます。 P2 (x2,y2) P3 (x3,y3) P1 (x1,y1) P4 (x4,y4) 一方、これら 4 点からベジェ曲線を定義して描いたものが、上の曲線です。このベジェ曲 線は次のようにして描けます。 144 CG プログラミング論 平成 26 年 7 月 28 日 平面上の4点 P1(x1,y1)、P2(x2,y2)、P3(x3,y3)、P4(x4,y4)が与えられたときに、これから定 義されるベジェ曲線は、tをパラメータとして次の様に表されます。 x (t ) x1 B1 (t ) x 2 B2 (t ) x3 B3 (t ) x 4 B4 (t ) 4 xi Bi (t ) i 1 y (t ) y1 B1 (t ) y 2 B2 (t ) y 3 B3 (t ) y 4 B4 (t ) 0≦t≦1 (1) 4 y B (t ) i 1 i i そして、ベジェ係数 Bi(t)は次のように与えられます。 B1 (t ) (1 t ) 3 B2 (t ) 3t (1 t ) 2 B3 (t ) 3t 2 (1 t ) (2) B4 (t ) t 3 パラメータtを、例えば、{0, 0.1, 0.20, 0.3, ・・・, 1.0}の様に適当な刻み幅で区切り、各tに おける(x,y)を(1)式に従って計算して、それら各点を結べばベジェ曲線を描くことができま す。なお、(1)、(2)式から分かるように、 t=0 の時: (x,y)=(x1,y1) t=1 の時: (x,y)=(x4,y4) となります。つまり、ベジェ曲線は 4 点からなる区間の両端の点は必ず通ります。 それでは、次の課題で p.144 のベジェ曲線を描いてみましょう。 【応用課題 13-A】 プログラムを起動して[描画] ボタンをクリックすると、次のよ うなベジェ曲線を、点を直線で結 んだものと同時に描画するプロ グラムを作成しましょう。 このプログラムは次のように なります。 145 CG プログラミング論 平成 26 年 7 月 28 日 void DrawGraphics(Graphics g) { double[] x={ 50, 0,250, 200 };//最初に与える点のx座標 ① double[] y={ 0, 150, 150,0 }; //最初に与える点のy座標 int x1,y1,x2,y2; int Nb=50; //一区間のメッシュ数 ② int Np=4; //最初に与える点の数 int Kukan=1; //ベジェ曲線で近似する区間の数:今の場合1③ double[] xb=new double[Kukan*(Nb+1)]; //ベジェ曲線上の各点のx座標 double[] yb=new double[Kukan*(Nb+1)]; //ベジェ曲線上の各点のy座標 int xc=10; //描画中心のx座標 ④ int yc=200; //描画中心のy座標 //ベジェ曲線の描画 g.setColor(Color.blue); Bezie(Kukan,Nb,x,y,xb,yb); ⑤//ベジェ曲線で近似した点 Xb,Yb の計算 x1=xc+(int) xb[0]; //描画開始点のx座標 y1=yc-(int) yb[0]; //描画開始点のy座標 for (int i=1;i<Kukan*(Nb+1);i++) { x2=xc+(int) xb[i]; y2=yc-(int) yb[i]; g.drawLine(x1,y1,x2,y2); x1=x2; y1=y2; } // 最初に与えた点の(直線による)連結 x1=xc+(int) x[0]; y1=yc-(int) y[0]; g.setColor(Color.red); for(int i=1;i<Np;i++) { x2=xc+(int)x[i]; y2=yc-(int)y[i]; g.drawLine(x1,y1,x2,y2); x1=x2; y1=y2; } } <解説> ① 4 点 P1~P4 の座標を与えています。 ② ベジェ曲線で用いるパラメータt{ 0≦t≦1 }の t の分割数(メッシュ数)を Nb とい う変数で与えています。ここでは分割数(メッシュ数)を 50 点としています。 ③ ベジェ曲線で近似する区間はここでは一つです。複数の区間をつなげる場合は、 【応 用課題 13-C】で扱います。 ④ ベジェ曲線では、P1~P4 の区間内の任意の座標( x(t),y(t) )をパラメータt{ 0≦t≦1 } で指定します(p.145 の(1)式参照)。そして、tを{t0,t1,t2,・・・,tNb}というように、刻 んでいます。そこで、パラメータ ti に対応する x(ti),y(ti)をそれぞれ、xb[i]、yb[i]と表 しています。 146 CG プログラミング論 平成 26 年 7 月 28 日 ⑤ ④で定義した xb[i]、yb[i]、 (i=0~Nb)を求めるメソッド Bezie()を呼び出してい ます。これは新たに(独自に)定義するメソッドです。このプログラム作成の後、下の 様に記述して下さい。この部分の記述時には(Bezie が未定義なので)警告が出ます。 上の記述が完成したら、ベジェ曲線上の座標 xb[i]、yb[i]、 (i=0~Nb)を求めるメソッド Bezie を次のように記述して下さい。場所は DrawGraphics()の前後いずれでも結構です。 void Bezie(int Kukan,int Nb,double[] x, double[] y ,double[] Xb,double[] Yb) { double[][] B=new double[4][Nb+1]; //ベジェ係数 Bi(t)の宣言 double Db=1.0/Nb; //区間内の刻み幅 double t; //パラメータt //ベジェ係数 Bi(t)の計算 for(int i=0; i<= Nb; i++) { t=Db*i; B[0][i]=(1-t)*(1-t)*(1-t); B[1][i]=3*t*(1-t)*(1-t); B[2][i]=3*t*t*(1-t); B[3][i]=t*t*t; } //ベジェ曲線上の座標(x(tm),y(tm))の計算 int m=0; for (int i=0;i<Kukan;i++) { for (int j=0;j<=Nb; j++) { Xb[m]=0; Yb[m]=0; for (int k=0;k<4;k++) { Xb[m]=Xb[m]+x[i*3+k]*B[k][j]; Yb[m]=Yb[m]+y[i*3+k]*B[k][j]; } m=m+1; } } } ベジェ係数 Bi(tj), ( i=0~3, j=0~Nb )を 2 次元配列 B[i][j]として表しています。それ 以外は、p.145 の(2)式および(1)式にしたがって、Bi(t)および ( x(t),y(t) )を計算しているだ けなので、内容は理解できるでしょう。 ※ この課題は、実行結果を確認後「実行結果を確認しました」と書いて送って下さい。 147 CG プログラミング論 平成 26 年 7 月 28 日 【応用課題 13-B】 上のプログラムにおいて、P1~P4 の座標をそれぞれ次の①~③のように変更しました。 このとき描かれるベジェ曲線は、それぞれ下図のいずれでしょうか。①~③それぞれに該 当する図の記号を答えて下さい。 ① P1(,x,y)=(50,0), P2(x,y)=(250,150), P3(x,y)=(0,150), P4(x,y)=(200,0) ② P1(,x,y)=(50,0), P2(x,y)=(0,150), P3(x,y)=(200,0), P4(x,y)=(250,150) ② P1(,x,y)=(50,0), P2(x,y)=(200,0), P3(x,y)=(0,150), P4(x,y)=(250,150) (A) (B) (C) 148 CG プログラミング論 平成 26 年 7 月 28 日 13-3 ベジェ曲線による補間① - 複数区間の場合 ベジェ曲線は4点で1つの曲線を近似するので、4点を超える場合には、図のように接 続を繰り返す事になります。その際、第3番目と第5番目の点の間に第4番目の点が位置 するようにしておくと、順次なめらかに接続されて行きます。 P2 P3 P1 P4 P7 P5 P6 ここで、点の個数と区間数の間には次の関係があることが分かります。 点の個数 = 3 × 区間数 + 1 【応用課題 13-C】 【応用課題 13-A】で作成したプログラムを用いて、次のような 7 点を補間するベジェ曲 線を描きましょう。 P2(0,100) P3(100,100) P1(0,0) P4(100,0) P7(250,0) P5(100,-100) P6(175,-125) 149 CG プログラミング論 平成 26 年 7 月 28 日 このプログラムは、 【応用課題 13-A】のプログラムにおいて、下の下線部を変更し、空欄 ①を埋めることで作成できます(メソッド Bezie はそのまま使えます)。任意の点の個数 Np の場合の区間数を与えるように①を埋めてプログラムを完成させて下さい。 void DrawGraphics(Graphics g) { double[] x={ 0,0,100,100,100,175,250 };//最初に与える点のx座標 double[] y={ 0,100,100,0,-100,-125,0 };//最初に与える点のy座標 int x1,y1,x2,y2; int Nb=50; //一区間のメッシュ数 int Np=7; //最初に与える点の数 int Kukan= ; //ベジェ曲線で近似する区間の数 ① double[] xb=new double[Kukan*(Nb+1)]; //ベジェ曲線上の各点のx座標 double[] yb=new double[Kukan*(Nb+1)]; //ベジェ曲線上の各点のy座標 int xc=10; //描画中心のx座標 int yc=200; //描画中心のy座標 //ベジェ曲線の描画 g.setColor(Color.blue); Bezie(Kukan,Nb,x,y,xb,yb); //ベジェ曲線で近似した点 Xb,Yb の計算 x1=xc+(int) xb[0]; //描画開始点のx座標 y1=yc-(int) yb[0]; //描画開始点のy座標 for (int i=1;i<Kukan*(Nb+1);i++) { x2=xc+(int) xb[i]; y2=yc-(int) yb[i]; g.drawLine(x1,y1,x2,y2); x1=x2; y1=y2; } // 最初に与えた点の(直線による)連結 x1=xc+(int) x[0]; y1=yc-(int) y[0]; g.setColor(Color.red); for(int i=1;i<Np;i++) { x2=xc+(int)x[i]; y2=yc-(int)y[i]; g.drawLine(x1,y1,x2,y2); x1=x2; y1=y2; } } ヒント 前ページの式より 区間数=(点の個数-1)/3 となります。今の場合、点の個数は Np で与えられています。 150 CG プログラミング論 平成 26 年 7 月 28 日 第14章.回転体の描画 【学習のねらい】 ① CG の世界では、前章で学習したベジェ曲線で表した曲線を(適当な軸を中心にして) 回転させることで立体的な図形を作成することがよく行われます。これは、大変有力 な方法なので、本講義の最後の単元として学習しておきましょう。 14-1 回転体の描画 回転体は、適当な回転軸の回りに外形となる曲線(輪郭線)を回転させて描きます。輪郭 線はベジェ曲線を用いて描きます。 y ②y軸を中心に回転させる。 ①ベジェ曲線を用いて輪郭線を描く。 x z 例えば下の左図は曲線上の点群をy軸に関して回転させたときに描かれる円形断面を表し ています。そしてこれを用いて隣接する円形断面間でポリゴン(四角形)を生成し、12 章 で学習したシェーディングを行なうと、右図が得られます。本章ではこういった立体図形 を 描 きま し ょ う。 151 CG プログラミング論 平成 26 年 7 月 28 日 さて、回転体の輪郭を与えるベジェ曲線上の点の各々は、下図のように回転体断面の半径 と高さの意味を持っています。例えば断面を xy 平面にとると、半径は x 座標の値、高さは y座標の値となります。このベジェ曲線上の各点(x,y)を、y軸を中心に回転すれば、回 転図形が得られます。 ベジェ曲線上の点 (2 次元) 回転角Φ 【応用課題 14-A】 【応用課題 12-A】のプ ログラムを改良して、回 転体を描くプログラムを 作成しましょう。 【応用課題 12-A】のプロ ジェクトをコピーしてそ れを利用します。 ここで作成するのは、左 のような図形です。隠面 処理およびシェーディン グは次の課題で行います。 まず、この描画図形に合 わせて、フレームを縦に 伸ばして下さい。 152 CG プログラミング論 平成 26 年 7 月 28 日 続いて、NewJFrame.java 内の DrawGraphics()を次のように修正します。下線と点線枠 内が【応用課題 12-A】からの変更部分です。 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[300][200][4]; double[][][] faceY=new double[300][200][4]; double[][][] faceZ=new double[300][200][4]; ① // ----( グラスの輪郭線データ )--------double[ ] x = { 100, 50, 10, 10, 10, 40, 70, 100, 110, 70, 60, 56, 52 }; double[ ] y = { 0, 6, 30, 70, 120, 140, 160, 180, 220, 270, 280, 290, 310 }; int Np3=13; //最初に与える点の数 int Nb=20; //ベジェ補間の一区間のメッシュ数 int Kukan=(Np3-1)/3; //区間数 int Np1=Kukan*(Nb+1),Np2=50,Np=4; KaitenTai(Np1,Np2,Kukan,Nb,x,y,faceX,faceY,faceZ); //回転体の座 標の計算 //光線ベクトルの定義 double RayX=Double.parseDouble(jTextFieldRayX.getText()); double RayY=Double.parseDouble(jTextFieldRayY.getText()); double RayZ=Double.parseDouble(jTextFieldRayZ.getText()); double P_a=0.3,P_d=1.0,P_s=1.0; //各成分の最大強度の設定 ② Hidden3D h3D= new Hidden3D(); //hdden3D オブジェクトの生成 h3D.setData(Vx,Vy,Vz,Np,Np1,Np2,faceX,faceY,faceZ); //描画に必 要なデータを設定する h3D.MyPaint(g); //画像の描画 } ここに、①は、 【応用課題 12-A】において Sphere()というメソッドを用いて球面上の(x,y,z) 座標を求めていた部分を、回転体の(x,y,z)座標を求める部分に置き換えたものです。そのた め、Sphere()ではなく新たに Kaitentai()というメソッドを定義してこれを呼び出して います。これは、xy 平面で与えた座標(x,y)を基に回転体の座標(faceX、faceY、faceZ)を 求めるメソッドです。これを次ページのように記述して下さい。 なお、②では、まだシェーディングを行わないので、Shading クラスではなく、Hidden3D クラスを用いています。 153 CG プログラミング論 平成 26 年 7 月 28 日 続いて、NewJFrame.java 内に新たなメソッド Kaitentai()を次ページのように記述 して下さい。 154 CG プログラミング論 平成 26 年 7 月 28 日 void KaitenTai(int N_Y,int N_Fai,int Kukan,int Nb ,double[] x,double[] y,double faceX[][][] ,double faceY[][][],double faceZ[][][] ) { double[][] B=new double[4][100]; double[] Xb=new double[N_Y]; double[] Yb=new double[N_Y]; double y1,y2,Fai1,Fai2; double d_Fai=2*Math.PI/N_Fai; double[] KaitenX=new double[4];//四角形ポリゴンのx座標 double[] KaitenY=new double[4];//四角形ポリゴンのy座標 double[] KaitenZ=new double[4];//四角形ポリゴンのz座標 Bezie(Kukan,Nb,x,y,Xb,Yb); //ベジェ曲線で補間する //回転体の各ポリゴンの座標 Bezie()は次ページで定義する。 for(int i=0;i<N_Y-1;i++) { y1=Yb[i]; y2=Yb[i+1]; KaitenY[0]=y2; KaitenY[1]=y2; KaitenY[2]=y1; KaitenY[3]=y1; for(int j=0;j<N_Fai;j++) { Fai1=j*d_Fai; Fai2=(j+1)*d_Fai; KaitenX[0]=Xb[i+1]*Math.cos(Fai2); KaitenX[1]=Xb[i+1]*Math.cos(Fai1); KaitenX[2]=Xb[i]*Math.cos(Fai1); KaitenX[3]=Xb[i]*Math.cos(Fai2); KaitenZ[0]=Xb[i+1]*Math.sin(Fai2); KaitenZ[1]=Xb[i+1]*Math.sin(Fai1); KaitenZ[2]=Xb[i]*Math.sin(Fai1); KaitenZ[3]=Xb[i]*Math.sin(Fai2); for (int k=0;k<4;k++) { faceX[i][j][k]=KaitenX[k]; //(i,j)番目のポリゴンのx座標 faceY[i][j][k]=KaitenY[k]; //(i,j)番目のポリゴンのy座標 faceZ[i][j][k]=KaitenZ[k]; //(i,j)番目のポリゴンのz座標 } } } } p.152 の図より、 回転体表面の座標(x,y,z)は、xy 平面上に描いたベジェ曲線の座標を(xb,yb) とすると、次のように与えられます。 X x xb cos y yb xb , / 2 x z xb sin φ Φ z 155 Z CG プログラミング論 平成 26 年 7 月 28 日 xy 平面上に描いたベジェ曲線の座標(xb,yb)を求めるメソッド Bezie()は【応用課題 13-A】 (p.147)と同様に次のように記述します。 void Bezie(int Kukan,int Nb,double[] x, double[] y ,double[] Xb,double[] Yb) { double[][] B=new double[4][Nb+1]; //ベジェ係数 Bi(t)の宣言 double Db=1.0/Nb; //区間内の刻み幅 double t; //パラメータt //ベジェ係数 Bi(t)の計算 for(int i=0; i<= Nb; i++) { t=Db*i; B[0][i]=(1-t)*(1-t)*(1-t); B[1][i]=3*t*(1-t)*(1-t); B[2][i]=3*t*t*(1-t); B[3][i]=t*t*t; } //ベジェ曲線上の座標(x(tm),y(tm))の計算 int m=0; for (int i=0;i<Kukan;i++) { for (int j=0;j<=Nb; j++) { Xb[m]=0; Yb[m]=0; for (int k=0;k<4;k++) { Xb[m]=Xb[m]+x[i*3+k]*B[k][j]; Yb[m]=Yb[m]+y[i*3+k]*B[k][j]; } m=m+1; } } } 156 CG プログラミング論 平成 26 年 7 月 28 日 次に、Hidden3D.java に移って、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=150; //描画中心のx座標 int yc=300; //描画中心の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]; //ポリゴンの各頂点のx座標 z[k]=faceZ[i][j][k]; //ポリゴンの各頂点のx座標 } // 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.152 のように描画されるか確認して下さい。 ※ この課題は、実行結果を確認後「実行結果を確認しました」書いて送ってください。 157 CG プログラミング論 平成 26 年 7 月 28 日 14-2 シェーディングの改良 【応用課題 14-B】 次に、隠面消去を行ってみましょう。 【応用課題 14-A】のプログラムで、Hidden3D.java の MyPaint()メソッドでコメントアウトした部分を復活させて下さい。 public void MyPaint(Graphics g) { ・・・ for(int i=0;i<Np1;i++) { for(int j=0;j<Np2;j++) { for(int k=0;k<Np;k++) { ・・・ } if(Kashi_hantei()) { zahyo_henkan(); //ポリゴンの各頂点の座標を視点座標系へ変換 for(int k=0;k<Np;k++) { xp[k]=xc+(int) xv[k]; この 2 行を復活させる。 yp[k]=yc-(int) yv[k]; } g.drawPolygon(xp,yp,Np); } } } } 隠面処理を復活させた後、プログラムを実行すると、隠面消去が施されます。ところが、 この場合、下図のように描画されるはずです。ワイングラスのグラスの内側が表示されて いませんね。 これは、隠面処理で消去されてしまったためです。 第 11 章で学習した隠面処理によると確かに該当す る面の外側は見えません。しかし、本来は面の内側 が見えるはずです。事情は次の通りです。 第 11 章(p.116)で学習した通り、下の様な状況 では面 C は見えませんが、裏面の面 D は見えます。 球面のように閉じた図形と違って、ワイングラスの ように開いた図形の場合は、裏面が見えることを考 慮しなければなり 面C ません。この点を 改良しましょう。 α 見えない 法線ベクトル 面D 見える 158 CG プログラミング論 平成 26 年 7 月 28 日 改良に進む前に、後の説明の都合上、これにシェーディングを施すようにしましょう。 DrawGraphics()メソッドの中で Hidden3D クラスを用いていた部分を、下の枠線部のよう に、Shading クラスを用いるように修正して下さい。 void DrawGraphics(Graphics g) { // 視点の座標の定義 ・・・ double RayY=Double.parseDouble(jTextFieldRayY.getText()); double RayZ=Double.parseDouble(jTextFieldRayZ.getText()); double P_a=0.3,P_d=1.0,P_s=1.0; //各成分の最大強度の設定 Shading shade= new Shading(); //shading オブジェクトの生成 shade.setData(Vx,Vy,Vz,Np,Np1,Np2,faceX,faceY,faceZ ,RayX,RayY,RayZ,P_a,P_d,P_s); //描画に必要なデータを設定する shade.MyPaint(g); //画像の描画 } もちろん、この段階ではシェーディングを行っても次のようにワイングラスの口の部分 が描画されないままです。 プログラムを作成したら実行し、上のように描画されることを確認して下さい。 ※ この課題は、実行結果を確認後「実行結果を確認しました」書いて送ってください。 159 CG プログラミング論 平成 26 年 7 月 28 日 さて、裏面が見えるようにするためには、次の様にします。 1. 可視判定で「見えない」と判定された場合、裏面について、第 12 章(p.133~134)で 説明した、ディフーズ成分の強度dおよびスペキュラー成分の強度sを計算する。 2. 裏面の法線ベクトルは表面のそれと逆向きになる。そこで、p.133 に示した法線ベクト ルと光源との間の角度α’d は次のようになる。 光源 VN 法線ベクトル VR αd 光線ベクトル 表面 α’d 裏面 裏面の法線ベクトル 3. このとき、 cosα’d=-cosαd となる。 4. 同様に、裏面に対する法線ベクトルと p.134 で示した VH ベクトルとの角度をα’s とす ると、cosα’s=-cosαs となる。 5. cosα’d と cosα’s を用いて、dおよびsを計算する。 この考えを用いて、次の【応用課題 14-C】で裏面を描けるように改良しましょう。 160 CG プログラミング論 平成 26 年 7 月 28 日 【応用課題 14-C】 【応用課題 14-B】のプログラムを改良しましょう。Shading.java に戻って、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=150; //描画中心のx座標 int yc=300; //描画中心のy座標 double intensity,Max_intensity; int rgb; Max_intensity=P_a+P_d+P_s; 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]; //ポリゴンの各頂点のx座標 z[k]=faceZ[i][j][k]; //ポリゴンの各頂点のx座標 } zahyo_henkan(); //ポリゴンの各頂点の座標を視点座標系へ変換 for (int k = 0; k < Np; k++) { xp[k] = xc + (int) xv[k]; yp[k] = yc - (int) yv[k]; } if(Kashi_hantei()) { ShadeCalc(); intensity=P_a+P_d*D_strength+P_s*S_strength;//目に入って来 る光の強度 rgb=(int) (255*intensity/Max_intensity); } else { ShadeCalc2(); intensity=P_a+P_d*D_strength+P_s*S_strength;//目に入って来 る光の強度 rgb=(int) (255*intensity/Max_intensity); } g.setColor(new Color(rgb, rgb, rgb )); g.fillPolygon(xp, yp, Np); } } } ここで、裏面を描くために新たに ShadeCalc2()を用意しています。これを次ページの ように記述して下さい。ShadeCalc()からの変更部分は、二つの枠で囲んだ部分のみです。 161 CG プログラミング論 平成 26 年 7 月 28 日 void ShadeCalc2() { double HousenV_x,HousenV_y,HousenV_z; //面の法線ベクトルの成分 double HousenV; //面の法線ベクトルの大きさ double V1_x,V1_y,V1_z,V2_x,V2_y,V2_z;//面上のベクトル V1,V2 の成分 double cos_alfad; //cosαd αd:法線ベクトルと光線ベクトルの間の角度 double HV_x,HV_y,HV_z; //H ベクトルの成分 double HV,RayV; //H ベクトル、光線ベクトルの大きさ double cos_alfas; //cosαs αs:法線ベクトルと H ベクトルの間の角度 // 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); //光線ベクトルの大きさ RayV=Math.sqrt(RayX*RayX+RayY*RayY+RayZ*RayZ); // 光線ベクトルと面(ポリゴン)の法線ベクトルとの内積 cos_alfad=(HousenV_x*RayX+HousenV_y*RayY+HousenV_z*RayZ) /HousenV/RayV; cos_alfad=-cos_alfad;//裏面に対する修正部分 // ディフューズ(Diffuse)成分強度の計算 D_strength=Math.max(0,cos_alfad); // スペキュラー(Specular)成分強度の計算 HV_x=Vx+RayX; HV_y=Vy+RayY; HV_z=Vz+RayZ; HV=Math.sqrt(HV_x*HV_x+HV_y*HV_y+HV_z*HV_z); cos_alfas=(HousenV_x*HV_x+HousenV_y*HV_y+HousenV_z*HV_z) /HousenV/HV; cos_alfas=-cos_alfas;//裏面に対する修正部分 S_strength=Math.pow(cos_alfas,40); } この修正の後に実行すると、次のようにワイングラスの口(裏面)部分がきちんと描かれ ます。 162 CG プログラミング論 平成 26 年 7 月 28 日 プログラムを作成したら実行し、上のように描画されることを確認して下さい。 ※ この課題は、実行結果を確認後「実行結果を確認しました」書いて送ってください。 さて、実はまだもう一つ改良すべき点が残っています。上のプログラムで、グラスの下か ら眺めるケースを試してみましょう。Vy=-1 に変更して描画して下さい。すると次のよう になるはずです。 163 CG プログラミング論 平成 26 年 7 月 28 日 これは少しおかしな描画になっています。本来であれば、次のように描画されなければな りません。 これは、手前のグラス表面の後に、本来は 視点から遠くにあって見えない(向こう 側)の面を描いてしまったために起こった 現象です。 164 CG プログラミング論 平成 26 年 7 月 28 日 このような問題はどの様に解決できるでしょうか?具体的に下図のような場合を考えてみ ましょう。この例の場合、面Cは、面Aに(全部、あるいは部分が)隠れて見えない面と なります。また、面 B の裏面も同様です。 法線ベクトル 面C 面B 視線ベクトル 面A このような場合、 「面を塗る順番を工夫する」ことによって、正しく描画することができま す。つまり、視点から見て奥の方にある面を先に描き、手前にある面を後から描くのです。 すると、遠方の面と手前の面が重なっていても、後から塗る(手前の)面が上書きされる ので、自然に正しく描画されることになります。すると、面の奥行きに従って、描画順序 の並べ替え(ソート)を行うことがポイントになります。 この考えにしたがって、次の【応用課題 14-D】でシェーディングを改良しましょう。 【応用課題 14-D】 【応用課題 14-C】のプログラムにおいて、Shading.java を開いて下さい。そして、 MyPaint()メソッドを次のように修正して下さい。下線部が修正部分で、点線枠部分が新 たに加えた部分です。 165 CG プログラミング論 平成 26 年 7 月 28 日 public void MyPaint(Graphics g) { int[] xp=new int[Np]; int[] yp=new int[Np]; ・・・ double intensity,Max_intensity; int[] rgb=new int[Np1*Np2]; //光の強度を格納する配列 Max_intensity=P_a+P_d+P_s; double[] dist=new double[Np1*Np2]; //視点から面までの距離 int[][] xp_sort=new int[Np1*Np2][Np]; int[][] yp_sort=new int[Np1*Np2][Np]; int No=0; 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]; //ポリゴンの各頂点のx座標 z[k]=faceZ[i][j][k]; //ポリゴンの各頂点のx座標 } zahyo_henkan(); //ポリゴンの各頂点の座標を視点座標系へ変換 for (int k = 0; k < Np; k++) { xp[k] = xc + (int) xv[k]; yp[k] = yc - (int) yv[k]; xp_sort[No][k]=xp[k]; yp_sort[No][k]=yp[k]; } ① dist[No]=zv[0]+zv[1]+zv[2]+zv[3]; //面から視点までの距離 if(Kashi_hantei()) { ShadeCalc(); intensity=P_a+P_d*D_strength+P_s*S_strength;//目に入って来 る光の強度 rgb[No]=(int) (255*intensity/Max_intensity); } else { ShadeCalc2(); intensity=P_a+P_d*D_strength+P_s*S_strength;//目に入って来 る光の強度 rgb[No]=(int) (255*intensity/Max_intensity); } No=No+1; } } ② SortFace(Np1*Np2,dist,xp_sort,yp_sort,rgb); //面の順序をソート for(int i=0;i<Np1*Np2;i++) { //視点から遠い順にシェーディングを行う g.setColor(new Color(rgb[i], rgb[i], rgb[i] )); g.fillPolygon(xp_sort[i], yp_sort[i], Np); } } 166 CG プログラミング論 平成 26 年 7 月 28 日 【プログラムの解説】 ① 視点から面までの距離 dist は、各点の距離の合計としています。 YV 0 zv[0] zv[1] 1 ZV zv[3] 3 2 zv[2] XV ② 視点からの距離が遠い順に描画面をソートするメソッドです。次のように記述して下 さい。ここに、選択ソートを使って、距離 dist が大きい順にソートしています。 void SortFace(int NMax,double[] dist,int[][] xp,int[][] yp ,int[] rgb) { int TempI,Pos; double TempD; int[] TempP=new int[Np]; for (int i=0;i<NMax-1;i++) { Pos=i; for (int j=i+1; j<NMax;j++) { if(dist[j] > dist[Pos] ) { Pos=j; } } TempD=dist[i]; dist[i]=dist[Pos]; dist[Pos] = TempD; TempP=xp[i]; xp[i]=xp[Pos]; xp[Pos] = TempP; TempP=yp[i]; yp[i]=yp[Pos]; yp[Pos] = TempP; TempI=rgb[i]; rgb[i]=rgb[Pos]; rgb[Pos] = TempI; } } 作成したら、プログラムを実行し、次ページの様に表示されることを確認して下さい。 167 CG プログラミング論 平成 26 年 7 月 28 日 ※ この課題は、実行結果を確認後「実行結果を確認しました」書いて送ってください。 168
© Copyright 2025 ExpyDoc