mieki256's diary



2024/04/16(火) [n年前の日記]

#1 [prog][cg_tools] OpenGLでモデルデータを読み込めそうか調べてる。その6

C/C++ とOpenGLで、何かしらのモデルデータを読み込んで描画したい。

今回は、wavefront形式(.obj)を Pythonスクリプトで読み込んで、C言語の配列の形で出力して、それを使って OpenGLで描画するC言語のソースを書いてみた。C/C++ でモデルデータファイルを読み込んでいるわけではないけれど、.exeの中にモデルデータを含めてしまいたい場合は、こういうやり方でもいいんじゃないかなあ、と…。

動作確認環境は以下。
先に実行結果を。今回作成した 01_drawobj.exe を実行すると以下のような見た目になる。モデルデータを描画することができている。

使用するモデルデータ :

blender 3.6.9 x64 LTS で簡単なモデルデータを作成して、wavefront形式(.obj)でエクスポートした。ファイル → エクスポート → Wavefront (.obj) を選択。
  • .objファイルは、頂点座標、テクスチャのUV座標、法線情報、面情報が保存されているファイル。
  • .mtlファイルは、マテリアル情報が保存されているファイル。

blender から .obj をエクスポートする際、四角形を三角形にするオプション(メッシュの三角面化)にチェックを入れる。後で出てくる Pythonスクリプトも、C言語のソースも、三角形ポリゴンにのみ対応させてある状態なので…。

作成したモデルデータは以下。一つは、只の四角い箱。各面が別の色になっている。

_cube01.obj
_cube01.mtl

cube01_ss01.gif


以下のモデルデータは、blenderでお馴染みのスザンヌ。目や口のあたりを別の色にしている。

_suzanne01.obj
_suzanne01.mtl

suzanne01_ss01.gif

PythonスクリプトでC言語の配列に変換 :

この wavefront形式(.obj) と .mtl を、Pythonスクリプトを使って、C言語の配列の形に変換して出力する。今回書いたPythonスクリプトは以下。ライセンスは CC0 / Public Domain ということで…。

_pyobj2c.py

使用方法は以下。
python pyobj2c.py INPUT.obj > OUTPUT.h

例えば hoge.obj というファイルを読み込ませた場合は、以下の名前の配列を作成する。
  • const float hoge_obj_vtx[] ... 頂点配列。1つにつき、{x, y, z} を持つ。
  • const float hoge_obj_nml[] ... 法線情報配列。1つにつき、{x, y, z} を持つ。
  • const float hoge_obj_uv[] ... テクスチャ座標配列。1つにつき、{x, y} を持つ。
  • const float hoge_obj_col[] ... 頂点カラー配列。1つにつき、{r, g, b, a} を持つ。
  • const unsigned int hoge_obj_vtx_size ... 頂点配列の個数
  • const unsigned int hoge_obj_nml_size ... 法線情報配列の個数
  • const unsigned int hoge_obj_uv_size ... テクスチャ座標配列の個数
  • const unsigned int hoge_obj_col_size ... 頂点カラー配列の個数
OpenGL 1.1 の glDrawArrays() を使って描画することを前提にした配列になっている。ちなみに、頂点カラーについては、マテリアル情報の Kd (Diffuse色)だけを取り出して頂点カラーにしている。


前述の2つの .obj を変換。以下の結果が得られた。

_cube01.h
_suzanne01.h

これらの配列を、C言語のソースで #include して利用する。

C言語のソース :

C言語のソースは以下。ライセンスは CC0 / Public Domain ってことで。ちなみに、ウインドウの作成等はGLFW3を利用している。

_01_drawobj.c
#include <stdlib.h>
#include <stdio.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GLFW/glfw3.h>
// #include <SOIL/SOIL.h>

#include "cube01.h"
#include "suzanne01.h"

#define WDW_TITLE "Draw wavefront obj"

// Window size
#define SCRW 1280
#define SCRH 720

#define FOV 50.0

#define ENABLE_LIGHT 1

#if ENABLE_LIGHT
const float light_pos[4] = {1.0, 1.0, 1.0, 0.0};
const float light_ambient[4] = {0.2, 0.2, 0.2, 1.0};
const float light_diffuse[4] = {0.8, 0.8, 0.8, 1.0};
const float light_specular[4] = {0.7, 0.7, 0.7, 1.0};
#endif

typedef struct
{
  int scrw;
  int scrh;
  float fovy;
  float znear;
  float zfar;

  double angle;
} GWK;

static GWK gw;

// ----------------------------------------
// Render
void render(void)
{
  gw.angle += (45.0 / 60.0);

  // init OpenGL
  glViewport(0, 0, gw.scrw, gw.scrh);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(gw.fovy, (double)gw.scrw / (double)gw.scrh, gw.znear, gw.zfar);
  glMatrixMode(GL_MODELVIEW);

  // clear screen
  glClearColor(0, 0, 0, 1);
  glClearDepth(1.0);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  glDepthFunc(GL_LESS);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_BLEND);
  glEnable(GL_NORMALIZE);

  glLoadIdentity();

#if ENABLE_LIGHT
  glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
  glEnable(GL_COLOR_MATERIAL);

  glLightfv(GL_LIGHT0, GL_POSITION, light_pos);
  glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
  glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
  glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
#endif

  // obj move and rotate
  glTranslatef(0.0, 0.0, -10.0);
  float scale = 3.0;
  glScalef(scale, scale, scale);
  glRotatef(-20.0, 1, 0, 0);
  // glRotatef(gw.angle * 0.5, 1, 0, 0);
  glRotatef(gw.angle, 0, 1, 0);

  glEnable(GL_CULL_FACE);
  glCullFace(GL_BACK);

  // draw vertex array

  glEnableClientState(GL_VERTEX_ARRAY);
  // glEnableClientState(GL_TEXTURE_COORD_ARRAY);
  glEnableClientState(GL_NORMAL_ARRAY);
  glEnableClientState(GL_COLOR_ARRAY);

#if 1
  glVertexPointer(3, GL_FLOAT, 0, suzanne01_obj_vtx);
  // glTexCoordPointer(2, GL_FLOAT, 0, suzanne01_obj_uv);
  glNormalPointer(GL_FLOAT, 0, suzanne01_obj_nml);
  glColorPointer(4, GL_FLOAT, 0, suzanne01_obj_col);
  glDrawArrays(GL_TRIANGLES, 0, suzanne01_obj_vtx_size);
#else
  glVertexPointer(3, GL_FLOAT, 0, cube01_obj_vtx);
  glNormalPointer(GL_FLOAT, 0, cube01_obj_nml);
  glColorPointer(4, GL_FLOAT, 0, cube01_obj_col);
  glDrawArrays(GL_TRIANGLES, 0, cube01_obj_vtx_size);
#endif

  glDisableClientState(GL_COLOR_ARRAY);
  glDisableClientState(GL_NORMAL_ARRAY);
  // glDisableClientState(GL_TEXTURE_COORD_ARRAY);
  glDisableClientState(GL_VERTEX_ARRAY);
}

// ----------------------------------------
// init animation
void init_animation(int w, int h)
{
  gw.scrw = w;
  gw.scrh = h;
  gw.angle = 0.0;

  gw.fovy = FOV;
  gw.znear = 1.0;
  gw.zfar = 1000.0;

  glViewport(0, 0, (int)gw.scrw, (int)gw.scrh);
}

// ----------------------------------------
// Error callback
void error_callback(int error, const char *description)
{
  fprintf(stderr, "Error: %s\n", description);
}

// ----------------------------------------
// Key callback
static void key_callback(GLFWwindow *window, int key, int scancode, int action, int mods)
{
  if (action == GLFW_PRESS)
  {
    if (key == GLFW_KEY_ESCAPE || key == GLFW_KEY_Q)
    {
      glfwSetWindowShouldClose(window, GLFW_TRUE);
    }
  }
}

// ----------------------------------------
// window resize callback
static void resize(GLFWwindow *window, int w, int h)
{
  if (h == 0)
    return;

  gw.scrw = w;
  gw.scrh = h;
  glfwSetWindowSize(window, w, h);
  glViewport(0, 0, w, h);
}

// ----------------------------------------
// Main
int main(void)
{
  GLFWwindow *window;

  glfwSetErrorCallback(error_callback);

  if (!glfwInit())
  {
    // Initialization failed
    exit(EXIT_FAILURE);
  }

  glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 1); // set OpenGL 1.1
  glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);

  // create window
  window = glfwCreateWindow(SCRW, SCRH, WDW_TITLE, NULL, NULL);
  if (!window)
  {
    // Window or OpenGL context creation failed
    glfwTerminate();
    exit(EXIT_FAILURE);
  }

  glfwSetKeyCallback(window, key_callback);
  glfwSetWindowSizeCallback(window, resize);

  glfwMakeContextCurrent(window);
  glfwSwapInterval(1);

  // Init OpenGL
  int scrw, scrh;
  glfwGetFramebufferSize(window, &scrw, &scrh);
  init_animation(scrw, scrh);

  // main loop
  while (!glfwWindowShouldClose(window))
  {
    render();
    glfwSwapBuffers(window);
    glfwPollEvents();
  }

  glfwDestroyWindow(window);
  glfwTerminate();
  exit(EXIT_SUCCESS);
}

render() の中で OpenGL の描画をしている。glDrawArrays() の前後を見れば、今回のモデルデータの利用の仕方が分かるだろうか…。

  glEnable(GL_CULL_FACE);
  glCullFace(GL_BACK);

  glEnableClientState(GL_VERTEX_ARRAY);
  // glEnableClientState(GL_TEXTURE_COORD_ARRAY);
  glEnableClientState(GL_NORMAL_ARRAY);
  glEnableClientState(GL_COLOR_ARRAY);

  glVertexPointer(3, GL_FLOAT, 0, suzanne01_obj_vtx);
  // glTexCoordPointer(2, GL_FLOAT, 0, suzanne01_obj_uv);
  glNormalPointer(GL_FLOAT, 0, suzanne01_obj_nml);
  glColorPointer(4, GL_FLOAT, 0, suzanne01_obj_col);

  glDrawArrays(GL_TRIANGLES, 0, suzanne01_obj_vtx_size);

  glDisableClientState(GL_COLOR_ARRAY);
  glDisableClientState(GL_NORMAL_ARRAY);
  // glDisableClientState(GL_TEXTURE_COORD_ARRAY);
  glDisableClientState(GL_VERTEX_ARRAY);
  • glEnableClientState() で、利用したい情報を有効にする。
  • GL_VERTEX_ARRAY, GL_TEXTURE_COORD_ARRAY, GL_NORMAL_ARRAY, GL_COLOR_ARRAY 等を指定することで、頂点座標配列、テクスチャ座標配列、法線情報配列、頂点カラー配列を有効にできる。
  • glVertexPointer()、glTexCoordPointer()、glNormalPointer()、glColorPointer() で、利用したい頂点配列等を指定できる。
  • glDrawArrays() で、頂点配列を使って描画。
  • glDisableClientState() で、利用したい情報を無効化。


Makefileは以下。MinGW gcc 6.3.0、MSYS2 gcc 13.2.0、Ubuntu Linux 22.04 LTS + gcc 11.4.0 でビルドできることを確認した。

_Makefile
MODELS = cube01.h suzanne01.h
SRCS = 01_drawobj.c $(MODELS)

ifeq ($(OS),Windows_NT)
# Windows
TARGET = 01_drawobj.exe
GCC_VERSION=$(shell gcc -dumpversion)

ifeq ($(GCC_VERSION),6.3.0)
# MinGW gcc 6.3.0
LIBS = -static -lopengl32 -lglu32 -lwinmm -lgdi32 -lglfw3dll -mwindows
else
# MinGW gcc 9.2.0, MSYS2
LIBS = -static -lopengl32 -lglu32 -lwinmm -lgdi32 -lglfw3 -mwindows
endif

else
# Linux (Ubuntu Linux 22.04 LTS, gcc 11.4.0)
TARGET = 01_drawobj
LIBS = -lGL -lGLU -lglfw -lm
endif

$(TARGET): $(SRCS) Makefile
    gcc -o $@ $(SRCS) $(LIBS)

%.h: %.obj
    python pyobj2c.py $< > $@

.PHONY: clean
clean:
    rm -f *.o $(TARGET) $(MODELS)

ちなみに、MinGW gcc 6.3.0 でビルドすると、別途 glfw3.dll が必要になるけれど、MSYS2 gcc 13.2.0 でビルドすれば GLFW3 をスタティックリンクできるので、MSYS2 を使って実験したほうがいいと思う。

以下を打ってビルド。
make clean
make
01_drawobj.exe が生成される。

2024/04/15(月) [n年前の日記]

#1 [prog][cg_tools] OpenGLでモデルデータを読み込めそうか調べてる。その5

C/C++ とOpenGLで、何かしらのモデルデータを読み込んで描画したい。

wavefront形式(.obj)を読み込んで、C言語の配列の形で出力する Pythonスクリプトは書けたので、C言語でOpenGLを使って描画するソースを書いて動作確認中。一応描画はできたような気がする。明日の日記でまとめよう…。

2024/04/14() [n年前の日記]

#1 [prog][cg_tools] OpenGLでモデルデータを読み込めそうか調べてる。その4

C/C++ とOpenGLで、何かしらのモデルデータを読み込んで描画したい。

wavefront形式(.obj)を読み込んで、C言語の配列の形で出力する Pythonスクリプトを書いてるところ。複数のマテリアルを割り当ててある場合は、頂点カラーにマテリアルの Kd 値を割り振るようにしてみた。

glDrawElements()はビミョーに使えない印象 :

OpenGL 1.1 の glDrawElements() について調べてる。

OpenGL 1.1 で頂点配列を使ってポリゴンを描画する場合、glDrawArrays() か glDrawElements() を使えるらしいのだけど。
  • glDrawArrays() ... 頂点配列を渡して描画する。
  • glDrawElements() ... 頂点配列のインデックス値の配列を渡して描画する。

頂点配列のインデックス値を渡して描画するほうが、頂点配列内の座標値が重複したりしないので、データ量は少なくて済む。実際、wavefront形式も、頂点座標群とは別に、面を構成する情報としてインデックス値群を持っているので、このメリットを意識したフォーマットになっている。

しかし各頂点には、頂点座標の他にも、法線情報(Normal情報)、テクスチャのUV座標、頂点カラー情報も持たせないといけないはずで…。

wavefront形式の場合、頂点配列、法線情報、テクスチャのUV座標のインデックス値が、別々の値になってる場面がほとんど。しかし、OpenGL の glDrawElements() のサンプルコードを見る限り、どうやら頂点、法線、UVで、別々のインデックス値を指定することはできないようで…。となると、wavefront形式が持っている面情報 = インデックス値列をそのまま OpenGL に流用することはできないようだなと…。

仕方ないので、Pythonスクリプト側で、頂点座標、法線情報、UV座標、頂点カラーを、全部配列にずらずらと展開してから出力してしまうことにした。それらの配列を使って、glDrawElements() ではなく、glDrawArrays() で描画する。

こういうデータの持ち方では、座標値が重複するのでデータ量はかなり増えてしまうけど、これはもうどうしようもないかなと…。いやまあ、glBegin() と glEnd() を使って、ポリゴンを1枚1枚ループを回して描画するようにすれば、wavefront形式が持ってる面情報をそのまま使うこともできるだろうけど、その代わり処理が遅くなってしまうはず。データは増えるけど処理が速いか、データは減るけど処理が遅いか、どっちを取るか、という話になるのだろうか。

本当に頂点配列を使うと処理は速くなるのかな。ベンチマークを取ったわけじゃないから確実なことは言えない気もする…。

以上、3 日分です。

過去ログ表示

Prev - 2024/04 -
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

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project