https://github.com/ingowald/optix7course
이전과 마찬가지로 달라진 점만 작성한다.
// 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;
int diffuseTextureID { -1 };
};
struct Texture {
~Texture()
{ if (pixel) delete[] pixel; }
uint32_t *pixel { nullptr };
vec2i resolution { -1 };
};
struct Model {
~Model()
{
for (auto mesh : meshes) delete mesh;
for (auto texture : textures) delete texture;
}
std::vector<TriangleMesh *> meshes;
std::vector<Texture *> textures;
//! bounding box of all vertices in the model
box3f bounds;
};
Model *loadOBJ(const std::string &objFile);
}
TriangleMesh에 diffuseTextureID 가 생겼다.
Texture 구조체가 생겼으며 Texture가 저장될 예정이다.
그리고 Model에 Texture의 list가 저장된다.
// Model.cpp
int loadTexture(Model *model,
std::map<std::string,int> &knownTextures,
const std::string &inFileName,
const std::string &modelPath)
{
if (inFileName == "")
return -1;
if (knownTextures.find(inFileName) != knownTextures.end())
return knownTextures[inFileName];
std::string fileName = inFileName;
// first, fix backspaces:
for (auto &c : fileName)
if (c == '\\') c = '/';
fileName = modelPath+"/"+fileName;
vec2i res;
int comp;
unsigned char* image = stbi_load(fileName.c_str(),
&res.x, &res.y, &comp, STBI_rgb_alpha);
int textureID = -1;
if (image) {
textureID = (int)model->textures.size();
Texture *texture = new Texture;
texture->resolution = res;
texture->pixel = (uint32_t*)image;
/* iw - actually, it seems that stbi loads the pictures
mirrored along the y axis - mirror them here */
for (int y=0;y<res.y/2;y++) {
uint32_t *line_y = texture->pixel + y * res.x;
uint32_t *mirrored_y = texture->pixel + (res.y-1-y) * res.x;
int mirror_y = res.y-1-y;
for (int x=0;x<res.x;x++) {
std::swap(line_y[x],mirrored_y[x]);
}
}
model->textures.push_back(texture);
} else {
std::cout << GDT_TERMINAL_RED
<< "Could not load texture from " << fileName << "!"
<< GDT_TERMINAL_DEFAULT << std::endl;
}
knownTextures[inFileName] = textureID;
return textureID;
}
addVertex 함수와 유사하게 file 이름으로 동일한 Texture를 load 하지 않게 한다.
stbi_load로 image를 부른다.
image가 오류가 발생하지 않는다면 Texture를 저장하고, sbti library는 y축이 뒤집어지는 작업이 있기 때문에 뒤집어주고 model->textures에 저장한다. 그리고 같은 texture를 load 하지않게 knownTextures에 file name으로 textures index를 저장하고, 반환한다.
// Model.cpp
Model *loadOBJ(const std::string &objFile)
{
Model *model = new Model;
const std::string modelDir
= objFile.substr(0,objFile.rfind('/')+1);
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(),
modelDir.c_str(),
/* triangulate */true);
if (!readOK) {
throw std::runtime_error("Could not read OBJ model from "+objFile+" : "+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;
std::map<std::string, int> knownTextures;
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->diffuseTextureID = loadTexture(model,
knownTextures,
materials[materialID].diffuse_texname,
modelDir);
}
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;
}
}
TriangleMesh에 저장할 때 texture의 id도 저장하며, diffuse도 제대로 저장한다.
texture의 이름은 보다시피 material[materialID].diffuse_texname으로 불러올 수 있다.
// SampleRenderer.cpp
/*! constructor - performs all setup, including initializing
optix, creates module, pipeline, programs, SBT, etc. */
SampleRenderer::SampleRenderer(const Model *model)
: model(model)
{
initOptix();
std::cout << "#osc: creating optix context ..." << std::endl;
createContext();
std::cout << "#osc: setting up module ..." << std::endl;
createModule();
std::cout << "#osc: creating raygen programs ..." << std::endl;
createRaygenPrograms();
std::cout << "#osc: creating miss programs ..." << std::endl;
createMissPrograms();
std::cout << "#osc: creating hitgroup programs ..." << std::endl;
createHitgroupPrograms();
launchParams.traversable = buildAccel();
std::cout << "#osc: setting up optix pipeline ..." << std::endl;
createPipeline();
createTextures();
std::cout << "#osc: building SBT ..." << std::endl;
buildSBT();
launchParamsBuffer.alloc(sizeof(launchParams));
std::cout << "#osc: context, module, pipeline, etc, all set up ..." << std::endl;
std::cout << GDT_TERMINAL_GREEN;
std::cout << "#osc: Optix 7 Sample fully set up" << std::endl;
std::cout << GDT_TERMINAL_DEFAULT;
}
추가적으로 createTextures() 함수가 생겼다.
// SampleRenderer.cpp
void SampleRenderer::createTextures()
{
int numTextures = (int)model->textures.size();
textureArrays.resize(numTextures);
textureObjects.resize(numTextures);
for (int textureID=0;textureID<numTextures;textureID++) {
auto texture = model->textures[textureID];
cudaResourceDesc res_desc = {};
cudaChannelFormatDesc channel_desc;
int32_t width = texture->resolution.x;
int32_t height = texture->resolution.y;
int32_t numComponents = 4;
int32_t pitch = width*numComponents*sizeof(uint8_t);
channel_desc = cudaCreateChannelDesc<uchar4>();
cudaArray_t &pixelArray = textureArrays[textureID];
CUDA_CHECK(MallocArray(&pixelArray,
&channel_desc,
width,height));
CUDA_CHECK(Memcpy2DToArray(pixelArray,
/* offset */0,0,
texture->pixel,
pitch,pitch,height,
cudaMemcpyHostToDevice));
res_desc.resType = cudaResourceTypeArray;
res_desc.res.array.array = pixelArray;
cudaTextureDesc tex_desc = {};
tex_desc.addressMode[0] = cudaAddressModeWrap;
tex_desc.addressMode[1] = cudaAddressModeWrap;
tex_desc.filterMode = cudaFilterModeLinear;
tex_desc.readMode = cudaReadModeNormalizedFloat;
tex_desc.normalizedCoords = 1;
tex_desc.maxAnisotropy = 1;
tex_desc.maxMipmapLevelClamp = 99;
tex_desc.minMipmapLevelClamp = 0;
tex_desc.mipmapFilterMode = cudaFilterModePoint;
tex_desc.borderColor[0] = 1.0f;
tex_desc.sRGB = 0;
// Create texture object
cudaTextureObject_t cuda_tex = 0;
CUDA_CHECK(CreateTextureObject(&cuda_tex, &res_desc, &tex_desc, nullptr));
textureObjects[textureID] = cuda_tex;
}
}
// SampleRenderer.h
class SampleRenderer{
...
/*! @{ one texture object and pixel array per used texture */
std::vector<cudaArray_t> textureArrays;
std::vector<cudaTextureObject_t> textureObjects;
/*! @} */
}
texture pixel들을 device memory로 올리기 위해서 cudaArray_t에 MallocArray 함수와, Memcpy2DToArray 함수를 이용해서 복사한다.
numcomponents는 rgba로 4개이다.
pitch는 각 행의 바이트 수가 저장된다.
cudaTextureObject_t는 texture에 관한 설명을 나타낸다.
resource가 Array type이고, array.array로 cudaArray_t를 넘겨준다.
cudaTextureDesc는 Texture에 대한 설정을 한다.
addressMode는 가로 세로 방향의 주소 모드를 cudaAddressModeWrap으로 텍스처가 반복되는 모드로 동작한다.
filterMode는 linear interpolation을, readMode는 텍스처 값을 정규화된 부동 소수점 값으로 반환한다.
normalizedCoords는 정규화된 texCoord로 설정한다.
외에도 확대 축소에 대한 설정을 해준다.
cudaTextureObject_t를 CreateTextureObject로 초기화시켜준다.
// 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++) {
auto mesh = model->meshes[meshID];
HitgroupRecord rec;
// all meshes use the same code, so all same hit group
OPTIX_CHECK(optixSbtRecordPackHeader(hitgroupPGs[0],&rec));
rec.data.color = mesh->diffuse;
if (mesh->diffuseTextureID >= 0) {
rec.data.hasTexture = true;
rec.data.texture = textureObjects[mesh->diffuseTextureID];
} else {
rec.data.hasTexture = false;
}
rec.data.index = (vec3i*)indexBuffer[meshID].d_pointer();
rec.data.vertex = (vec3f*)vertexBuffer[meshID].d_pointer();
rec.data.normal = (vec3f*)normalBuffer[meshID].d_pointer();
rec.data.texcoord = (vec2f*)texcoordBuffer[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();
}
이전과 마찬가지로 hit group만 변경하면 된다.
// LaunchParams.h
#pragma once
#include "gdt/math/vec.h"
#include "optix7.h"
namespace osc {
using namespace gdt;
struct TriangleMeshSBTData {
vec3f color;
vec3f *vertex;
vec3f *normal;
vec2f *texcoord;
vec3i *index;
bool hasTexture;
cudaTextureObject_t texture;
};
struct LaunchParams
{
struct {
uint32_t *colorBuffer;
vec2i size;
} frame;
struct {
vec3f position;
vec3f direction;
vec3f horizontal;
vec3f vertical;
} camera;
OptixTraversableHandle traversable;
};
} // ::osc
textureID가 존재하면 texture와 관련된 설정을 해준다.
// devicePrograms.cu
extern "C" __global__ void __closesthit__radiance()
{
const TriangleMeshSBTData &sbtData
= *(const TriangleMeshSBTData*)optixGetSbtDataPointer();
// ------------------------------------------------------------------
// gather some basic hit information
// ------------------------------------------------------------------
const int primID = optixGetPrimitiveIndex();
const vec3i index = sbtData.index[primID];
const float u = optixGetTriangleBarycentrics().x;
const float v = optixGetTriangleBarycentrics().y;
// ------------------------------------------------------------------
// compute normal, using either shading normal (if avail), or
// geometry normal (fallback)
// ------------------------------------------------------------------
vec3f N;
if (sbtData.normal) {
N = (1.f-u-v) * sbtData.normal[index.x]
+ u * sbtData.normal[index.y]
+ v * sbtData.normal[index.z];
} else {
const vec3f &A = sbtData.vertex[index.x];
const vec3f &B = sbtData.vertex[index.y];
const vec3f &C = sbtData.vertex[index.z];
N = normalize(cross(B-A,C-A));
}
N = normalize(N);
// ------------------------------------------------------------------
// compute diffuse material color, including diffuse texture, if
// available
// ------------------------------------------------------------------
vec3f diffuseColor = sbtData.color;
if (sbtData.hasTexture && sbtData.texcoord) {
const vec2f tc
= (1.f-u-v) * sbtData.texcoord[index.x]
+ u * sbtData.texcoord[index.y]
+ v * sbtData.texcoord[index.z];
vec4f fromTexture = tex2D<float4>(sbtData.texture,tc.x,tc.y);
diffuseColor *= (vec3f)fromTexture;
}
// ------------------------------------------------------------------
// perform some simple "NdotD" shading
// ------------------------------------------------------------------
const vec3f rayDir = optixGetWorldRayDirection();
const float cosDN = 0.2f + .8f*fabsf(dot(rayDir,N));
vec3f &prd = *(vec3f*)getPRD<vec3f>();
prd = cosDN * diffuseColor;
}
optixGetTriangleBarycentrics()를 통해서 ray가 맞은 곳의 삼각형의 bartCentric 좌표를 얻는다.
normal 값이 들어있다면 barycentric을 기반으로 normal 값을 얻는다.
만약 texture를 갖고 없으면 diffuse color로 설정한다.
만약 teture르 갖고 있다면 barycentric을 기반으로 texcoord를 구하고, tex2D로 texcoord에 해당하는 픽셀 값을 얻는다.