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 を実行すると以下のような見た目になる。モデルデータを描画することができている。
今回は、wavefront形式(.obj)を Pythonスクリプトで読み込んで、C言語の配列の形で出力して、それを使って OpenGLで描画するC言語のソースを書いてみた。C/C++ でモデルデータファイルを読み込んでいるわけではないけれど、.exeの中にモデルデータを含めてしまいたい場合は、こういうやり方でもいいんじゃないかなあ、と…。
動作確認環境は以下。
- Windows10 x64 22H2, MinGW gcc 6.3.0, Python 3.10.10 x64, GNU Make 3.81
- Windows10 x64 22H2, MSYS2 gcc 13.2.0 x64, Python 3.10.10 x64, GNU Make 4.4.1, GLFW 3.4-1
- Ubuntu Linux 22.04 LTS x64, gcc 11.4.0, Python 3.10.12, GNU Make 4.3, libglfw3-dev 3.3.6-1
先に実行結果を。今回作成した 01_drawobj.exe を実行すると以下のような見た目になる。モデルデータを描画することができている。
◎ 使用するモデルデータ :
blender 3.6.9 x64 LTS で簡単なモデルデータを作成して、wavefront形式(.obj)でエクスポートした。ファイル → エクスポート → Wavefront (.obj) を選択。
blender から .obj をエクスポートする際、四角形を三角形にするオプション(メッシュの三角面化)にチェックを入れる。後で出てくる Pythonスクリプトも、C言語のソースも、三角形ポリゴンにのみ対応させてある状態なので…。
作成したモデルデータは以下。一つは、只の四角い箱。各面が別の色になっている。
_cube01.obj
_cube01.mtl
以下のモデルデータは、blenderでお馴染みのスザンヌ。目や口のあたりを別の色にしている。
_suzanne01.obj
_suzanne01.mtl
- .objファイルは、頂点座標、テクスチャのUV座標、法線情報、面情報が保存されているファイル。
- .mtlファイルは、マテリアル情報が保存されているファイル。
blender から .obj をエクスポートする際、四角形を三角形にするオプション(メッシュの三角面化)にチェックを入れる。後で出てくる Pythonスクリプトも、C言語のソースも、三角形ポリゴンにのみ対応させてある状態なので…。
作成したモデルデータは以下。一つは、只の四角い箱。各面が別の色になっている。
_cube01.obj
_cube01.mtl
以下のモデルデータは、blenderでお馴染みのスザンヌ。目や口のあたりを別の色にしている。
_suzanne01.obj
_suzanne01.mtl
◎ PythonスクリプトでC言語の配列に変換 :
この wavefront形式(.obj) と .mtl を、Pythonスクリプトを使って、C言語の配列の形に変換して出力する。今回書いたPythonスクリプトは以下。ライセンスは CC0 / Public Domain ということで…。
_pyobj2c.py
使用方法は以下。
例えば hoge.obj というファイルを読み込ませた場合は、以下の名前の配列を作成する。
前述の2つの .obj を変換。以下の結果が得られた。
_cube01.h
_suzanne01.h
これらの配列を、C言語のソースで #include して利用する。
_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 ... 頂点カラー配列の個数
前述の2つの .obj を変換。以下の結果が得られた。
_cube01.h
_suzanne01.h
これらの配列を、C言語のソースで #include して利用する。
◎ C言語のソース :
C言語のソースは以下。ライセンスは CC0 / Public Domain ってことで。ちなみに、ウインドウの作成等はGLFW3を利用している。
_01_drawobj.c
render() の中で OpenGL の描画をしている。glDrawArrays() の前後を見れば、今回のモデルデータの利用の仕方が分かるだろうか…。
Makefileは以下。MinGW gcc 6.3.0、MSYS2 gcc 13.2.0、Ubuntu Linux 22.04 LTS + gcc 11.4.0 でビルドできることを確認した。
_Makefile
ちなみに、MinGW gcc 6.3.0 でビルドすると、別途 glfw3.dll が必要になるけれど、MSYS2 gcc 13.2.0 でビルドすれば GLFW3 をスタティックリンクできるので、MSYS2 を使って実験したほうがいいと思う。
以下を打ってビルド。
_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 make01_drawobj.exe が生成される。
[ ツッコむ ]
以上、1 日分です。