ProcessingからOpenGLのメソッドを呼び出す

12月 19th, 2011

Processing Advent Calendar 2011(19日目)

ProcessingからOpenGLのメソッドを呼び出す

Processingでは、OpenGLのメソッドを直接呼び出すことができます。
OpenGLのメソッド呼び出すことで、Processingには用意されていない機能を使うことも可能です。

OpenGLを呼び出すには、size()の第3引数を使い、描画モードをOPENGLモードにします。
その後、Processing内部で使用されているgをPGraphicsOpenGLにキャストして取得します。
ここで取得したものに対して、beginGL()した戻り値を使うと、OpenGLのメソッドを呼び出すことができます。

テンプレートは下記になります。

  import javax.media.opengl.*;
  import processing.opengl.*;
 
  PGraphicsOpenGL pgl;
  GL gl;
 
  void setup(){
    size(100, 100, OPENGL);
    pgl = (PGraphicsOpenGL)g;
  }
 
  void draw(){
    gl = pgl.beginGL();
    //  ここでOpenGLのメソッドを呼び出す。
    pgl.endGL();
  }

加算合成

光の表現などに使われる加算合成。Processingでこれを実現するためには、brendColor()を使用するか、自分でr,g,bの各要素の値を計算する必要があります。
しかし、OpenGLには、半透明の色の合成方法を決めるメソッドがあるため、これを呼び出すことで、簡単に加算合成を実装できます。

半透明の色の合成の設定はglBlendFunc()で行います。
引数はそれぞれ、OpenGLの定数

GL_SRC_ALPHA

GL_ONE

を用います。

引数の内容を軽く解説しておくと、第1引数は送り元の色の不透明度、第2引数は送り先の色の不透明度になります。
送り先(つまり既に描画されている色)は、不透明なので完全(100%)を表すGL_ONE。
一方、送り元(これから描画しようとする色)は、その色の不透明度を使うべきなので、GL_SRC_ALPHAを使います。
他にも定数は各種あり、引数に入れる内容を変えると、いろいろな色の合成をおこなうことができます。

上:加算合成をしなかった場合。
下:加算合成をした場合。


円が重なっている部分で色が加算されて明るくなっています。

import javax.media.opengl.*;
import processing.opengl.*;
 
PGraphicsOpenGL pgl;
GL gl;
 
void setup() {
  size(200, 200, OPENGL);
  pgl = (PGraphicsOpenGL)g;
}
 
void draw() {
  background(0);
  fill(128, 96, 64, 127);
 
  gl = pgl.beginGL();
 
  //  色の合成方法を加算合成にする
  gl.glBlendFunc(gl.GL_SRC_ALPHA,gl.GL_ONE);
 
  for (int y = 0; y < height * 2; y += 100) {
    for (int x = 0; x < width * 2; x += 100) {
      ellipse(x, y, 250, 250);
    }
  }
 
  pgl.endGL();
}

フォグ

フォグとは、3Dにおいて、遠くのものに対して霧がかかったかのように見せることで、距離感を表現する(さらには、描画するオブジェクトを減らすことで処理速度を稼ぐ)ために使われる技法です。
OpenGLには、フォグをかけるメソッドがありますので、これを呼ぶとProcessingでもフォグをかけることができるようになります。

glEnable()を使うと、OpenGLの機能を有効にすることができます。
今回はフォグを使いたいので、

GL_FOG

を指定します。
また、フォグの強さを指定する必要があるので、続けてglFogf()を呼び出します。
(OpenGLは引数の型によって、glXXXi(int)やglXXXf(float)と、サフィックスをつけているようです。今回の場合は値が整数なので、glFogi()で動作します。)

glFogf()の第1引数は、設定する内容、第2引数にはその値を書きます。
まず、フォグの種類として

GL_FOG_MODE

GL_LINER

に設定します。これは視点からの距離を指定し、その範囲に対して均等にフォグを掛けるものです。
これ以外にも、

GL_EXP

GL_EXP2

というものがあります、これらは距離の値を用いてフォグを掛けるものになります。
GL_LINERでは、フォグの開始点と終了点を指定する必要があるのでそれぞれ、GL_FOG_STARTとGL_FOG_ENDでフォグを掛ける範囲を指定します。

パラメータをいろいろ調整することで、フォグの色を変えたり、凄い深い霧を表現することができます。

上:フォグを掛けなかった場合。
下:フォグを掛けた場合。


フォグを掛けない場合、全ての球体が見えていますが、フォグを掛けた物では、遠い球体が見えなくなっており、自然に近くなっています。(このように、見えなくなった球体の描画をスキップすれば高速化にもなります)

import javax.media.opengl.*;
import processing.opengl.*;
 
PGraphicsOpenGL pgl;
GL gl;
 
void setup() {
  size(200, 200, OPENGL);
  pgl = (PGraphicsOpenGL)g;
  noStroke();
  smooth();
}
 
void draw() {
  background(0);
 
  gl = pgl.beginGL();
  translate(0, 400, 0);
  fill(255);
  directionalLight(255, 255, 255, 1, 1, -1);
 
  //  フォグを有効にする
  gl.glEnable(gl.GL_FOG);
  gl.glFogf(gl.GL_FOG_MODE, gl.GL_LINEAR);
  gl.glFogf(gl.GL_FOG_START, 0);
  gl.glFogf(gl.GL_FOG_END, 1500);
 
  for (int i = 0; i < 100; i++) {
    translate(20, -20, -60);
    sphere(50);
  }
 
  pgl.endGL();
}

OpenGLのリファレンスはOpenGLSDKのページなどを見ることで確認できますが、今回のような単純な機能であれば、「OpenGLを使ってゲームを作る」などの本などにも書かれていますので、その内容で理解したり、コードを移植してくることもできるでしょう。


余談ですが、先日の光のラインをOpenGLで描いた物は下記になります。
Processingのellipse()がそのまま使えるため、描画が楽になっています。

import javax.media.opengl.*;
import processing.opengl.*;
 
PGraphicsOpenGL pgl;
GL gl;
 
float halfWidth, halfHeight;  //  画面の幅の真ん中
final int MAX_LINE = 30;  //  ネオンラインの個数
NeonLine[] neonLine = new NeonLine[MAX_LINE];  //  ネオンライン
 
final int DRAW_TIMES = 10;  //  一度に描画する回数
final int BORDER_DEAD_COUNT = 8;  //  復活するのに必要な無効ライン数
 
void setup() {
  size(600, 600, OPENGL);
  pgl = (PGraphicsOpenGL)g;
  halfWidth = width / 2;
  halfHeight = height / 2;
  background(0);
  noFill();
  noStroke();
 
  //  ネオンラインを作成する
  for (int i = 0; i < MAX_LINE; i++) {
    neonLine[i] = new NeonLine();
  }
}
 
void draw() {
  gl = pgl.beginGL();
 
  //  画面を少しずつ暗くする(軌跡のため)
  fill(0, 0, 0, 10);
  rect(0, 0, width * 2, height * 2);
 
  gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE);
 
 
 
  int deadCounter = 0;  //  画面外に出たネオンラインの個数
 
  //  全てのネオンラインについて、画面内いれば描画、画面外ならばカウンターを増やす。
  for (int j = 0; j < MAX_LINE;j++) {
    if (neonLine[j].live) {
 
      //  綺麗に描画するため一度に描画する
      for (int i = 0; i < DRAW_TIMES; i++) {
        neonLine[j].draw(pixels);
      }
    } 
    else {
      deadCounter++;
    }
  }
 
  //  画面外にあるネオンラインの数が閾値を超えていたら、画面内に向けて再復活させる
  if (deadCounter >= BORDER_DEAD_COUNT) {
    float x = 0, y = 0;
 
    //  復活位置を決める
    if (random(1) > 0.5) {
      x = int(random(2)) * width * 2;
      y = random(height * 2);
    } 
    else {
      x = random(width * 2);
      y = int(random(2)) * height * 2;
    }
 
    //  画面外に行ってしまったネオンラインを復活する
    for (int i = 0; i < MAX_LINE; i++) {
      if (!neonLine[i].live) {
        neonLine[i].revival(x, y);
      }
    }
  }
 
  pgl.endGL();
}
 
class NeonLine {
  float x;  //  X座標
  float y;  //  Y座標
  float speed;  //  速度
  float direction;  //  進行方向の角度
  float addDirection;  //  進行方向の角度の加算値
  float accelDirection;  //  進行方向の角度の加速度
  float addlDirectionRange;  //  進行方向の角度のリセット幅
  float stateCounter;  //  状態を変更するためのカウンター
  color col;  //  色
  boolean live = false;  //  画面内いるかどうか
 
  final int BORDER_STATE_COUNTER = 150;  //  状態変更カウンターの閾値
 
  NeonLine() {
    //  色を決める
    switch((int)random(6)) {
    case 0:
      col = color(255, 255, 128, 3);  //  イエロー
      break;
    case 1:
      col = color(255, 128, 255, 3);  //  マゼンタ
      break;
    case 2:
      col = color(128, 255, 255, 3);  //  シアン
      break;
    case 3:
      col = color(255, 128, 128, 3);  //  赤
      break;
    case 4:
      col = color(128, 255, 128, 3);  //  青
      break;
    case 5:
      col = color(128, 128, 255, 3);  //  緑
      break;
    }
  }
 
  void revival(float _x, float _y) {
    x = _x;
    y = _y;
    speed = 2 + random(0.5);
    direction = degrees(atan2(height - y, width - x));
    addDirection = 0;
    accelDirection = 0;
    addlDirectionRange = random(6);
    stateCounter = -random(200);
    live = true;
  }
 
  void draw(int[] pixels) {
    if (live) {
      x += cos(radians(direction)) * speed;
      y += sin(radians(direction)) * speed;
      direction += addDirection;
      addDirection += accelDirection;
 
      //  状態カウンターが閾値を超えたら進行方向角度の加速度を変えることで、進む向きを変える
      stateCounter++;
      if (stateCounter >= BORDER_STATE_COUNTER) {
        stateCounter = 0;
        addDirection = random(addlDirectionRange) - addlDirectionRange / 2;
      }
 
      //  画面内にいるかどうかの判定
      boolean deadFlg = false;    
      if (abs(x - width) >= width) {
        live = false;
        return;
      }
      if (abs(y - height) >= height) {
        live = false;
        return ;
      } 
 
      //  大きさを変えて複数回描画することで輝きを表現
      for (int i = 1; i < 5; i++) {
        neonHead(i);
      }
    }
  }  
 
  //  円を描く
  private void neonHead(int size) {
    stroke(col);
    strokeWeight(size * size);
    ellipse(x, y, size * size, size * size);
  }
}
 
void mousePressed() {
  for (int i = 0; i < MAX_LINE; i++) {
    neonLine[i].x = mouseX * 2;
    neonLine[i].y = mouseY * 2;
  }
}

Tags: ,
Posted in Processing Advent Calendar2011 | No Comments »

Comments

Leave a Reply

 Comment Form