【OpenGL】プリミティブ関数と光学特性のサンプルまとめ!

スポンサーリンク

 

こんにちは!HELLO!您好!привет там ! 안녕하세요 !Hola !

最近はスクリプト言語の勉強ばかりでメイン武器のC/C++言語は放置ぎみだったので、今日は久しぶりにOpenGLを使って C/C++言語で遊んでみました。

3DCGのオブジェクトをデザインする際の「作業場」となるプログラムを作りました。

複数種類のプリミティブ関数を用いて新しい形のオブジェクトを作る際に XYZ軸方向に自由に回転させて眺めたり、

実験的に色々な光学特性を与えたりしながら気軽にデザインできる「作業場」です。

 

me

OpenGLを勉強しながら作品作りをしていた時期にずっと欲しかった環境です。

そのときは面倒そうなのでわざわざ作りませんでしたが、

あれば結構役に立つプログラムだと思います。

 

今日は特段何かをデザインする訳でもないので、

OpenGLで利用可能な全プリミティブ関数を「被写体」としてまとめました。

おまけで自作の円柱描画関数も作りました。

円柱オブジェクトは頻繁に使いますが OpenGLのライブラリに用意されていないので自分で作る必要があります。

また、光学特性のサンプルとして金・銀・銅などの金属やプラスチック、ゴムといった素材を作成しました。

これから紹介するプログラムは4つのファイルから構成されていますが、

それぞれに記述されている主な内容は以下の通りです。

  1. workshop.cpp メインプログラム
  2. primitive.h プリミティブ関数と円柱描画関数をまとめたオブジェクト描画関数を定義
  3. mySylinder.h  円柱描画関数を定義
  4. myMaterial.h 表面属性の定義とセット関数を定義

今日の主要プログラムは workshop.cpp でマウス操作とその際のオブジェクトの挙動を制御する処理を書きました。

1つのプログラムにまとめて書くこともできましたが、可読性や部品化を考えて分割しました。

 

プログラムのダウンロード

 

当プログラムのダウンロードはここからお願いします。

 

OpenGL に触れたことがある方は読めば理解できるかと思いますが、

以下でプログラムの仕様や実装上の要点について簡単に説明しますので興味がある方はどうぞ。

 

プログラムの仕様

操作方法

マウスとキーボードを利用します。

まずマウス操作によるオブジェクトの挙動です。

  1. 左右方向の左クリックによるドラッグ : Y軸周りの回転
  2. 上下方向の左クリックドラッグ : X軸周りの回転
  3.  Ctrl + 上下方向の左クリックによるドラッグ : Z軸周りの回転
  4. 上下方向の右クリックによるドラッグ : 視点とオブジェクトの距離の変化

 

次にキーボード操作について簡単に説明します。

  1. d : 次のオブジェクトに切り替え
  2. a : 前のオブジェクトに切り替え
  3. w : ワイヤモードに切り替え
  4. f : スムースシェーディングとフラットシェーディングの切り替え
  5. c : 次の光学特性に切り替え
  6. z : 前の光学特性に切り替え
  7. r : オブジェクトの角度と位置の初期化

 

実際の動作例

参考までにプログラムを動かした様子を収めた実行例のキャプチャをお見せします。

Windows の動画キャプチャを利用しているため画質が荒いですがご勘弁ください。

諸事情によりマウス操作がスムーズに行えなかったため動画が一部カクカクしています。

 



制御方法

基本的にはソースコードを読んでいただければ理解できるかと思いますが、重要な部分だけ簡単に説明します。

以下はマウス操作を制御する主要部分のソースコードです。

 

クリックしたマウスポインタの座標は myMouse関数により取得されグローバル変数のmXとmYに格納されます。

このときクリックしたボタンの種類はグローバル変数のmButtonに格納されます。

次に上記の変数に格納された値に基づいてmyMotion関数で処理します。

ひとまずドラックさせたマウスの移動変化量を12行目と13行目で計算しそれぞれ dxとdyに格納しておきます。

me

ここから mButtonに格納されたクリックの種類により処理が異なります。

mButtonに格納された数値をもとに switch文を用いて条件分岐させます。

 

左クリックの場合、fmod関数を用いてオブジェクトの回転角度を決定するグローバル変数 rotateを適宜変更します。

rotateX、rotateY、rotateZ はそれぞれ XYZ軸周りの回転角度を意味しています。

rotateX は無条件に dyによって計算されますが、rotateYとrotateZは条件により場合分けして計算します。

Ctrlキーが押されている場合は dxに基づいて rotateZ を変更し、押されていない場合は rotateYを変更します。

Ctrlキーが押されているかを判定するためには glutGetModifiersを使用します。

右クリックによるドラッグの場合は移動量のdyを適当な値で割った値を減算することで distanceを変更します。

me

ここでは dy/50 としていますが分母が小さいほど感度が良くなります。

最後にオブジェクトに対して上記の rotateとdistance の値をもとに幾何変換を適応させればよいです。

 

me

次にキーボード操作の制御方法について簡単に説明します。

以下は主要部分のソースコードです。

 

読んでいただければ理解できると思いますので、私が工夫した点だけ説明します。

まず 5行目から10行目まではプリミティブ関数関数を切り替える処理を記述していますが、

前後どちらに進んでも時計回りもしくは反時計回りの順序でプリミティブを選択できるように工夫しました。

地味ですみません。。。

17行目から22行目までの光学特性の切り替えに関しても同様の実装です。

 

各種プリミティブ関数

関数一覧

以下ではソリッドモデルをまとめています。

me

SolidをWireに書き換えればワイヤモデルのプリミティブを描画することができます。

球体glutSolidSphere( double radius, int slices, int stacks )
※ slices: 緯度の分割数, stacks: 経度の分割数
立方体glutSolidCube( double size )
円錐glutSolidCone( double radius, double height, int slices, int stacks )
※ slices: 分割数, stacks: 分割数
4面体glutSolidTetrahedron( void )
8面体glutSolidOctahedron( void )
12面体glutSolidDodecahedron( void )
20面体glutSolidIcosahedron( void )
トーラスglutSolidTorus( double innerR, double outerR, int nsides, int rings )
※ innerR: 内半径, outerR: 外半径, nsides: 分割数, rings: 分割数
ティーポットglutSolidTeapot( double size )

 

円柱描画関数の定義

OpenGLのライブラリには球体や立方体、円錐などのプリミティブ描画関数が用意されていますが何故か円柱の描画関数はありません。

me

ティーポットを描画する遊び心満載な関数はあるのにどうして汎用的な円柱を描画する関数は無いのでしょうか?笑

ということで円柱を描画する場合は関数を自作する必要があります。

以下は円柱を描画する関数です。

GLUTによる「手抜き」OpenGL入門を参考にさせていただきました。

 

基本的なアルゴリズムは回答6にある通りですが、OpenGLのプリミティブ関数は描画する中心座標に関して対称に展開される仕様であるので同様に実装しました。

具体的には 11行目や 48行目のように y = 0.5 * h とすることで引数に与えられた高さを半分にしておくことで対応できます。



表面属性

設定方法

コンピュータグラフィクスでは物体表面のもつ光の反射特性である表面属性を設定することにより物体の色と質感などの素材を決定することができます。

材質の特性を決定するパラメータには主に以下の3つがよく使われます。

  1. GL_AMBIENT  :環境光反射成分
  2. GL_DIFFUSE  :拡散反射成分
  3. GL_SPECULAR  :鏡面反射成分

設定値は R, G, B, A の値をそれぞれ 0.0 ~ 1.0 とし配列に格納します。

配列を glMaterial関数に渡して呼び出すことでオブジェクトに反映させます。

以下は金色のティーポットを描画する関数の参考コードです。

 

サンプル一覧

GLfloat ambient[] = { 0.25, 0.20, 0.07, 1.0 };
GLfloat diffuse[] = { 0.75, 0.61, 0.23, 1.0 };
GLfloat specular[] = { 0.63, 0.56, 0.37, 1.0 };
GLfloat shininess = 50;
GLfloat ambient[] = { 0.19, 0.19, 0.19, 1.0 };
GLfloat diffuse[] = { 0.51, 0.51, 0.51, 1.0 };
GLfloat specular[] = { 0.51, 0.51, 0.51, 1.0 };
GLfloat shininess = 0.4;
GLfloat ambient[] = { 0.19, 0.07, 0.02, 1.0 };
GLfloat diffuse[] = { 0.70, 0.27, 0.08, 1.0 };
GLfloat specular[] = { 0.26, 0.14, 0.09, 1.0 };
GLfloat shininess = 0.1;
ルビーGLfloat ambient[] = { 0.17, 0.01, 0.01, 1.0 };
GLfloat diffuse[] = { 0.61, 0.04, 0.04, 1.0 };
GLfloat specular[] = { 0.73, 0.63, 0.63, 1.0 };
GLfloat shininess = 0.6;
青銅GLfloat ambient[] = { 0.21, 0.13, 0.05, 1.0 };
GLfloat diffuse[] = { 0.71, 0.43, 0.18, 1.0 };
GLfloat specular[] = { 0.39, 0.27, 0.17, 1.0 };
GLfloat shininess = 0.2;
真珠GLfloat ambient[] = { 0.25, 0.21, 0.21, 1.0 };
GLfloat diffuse[] = { 1.00, 0.83, 0.83, 1.0 };
GLfloat specular[] = { 0.30, 0.30, 0.30, 1.0 };
GLfloat shininess = 0.9;
エメラルドGLfloat ambient[] = { 0.02, 0.17, 0.02, 1.0 };
GLfloat diffuse[] = { 0.08, 0.61, 0.08, 1.0 };
GLfloat specular[] = { 0.63, 0.73, 0.63, 1.0 };
GLfloat shininess = 0.6;
黒曜石GLfloat ambient[] = { 0.05, 0.05, 0.07, 1.0 };
GLfloat diffuse[] = { 0.18, 0.17, 0.23, 1.0 };
GLfloat specular[] = { 0.33, 0.33, 0.35, 1.0 };
GLfloat shininess = 0.3;
 真鍮GLfloat ambient[] = { 0.33, 0.22, 0.03, 1.0 };
GLfloat diffuse[] = { 0.78, 0.57, 0.11, 1.0 };
GLfloat specular[] = { 0.99, 0.94, 0.81, 1.0 };
GLfloat shininess = 0.23;
 赤色プラスチックGLfloat ambient[] = { 0.0, 0.0, 0.0, 1.0 };
GLfloat diffuse[] = { 0.5, 0.0, 0.0, 1.0 };
GLfloat specular[] = { 0.7, 0.6, 0.6, 1.0 };
GLfloat shininess = 0.25;
 黄色プラスチックGLfloat ambient[] = { 0.0, 0.0, 0.0, 1.0 };
GLfloat diffuse[] = { 0.5, 0.5, 0.0, 1.0 };
GLfloat specular[] = { 0.6, 0.6, 0.5, 1.0 };
GLfloat shininess = 0.25;
 緑色プラスチックGLfloat ambient[] = { 0.0, 0.0, 0.0, 1.0 };
GLfloat diffuse[] = { 0.1, 0.35, 0.1, 1.0 };
GLfloat specular[] = { 0.45, 0.55, 0.45, 1.0 };
GLfloat shininess = 0.25;
シアンプラスチックGLfloat ambient[] = { 0.0, 0.1, 0.06, 1.0 };
GLfloat diffuse[] = { 0.0, 0.51, 0.51, 1.0 };
GLfloat specular[] = { 0.5, 0.5, 0.5, 1.0 };
GLfloat shininess = 0.25;
黒色プラスチックGLfloat ambient[] = { 0.0, 0.0, 0.0, 1.0 };
GLfloat diffuse[] = { 0.01, 0.01, 0.01, 1.0 };
GLfloat specular[] = { 0.50, 0.50, 0.50, 1.0 };
GLfloat shininess = 0.25;
 赤色ゴムGLfloat ambient[] = { 0.05, 0.0, 0.0, 1.0 };
GLfloat diffuse[] = { 0.5, 0.4, 0.4, 1.0 };
GLfloat specular[] = { 0.7, 0.04, 0.04, 1.0 };
GLfloat shininess = 0.08;
黄色ゴムGLfloat ambient[] = { 0.05, 0.05, 0.0, 1.0 };
GLfloat diffuse[] = { 0.5, 0.4, 0.4, 1.0 };
GLfloat specular[] = { 0.7, 0.7, 0.04, 1.0 };
GLfloat shininess = 0.08;
緑色ゴムGLfloat ambient[] = { 0.0, 0.05, 0.0, 1.0 };
GLfloat diffuse[] = { 0.4, 0.5, 0.4, 1.0 };
GLfloat specular[] = { 0.04, 0.7, 0.04, 1.0 };
GLfloat shininess = 0.08;
シアンゴムGLfloat ambient[] = { 0.0, 0.05, 0.05, 1.0 };
GLfloat diffuse[] = { 0.4, 0.5, 0.5, 1.0 };
GLfloat specular[] = { 0.04, 0.7, 0.7, 1.0 };
GLfloat shininess = 0.08;
黒色ゴムGLfloat ambient[] = { 0.02, 0.02, 0.02, 1.0 };
GLfloat diffuse[] = { 0.01, 0.01, 0.01, 1.0 };
GLfloat specular[] = { 0.4, 0.4, 0.4, 1.0 };
GLfloat shininess = 0.08;

 

セット関数の定義

myMaterial.h のファイルには上記の表面属性を定義する関数がそれぞれ記述されています。

それらの関数は自作の setMaterial関数にまとめ条件分岐により呼び出します。

setMaterial関数は myDisplay関数の drawPrimitive関数の前で呼び出します。

 

おわりに

 

OpenGLは C/C++の練習や復習も兼ねて楽しく3DCGを学ぶことができるのでおすすめです。

たしかに難易度はやや高めですが、慣れてしまえば自分で調べて色々試すことができて面白いです。

ということで「新学期本格始動前に C言語の復習を軽くしてみた」の回でした。

ではまた!

スポンサーリンク


ABOUT ME!

yuk!

国立大学情報学科に通う大学生です。天然パーマと戦いながらすーぱーエンジニアを目指し技術とセンスを磨いています。 室内に引きこもりがちでヘビメタと猫と甘いものが救いのキーボードカチャカチャ生活ですが、最近はブログで文章を書くことが楽しいです。モットーは「 Who dares wins. = 人生是一箇,活殺全在我。」好きな言葉は「マジ卍」。