Skip to content

Embree 4.3.0 occlusion filter doesnt work as expected #614

Description

@Trylz

I am adding transparency support to my ray tracing software. Here what i am trying to achieve.
This is output of my GPU raytracer:
.

Image

With CPU and Embree i get this. The shadows are broken:
Image

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:
Image

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.

Image

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions