通常の方法は、面の 3 つの頂点すべてに同じ法線ベクトルを格納することです。このようなもの:
Vertex
{
Vector3 position;
Vector3 normal;
}
std::vector<Vertex> vertices;
std::vector<uint32_t> indices;
for(each face f)
{
Vector3 faceNormal = CalculateFaceNormalFromPositions(f); // Generate normal for given face number `f`;
for(each vertex v)
{
Vertex vertex;
vertex.position = LoadPosition(f, v); // Load position from OBJ based on face index (f) and vertex index (v);
vertex.normal = faceNormal;
vertices.push_back(vertex);
indices.push_back(GetPosIndex()); // only position index from OBJ file needed
}
}
注:通常、面法線の代わりに頂点法線を使用することをお勧めします。これは、頂点法線を使用すると、見栄えの良いライティング アルゴリズムを適用できるためです (ピクセル単位のライティング)。
for(each face f)
{
for(each vertex v)
{
Vertex vertex;
vertex.position = LoadPosition(f, v);
vertex.normal = ...precalculated somewhere...
vertices.push_back(vertex);
}
}
注 2:通常、実行時に計算するのではなく、事前に計算された法線をアセット ファイルから読み取る必要があります。
for(each face f)
{
for(each vertex v)
{
Vertex vertex;
vertex.position = LoadPosition(f, v);
vertex.normal = LoadNormal(f, v);
vertices.push_back(vertex);
}
}
.obj 形式では、頂点ごとの法線を保存できます)。グーグルからの例:
# cube.obj
#
g cube
# positions
v 0.0 0.0 0.0
v 0.0 0.0 1.0
v 0.0 1.0 0.0
v 0.0 1.0 1.0
v 1.0 0.0 0.0
v 1.0 0.0 1.0
v 1.0 1.0 0.0
v 1.0 1.0 1.0
# normals
vn 0.0 0.0 1.0
vn 0.0 0.0 -1.0
vn 0.0 1.0 0.0
vn 0.0 -1.0 0.0
vn 1.0 0.0 0.0
vn -1.0 0.0 0.0
# faces: indices of position / texcoord(empty) / normal
f 1//2 7//2 5//2
f 1//2 3//2 7//2
f 1//6 4//6 3//6
f 1//6 2//6 4//6
f 3//3 8//3 7//3
f 3//3 4//3 8//3
f 5//5 7//5 8//5
f 5//5 8//5 6//5
f 1//4 5//4 6//4
f 1//4 6//4 2//4
f 2//1 6//1 8//1
f 2//1 8//1 4//1
C++ のサンプル コード (未テスト)
struct Vector3{ float x, y, z; };
struct Face
{
uint32_t position_ids[3];
uint32_t normal_ids[3];
};
struct Vertex
{
Vector3 position;
Vector3 normal;
};
std::vector<Vertex> vertices; // Your future vertex buffer
std::vector<uint32_t> indices; // Your future index buffer
void ParseOBJ(std::vector<Vector3>& positions, std::vector <Vector3>& normals, std::vector<Face>& faces) { /*TODO*/ }
void LoadOBJ(const std::wstring& filename, std::vector<Vertex>& vertices, std::vector<uint32_t>& indices)
{
// after parsing obj file
// you will have positions, normals
// and faces (which contains indices for positions and normals)
std::vector<Vector3> positions;
std::vector<Vector3> normals;
std::vector<Face> faces;
ParseOBJ(positions, normals, faces);
for (auto itFace = faces.begin(); itFace != faces.end(); ++itFace) // for each face
{
for (uint32_t i = 0; i < 3; ++i) // for each face vertex
{
uint32_t position_id = itFace->position_ids[i]; // just for short writing later
uint32_t normal_id = itFace->normal_ids[i];
Vertex vertex;
vertex.position = positions[position_id];
vertex.normal = normals[normal_id];
indices.push_back(position_id); // Note: only position's indices
vertices.push_back(vertex);
}
}
}
頂点内の法線のデータをマージした後は、法線のインデックスはもう必要ないことに注意してください。したがって、法線はインデックス化されません (また、2 つの等しい法線が異なる頂点に格納される可能性があり、これはスペースの無駄です)。ただし、位置にはインデックスが付けられているため、インデックス付きレンダリングを引き続き使用できます。
もちろん、最新の GPU のプログラム可能なパイプラインにより、よりトリッキーなことが可能になると言わざるを得ません。
- それぞれのバッファを作成します: 位置、法線、pos_indices、nor_indices
- 現在の vertex_id を使用して、シェーダーの現在の position_id と対応する位置、normal_id と対応する法線を読み取ります
- シェーダーで法線を生成することもできます (したがって、normal_id と法線バッファーはまったく必要ありません)。
- ジオメトリ シェーダーで顔を組み立てることができます
- ...ここで別の悪いアイデア =) そのようなアルゴリズムでは、レンダリング システムがより複雑になり、ほとんど利益が得られません。