忍者ブログ

カレンダー

04 2024/05 06
S M T W T F S
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

最新コメント

[11/20 かいせい]
[11/18 NONAME]
[10/01 かいせい]
[10/01 masafumi]
[09/28 なんとなく]

最新トラックバック

プロフィール

HN:
Kaisei+
性別:
男性

バーコード

ブログ内検索

カウンター

[PR]

×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。

ドロネー図(Delaunay diagram)の描画 導入

私がドロネー図に出会ったのは学部2年のころで,その当時はさっぱり利用用途がわからなかった.
ドロネー図は与えられた母点から三角形を計算するために使われることが多いような気がします.確かに図の構成要素は三角形のみなのですが,ただ単に三角形を作っていくわけではありません.
2次元上の場合だと,与えられた点群のうち3点を結んで三角形にしたとき,その三角形の外接円の内側に頂点以外の点が入らないように三角形を計算していきます.そのように構築した図が2次元上のドロネー図となります.
3次元上だと四面体の外接球の中に四面体の頂点以外の点がないように,四面体を構築した図がドロネー図となります.
うさぎで見てみましょう.

bunny.png
これが入力となります.
そして,ドロネー図にした結果が次のこれです.
bunny_delaunay.png
みごとに梱包されたようになっています.
面で区切られているため,当然のように面しか見えません.
この中にはぎっしりと四面体が敷き詰められています.

四面体の辺のみを描画したのこれです.

bunny_delaunay_edges.png

中は見えるようになったけど,数が多すぎてわけわからん状態です.
しかし,これがドロネー図です.
PR

OBJファイルの読み込み その3

.objファイルから読み込んだデータをどのように持とうが構わないと思いますが,ボクの場合は前回お話した通りに持っています.
今回はどのようにファイルから読みだしていくかを話していこうと思います.
ボクのプログラムの書き方はゲッターとセッターをしっかり書いてしまうタイプなので,まともな方だとは思っているのですが,このような記事を書くときは不便でして,関数の説明を一からしなければなりません.しかし,そんな説明をするほど元気ではないので,簡単にどのような手続きをとればよいのかを記述します.
前回の記事で記述しているように.objファイルは一行に一頂点,一面を定義しているので,ファイルの読み込み時に一行ずつ読み込んでいけばよいのです.
これには,ファイルの入力処理がわかっていればよいので,本か他のサイトで調べてください.
ちなみにボクはVC++でコードを書いており,ファイル周りに関してはc++よりcの関数のほうが使いやすいと思っています.fopen_s関数でファイルポインターを掴んで,fgets関数で300文字くらいを指定して,一行読み込んでいます.
次に,一行を読み込んでスペース区切りで切りだされた文字を見ます.その文字が「v」「vt」「vn」「f」なら気にすることにしましょう.それ以外は「#」か「g」か「mtllib」ですので,今は無視しておきましょう.とくに「#」から始まる行はコメント行なので永年無視です.
vの場合は,頂点座標として格納する.
vtならば,テクスチャ座標として格納する.
vnならば,法線として格納する.
fならば,面として格納する.
と言った感じに場合わけの処理になります.
このとき,前回の説明で用意してあるフラグを立ててやれば,描画処理の時にテクスチャ座標がないのか,法線がないのかわかりやすいので,楽です.
「v」「vt」「vn」ならば一行読み取った文字列を全てスペース区切りにして行末まで送っていきます.これでこの行の読み込みは完了するはずです.

「f」の行はちょろっとひねりが必要になります.
スペース区切りで参照している頂点の数はわかるのですが,問題は切り出したあとで,スラッシュ区切りの部分です.
cで用意されているstrtok_sを区切り文字を用いて「a//c」のようにスラッシュが二重に続いたものを処理した場合,スラッシュが一度で2つ飛ばされて処理されてしまいます.
ですので,文字を一文字見ていってスラッシュ区切りを実装するしかありません.
ボクはトークンの切り出し関数mystrtokを作って,スラッシュを一文字ずつ抜き出していました.

char *mystrtok(char *s, char *t)
{
    static char buf[50];
    static char *headp;
    char *tailp;
    char *p;
   
    if (s != NULL) {
        strcpy_s(buf, s);
        headp = buf; /* headpは先頭を指す */
    }
    //sがNULLでかつheadpがNULLの場合,これは終点に達しているということ
    if(headp==NULL){
        return NULL;
    }
    //headp文字列からtを検索
    // tailpは区切り文字を指す
    tailp = strstr(headp, t);
   
    //区切りがない場合,最後という判定でこのときのheadpを返す
    if (tailp == NULL){
        p = headp;
        headp = NULL;
        return p;
    }

    //このときtailpがheadpと同じ場合,区切り文字が連続に続いているということでNULLを返す
    if(tailp==headp){
        headp = tailp + strlen(t); /* 次の頭 */
        return NULL;
    }

    *tailp = '\0';
    p = headp;
    headp = tailp + strlen(t); /* 次の頭 */
    return p;
}

この関数my_strtok()は引数に,区切りたい文字列sと,区切り文字列tとします.簡単に言うと,標準ライブラリのstrtok()と同じ使い方です.
最初に引数に入れた区切りたい文字列sは,先頭から区切り文字列tまでを切り出して返り値として返します.
区切りたい文字列sから次のトークンを得たいときは,NULLをsに入れ,区切り文字tを入力します.すると次のトークンが返ってきます.
今の場合,スラッシュ区切りにしたいので,面のスペース区切りしたあとの文字列と"/"を引数として指定します.
区切りたい文字列sが「200/201/202」の場合,mystrtok(s,"/")とします.この返り値は200となります.
次に,次のトークンがほしいので,mystrtok(NULL,"/")とします.すると,201が返ってきます.
最後のトークンがほしいので再度mystrtok(NULL,"/")とします.もちろん,202が返ってきます.

仮に,区切りたい文字列sが「200//202」だったら,標準ライブラリのstrtok()では,二重のスラッシュ//を1区切りとしてみなします.しかし,mystrtok()では,/毎に区切っていきます.
ですから,mystrtok(s,"/"),mystrtok(NULL,"/"),mystrtok(NULL,"/")とした場合,200,NULL,202と返します.
区切りたい文字列が最後まで行くと,それからは常にNULLしか返さなくなります.

これで取り出して,メモリに格納していけば,面情報を得ることができるので読み込み終了となります.

OBJファイルの読み込み その2

前回の話でなんとなく.objファイルを読み込むモチベーションを感じてもらえたことでしょう.
さて,じゃ読み込みましょうかね.
先に言っておきますが,ここではcもしくはc++で開発します.ボクみたいな面倒くさがりさんは,開発言語はcもしくはc++くらいしか使う気がありません.型を定義しないような言語はデバッグが面倒なことが多いので使いません.

読み込む前に,まず.objファイルがどのような構造なのかを知らなければなりません.
ということで調べましょう.
「cg objファイル フォーマット」とかで検索すれば,なんか出てきます.
で,だいたいそれで正解です.検索ワードにcgを入れないと,cのコンパイルした時にできるようなobjファイルのことについて出てきちゃいます.今はそんなのを知ってもしょうがないですからね.

まぁとにかく調べたらどのサイトも以下のようなことが書いてあると思います.
「v」は頂点x y z
「vt」はテクスチャ座標
「vn」は法線x y z
「f」は面
「g」はグループ
ほかにもマテリアルとか何とかってあるかもしれませんがその辺は無視で.

これらの記号を見ても,初見だと意味わからないですよね.
ボクもそうでした.
これはどういう意味かというと,行のはじめにvがついていた場合,その列は頂点のxyzを記してあるということです.
.objファイルの性質として,一行に1頂点,もしくは1面を定義します.

v 1 0 0

これは{x,y,z}={1,0,0}という頂点を指しているということです,スペース区切りでxyzを並べています.
つぎに,vtですがこれはテクスチャを貼り付けるための座標が記されます.

vt 1 1

これはテクスチャ座標の(1,1)を指します.
そして,vnは法線です.

vn 0 0 1

この場合,法線ベクトルが(0,0,1)だということです.
ここまでが座標に関することです.
なんとなくわかってきたでしょうか.
これらv,vt,vnは座標に関する情報の定義でしたが,これだけではポリゴンメッシュとして定義できません.
面の定義が必要です.
面はfで始まる行で定義されています.しかし,座標に関する定義とは大きく違う書き方をしています.

f a1/b1/c1 a2/b2/c2 a3/b3/c3 a4/b4/c4 ...

このような書き方になっています.
スペース区切りでa/b/cが並んでいます.
それぞれaは頂点座標のインデックス,bはテクスチャ座標のインデックス,cは法線のインデックスです.
a/b/cが3つ並んでいた場合,それは三角形を示します.
4つ並んでいると四角形,5つだと五角形ということになります.
インデックスは最初に定義されたvには1番がつけられ,それ以降は1ずつ増えた番号与えられていきます.
a/b/cはスラッシュ区切りになっており,場合によってはテクスチャ座標が抜かれたり法線が省略されたりします.
テクスチャ座標が参照されないような場合は,

f a1//c1 a2//c2 a3//c3 a4//c4 ...

となっています.
必ずしもスラッシュの数が2つあるわけではなく,

f a1 a2 a3 a4 ...

のように省略される場合もあります.

gのグループについてはgがついたあとの面はグループ化されるということです.
それだけです.

以上がフォーマットについてでしたが,比較的簡単に読み込めそうですよね.
頂点の座標などのv,vt,vnは簡単ですよね.
一行を読み込んで,一文字目を見て,それ以降を単純にスペース区切りで読み込めばよいだけですからね.
しかし,ちょっと厄介なのは面の読み込みです.
何個の頂点で面を構成しているかのかを示していないので,リスト構造や配列を用意してその中に入れてみて,数を数えなければなりません.
しかも,面の各頂点の情報は頂点,テクスチャ座標,法線ベクトルをそれぞれスラッシュ区切りで指しています.
考えるだけでも面倒ですよね.
面倒なのですが,書いてしまえば簡単なものです.

ボクはデータをまとめて持っておくのが好きなタイプですから,ファイルから抜き出したデータを一括して管理しておきます.
ですから,クラスを作って,その中に頂点座標やテクスチャ座標,法線ベクトル,面の情報を格納していきます.
また,面の情報は面の情報としてクラスとします.
それぞれをMeshDataクラスとFaceIndexクラスと名付けました.
この二つのクラスで400行くらいしかないので,短いのですが,この記事にべた張りするとあほみたいに長くなるので,何か別の方法で掲載します.いや,面倒になったらしないかも.
とにかく,これらのクラスは以下のようなメンバー変数を持ちます.
MeshDataクラスのメンバー変数は,

bool vertexF;
int verticesN;
double** vertices;

bool textureF;
int textureCoordN;
double **textureCoords;

bool normalF;
int normalsN;
double **normals;

bool faceF;
int facesN;
FaceIndex **faces;

としました.実は他にもいろいろ持たせてあるのですが,今は面倒なのでこれだけで.
vertexFやtextureF,normalF,faceFはそれぞれそのデータが有効なのかを示すために用意しました.
verticesNとverticesはそれぞれ頂点の数と頂点の座標,textureCoordNとtextureCoordはそれぞれテクスチャ座標の数とテクスチャ座標,normalsNとnormalsはそれぞれ法線ベクトルの数とベクトルです.
facesNとfacesはそれぞれ面の数と面の情報です.

FaceIndexには上で用意したvertices,textureCoordとnormalsのインデックスが格納されます.
FaceIndexクラスのメンバー変数は,

int faceN;
int *verIndex;
int *texIndex;
int *norIndex;

とし,faceNはこの面が何頂点で構成されているか,verIndexとtexIndex,norIndexはそれぞれvertices,textureCoordとnormalsのインデックスが格納されています.

用意したこれらに.objファイルから読みだしたものを格納していけば読み込み終了となります.
読み込みの関数はまた次回ということで.

OBJファイルの読み込み その1

3DCGを扱う上で形状を定義するということは必須なのですが,必須のわりにややこしい面をもっていたりします.
ポリゴンメッシュと呼ばれるような多角形のメッシュ構造が一般的に多く使われておりますが,他にも形状を定義するような関数モデルやボクセル,点群といったようにさまざまな種類の形状定義の仕方があります.
3Dを扱うために多くの種類の形式があることがわかります.
比較的一般的なポリゴンメッシュですが,そのファイルフォーマットもさまざまで,データの読み込み時にそのさまざまなフォーマットに合わせて読み込みのプログラムを書かなければならないです.
3DCG制作ツールを使ったことがある人はわかると思いますが,.objや.ply,.x,.3ds,.dfxなどさまざまです.最近は,COLLADAと名付けられたさまざまな用途に使えるように考えられたフォーマットもあります.しかし,市販のデータのフォーマットにはない場合が多くあるような印象を受けます.昔のものも含めると.objや.3ds,.dfxが多いような気がします.

そこで今回は簡単にですが,多く使われているような気がしている.objファイルの読み込みをやってみたいと思います.
まぁ,実験ということでメッシュの頂点と面の読み込みができれば良いということにしましょう.
.objのフォーマットでは,頂点,テクスチャ座標,頂点法線が定義できます.他にもメッシュのグループやマテリアルを設定したファイル名を持つこともできます.
その他にいろいろあるのかもしれませんがそれ以上は知りません.

まぁタイトルにもあるように,この記事は「その1」なわけですから,今後「その2」「その3」と徐々に,出し惜しみしながら書いていこうと思います.

まぁ今日のところは,読み込んでレンダリングできている画像だけ貼って終わろうかと思っているところです.
kaban.png
このかばんは.objファイルで保存されているわけですが,こうやって読み込み,レンダリングができるわけです.

いやー夏ですね

いやー,更新をさぼりにさぼってますね.
会社員になると,秘密が多くって,書きにくいことばっかりなんですね.おもしろいことも会社のためって感じで個人的には楽しくないですね.
まぁ個人を殺して,会社のために働けってのが日本らしくて良いですね.ボクは賛成です.それに従うかどうかは別にして.
ところで,このサイトの目的とか全然いってないので,ここで書いておこうと思います.
このサイトは基本的にボクの近況的なことをこっそりと文章に含めつつ,適当な情報を記載していこうと思っています.読みが深い人で,ボクの性格を知っている人とかに見られると,何をたくらんでいるのかばれそうですが,まぁその辺は気付いても黙っておいてください.