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 が生成される。
[ ツッコむ ]
以上です。