https://github.com/ingowald/optix7course
이전과 마찬가지로 추가된 부분만 설명한다.
// main.cpp
/*! main entry point to this example - initially optix, print hello
world, then exit */
extern "C" int main(int ac, char **av)
{
try {
Model *model = loadOBJ(
#ifdef _WIN32
// on windows, visual studio creates _two_ levels of build dir
// (x86/Release)
"../../models/sponza.obj"
#else
// on linux, common practice is to have ONE level of build dir
// (say, <project>/build/)...
"../models/sponza.obj"
#endif
);
Camera camera = { /*from*/vec3f(-1293.07f, 154.681f, -0.7304f),
/* at */model->bounds.center()-vec3f(0,400,0),
/* up */vec3f(0.f,1.f,0.f) };
// something approximating the scale of the world, so the
// camera knows how much to move for any given user interaction:
const float worldScale = length(model->bounds.span());
SampleWindow *window = new SampleWindow("Optix 7 Course Example",
model,camera,worldScale);
window->run();
} catch (std::runtime_error& e) {
std::cout << GDT_TERMINAL_RED << "FATAL ERROR: " << e.what()
<< GDT_TERMINAL_DEFAULT << std::endl;
std::cout << "Did you forget to copy sponza.obj and sponza.mtl into your optix7course/models directory?" << std::endl;
exit(1);
}
return 0;
}
Model.h의 loadOBJ 함수를 통해 obj 파일을 load한다.
https://casual-effects.com/data/
파일은 위 링크에서 다운받을 수 있다.
// Model.h
#pragma once
#include "gdt/math/AffineSpace.h"
#include <vector>
/*! \namespace osc - Optix Siggraph Course */
namespace osc {
using namespace gdt;
/*! a simple indexed triangle mesh that our sample renderer will
render */
struct TriangleMesh {
std::vector<vec3f> vertex;
std::vector<vec3f> normal;
std::vector<vec2f> texcoord;
std::vector<vec3i> index;
// material data:
vec3f diffuse;
};
struct Model {
~Model()
{ for (auto mesh : meshes) delete mesh; }
std::vector<TriangleMesh *> meshes;
//! bounding box of all vertices in the model
box3f bounds;
};
Model *loadOBJ(const std::string &objFile);
}
// Model.cpp
#include "Model.h"
#define TINYOBJLOADER_IMPLEMENTATION
#include "3rdParty/tiny_obj_loader.h"
//std
#include <set>
namespace std {
inline bool operator<(const tinyobj::index_t &a,
const tinyobj::index_t &b)
{
if (a.vertex_index < b.vertex_index) return true;
if (a.vertex_index > b.vertex_index) return false;
if (a.normal_index < b.normal_index) return true;
if (a.normal_index > b.normal_index) return false;
if (a.texcoord_index < b.texcoord_index) return true;
if (a.texcoord_index > b.texcoord_index) return false;
return false;
}
}
/*! \namespace osc - Optix Siggraph Course */
namespace osc {
/*! find vertex with given position, normal, texcoord, and return
its vertex ID, or, if it doesn't exit, add it to the mesh, and
its just-created index */
int addVertex(TriangleMesh *mesh,
tinyobj::attrib_t &attributes,
const tinyobj::index_t &idx,
std::map<tinyobj::index_t,int> &knownVertices)
{
if (knownVertices.find(idx) != knownVertices.end())
return knownVertices[idx];
const vec3f *vertex_array = (const vec3f*)attributes.vertices.data();
const vec3f *normal_array = (const vec3f*)attributes.normals.data();
const vec2f *texcoord_array = (const vec2f*)attributes.texcoords.data();
int newID = mesh->vertex.size();
knownVertices[idx] = newID;
mesh->vertex.push_back(vertex_array[idx.vertex_index]);
if (idx.normal_index >= 0) {
while (mesh->normal.size() < mesh->vertex.size())
mesh->normal.push_back(normal_array[idx.normal_index]);
}
if (idx.texcoord_index >= 0) {
while (mesh->texcoord.size() < mesh->vertex.size())
mesh->texcoord.push_back(texcoord_array[idx.texcoord_index]);
}
// just for sanity's sake:
if (mesh->texcoord.size() > 0)
mesh->texcoord.resize(mesh->vertex.size());
// just for sanity's sake:
if (mesh->normal.size() > 0)
mesh->normal.resize(mesh->vertex.size());
return newID;
}
Model *loadOBJ(const std::string &objFile)
{
Model *model = new Model;
const std::string mtlDir
= objFile.substr(0,objFile.rfind('/')+1);
PRINT(mtlDir);
tinyobj::attrib_t attributes;
std::vector<tinyobj::shape_t> shapes;
std::vector<tinyobj::material_t> materials;
std::string err = "";
bool readOK
= tinyobj::LoadObj(&attributes,
&shapes,
&materials,
&err,
&err,
objFile.c_str(),
mtlDir.c_str(),
/* triangulate */true);
if (!readOK) {
throw std::runtime_error("Could not read OBJ model from "+objFile+":"+mtlDir+" : "+err);
}
if (materials.empty())
throw std::runtime_error("could not parse materials ...");
std::cout << "Done loading obj file - found " << shapes.size() << " shapes with " << materials.size() << " materials" << std::endl;
for (int shapeID=0;shapeID<(int)shapes.size();shapeID++) {
tinyobj::shape_t &shape = shapes[shapeID];
std::set<int> materialIDs;
for (auto faceMatID : shape.mesh.material_ids)
materialIDs.insert(faceMatID);
for (int materialID : materialIDs) {
std::map<tinyobj::index_t,int> knownVertices;
TriangleMesh *mesh = new TriangleMesh;
for (int faceID=0;faceID<shape.mesh.material_ids.size();faceID++) {
if (shape.mesh.material_ids[faceID] != materialID) continue;
tinyobj::index_t idx0 = shape.mesh.indices[3*faceID+0];
tinyobj::index_t idx1 = shape.mesh.indices[3*faceID+1];
tinyobj::index_t idx2 = shape.mesh.indices[3*faceID+2];
vec3i idx(addVertex(mesh, attributes, idx0, knownVertices),
addVertex(mesh, attributes, idx1, knownVertices),
addVertex(mesh, attributes, idx2, knownVertices));
mesh->index.push_back(idx);
mesh->diffuse = (const vec3f&)materials[materialID].diffuse;
mesh->diffuse = gdt::randomColor(materialID);
}
if (mesh->vertex.empty())
delete mesh;
else
model->meshes.push_back(mesh);
}
}
// of course, you should be using tbb::parallel_for for stuff
// like this:
for (auto mesh : model->meshes)
for (auto vtx : mesh->vertex)
model->bounds.extend(vtx);
std::cout << "created a total of " << model->meshes.size() << " meshes" << std::endl;
return model;
}
}
addVertex를 확인해보면, idx가 이미 knownVertices map에 있으면 기존 정점 ID를 반환하고 종료한다.
tinyobj::attrib_t의 instance인 attributes에 있는 vertex, normal, texCoord의 배열에 대한 포인터를 이용하여 각 list의 포인터를 저장한다.
TriangleMesh vetrex에 저장되는 index를 value로 idx를 knownVertices의 key로 사용한다.
정점에 normal index가 존재할 경우(idx.normal_index >= 0) mesh->vertex가 mesh->vertex와 크기가 동일하게끔 현재 idx의 normal 값을 넣어준다.
마찬가지로 normal 동일하게 texCoord도 동일하게 한다.
후에 함수의 사용을 편안하게끔 mesh의 vertex와 texcoord list를 vertex의 크기와 동일하게 하고, mesh->vertex에 input된 vertex의 index를 반환한다.
이러한 일련의 작업은 idx를 통해서 vertex와 normal, texcoord를 한번에 접근하기 위한 것으로 보인다.
이어서 loadOBJ 함수를 보겠다.
objFrile 인자는 파일 경로에 대한 입력이다. 폴더 경로를 mtlDir를 통해 얻고 tinyobj의 LoadObj 함수를 통해 파일명, 경로명과 출력값으로 호출한다.
material 값을 읽을 수 없으면 오류를 발생시키고 종료시킨다.
attributes은 file 안에 vertex array 정보가 담겨 있다.
shape는 file 안에 분리되어있는 각각의 object에 대한 정보를 나타낸다.
matrials는 각각의 shape의 material에 대한 정보를 담는다.
각각의 shape에 대해서 반복을 한다.
shape에서 사용하는 중복되지 않는 material id list를 얻기 위해 materialIDs set으로 얻는다.
material id가 같은 것끼리 TriangleMesh에 저장한다.
TriangleMesh에 저장되는 값은 addVertex 함수를 통해서 vertex list가 저장되고, loadOBJ 함수에서 addVertex의 반환 값인 vertex list에서의 index가 저장된다.
diffuse는 원래는 material에서 불러와야 겠지만, 여기서는 randomColor를 저장한다.
model에는 이러한 TriangleMesh가 저장된다.
정리하면 Modle에는 TriangleMesh가 저장되고 TriangleMesh는 file 안에 있는 각각의 object(shape)별로 그리고 다른 material로 저장된다.
그리고 Modle의 모든 vertex를 감쌀 수 있는 boundbox를 model의 bounds에 저장한다.
// SampleRenderer.cpp
SampleRenderer::SampleRenderer(const Model *model)
: model(model)
TriangleMesh가 아닌 Model 값을 저장한다.
// SampleRenderer.cpp
OptixTraversableHandle SampleRenderer::buildAccel()
{
PING;
PRINT(model->meshes.size());
vertexBuffer.resize(model->meshes.size());
indexBuffer.resize(model->meshes.size());
OptixTraversableHandle asHandle { 0 };
// ==================================================================
// triangle inputs
// ==================================================================
std::vector<OptixBuildInput> triangleInput(model->meshes.size());
std::vector<CUdeviceptr> d_vertices(model->meshes.size());
std::vector<CUdeviceptr> d_indices(model->meshes.size());
std::vector<uint32_t> triangleInputFlags(model->meshes.size());
for (int meshID=0;meshID<model->meshes.size();meshID++) {
// upload the model to the device: the builder
TriangleMesh &mesh = *model->meshes[meshID];
vertexBuffer[meshID].alloc_and_upload(mesh.vertex);
indexBuffer[meshID].alloc_and_upload(mesh.index);
triangleInput[meshID] = {};
triangleInput[meshID].type
= OPTIX_BUILD_INPUT_TYPE_TRIANGLES;
// create local variables, because we need a *pointer* to the
// device pointers
d_vertices[meshID] = vertexBuffer[meshID].d_pointer();
d_indices[meshID] = indexBuffer[meshID].d_pointer();
triangleInput[meshID].triangleArray.vertexFormat = OPTIX_VERTEX_FORMAT_FLOAT3;
triangleInput[meshID].triangleArray.vertexStrideInBytes = sizeof(vec3f);
triangleInput[meshID].triangleArray.numVertices = (int)mesh.vertex.size();
triangleInput[meshID].triangleArray.vertexBuffers = &d_vertices[meshID];
triangleInput[meshID].triangleArray.indexFormat = OPTIX_INDICES_FORMAT_UNSIGNED_INT3;
triangleInput[meshID].triangleArray.indexStrideInBytes = sizeof(vec3i);
triangleInput[meshID].triangleArray.numIndexTriplets = (int)mesh.index.size();
triangleInput[meshID].triangleArray.indexBuffer = d_indices[meshID];
triangleInputFlags[meshID] = 0 ;
// in this example we have one SBT entry, and no per-primitive
// materials:
triangleInput[meshID].triangleArray.flags = &triangleInputFlags[meshID];
triangleInput[meshID].triangleArray.numSbtRecords = 1;
triangleInput[meshID].triangleArray.sbtIndexOffsetBuffer = 0;
triangleInput[meshID].triangleArray.sbtIndexOffsetSizeInBytes = 0;
triangleInput[meshID].triangleArray.sbtIndexOffsetStrideInBytes = 0;
}
// ==================================================================
// BLAS setup
// ==================================================================
OptixAccelBuildOptions accelOptions = {};
accelOptions.buildFlags = OPTIX_BUILD_FLAG_NONE
| OPTIX_BUILD_FLAG_ALLOW_COMPACTION
;
accelOptions.motionOptions.numKeys = 1;
accelOptions.operation = OPTIX_BUILD_OPERATION_BUILD;
OptixAccelBufferSizes blasBufferSizes;
OPTIX_CHECK(optixAccelComputeMemoryUsage
(optixContext,
&accelOptions,
triangleInput.data(),
(int)model->meshes.size(), // num_build_inputs
&blasBufferSizes
));
// ==================================================================
// prepare compaction
// ==================================================================
CUDABuffer compactedSizeBuffer;
compactedSizeBuffer.alloc(sizeof(uint64_t));
OptixAccelEmitDesc emitDesc;
emitDesc.type = OPTIX_PROPERTY_TYPE_COMPACTED_SIZE;
emitDesc.result = compactedSizeBuffer.d_pointer();
// ==================================================================
// execute build (main stage)
// ==================================================================
CUDABuffer tempBuffer;
tempBuffer.alloc(blasBufferSizes.tempSizeInBytes);
CUDABuffer outputBuffer;
outputBuffer.alloc(blasBufferSizes.outputSizeInBytes);
OPTIX_CHECK(optixAccelBuild(optixContext,
/* stream */0,
&accelOptions,
triangleInput.data(),
(int)model->meshes.size(),
tempBuffer.d_pointer(),
tempBuffer.sizeInBytes,
outputBuffer.d_pointer(),
outputBuffer.sizeInBytes,
&asHandle,
&emitDesc,1
));
CUDA_SYNC_CHECK();
// ==================================================================
// perform compaction
// ==================================================================
uint64_t compactedSize;
compactedSizeBuffer.download(&compactedSize,1);
asBuffer.alloc(compactedSize);
OPTIX_CHECK(optixAccelCompact(optixContext,
/*stream:*/0,
asHandle,
asBuffer.d_pointer(),
asBuffer.sizeInBytes,
&asHandle));
CUDA_SYNC_CHECK();
// ==================================================================
// aaaaaand .... clean up
// ==================================================================
outputBuffer.free(); // << the UNcompacted, temporary output buffer
tempBuffer.free();
compactedSizeBuffer.free();
return asHandle;
}
Model의 사이즈만큼 Build Input을 만들어서 Acceleration struct를 만들어준다.
// SampleRenderer.cpp
void SampleRenderer::buildSBT()
{
// ------------------------------------------------------------------
// build raygen records
// ------------------------------------------------------------------
std::vector<RaygenRecord> raygenRecords;
for (int i=0;i<raygenPGs.size();i++) {
RaygenRecord rec;
OPTIX_CHECK(optixSbtRecordPackHeader(raygenPGs[i],&rec));
rec.data = nullptr; /* for now ... */
raygenRecords.push_back(rec);
}
raygenRecordsBuffer.alloc_and_upload(raygenRecords);
sbt.raygenRecord = raygenRecordsBuffer.d_pointer();
// ------------------------------------------------------------------
// build miss records
// ------------------------------------------------------------------
std::vector<MissRecord> missRecords;
for (int i=0;i<missPGs.size();i++) {
MissRecord rec;
OPTIX_CHECK(optixSbtRecordPackHeader(missPGs[i],&rec));
rec.data = nullptr; /* for now ... */
missRecords.push_back(rec);
}
missRecordsBuffer.alloc_and_upload(missRecords);
sbt.missRecordBase = missRecordsBuffer.d_pointer();
sbt.missRecordStrideInBytes = sizeof(MissRecord);
sbt.missRecordCount = (int)missRecords.size();
// ------------------------------------------------------------------
// build hitgroup records
// ------------------------------------------------------------------
int numObjects = (int)model->meshes.size();
std::vector<HitgroupRecord> hitgroupRecords;
for (int meshID=0;meshID<numObjects;meshID++) {
HitgroupRecord rec;
// all meshes use the same code, so all same hit group
OPTIX_CHECK(optixSbtRecordPackHeader(hitgroupPGs[0],&rec));
rec.data.color = model->meshes[meshID]->diffuse;
rec.data.vertex = (vec3f*)vertexBuffer[meshID].d_pointer();
rec.data.index = (vec3i*)indexBuffer[meshID].d_pointer();
hitgroupRecords.push_back(rec);
}
hitgroupRecordsBuffer.alloc_and_upload(hitgroupRecords);
sbt.hitgroupRecordBase = hitgroupRecordsBuffer.d_pointer();
sbt.hitgroupRecordStrideInBytes = sizeof(HitgroupRecord);
sbt.hitgroupRecordCount = (int)hitgroupRecords.size();
}
hitgroup record를 model의 수만큼 만든다.