5.1 3Dプログラミング1

      5.1 3Dプログラミング1 はコメントを受け付けていません

それでは、ここからは3Dプログラミングを体験してみます。Processingの場合、3Dの機能は基本的なものが実装されていますが、工夫次第では、2Dではできないような表現が可能になるでしょう。

5.1.1 まずは立体を描画してみる

まずは、基本的な3Dモデルである立方体(cube)を表示、コントロールするところから始めます。
ここでは、processing標準の3D描画機能であるP3Dを使います。

まず、立方体を表示します【図5.1-a】。

2016_09_28_16_03
図5.1-a
リスト5.1-a
void setup() {
  //P3Dと書くことによって、3Dを使うことを明示する
  size(400, 400, P3D);
}
 
void draw() {
  background(0);
 
  translate(width/2, height/2);    //立体の中心を画面中央に移動
 
  //ラジアンで指定。Y軸に対して60度回転。2PI=360°なので、PI/3が60度
  rotateY(PI/3);
  box(150, 150, 150);    //150 x 150 x 150pxの立方体を描画
}

【リスト5.1-a】のコードを解説します。
サイズを宣言する際に、末尾に「P3D」を宣言します。

size(400, 400, P3D);

次に、「translate」を使って、中心点を移動します。

translate(width/2, height/2);

そして、Y軸を中心にして60度回転します。360°=2PIなので、60°=PI/3になります。

rotateY(PI/3);

最後に、150 x 150 x 150pxの立方体を描画します。

box(150, 150, 150);

5.1.2 x, y, z軸の理解

それでは、x, y, z軸の関係性を確認しましょう。3Dの場合はz軸が加わります。

coordinates
図5.1-b

z軸の値を変えてみましょう。一見立方体が小さくなったかのようですが、これは視点から遠ざかっているのです。

2016_09_28_16_05
図5.1-c
リスト5.1-b
void setup() {
  //P3Dと書くことによって、3Dを使うことを明示する
  size(400, 400, P3D);
}

void draw() {
  background(0);

  //立体の中心を画面中央に移動し、奥行きを200pxを奥にする
  translate(width/2, height/2, -200);

  //ラジアンで指定。Y軸に対して60度回転。2PI=360°なので、PI/3が60度
  rotateY(PI/3);
  box(150, 150, 150);    //150 x 150 x 150pxの立方体を描画
}

5.1.3 座標系の移動(translate)

前述のサンプルでtranslate()を使いましたが、実は「4.1 座標変換」で2Dグラフィック作成の際にも使っています。3Dプログラミングでは、物体の移動は座標変換が基本になります。【図5.1-d】のグラフィックを描画してみましょう。このサンプルではfor文を使ってboxを複製しています。

2016_09_28_16_09
図5.1-d

translate()を使って移動させるのですが、これはオブジェクト単体を移動するのではなく、空間全体を移動するのでしたね。ですから、複数のオブジェクトが画面上にある場合はすべて移動します。

次のようにtranslate()を繰り返すと、その座標は加算されます。描画されるたびに基準点が変わります。

リスト5.1-c
void setup() {
  size(400, 400, P3D);
}

void draw() {
  background(0);

  //立体の中心を画面中央に移動
  translate(width/2, height/2);

  //立方体を、30ピクセルごとに5個生成
  for (int i = 0; i < 5; i ++) {
    translate(30, 0);
    box(20, 20, 20);    //20 x 20 x 20pxの立方体を描
  }
}

5.1.4 座標系の回転(rotate)

次は回転です。回転はrotate()を使います。
rotate()も、translate()と同じように空間全体を回転します。回転方向は【図5.1-e】のようになります。

rotate_direction
図5.1-e

このサンプルはy軸を中心に回転しています【リスト5.1-d】。rotateX(), rotateZ()も使って、他の軸を基準にした回転も加えてみましょう。

2016_09_28_18_16
図5.1-f
リスト5.1-d
float theta = 0.0;    //角度

//2PIを360度で割っているのでradは度数法の1度。
float rad = TWO_PI/360.0;

void setup() {
  size(400, 400, P3D);
}

void draw() {
  background(0);

  //立体の中心を画面中央に移動
  translate(width/2, height/2);
  rotateY(theta);    //Y軸に対してangleの数値分だけ回転
  box(150, 150, 150);    //150x150x150pxの立方体を描

  theta += rad;    //時間を進める
  
  //1周期分終わったら原点に戻る
  if (theta > TWO_PI) theta = 0.0;
}

5.1.5 座標系の拡大縮小(scale)

もちろん、scale()も使えます。引数は1.0が等倍なので、2倍の場合には2.0になります。3Dの場合でもフェード効果は適用できるので、フェードをかけてみました。見え方がかなり変わります。

2016_09_28_18_15
図5.1-g
リスト5.1-e
float theta = 0.0;    //角度
 
//2PIを360度で割っているのでradは度数法の1度。
float rad = TWO_PI/360.0;
 
void setup() {
  size(400, 400, P3D);
  background(0);
  noStroke();
}
 
void draw() {
  fade(true);
 
  translate(width/2, height/2);    //立体の中心を画面中央に移動 
  rotateY(theta);    //Y軸に対してangleの数値分だけ回転
 
  //sin(theta)の計算結果はは-1.0~1.0なので、+1,0で0.0~2.0になる
  scale(sin(theta) + 1.0);
 
  fill(255);
  box(150, 150, 150);    //150 x 150 x 150pxの立方体を描
 
  theta += rad;    //時間を進める
  if (theta > TWO_PI) theta = 0.0;    //1周期分終わったら原点に戻る
}
 
//フェード用関数
void fade(boolean _fadeFlag) {
  if (_fadeFlag) {
    fill(0, 10);  //透明度のあるrectを描画
    rect(0, 0, width, height);
  } else {
    background(0);
  }
}

5.1.6 座標系の保存

次に、3Dグラフィックスにおける必須の概念である、座標系の保存(pushMatrix, popMatrix)です。実はこれも「4.1.5 pushMatrix, popMatrix(座標系の保存と呼び出し)」で体験しています。

・pushMatrix → 現在の座標系を保存する
・popMatrix → 保存した座標系を再展開する

でしたね。

それでは、このpushMatrix()とpopMatrix()を使って複製されたboxの座標を指定してみます。
X軸方向に6個作成します。ポイントは、回転の中心軸を中央にすることです。

2016_09_28_17_41
図5.1-h
リスト5.1-f
float boxSize = 20;    //立方体のサイズ
float distance = 30;    //立方体同士の距離
float halfDis;    //立方体同士の一辺の全体の距離の半分
int boxNum = 6;    //立方体の数

void setup() {
  size(400, 400, P3D);
  halfDis = distance*(boxNum - 1)/2;    //6個並んだ際の距離の半分
}

void draw() {
  background(0);
  stroke(255, 0, 0, 100);
  line(width/2, 0, width/2, height);
  line(0, height/2, width, height/2);

  translate(width/2, height/2);    //立体の中心を画面中央に移動   
  rotateY(radians(mouseX));
  rotateX(radians(mouseY));

  stroke(0);
  fill(255);    

  //立方体を、x軸方向に30ピクセルごとに並べて6個生成
  for (int x = 0; x < boxNum; x ++) {
    pushMatrix();
    translate(x*distance - halfDis, 0, 0);
    box(boxSize, boxSize, boxSize);    //20x20x20pxの立方体を描く
    popMatrix();
  }
}

次にy方向にも複製します。

2016_09_28_17_42
図5.1-i
リスト5.1-g
float boxSize = 20;    //立方体のサイズ
float distance = 30;    //立方体同士の距離
float halfDis;    //立方体同士の一辺の全体の距離の半分
int boxNum = 6;    //立方体の数

void setup() {
  size(400, 400, P3D);
  halfDis = distance*(boxNum - 1)/2;    //6個並んだ際の距離の半分
}

void draw() {
  background(0);
  stroke(255, 0, 0, 100);
  line(width/2, 0, width/2, height);
  line(0, height/2, width, height/2);

  translate(width/2, height/2);    //立体の中心を画面中央に移動   
  rotateY(radians(mouseX));
  rotateX(radians(mouseY));

  stroke(0);
  fill(255);    

  //立方体を、x, y軸方向に30ピクセルごとに並べて6個生成
  for (int y = 0; y < boxNum; y ++) {    
    for (int x = 0; x < boxNum; x ++) {
      pushMatrix();
      translate(x*distance - halfDis, y*distance - halfDis, 0);
      box(boxSize, boxSize, boxSize);    //20x20x20pxの立方体を描く
      popMatrix();
    }
  }
}


この要領でz方向にも複製します。

2016_09_28_17_43
図5.1-j
リスト5.1-h
float boxSize = 20;    //立方体のサイズ
float distance = 30;    //立方体同士の距離
float halfDis;    //立方体同士の一辺の全体の距離の半分
int boxNum = 6;    //立方体の数

void setup() {
  size(400, 400, P3D);
  halfDis = distance*(boxNum - 1)/2;    //6個並んだ際の距離の半分
}

void draw() {
  background(0);
  stroke(255, 0, 0, 100);
  line(width/2, 0, width/2, height);
  line(0, height/2, width, height/2);

  translate(width/2, height/2);    //立体の中心を画面中央に移動   
  rotateY(radians(mouseX));
  rotateX(radians(mouseY));

  stroke(0);
  fill(255, 255, 255);    

  //立方体を、x, y, z軸方向に30ピクセルごとに並べて6個生成
  for (int z = 0; z < boxNum; z ++) { 
    for (int y = 0; y < boxNum; y ++) { 
      for (int x = 0; x < boxNum; x ++) {
        pushMatrix();
        translate(x*distance - halfDis, y*distance - halfDis, z*distance - halfDis);
        box(boxSize, boxSize, boxSize);    //20x20x20pxの立方体を描く
        popMatrix();
      }
    }
  }
}

5.1.7 回転した座標系の保存

回転(rotate)の場合もtranslate()と考え方は一緒です。一つのオブジェクトを回転させるたびに、pushMatrix()とpopMatrix()を使うと、x, y, z座標はそのままでそれぞれのオブジェクトが回転します。

2016_09_28_17_49
図5.1-k
リスト5.1-i
void setup() {
  size(400, 400, P3D);
}
 
void draw() {
  background(0);
 
  for (int i = 0; i < 3; i++) { 
    pushMatrix();
    translate((i + 1)*100, height/2);    //立体の中心を移動 
    rotateY(radians(mouseX));    //y軸に対してangleの数値分だけ回転
    box(50, 50, 50);    //50x50x50pxの立方体を描
    popMatrix();
  }
}


5.1.8 vertex(頂点)を使用した図形の描画

ここでは、vertex(頂点)を使用して図形を自作してみます。サンプルは分かりやすくするために基本図形を使っていますが、vertexを使うと様々な形の図形を描くことができます。まずは、beginShape()で形を描くことを宣言しvertex(x, y)で一つずつ頂点を指定します。
vertexの描画には次のルールがあります。

・vertexの頂点をつなぐ順番は時計回りでも反時計回りでもいい
・何点でも作ることができる
・endShape(CLOSE)で図形を閉じる

2016_09_28_18_53
図5.1-l
リスト5.1-j
size(400, 400, P3D);
 
beginShape();
vertex(20, 20);
vertex(120, 20);
vertex(180, 180);
vertex(20, 120);
endShape(CLOSE);

次に、vertexで四角形を描いてみます。QUADSを宣言する場合には、必ずvertexの数が4の倍数になるようにしましょう。

2016_09_28_19_01
図5.1-m
リスト5.1-k
void setup() {
  size(400, 400, P3D);
}

void draw() {
  background(0);
  translate(width/2, height/2);    //立体の中心を画面中央に移動  
  //y軸に対してマウスのX軸の動きによって角度を変える
  rotateY(radians(mouseX));

  fill(255);

  beginShape(QUADS);    //四角形を描くことを宣言する
  //正面
  vertex(100, 50, 0);  //一点ずつ順番に座標を指定する
  vertex(100, -50, 0);
  vertex(-100, -50, 0);
  vertex(-100, 50, 0);

  //縦
  vertex(0, 50, 100);  //一点ずつ順番に座標を指定する
  vertex(0, -50, 100);
  vertex(0, -50, -100);
  vertex(0, 50, -100);
  endShape();    //四角形を閉じる
}

5.1.9 リアルタイム映像と3D

3Dグラフィックスとビデオライブラリを組み合わせると、取り込んだ映像を立体的に表示できたりもします。興味がある人は自分で勉強してみましょう。

注意! 以下のサンプルは2020年6月現在、macOS Catalinaではエラーが出ます。Windowsでは動くと思います。

2017_01_03_22_19
図5.1-n
リスト5.1-l
import processing.video.*;    //ビデオのライブラリをインポート
 
Capture video;
 
void setup() {
  size(640, 480, P3D);
  video = new Capture(this, 80, 60, 60);
  noStroke();
  video.start();
}
 
void draw() {
  lights();
  background(0);
  //画面中央にオブジェクトがくるように移動する
  translate(width/2, height/2, -200.0);
 
  //マウスの動きによって角度が変わる。
  rotateX(radians(-mouseY));
  rotateY(radians(mouseX));
 
  if (video.available()) {  //もしキャプチャができたら、
    video.read(); //ビデオフレームの読み込み
    video.loadPixels(); //ビデオのピクセルを操作できるようにする
 
    //1ピクセルごとに色を調べる。
    for (int y = 0; y < video.height; y ++) {
      for (int x = 0; x < video.width; x ++) {
        //ビデオのピクセルを抜き出す
        int pixelColor = video.pixels[y*video.width + x];
 
        //赤、緑、青をそれぞれ抽出する。以下の3行はビットシフトといって、
        //理解するのが結構大変なので、ここでは詳細は説明しない。
        int r = (pixelColor >> 16) & 0xff;
        int g = (pixelColor >> 8) & 0xff;
        int b = pixelColor & 0xff;
 
        pushMatrix();    //座標を保存
        translate(x*8 - width/2, y*8 - height/2, 0.0);        
        fill(r, g, b);    //色を適応させる
        //直方体を作成。手前から奥の長さは、色によって変化する。最大値は300。
        //黒のときに300、白のときに0になる。
        box(7, 7, float((255 - r)*(255 - g)*(255-b))/pow(255.0, 3)*300.0);
        popMatrix();    //座標を復帰
      }
    }
  }
}