Problem rendering spheres inside OptiX

Hi everyone. First I need to say that I’m a begginer using OptiX and I have some issues rigth now. I’m trying to implement a set of spheres inside a 3D graphic environment. The steps I followed were:
1.- Set a OptiX context.
2.- In order to build my OptiX graph I created a Geometry, Material, Geometry Instance and an acceleration structure. I joined this to the Group as a child (root of the graph).
3.- Geometry consist in a buffer of foat4 type data with 1D (width= 20000).
4.- I set all the OptiX programs (Intersection, bounds, ray tracing, miss, exception). I did this following SDK examples ( sphere.cu for example).
5.- In order to render spheres (spheres must have a different uniform distribution for each frame), I update buffer just as is shown in code example (I tested data and it is correct, it is uniform distribution).
6.- I used accel-> markdirty() to get geometry buffer updated in each frame. (Also I tried with refit but it didn’t work).
Now, my problem is that when spheres appear in the scene it is like some of them could not be detected by ray tracing after rtTrace invokes intersection program. I was thinking that it is a problem with acceleration but I already tried with Trbvh, NoAccel and Bvh and the output is the same. I tested and I’m sure that I’m sending correct data to kernel from my cpp file. I don’t receive any clue in miss program and also there are not exceptions detected. Ray tracing is reconstructing in a correct way the environment behind the spheres. I will put my code in order to get some support, I’ll really appreciate your help.
I’m using CUDA 7.0, OptiX 3.9 (I can’t upgrade them since another algorithm depends on that)
NVIDIA Graphics processor Quadro M6000 24G.

// Setting Geometry:

        sphere = m_context->createGeometry();
	sphere->setPrimitiveCount(spheresnum);

	Program intersect = m_context->createProgramFromPTXFile( camProgramFile, "intersect" );
	Program bounds = m_context->createProgramFromPTXFile( camProgramFile, "bounds" );

	buffers.position = m_context->createBuffer(RT_BUFFER_INPUT,RT_FORMAT_FLOAT4,spheresnum);
	buffers.radius = m_context->createBuffer(RT_BUFFER_INPUT,RT_FORMAT_FLOAT,spheresnum);

	sphere->setIntersectionProgram(intersect);
	sphere->setBoundingBoxProgram(bounds);

	m_context["spherepos"]->setBuffer(buffers.position);
	m_context["sphererad"]->setBuffer(buffers.radius);

//Geometry Instance:

        GeometryInstance gi = m_context->createGeometryInstance();
	gi->setMaterialCount(1);
	gi->setGeometry(sphere);
	gi->setMaterial(0,material);

	//geometry group

	geometrygroup = m_context->createGeometryGroup();
	geometrygroup->setChildCount(1);
	geometrygroup->setChild(0,gi);
	geometrygroup->setAcceleration(m_context->createAcceleration("Trbvh","Bvh"));;

// Rendering:

        float* pos = reinterpret_cast<float*>(buffers.position->map());

	for(long unsigned int i= 0, index = 0; i<spheresnum; i++)
	{
		positions[i].x = dist_x(mt);
		positions[i].y = dist_y(mt);
		positions[i].z = dist_z(mt);
		positions[i].w = dist_w(mt);

		pos[index++]= positions[i].x;
		pos[index++]= positions[i].y;
		pos[index++]= positions[i].z;
		pos[index++]= positions[i].w;
	}

	buffers.position->unmap();
        geometrygroup->getAcceleration()->markDirty();



Intersection Program:

float4 pos = spherepos[primIdx];
	float3 sphere = make_float3(pos.x,pos.y,pos.z);
	
	float3 O = ray.origin - sphere;
	float3 D = ray.direction;
	float radius = sphererad[primIdx];
	//float radius = new_rad[primIdx];
	float b = dot(O,D);
	float c = dot(O,O) - radius*radius;
	float disc = b*b-c;
	
	if(disc>0.0f)
	{
		float sdisc = sqrtf(disc);
		float root1 = (-b - sdisc);
		
		bool do_refine = false;
		
		float root11 = 0.0f;
		
		if(use_robust_method && fabsf(root1) > 1.0f * radius)
		{
			do_refine = true;
		}
		
		if(do_refine)
		{
			float3 O1 = O + root1 * ray.direction;
			b = dot(O1,D);
			c = dot(O1,O1) - radius*radius;
			disc = b*b - c;
		
		
			if(disc > 0.0f)
			{
				sdisc = sqrtf(disc);
				root11 = (-b - sdisc);
			}
		
		}
		
		
		bool check_second = true;
		if(rtPotentialIntersection(root1+root11))
		{
			shading_normal = geometric_normal = (O +(root1+root11)*D)/radius;
			
			if(rtReportIntersection(0))
			check_second = false;
		}
		
		if(check_second)
		{
			float root2 = (-b + sdisc) + (do_refine ? root1 : 0);
			if(rtPotentialIntersection(root2))
			{
				shading_normal = geometric_normal = (O + root2*D)/radius;
				rtReportIntersection(0);
			}
		}
	}

Bounding Program:

const float3 cen = make_float3(spherepos[primIdx]);
const float3 rad = make_float3(sphererad[primIdx]);

optix::Aabb* aabb = (optix::Aabb*)result;

if(rad.x > 0.0f && !isinf(rad.x))
{
  aabb->m_min = cen - rad;
  aabb->m_max = cen + rad;
}
else
{
  aabb->invalidate();
}

Explain this code line (48): positions[i].w = dist_w(mt);

The original sphere bounding box and intersection routines in the OptiX SDK only use the float4 sphere description with the position of the center in .xyz and the radius in .w.

Why did you set the .w to something else and added a separate radius buffer?
Does it work with the original code?

Why did you set the two buffers globally at the context and not at the sphere Geometry scope?

// m_context["spherepos"]->setBuffer(buffers.position);
// m_context["sphererad"]->setBuffer(buffers.radius);
// Should
sphere["spherepos"]->setBuffer(buffers.position);
sphere["sphererad"]->setBuffer(buffers.radius);

You didn’t provide the code to set the radii.

You mean your scene root is a Group node which has the GeometryGroup with the spheres as additional child?
In that case you need to call markDirty() on both the GeometryGroup’s acceleration structure and all accelerations structures above it, which in this case is the root Group’s the acceleration structure after you have changed the Geometry buffer contents.

Thanks for the reply,

1.- I added a separate buffer for radius ( it is not necessary since as you said I could use positions[i].w to store radius value, but it doesn’t affect), so, answering your question in the current implementation positions[i].w = dist_w(mt) is not used, I only use float4 because I read that OptiX works better than using float3. This is the code for set radius parameter:

float* rad = reinterpret_cast<float*>(buffers.radius->map());

for(unsigned int i= 0, index = 0; i<spheresnum; i++)
{
	rad[index++]= rain_size_factor*dist_rad(mt);
}

buffers.radius->unmap();

Then, I think that there is not problem using a different buffer for radius, it works as in the SDK example since I’m taking the radius from a different buffer but I’m using primIdx to index radius as positions inside intersection program ( please tell me if there is a problem doing this, maybe because of OptiX multithread).

2.- You are right, I wrote older code, my new version include the Geometry scope just as you mentioned:

sphere[“spherepos”]->setBuffer(buffers.position);
sphere[“sphererad”]->setBuffer(buffers.radius);

3.- Exactly, I joined my GeometryGroup as Group node child. Now, I’m also using markDirty() in Group Acceleration’s and the output is still the same:

group->getAcceleration()->markDirty();

Do you mean you got an intersection program invocation on some sphere but there is no closest hit invocation for that?
That’s expected behaviour for any geometric primitive which is not the closest hit.
The BVH traversal and therefore the intersection (and potential any_hit program) invocations are not in the order of the ray direction.

You just attached two completely different images. What exactly is wrong with them?

I would recommend to revert the sphere definition to the float4 representation of center and radius. That’s going to be more efficient than a separate array.

I get what you say, that is not the problem, I understand that if there is a closest hit, then objects behind won’t be intersected, look at my positions set function:

float4 positions[spheresnum];
	uint64_t seed = std::random_device{}() | std::chrono::system_clock::now().time_since_epoch().count();

	//std::mt19937 mt(rd());
	std::mt19937 mt(seed);
	std::uniform_real_distribution<double> dist_x(current_pos.x-2.0,current_pos.x+2.0);
	std::uniform_real_distribution<double> dist_y(current_pos.y-4.0, current_pos.y+4.0);
	std::uniform_real_distribution<double> dist_z(0.0,1.0);
	std::uniform_real_distribution<double> dist_w(0.003,0.005);

	float* pos = reinterpret_cast<float*>(buffers.position->map());

	for(long unsigned int i= 0, index = 0; i<spheresnum; i++)
	{
		positions[i].x = dist_x(mt);
		positions[i].y = dist_y(mt);
		positions[i].z = dist_z(mt);
		positions[i].w = rain_size_factor*dist_w(mt);

		pos[index++]= positions[i].x;
		pos[index++]= positions[i].y;
		pos[index++]= positions[i].z;
		pos[index++]= positions[i].w;
	}

	buffers.position->unmap();

As you can see,I’m using uniform distribution to display xyz, if you look images, there is an awful distribution ( it seems like spheres are detected only in a segment of the image, it is like there is not intersection in all the spheres that should be in the front). Distribution would be just as the image I will attach.

Ready, I’m using float4 instead of separate buffers.

what did you mean with “The BVH traversal and therefore the intersection (and potential any_hit program) invocations are not in the order of the ray direction.”? Am I wrong using BVH?
lluvia21.png

What? You’re generating random numbers, not a regular grid.
https://en.cppreference.com/w/cpp/numeric/random/uniform_real_distribution

There are only 20,000 sphere positions in your defined volume and depending on what a unit is in your raytracer, how did you determine that that 3D distribution of random numbers is not uniform from looking at an image?

Sorry, I forgot to mention that I already tried with a regular grid, setting one of the 3 variables to a constant value such y=3, when I saw the image it is like left or right side of the grid ( image ) disappear just as in the 3D visualization. I will provide you an image.

Are you assuming that you’ll hit all spheres in the scene independently of their size and distance to the camera?
That obviously won’t happen with a discrete sampling of the screen.
The resulting patterns of hit or missed spheres depends on the distribution of the spheres and ray samples and could produce any kind of sampling artifacts like what you describe.