Graphics3D API
ヘッダ: shapoco/gfx3d/gfx3d.hpp
データ構造
Vertex / VertexBuffer
struct Vertex {
vec3f position;
vec3f normal;
vec2f uv; // 環境マッピング時は未使用
gfx2d::Color color; // ARGB8888。MaterialFlags::VERTEX_COLOR のときに使用 (α は無視)
};
constexpr gfx2d::Color VERTEX_WHITE = 0xFFFFFFFFu;
// 16 バイトの圧縮頂点。フラッシュ上のモデルデータを小さくする
struct PackedVertex {
int16_t position[3]; // VertexBuffer::scale 倍して bias を足した値が座標
int16_t uv[2]; // 1/1024 単位 (範囲 -32〜32)
int8_t normal[3]; // 1/127 単位
uint8_t color[3]; // R, G, B
};
struct VertexBuffer {
uint16_t vertexCount;
const Vertex *vertices; // nullptr なら packed を使う
const PackedVertex *packed = nullptr; // 16 バイト頂点
vec3f scale = {1, 1, 1}; // packed の座標スケール
vec3f bias = {0, 0, 0}; // packed の座標オフセット
};
VertexBuffer は 36 バイトの Vertex と 16 バイトの PackedVertex のどちらでも持てます。
圧縮頂点の座標はプリミティブのバウンディングボックスを 65534 分割した精度、法線は約 1% の誤差で
単位長になり、どちらも出力フォーマットの分解能より十分細かい値です。
デコードは頂点ごとに 1 回だけ (頂点キャッシュが吸収する) なので、効くのはフラッシュ使用量です。
bin/gltf2cpp --vertex-format packed がこの形式を出力します
(gltf2cpp: glTF モデルを C++ コードに変換する)。
VertexBuffer はどちらの形式を指す場合でも packed とスケール・オフセットを持つため、
32bit ターゲットで 36 バイトです (ポインタと個数だけなら 8 バイト)。
Vertex を使うバッファはこの 28 バイトを余分に払うことになり、
圧縮頂点は 1 頂点あたり 20 バイトを節約します。
プリミティブあたり 2 頂点以上あれば圧縮したほうが小さくなりますが、
数頂点のプリミティブが多数あるモデルでは効果が小さくなります。
Texture
gfx2d::Texture をそのまま使います (Surface と Texture)。任意の有効フォーマットが使えますが、
幅と高さは 2 の冪でなければなりません。
Material
namespace MaterialFlags {
constexpr uint32_t TEXTURE = 1u << 0; // テクスチャを使う
constexpr uint32_t ENV_MAP = 1u << 1; // テクスチャを環境マップとして使う
constexpr uint32_t DOUBLE_SIDED = 1u << 2; // 両面描画 (バックフェイスカリング無効)
constexpr uint32_t VERTEX_COLOR = 1u << 3; // Vertex::color を乗算する
}
struct Material {
colorf diffuse; // 拡散反射色。a は不透明度
colorf ambient; // 環境反射色
const Texture *texture; // 未使用なら nullptr
BlendMode blendMode; // NONE, ALPHA, ADD
uint32_t flags; // MaterialFlags の組み合わせ
};
static const g3::Material matGlass = {
{0.4f, 0.7f, 1.0f, 0.45f}, {0.4f, 0.7f, 1.0f, 1.0f},
nullptr, g3::BlendMode::ALPHA, g3::MaterialFlags::DOUBLE_SIDED,
};
Primitive
enum class PrimitiveType : uint8_t {
TRIANGLES, TRIANGLE_STRIP, TRIANGLE_FAN, // ライティング・テクスチャ・カリングあり
POINTS, LINES, LINE_STRIP, LINE_LOOP // ライティングなし、1 px (点は pointSize)、カリングなし
};
点と線については 3D レンダラの概念 の「点と線」を参照してください。
struct Primitive {
PrimitiveType type;
const VertexBuffer *vertexBuffer;
uint16_t indexCount;
const uint16_t *indices;
const Material *material; // nullptr なら setMaterial() で設定したマテリアル
};
Config / LayerFlags
struct Config {
int16_t screenWidth = 0, screenHeight = 0;
void *arena = nullptr; // 作業メモリ
size_t arenaSize = 0;
int spanCapacity = 0; // 1 ラインに持てる線分数 (レンダリングコンテキストごと)。0 なら既定値
int renderContexts = 1; // 同時に実行できる render() の数 (1〜4)
};
Config defaultConfig(int16_t w, int16_t h, void *arena, size_t arenaSize);
namespace LayerFlags {
constexpr uint32_t NO_DEPTH = 1u << 0; // 深度を持たず、投入順で前後が決まる
}
既定値は defaultConfig() で受け取り、変えたいメンバだけ書き換えて init() に渡します。
メンバは後方互換な既定値付きで追加されることがあります。
Stats
struct Stats {
size_t arenaSize; // init() に渡したアリーナのサイズ
size_t arenaUsed; // 直近フレームで実際に使った量 (固定分 + 三角形 + 線分ピーク)
size_t triBytes; // 三角形バッファの使用量 (レコード + エントリ 4 バイト/個)
size_t triBytesTotal; // 三角形バッファに使える量
int triCount; // 現在のシーンの三角形数 (カリング後)
int triDropped; // バッファあふれで破棄した数 (beginScene() でリセット)
int layerCount; // 現在のシーンのレイヤ数 (beginScene() でリセット)
int layersDropped; // 空きがなく無視した beginLayer() の数 (beginScene() でリセット)
int spanCapacity; // 線分プールの容量 (コンテキストごと)
int spanPeak; // 1 ラインで同時に使った線分数の最大、最も使ったコンテキストの値 (beginRender() でリセット)
int spanDropped; // プールあふれで破棄した数、全コンテキストの合計 (beginRender() でリセット)
int badIndices; // 添字範囲外で破棄した三角形数 (beginScene() でリセット)
int nodesDropped; // スタック満杯で飛ばしたノード数 (beginScene() でリセット)
};
初期化
メンバー |
説明 |
|---|---|
|
初期化パラメータを与える。アリーナが小さすぎる場合は未初期化のまま |
|
|
|
アリーナを手放す。以降の描画呼び出しは何もしない |
|
初期化済みか |
|
画面サイズ |
シーンの構築
メンバー |
説明 |
|---|---|
|
三角形バッファ、レイヤ、スタック、現在の行列をリセットして構築を始める |
|
構築を終える |
|
新しいレイヤを開く。以降のプリミティブはそれまでの全てより手前に描かれる。空きがなければ無視される |
|
現在のレイヤを閉じる。以降のプリミティブは既定フラグの新しいレイヤに入る |
|
現在の行列を単位行列にする |
|
平行移動を右から乗じる |
|
回転 (ラジアン、軸は正規化不要) |
|
スケール |
|
任意の行列を右から乗じる |
|
視点行列を右から乗じる (gluLookAt 相当) |
|
現在の行列とマテリアルをスタックに保存する。満杯なら false を返して何もしない |
|
復元する |
|
現在のマテリアルを設定する (ポインタを保持するので endRender() まで有効なオブジェクトを渡す) |
|
プリミティブを追加する。頂点処理はこの時点で行われる |
|
POINTS の大きさ (正方形、1〜64 px、既定 1) |
|
以後のプリミティブの NDC 深度 (-1..1) に加える値。負で手前。共面のポリゴンの上にワイヤーフレームを描くときに使う (例: -0.002) |
ライトと背景
メンバー |
説明 |
|---|---|
|
平行光源。 |
|
|
|
環境光 |
|
背景色を設定し、背景の塗りを有効にする |
|
背景を塗らず、描画先の内容を残す |
|
投影
メンバー |
説明 |
|---|---|
|
透視投影 (fovY はラジアン) |
|
正射影 |
レンダリング
メンバー |
説明 |
|---|---|
|
三角形を奥から順にソートする |
|
画面領域 (x, y, w, h) を |
|
レンダリングコンテキスト |
|
レンダリングを終える |
|
統計 ( |
|
プリミティブ 1 個が三角形バッファで使うバイト数 (レコード + エントリ)。深度平面・補間色・テクスチャ座標の有無ごと。ビルド設定と |
形状と静的シーン
putCube() などの形状関数は 基本形状、putMesh() / putNode() / putScene() は 静的シーン (Mesh / Node / Scene) を参照してください。
使用例: 帯状レンダリングと統計
g3d.beginRender();
for (int y = 0; y < SCREEN_H; y += BAND_H) {
g3d.render(0, y, SCREEN_W, BAND_H, band);
lcd.writeAsync(0, y, band);
}
g3d.endRender();
使用例: デュアルコア
Config::renderContexts = 2 にすると、2 つのコアで画面の上下半分を同時に描けます。
シーンの構築と beginRender() は片方のコアで行い、両方の render() が終わってから endRender() を呼びます。
g3::Config cfg = g3::defaultConfig(W, H, arena, sizeof(arena));
cfg.renderContexts = 2;
g3d.init(cfg);
// core 1
void core1Main() {
for (;;) {
multicore_fifo_pop_blocking(); // フレームの準備ができた
g3d.render(1, 0, H / 2, W, H / 2, fb, 0, H / 2);
multicore_fifo_push_blocking(1);
}
}
// core 0
buildScene();
g3d.beginRender();
multicore_fifo_push_blocking(1);
g3d.render(0, 0, 0, W, H / 2, fb);
multicore_fifo_pop_blocking();
g3d.endRender();
コンテキストを 1 つ増やすごとに、線分プール 1 つ分、画面 1 行あたり 4 バイト、プリミティブ 1 個あたり 2 バイトを使います。
統計の確認
g3::Stats st = g3d.getStats();
if (st.triDropped || st.spanDropped) {
// アリーナが足りない: 大きくするか、シーンを簡略化する
}