Advertisement

Forward path tracing

Started by January 27, 2024 06:33 PM
4 comments, last by taby 10 months, 4 weeks ago

I have a forward path tracer working, except that it works too good to be realistic.

https://github.com/sjhalayka/bidirectional_path_tracer/blob/3a979a9a0853cee51ccddf167cd8236efa9d6e89/raygen.rgen#L590

float trace_path_forward2(const vec3 eye, const vec3 direction, const float hue, const float eta)
{
	const vec3 mask = hsv2rgb(vec3(hue, 1.0, 1.0));

	Vertex A, B, C;

	const int index = int(round(stepAndOutputRNGFloat(prng_state)*float(ubo.light_tri_count - 1)));
	get_triangle_vertices_by_light_index(A, B, C, index);

	const vec3 light_o = getRandomPointOnTriangle(A.pos, B.pos, C.pos);
	const vec3 light_d = cosWeightedRandomHemisphereDirection(get_normal_by_light_index(index), prng_state);

	const float energy = 1.0;

	int step_count = 0;
	vec3 step_locations[max_bounces + 2];
	vec3 step_directions[max_bounces + 2];

	step_locations[step_count] = light_o;
	step_directions[step_count] = light_d;
	step_count++;

	if(false == is_clear_line_of_sight(eye, light_o))
	{	
		vec3 o = light_o;
		vec3 d = light_d;

		for(int i = 0; i < max_bounces; i++)
		{
			traceRayEXT(topLevelAS, gl_RayFlagsOpaqueEXT, 0xff, 0, 0, 0, o, 0.001, d, 10000.0, 0);

			const vec3 hitPos = o + d * rayPayload.dist;

			o = hitPos + rayPayload.normal * 0.01;
			d = cosWeightedRandomHemisphereDirection(rayPayload.normal, prng_state);
			
			step_locations[step_count] = o;
			step_directions[step_count] = d;
			step_count++;

			if(true == is_clear_line_of_sight(eye, step_locations[step_count - 1]))
				break;
		}
	}

	step_locations[step_count] = eye;
	step_directions[step_count] = -direction;
	step_count++;

	// Reverse the path
	uint start = 0;
	uint end = step_count - 1;

	while(start < end) 
	{ 
		vec3 temp = step_locations[start];  
		step_locations[start] = step_locations[end]; 
		step_locations[end] = temp; 

		temp = step_directions[start];  
		step_directions[start] = step_directions[end]; 
		step_directions[end] = temp; 

		start++;
		end--; 
	}

	float ret_colour = 0;
	float local_colour = energy;
	float total = 0;

	for(int i = 0; i < step_count - 1; i++)
	{
		vec3 step_o = step_locations[i];
		vec3 step_d = -step_directions[i];

		traceRayEXT(topLevelAS, gl_RayFlagsOpaqueEXT, 0xff, 0, 0, 0, step_o, 0.001, step_d, 10000.0, 0);

		if(rayPayload.dist == -1)
			return 0.0;

		local_colour *= (rayPayload.color.r*mask.r + rayPayload.color.g*mask.g + rayPayload.color.b*mask.b);

		total += mask.r;
		total += mask.g;
		total += mask.b;

		if(i == step_count - 2)
			ret_colour += local_colour;
	}

	return ret_colour / total;
}

As you can see, the light contribution is always 1.0.

Any insights?

I understand only vaguely what you might try to do, but why this?

	if(false == is_clear_line_of_sight(eye, light_o))

I guess you construct a path from the light, but why does it matter if the eye can see this light to do so?

I also wonder why you have to trace rays again in the second loop, although the path is already constructed.

Potential optimization: Because the path array lives in registers, any access to them is very slow (because registers are not in continuous memory, indexing does not work and a search is needed, probably using a switch statement with each index being one case),
i would not reverse the path by swapping elements. I would reverse the index in the second loop instead.

Edit: Actually idk if compilers are allowed to use LDS memory for the arrays for such shaders.
But if the number of bounces is a compile time constant, the compiler can avoid to use arrays at all, which would be ideal. Other wise i would do it manually using multiple variables instead the array.

Advertisement

JoeJ said:

I understand only vaguely what you might try to do, but why this?

	if(false == is_clear_line_of_sight(eye, light_o))

I guess you construct a path from the light, but why does it matter if the eye can see this light to do so?

I also wonder why you have to trace rays again in the second loop, although the path is already constructed.

If the eye can see the light, then it's all good. But, the light is a volumetric thing. I pick the light triangle pseudorandomly. Some times the back of the light is where the the triangle exists: something that is obscured by the front of the light.

I will work at using only one loop.

Potential optimization: Because the path array lives in registers, any access to them is very slow (because registers are not in continuous memory, indexing does not work and a search is needed, probably using a switch statement with each index being one case),
i would not reverse the path by swapping elements. I would reverse the index in the second loop instead.

Noted!

Edit: Actually idk if compilers are allowed to use LDS memory for the arrays for such shaders.
But if the number of bounces is a compile time constant, the compiler can avoid to use arrays at all, which would be ideal. Other wise i would do it manually using multiple variables instead the array.

Alright. I'll keep this in mind.

Thanks for replies JoeJ.

taby said:
Alright. I'll keep this in mind.

It's surely worth to investigate, but ofc. only after you make it work.

Maybe tracing the rays is so slow, you'll see no reward for your optimization work.
But that's something that can change once the GPU has enough other work around at the same time. E.g., if you reduce register allocation for the price to allocate LDS, this can either hinder or help other shaders, depending on their own allocation requirements.

So if you wanted to optimize GPU seriously, there is no way around profiling tools to figure such things out.
But in general, just avoid local arrays whenever possible.

Thanks for all of your insight. I have tried a dozen different ways, but I've yet to make the forward path tracer work properly. Thanks for sharing my pain. LOL

This topic is closed to new replies.

Advertisement