Full BASICによる3Dグラフィックス

空間の射影変換

A=(aij)を4行4列の行列とするとき, 点( x,y,z )を行列演算




( x' y' z' c ) = ( x y z 1 )
(
a11 a12 a13 a14
a21 a22 a23 a24
a31 a32 a33 a34
a41 a42 a43 a44
)

により定まる点(x'/c, y'/c, z'/c)に対応させる変換を行列Aの定める射影変換という。

 射影変換は,合同変換,相似変換,アフィン変換を包含する概念である。
たとえば,x軸のまわりの角θの回転は,

(
1    0 0 0
0 cosθ sinθ 0
0 -sinθ cosθ 0
0 0 0 1
)

が定める射影変換であり,y軸のまわりの角θの回転は,

(
cosθ 0 -sinθ 0
0 1    0 0
sinθ 0 cosθ 0
0 0 0 1
)

が定める射影変換である。また,ベクトル(a,b,c)が定める平行移動は

(
1    0    0    0
0 1 0 0
0 0 1 0
a b c 1
)

が定める射影変換である。

<Note>
 Full BASICでは変換を行ベクトルの形で書く。そのため,行列Aが表す変換に続けて行列Bが表す変換を行う合成変換を表す行列はA*Bである。

絵定義の行列による変形

DRAW文で絵定義を呼び出すとき,WITH句に4行4列の行列を指定して変形を行うことができる。

AをDIM A(4,4)で宣言された4行4列の行列とするとき, DRAW a_pict with A を実行すると,絵定義に行列Aの定める変換が適用されて描かれる。 ただし,最終的に画面上に描画されるときには,z座標は無視される。
 DRAW文のWITH句には,4行4列の行列または組込みの変換関数の任意個の積を書くことができる。積は * で表す。

 組込みの変換関数は,たとえば,



ROTATE(α) =
(
cosα sinα 0 0
-sinα cosα 0 0
0 0 1    0
0 0 0 1
)


SHIFT(a,b) =
(
1    0 0 0
0 1    0 0
0 0 1    0
a b 0 1
)

のように定義されている。

 そこで,y座標をx座標に,z座標をy座標に,x座標をz座標に対応させる変換を表す行列



A =
(
0 0 1    0
1    0 0 0
0 1    0 0
0 0 0 1
)

と,x座標をy座標に,y座標をz座標に,z座標をx座標に対応させる変換を表す行列



B =
(
0 1    0 0
0 0 1    0
1    0 0 0
0 0 0 1
)

を用意すると,x軸のまわりの角θの回転を表す行列を,A*ROTATE(θ)*Bで求めることができる。


 次のプログラムの1000行以降に示す外部絵定義tetraは,底面をxy平面上にもち,底面の重心が原点で,点(1,0,0)に頂点をもつ正四面体(辺のみ)を描く(詳細は後述)。  このプログラムは,tetraをx軸のまわりに120°回転して描く。

100 DECLARE EXTERNAL PICTURE tetra
110 OPTION ANGLE DEGREES
120 DIM a(4,4), b(4,4)
130 MAT READ a
140 DATA 0,0,1,0
150 DATA 1,0,0,0
160 DATA 0,1,0,0
170 DATA 0,0,0,1
180 MAT READ b
190 DATA 0,1,0,0
200 DATA 0,0,1,0
210 DATA 1,0,0,0
220 DATA 0,0,0,1
230 SET WINDOW -2,2,-2,2
240 DRAW tetra WITH a*ROTATE(120)*b
250 END
260 !
1000 EXTERNAL PICTURE tetra
1010 OPTION ANGLE DEGREES
1020 PICTURE face
1030    PLOT LINES: 1,0; COS(120),SIN(120); COS(240),SIN(240); 1,0
1040 END PICTURE
1050 DIM ry(4,4)
1060 MAT ry=IDN                                          ! IDNは単位行列を表す
1070 LET ry(1,1)=1/3
1080 LET ry(1,3)=-SQR(8)/3
1090 LET ry(3,1)=SQR(8)/3
1100 LET ry(3,3)=1/3
1110 DRAW face
1120 DRAW face WITH SHIFT(1/2,0)*ry*SHIFT(-1/2,0)
1130 DRAW face WITH SHIFT(1/2,0)*ry*SHIFT(-1/2,0)*ROTATE(120)
1140 DRAW face WITH SHIFT(1/2,0)*ry*SHIFT(-1/2,0)*ROTATE(240)
1150 END PICTURE  

(注) 100行の DECLARE EXTERNAL PICTURE 文は,(仮称)十進BASICでは省略可。規格に合わせるために記述してある。

立体図形を描く絵定義

 Full BASICにはxy平面上の図形を描く描画命令しかないが,描画命令を絵定義の内側に描き,その絵定義を行列で変換すれば空間の点,線分,あるいは平面上の領域を描くことができる。
 上述の例でいえば,描画命令は1020〜1040行の内部絵定義faceに存在する。faceを空間に変換して描くことで正四面体を描く絵定義になる。

正四面体の辺のみを描く絵定義の作成

 上の例で,1020〜1040行の絵定義faceは,xy平面上に,重心が原点,一頂点が点(1, 0)の正三角形(底面)を描く。  正四面体の面角をαとすると,cosα=1/3,sinα=SQR(8)/3 であるので,y軸のまわりの角αの回転を表す行列



ry =
(
cosα 0 -sinα 0
0 1    0 0
sinα 0 cosα 0
0 0 0 1
)

を用意する。Full BASICのDATA文に計算式を書くことはできないので,1060〜1100行に示すように,一度,単位行列を代入して,単位行列と異なる成分は個別に値を与えて作成する。
 側面のうちの一つは,底面を直線x=-1/2について角αの回転を行えば得られる。この変換は,直線x=-1/2がy軸に移るように図形を平行移動してy軸のまわりで回転し,再度,y軸が直線x=-1/2に移る平行移動を行えば実現できる。(1120行)
 残りの2面は,この側面をz軸のまわりに120°,240°回転すれば得られる。

面を描く

 面を描く場合には,見える面と隠れて見えない面の識別が必要になる。
 正多面体のような凸多面体を描く場合には,面に表裏を定義しておいて,面の表が手前を向いている場合にかぎり面を描画する手法が使える。
 右手座標系を採用すると,z軸の正の向きは画面手前方向である(マイクロソフト社の標準は左手系のなので注意)。従って,面の表がz軸の正の向きを向いている面を描くことにすればよい。
 Full BASICには絵定義実行中に現在適用される変換を表す行列を取得する行列関数TRANSFORMが用意されている。それを利用して面の正の向きを表す法線ベクトルを変換した結果を配列に取得することができる。さらに,そのベクトルとある特定の方向ベクトル(光源の向き)との内積を計算して面の明度を変えて描くことができる。

正四面体を描く

 今回は,底面の1辺がx軸に平行で,頂点がz軸上にある正四面体を元に描く。
 x軸上に1辺があって頂点がy軸上にある一辺の長さ2の正三角形を描く絵定義

1210 EXTERNAL PICTURE face
1260    PLOT AREA: -1,0; 1,0; 0, SQR(3)  
1280 END PICTURE

を用意し,これを変換することで各面(4面)を作る。
 正四面体の側面が底面となす角の余弦は1/3であるので,cosα=1/3となる角αを求め,x軸のまわりにαだけfaceを回転して描けば側面の'もと'が得られる。ここでは,頂点がz軸上にあるようにしたいので,それをさらにy軸の負の向きにSQR(3)/3だけ平行移動する。そして,それをさらに120°,240°回転したものを描けば,3側面が完成する。
 底面は裏面が表になるので,faceを,一度,裏返ししてから,y軸の負の向きにSQR(3)/3だけ平行移動する。裏返しは,y軸に関する180°回転で実現する。

1000 EXTERNAL PICTURE tetra
1010 DECLARE EXTERNAL PICTURE face
1020 OPTION ANGLE DEGREES
1030 DIM rx(4,4),ry(4,4)
1040 MAT rx=IDN           ! rxはx軸のまわりのACOS(1/3)の回転
1050 LET a=ACOS(1/3)  ! 面角
1060 LET rx(2,2)=COS(a)
1070 LET rx(2,3)=SIN(a)
1080 LET rx(3,2)=-SIN(a)
1090 LET rx(3,3)=COS(a)
1100 MAT ry=IDN           ! ryはy軸のまわりの180°回転
1110 LET ry(1,1)=-1
1120 LET ry(3,3)=-1
1130 DRAW face WITH rx * SHIFT(0,-SQR(3)/3)              ! 側面
1140 DRAW face WITH rx * SHIFT(0,-SQR(3)/3) * ROTATE(120)
1150 DRAW face WITH rx * SHIFT(0,-SQR(3)/3) * ROTATE(240)
1160 DRAW face WITH ry * SHIFT(0,-SQR(3)/3) ! 底面
1170 END PICTURE

見えない面を隠す〜法線ベクトルの計算

 合同変換(あるいは相似変換)のみを行う場合には,法線ベクトル(0,0,1)が,描かれるときにどう変わるか計算すれば,その面がどちらを向いているか判断できる。ベクトル(0,0,1)が移る先のベクトルは,原点(0,0,0)と点(0,0,1)が移る先を計算し,差を計算すれば求まる。外部絵定義faceの実行時に次の副プログラムを呼び出せば,法線ベクトルが計算できる。なお,Full BASICでは配列値をとる関数は定義できないので,副プログラムの配列引数として結果を受け取る形で記述する。

1310 EXTERNAL SUB makeNormal(N())
1320 DIM m(4,4),A(4),B(4)
1330 MAT m=TRANSFORM
1340 MAT READ A
1350 DATA 0,0,1,1       ! 点(0,0,1)
1360 MAT A=A*M          
1370 MAT A=(1/A(4))*A   ! 点(0,0,1)が移る先
1380 MAT READ B
1390 DATA 0,0,0,1       ! 原点(0,0,0)
1400 MAT B=B*M
1410 MAT B=(1/B(4))*B   ! 原点が移る先
1420 MAT A=A-B          ! ベクトル(0,0,1)の変換結果
1430 LET N(1)=A(1)
1440 LET N(2)=A(2)
1450 LET N(3)=A(3)
1460 END SUB 

面の明度の決定

 法線ベクトルがわかれば,あらかじめ仮定した光源の向きに対してなす角を計算することもできる。その角に応じて面の明るさを変えれば,実在感のある立体が描ける。
 次の外部副プログラムは,法線ベクトルを引数として与えると,ベクトル(-1,1,1)と法線ベクトルとのなす角の余弦に比例した明るさに色指標8の色を変更する。なす角の余弦の計算に,内積を求めるFull BASICの組込み関数DOTを利用している。

1490 EXTERNAL SUB setBrightness(N())
1500 DIM A(3)
1510 MAT READ A      ! 光源の向き
1520 DATA -1,1,1
1530 LET s=DOT(A,N)/(SQR(DOT(A,A))*SQR(DOT(N,N)))
1540 LET s=(0.8*s+1)/2
1550 SET COLOR MIX(8) s,s,s
1560 SET AREA COLOR 8
1570 END SUB

面を描く絵定義の修正

 面を描く絵定義のなかで上述の2つの副プログラムを呼び出す(現在変形は,変形を受ける絵定義の実行中でのみ取得できる)。 そのため,外部絵定義faceを次のように修正する。

1200 EXTERNAL PICTURE face
1210 DECLARE EXTERNAL SUB makeNormal, setBrightness
1220 DIM N(3)
1230 CALL makeNormal(N)
1240 IF N(3)>0 THEN     ! 外側が手前なら面を描く
1250    CALL setBrightness(N)
1260    PLOT AREA: -1,0; 1,0; 0, SQR(3)  
1270 END IF 
1280 END PICTURE


<補足>
 絵定義tetraのなかで法線ベクトルを計算することができないわけではない。たとえば,

1150 DRAW face WITH rx * SHIFT(0,-SQR(3)/3)             

の実行で描かれる面の法線ベクトルは,

    DIM m(4,4),n(3)
    MAT m=TRANSFORM
    MAT m=rx * SHIFT(0,-SQR(3)/3) * m

とすれば求まる。

射影変換

 我々が目で見たのと同じように立体を描くためには,近いものを大きく,遠いものを小さく描く必要がある。それを実現するのが射影変換である。
 z軸上の点(0, 0, t)から点(x, y, z)を見たとき,それをxy平面上に射影すると, 点( x/(1-z/t), y/(1-z/t), 0)に移る。









この変換は,結果のz座標は無視することにすると,行列

(
1    0 0 0
0 1    0 0
0 0 1    -1/t
0 0 0 1
)

の定める射影変換である。

 射影変換では,平面の法線ベクトルが変換された平面の法線ベクトルに移るとは限らない。そこで,平面上の2つのベクトルが移る先を求め,それらの外積を求めることで平面の法線ベクトルを計算する。

100 ! 正四面体
110 DECLARE EXTERNAL PICTURE tetra
120 OPTION ANGLE DEGREES
130 LET z0=10      ! 視点
140 DIM p(4,4)         ! 点 0,0,z0を中心とする射影               
150 MAT p=IDN
160 LET p(3,4)=-1/z0
170 LET theta0=70
180 REM z軸のまわりにtheta0回転し,
190 REM x軸のまわりにt°回転したtetraを,
200 REM 点0, 0, 10から見たように描く。
210 SET WINDOW -2, 2, -2, 2
220 FOR t=0 TO -100 STEP -0.1
230    DIM rotx(4,4)      ! x軸のまわりの回転
240    MAT rotx=IDN
250    LET rotx(2,2)=COS(t)
260    LET rotx(2,3)=SIN(t)
270    LET rotx(3,2)=-SIN(t)
280    LET rotx(3,3)=COS(t)
290    SET DRAW mode hidden
300    CLEAR
310    DRAW tetra WITH ROTATE(theta0) * rotx * p
320    SET DRAW mode explicit
330    WAIT DELAY 0.01
340 NEXT t
350 END 
360 !
980 ! 正四面体を描く
990 ! 底面をxy平面上に置き,底面の重心を原点とする。
1000 EXTERNAL PICTURE tetra
1010 DECLARE EXTERNAL PICTURE face
1020 OPTION ANGLE DEGREES
1030 DIM rx(4,4),ry(4,4)
1040 MAT rx=IDN           ! rxはx軸のまわりのACOS(1/3)の回転
1050 LET a=ACOS(1/3)  ! 面角
1060 LET rx(2,2)=COS(a)
1070 LET rx(2,3)=SIN(a)
1080 LET rx(3,2)=-SIN(a)
1090 LET rx(3,3)=COS(a)
1100 MAT ry=IDN           ! ryはy軸のまわりの180°回転
1110 LET ry(1,1)=-1
1120 LET ry(3,3)=-1
1130 DRAW face WITH rx * SHIFT(0,-SQR(3)/3)              ! 側面
1140 DRAW face WITH rx * SHIFT(0,-SQR(3)/3) * ROTATE(120)
1150 DRAW face WITH rx * SHIFT(0,-SQR(3)/3) * ROTATE(240)
1160 DRAW face WITH ry * SHIFT(0,-SQR(3)/3) ! 底面
1170 END PICTURE
1180 !
1190 ! 面を描く
1200 EXTERNAL PICTURE face
1210 DECLARE EXTERNAL SUB makeNormal, setBrightness
1220 DIM N(3)
1230 CALL makeNormal(N)
1240 IF N(3)>0 THEN     ! 外側が手前なら面を描く
1250    CALL setBrightness(N)
1260    PLOT AREA: -1,0; 1,0; 0, SQR(3)  
1270 END IF 
1280 END PICTURE
1290 !
1300 ! 変換された座標系における法線ベクトルを求める
1310 EXTERNAL SUB makeNormal(N())
1320 DIM m(4,4),A(4),B(4),C(4)
1330 MAT m=TRANSFORM
1340 MAT READ A
1350 DATA 0,0,0,1
1360 MAT READ B
1370 DATA 1,0,0,1
1380 MAT READ C
1390 DATA 1,1,0,1
1400 MAT A=A*M
1410 MAT B=B*M
1420 MAT C=C*M
1430 MAT A=(1/A(4))*A
1440 MAT B=(1/B(4))*B
1450 MAT C=(1/C(4))*C
1460 MAT REDIM A(3)
1470 MAT REDIM B(3)
1480 MAT REDIM C(3)
1490 MAT A=B-A
1500 MAT B=C-B
1510 MAT N=CROSS(A,B)
1520 END SUB 
1530 !
1540 ! 法線ベクトルの向きから面の明るさを決定する
1550 EXTERNAL SUB setBrightness(N())
1560 DIM A(3)
1570 MAT READ A      ! 光源の向き
1580 DATA -1,1,1
1590 LET s=DOT(A,N)/(SQR(DOT(A,A))*SQR(DOT(N,N)))
1600 LET s=(0.8*s+1)/2
1610 SET COLOR MIX(8) s,s,s
1620 SET AREA COLOR 8
1630 END SUB


<Note>
 290行と320行のSET DRAW MODE命令は十進BASICの独自拡張であるが,削除しても動作する。
 また,1510行の配列値関数CROSSは外積を求める十進BASIC独自の拡張関数である。外積を計算する副プログラムに置き換えることでJIS合致プログラムにすることができる。

図形を自由に回転させる

 主プログラムを次のように変更すると,マウスを画面上で縦方向にドラッグしたときx軸のまわりに図形を回転させ,横方向にドラッグするとy軸のまわりに図形を回転させる。
 ただし,480行で用いているMOUSE POLL文は(仮称)十進BASIC独自の拡張で,JIS Full BASICとの互換性はない。(JIS付属書の範囲まで許せば,単文字入力文CHARACTER INPUT NOWAITで文字キーあるいは数字キーを使うことで代用できる)
<補足>Linux,Intel MACではMOUSE POLL文は動作が緩慢で,スムーズに動作しません。

100 ! 正四面体
110 DECLARE EXTERNAL PICTURE tetra
120 OPTION ANGLE DEGREES
130 LET z0=20          ! 視点
140 DIM p(4,4)         ! 点 0,0,z0を中心とする射影               
150 MAT p=IDN
160 LET p(3,4)=-1/z0
170 LET theta0=45      ! z軸のまわりの回転角
180 LET t=-60          ! x軸のまわりの回転角初期値
190 LET s=0            ! y軸のまわりの回転角初期値
200 REM z軸のまわりにtheta0回転し,
210 REM x軸のまわりにt°回転,
220 REM y軸のまわりにs°回転したtetraを
230 REM 点(0, 0, z0) から見たように描く。
240 REM 画面上でマウスを横方向にドラッグすると図形をy軸のまわりに回転させ,
250 REM マウスを縦方向にドラッグすると,図形をx軸のまわりに回転させる。
260 DIM m(4,4)
270 MAT m=ROTATE(theta0)
280 SET WINDOW -2, 2, -2, 2
290 DO 
300    DIM rotx(4,4)      ! x軸のまわりの回転
310    MAT rotx=IDN
320    LET rotx(2,2)=COS(t)
330    LET rotx(2,3)=SIN(t)
340    LET rotx(3,2)=-SIN(t)
350    LET rotx(3,3)=COS(t)
360    DIM roty(4,4)      ! y軸のまわりの回転
370    MAT roty=IDN
380    LET roty(1,1)=COS(s)
390    LET roty(1,3)=-SIN(s)
400    LET roty(3,1)=SIN(s)
410    LET roty(3,3)=COS(s)
420    MAT m=m * rotx * roty 
430    SET DRAW mode hidden
440    CLEAR
450    DRAW tetra WITH m * p
460    PLOT TEXT, AT -2,-2: "マウスでドラッグすると回転。右クリックで終了"
470    SET DRAW mode explicit
480    MOUSE POLL x,y,l,r
490    IF r<>0 THEN EXIT DO
500    LET t=0
510    LET s=0
520    IF l<>0 THEN    
530       LET t=-20*(y-y0)
540       LET s= 20*(x-x0)
550    END IF 
560    LET x0=x
570    LET y0=y
580 LOOP
590 END 

3次元描画のもうひとつの手法は,SAMPLEフォルダの3DPLOT.BASを参照してください。


戻る