I am adding transparency support to my ray tracing software. Here what i am trying to achieve.
This is output of my GPU raytracer:
.
With CPU and Embree i get this. The shadows are broken:

I am using rtcOccluded1 for these shadows. The corresponding code:
struct OcclusionRay
{
RTCRay ray;
float occlusion = 0.0f;
};
inline float computeOpacity(const Graphics::Model::BaseModel* model, const RTCRay* ray, const RTCHit* hit)
{
using namespace Graphics::Material;
const Graphics::Model::Mesh* mesh = model->getFlatMeshes()[hit->instID[0]];
const EntityIdentifier& materialId = mesh->getMaterialId();
const BaseMaterial* material = model->fastGetMaterialRawPtr_FromEntityOrDefault(materialId);
float opacity = material->getBaseOpacity();
if (material->isDielectric())
{
const DefaultDielectric* dielectric = static_cast<const DefaultDielectric*>(material);
if (dielectric->getOpacityImageId())
{
const Math::Vec3 P(ray->org_x + (ray->tfar * ray->dir_x),
ray->org_y + (ray->tfar * ray->dir_y),
ray->org_z + (ray->tfar * ray->dir_z)
);
const nbUint32 triStartIdx = hit->primID * _PRIMITIVE_NB_VTX;
const auto v1 = mesh->buildTransformedVertexFromIndex(triStartIdx);
const auto v2 = mesh->buildTransformedVertexFromIndex(triStartIdx + 1);
const auto v3 = mesh->buildTransformedVertexFromIndex(triStartIdx + 2);
nbFloat32 area = 0.0f;
{
const Math::Vec3 e2 = v2.position - v1.position;
const Math::Vec3 e3 = v3.position - v1.position;
area = 0.5f * glm::length(glm::cross(e2, e3));
}
const Math::Vec3 coefs = Math::interpolate(v1.position, v2.position, v3.position, P, area);
const Math::Vec2 texCoord = material->interpolateTexCoordinates(v1.texCoord, v2.texCoord, v3.texCoord, coefs);
opacity = dielectric->getFinalOpacity(texCoord);
}
}
return opacity;
}
void CPUIntersector::occlusionFilter(const RTCFilterFunctionNArguments* args)
{
if (args->context == nullptr)
return;
_ASSERT(args->N == 1);
int* valid = args->valid;
if (valid[0] != -1)
return;
OcclusionRay* occRay = reinterpret_cast<OcclusionRay*>(args->ray);
RTCHit* hit = reinterpret_cast<RTCHit*>(args->hit);
occRay->occlusion += computeOpacity(s_model, &occRay->ray, hit);
if (occRay->occlusion >= 1.0f)
{
occRay->occlusion = 1.0f;
}
else
{
valid[0] = 0;
}
}
Instead of computing the barycentric weights manually i tried to use RTCHitN_u and RTCHitN_v
inline float computeOpacity(const Graphics::Model::BaseModel* model, const RTCRay* ray, const RTCHit* hit)
{
using namespace Graphics::Material;
const Graphics::Model::Mesh* mesh = model->getFlatMeshes()[hit->instID[0]];
const EntityIdentifier& materialId = mesh->getMaterialId();
const BaseMaterial* material = model->fastGetMaterialRawPtr_FromEntityOrDefault(materialId);
float opacity = material->getBaseOpacity();
if (material->isDielectric())
{
const DefaultDielectric* dielectric = static_cast<const DefaultDielectric*>(material);
if (dielectric->getOpacityImageId())
{
RTCHitN* hitN = (RTCHitN*)hit;
float u = RTCHitN_u(hitN, 1, 0);
float v = RTCHitN_v(hitN, 1, 0);
const Math::Vec2 texCoord = Math::Vec2(u, v);
opacity = dielectric->getFinalOpacity(texCoord);
}
}
return opacity;
}
This ouputs this:

It is better but it does not match the expected reference! The shadows look stretched!
While debugging i found that the ray origin and direction during the computeOpacity call doesnt match what i passed to rtcOccluded1!
Thanks for helping!
The complete code:
namespace Graphics { namespace Renderer { namespace Offline { namespace Intersector { namespace CPU
{
const Model::BaseModel* CPUIntersector::s_model = nullptr;
void CPUIntersector::createAndAttachInstance(RTCScene instanciedScene, const Model::Mesh* mesh)
{
RTCGeometry geometryInstance = rtcNewGeometry(m_device, RTC_GEOMETRY_TYPE_INSTANCE);
rtcSetGeometryInstancedScene(geometryInstance, instanciedScene);
rtcAttachGeometryByID(m_scene, geometryInstance, mesh->getFlatId());
rtcReleaseGeometry(geometryInstance);
const glm::mat4& transform = mesh->getTransform()->getMatrix();
rtcSetGeometryTransform(geometryInstance, 0, RTC_FORMAT_FLOAT4X4_COLUMN_MAJOR, (float*)&transform[0][0]);
rtcCommitGeometry(geometryInstance);
}
CPUIntersector::CPUIntersector(const Model::BaseModel* model,)
: BaseIntersector(IntersectorType::Cpu)
{
s_model = model;
#if defined(_CPU_PLATFORM_SSE4)
m_device = rtcNewDevice("isa=sse4.2");
#else
m_device = rtcNewDevice(nullptr);
#endif
m_scene = rtcNewScene(m_device);
rtcSetSceneBuildQuality(
m_scene,
RTCBuildQuality::RTC_BUILD_QUALITY_HIGH);
std::vector<Model::MeshGroup*> instances;
instances.reserve(s_model->getMeshGroups().size());
for (const EntityIdentifier& groupId : s_model->getMeshGroups())
{
const auto group = Model::getMeshGroupPtr_FromEntity(groupId);
if (group->isInstance())
{
instances.push_back(group.get());
continue;
}
std::vector<RTCScene> meshScenes;
meshScenes.reserve(group->m_meshes.size());
for (Model::Mesh* mesh : group->m_meshes)
{
// Create geometry
RTCGeometry geometry = rtcNewGeometry(m_device, RTC_GEOMETRY_TYPE_TRIANGLE);
rtcSetGeometryBuildQuality(
geometry,
RTCBuildQuality::RTC_BUILD_QUALITY_HIGH);
rtcSetGeometryOccludedFilterFunction(geometry, &CPUIntersector::occlusionFilter);
{
// Set vertices.
const auto& meshVertices = mesh->getRealVertices();
rtcSetSharedGeometryBuffer(
geometry,
RTC_BUFFER_TYPE_VERTEX,
0,
RTC_FORMAT_FLOAT3,
meshVertices.data(),
0,
sizeof(Graphics::FullVertex),
meshVertices.size());
}
{
// Set indices.
const auto& meshIndices = mesh->getRealIndices();
rtcSetSharedGeometryBuffer(
geometry,
RTC_BUFFER_TYPE_INDEX,
0,
RTC_FORMAT_UINT3,
meshIndices.data(),
0,
_PRIMITIVE_NB_VTX * sizeof(uint32_t),
meshIndices.size() / _PRIMITIVE_NB_VTX);
}
rtcCommitGeometry(geometry);
RTCScene instanciedScene = rtcNewScene(m_device);
meshScenes.push_back(instanciedScene);
rtcAttachGeometry(instanciedScene, geometry);
rtcReleaseGeometry(geometry);
rtcCommitScene(instanciedScene);
if (group->m_enabled)
{
createAndAttachInstance(instanciedScene, mesh);
}
}
m_rootEntityScenes[groupId] = std::move(meshScenes);
}
// Now other instances.
for (Model::MeshGroup* group : instances)
{
const auto rootSceneIter = m_rootEntityScenes.find(group->m_instanceRoot);
_ASSERT(rootSceneIter != m_rootEntityScenes.end());
const std::vector<RTCScene>& scenes = rootSceneIter->second;
for (uint32_t i = 0u; i < (uint32_t)group->m_meshes.size(); ++i)
{
createAndAttachInstance(scenes[i], group->m_meshes[i]);;
}
}
// Commit the root scene.
rtcCommitScene(m_scene);
}
CPUIntersector::~CPUIntersector()
{
// Root entity scenes.
for (auto& rootEntitySceneIter : m_rootEntityScenes)
{
for (RTCScene scene : rootEntitySceneIter.second)
rtcReleaseScene(scene);
}
// Root scene.
rtcReleaseScene(m_scene);
// Device.
rtcReleaseDevice(m_device);
}
RTCRayHit nativeRayToEmbree(const Math::Ray& ray)
{
RTCRay embreeRay;
embreeRay.org_x = ray.m_origin.x; embreeRay.org_y = ray.m_origin.y; embreeRay.org_z = ray.m_origin.z;
embreeRay.dir_x = ray.getDirection().x; embreeRay.dir_y = ray.getDirection().y; embreeRay.dir_z = ray.getDirection().z;
embreeRay.tnear = 0.0f;
embreeRay.tfar = 999999999.9f;
embreeRay.time = 0.0f;
embreeRay.mask = -1;
embreeRay.id = 0;
embreeRay.flags = 0;
RTCRayHit rayHit;
rayHit.ray = embreeRay;
rayHit.hit.geomID = RTC_INVALID_GEOMETRY_ID;
rayHit.hit.primID = RTC_INVALID_GEOMETRY_ID;
return rayHit;
}
IntersectionInfo CPUIntersector::embreeIsectInfoToNative(const RTCHit& hit, float t)
{
IntersectionInfo info;
if (hit.geomID != RTC_INVALID_GEOMETRY_ID)
{
info.meshIntersectData.t = t;
info.meshIntersectData.primId = hit.primID;
info.object = s_model->getFlatMeshes()[hit.instID[0]];
}
else
{
info.object = nullptr;
}
return info;
}
bool CPUIntersector::intersect(const Math::Ray& ray, IntersectionInfo& info)
{
RTCRayHit isect = nativeRayToEmbree(ray);
rtcIntersect1(m_scene, &isect);
info = embreeIsectInfoToNative(ray.m_origin, isect.hit, isect.ray.tfar);
return info.object != nullptr;
}
struct OcclusionRay
{
RTCRay ray;
float occlusion = 0.0f;
};
inline float computeOpacity(const Graphics::Model::BaseModel* model, const RTCRay* ray, const RTCHit* hit)
{
using namespace Graphics::Material;
const Graphics::Model::Mesh* mesh = model->getFlatMeshes()[hit->instID[0]];
const EntityIdentifier& materialId = mesh->getMaterialId();
const BaseMaterial* material = model->fastGetMaterialRawPtr_FromEntityOrDefault(materialId);
float opacity = material->getBaseOpacity();
if (material->isDielectric())
{
const DefaultDielectric* dielectric = static_cast<const DefaultDielectric*>(material);
if (dielectric->getOpacityImageId())
{
const Math::Vec3 P(ray->org_x + (ray->tfar * ray->dir_x),
ray->org_y + (ray->tfar * ray->dir_y),
ray->org_z + (ray->tfar * ray->dir_z)
);
const nbUint32 triStartIdx = hit->primID * _PRIMITIVE_NB_VTX;
const auto v1 = mesh->buildTransformedVertexFromIndex(triStartIdx);
const auto v2 = mesh->buildTransformedVertexFromIndex(triStartIdx + 1);
const auto v3 = mesh->buildTransformedVertexFromIndex(triStartIdx + 2);
nbFloat32 area = 0.0f;
{
const Math::Vec3 e2 = v2.position - v1.position;
const Math::Vec3 e3 = v3.position - v1.position;
area = 0.5f * glm::length(glm::cross(e2, e3));
}
const Math::Vec3 coefs = Math::interpolate(v1.position, v2.position, v3.position, P, area);
const Math::Vec2 texCoord = material->interpolateTexCoordinates(v1.texCoord, v2.texCoord, v3.texCoord, coefs);
opacity = dielectric->getFinalOpacity(texCoord);
}
}
return opacity;
}
void CPUIntersector::occlusionFilter(const RTCFilterFunctionNArguments* args)
{
if (args->context == nullptr)
return;
_ASSERT(args->N == 1);
int* valid = args->valid;
if (valid[0] != -1)
return;
OcclusionRay* occRay = reinterpret_cast<OcclusionRay*>(args->ray);
RTCHit* hit = reinterpret_cast<RTCHit*>(args->hit);
occRay->occlusion += computeOpacity(s_model, &occRay->ray, hit);
if (occRay->occlusion >= 1.0f)
{
occRay->occlusion = 1.0f;
}
else
{
valid[0] = 0;
}
}
float CPUIntersector::occlusion(const Math::Ray& ray)
{
#if defined(DEBBUGING_EMBREE)
std::lock_guard<std::mutex> lock(m_mutex);
#endif
OcclusionRay occRay;
occRay.ray = nativeRayToEmbree(ray).ray;
rtcOccluded1(m_scene, reinterpret_cast<RTCRay*>(&occRay));
return occRay.occlusion;
}
}}}}}
Tried a different opacity map with the RTCHitN_u and RTCHitN_v stuff.
It look like the shadows are too much repeated with these functions.

I am adding transparency support to my ray tracing software. Here what i am trying to achieve.
This is output of my GPU raytracer:
.
With CPU and Embree i get this. The shadows are broken:

I am using rtcOccluded1 for these shadows. The corresponding code:
Instead of computing the barycentric weights manually i tried to use RTCHitN_u and RTCHitN_v
This ouputs this:

It is better but it does not match the expected reference! The shadows look stretched!
While debugging i found that the ray origin and direction during the computeOpacity call doesnt match what i passed to rtcOccluded1!
Thanks for helping!
The complete code:
Tried a different opacity map with the RTCHitN_u and RTCHitN_v stuff.
It look like the shadows are too much repeated with these functions.