Advertisement

Path tracing in Vulkan

Started by August 21, 2023 09:47 PM
116 comments, last by taby 1 year, 1 month ago

100 channels works just fine. Just change one variable.

There is this convertor, in nanometres:

http://codingmess.blogspot.com/2009/05/conversion-of-wavelength-in-nanometers.html

The code is posted here for posterity:

def wav2RGB(wavelength):
    w = int(wavelength)

    # colour
    if w >= 380 and w < 440:
        R = -(w - 440.) / (440. - 350.)
        G = 0.0
        B = 1.0
    elif w >= 440 and w < 490:
        R = 0.0
        G = (w - 440.) / (490. - 440.)
        B = 1.0
    elif w >= 490 and w < 510:
        R = 0.0
        G = 1.0
        B = -(w - 510.) / (510. - 490.)
    elif w >= 510 and w < 580:
        R = (w - 510.) / (580. - 510.)
        G = 1.0
        B = 0.0
    elif w >= 580 and w < 645:
        R = 1.0
        G = -(w - 645.) / (645. - 580.)
        B = 0.0
    elif w >= 645 and w <= 780:
        R = 1.0
        G = 0.0
        B = 0.0
    else:
        R = 0.0
        G = 0.0
        B = 0.0

    # intensity correction
    if w >= 380 and w < 420:
        SSS = 0.3 + 0.7*(w - 350) / (420 - 350)
    elif w >= 420 and w <= 700:
        SSS = 1.0
    elif w > 700 and w <= 780:
        SSS = 0.3 + 0.7*(780 - w) / (780 - 700)
    else:
        SSS = 0.0
    SSS *= 255

    return [int(SSS*R), int(SSS*G), int(SSS*B)]
Advertisement

Here's a code to do the same thing.

RGB HSBtoRGB(unsigned short int hue_degree, unsigned char sat_percent, unsigned char bri_percent)
{
	float R = 0.0f;
	float G = 0.0f;
	float B = 0.0f;

	if(hue_degree > 359)
		hue_degree = 359;

	if(sat_percent > 100)
		sat_percent = 100;

	if(bri_percent > 100)
		bri_percent = 100;

	float hue_pos = 6.0f - ((static_cast<float>(hue_degree) / 359.0f) * 6.0f);

	if(hue_pos >= 0.0f && hue_pos < 1.0f)
	{
		R = 255.0f;
		G = 0.0f;
		B = 255.0f * hue_pos;
	}
	else if(hue_pos >= 1.0f && hue_pos < 2.0f)
	{
		hue_pos -= 1.0f;

		R = 255.0f - (255.0f * hue_pos);
		G = 0.0f;
		B = 255.0f;
	}
	else if(hue_pos >= 2.0f && hue_pos < 3.0f)
	{
		hue_pos -= 2.0f;

		R = 0.0f;
		G = 255.0f * hue_pos;
		B = 255.0f;
	}
	else if(hue_pos >= 3.0f && hue_pos < 4.0f)
	{
		hue_pos -= 3.0f;

		R = 0.0f;
		G = 255.0f;
		B = 255.0f - (255.0f * hue_pos);
	}
	else if(hue_pos >= 4.0f && hue_pos < 5.0f)
	{
		hue_pos -= 4.0f;

		R = 255.0f * hue_pos;
		G = 255.0f;
		B = 0.0f;
	}
	else
	{
		hue_pos -= 5.0f;

		R = 255.0f;
		G = 255.0f - (255.0f * hue_pos);
		B = 0.0f;
	}

...

	RGB rgb(R, G, B);

	return rgb;
}

taby said:
Here's a code to do the same thing.

I wonder what could be done to get rid of all those if elses.

I often think about some color representation using hue, because i'd like to experiment with brightness affecting hue to achieve higher saturation. (artistic goals in mind, no physical correctness)

If we store hue as a complex number, we can get rgb using dot products with primary directions at 0, 120 and 240 degrees. Channel signals would from smooth sine waves instead the discontinuous saw tooth signal those functions generate.
You would need only one rotation at 120 degrees to generate those 3 directions on the fly. So the amount of registers we need isn't huge and we get rid of branches.

However, we then need 4 values to store a color, if hue is complex.

Edit: I've quickly tried my idea, converting hue angle into rgb, but there is a problem:

I can't mix proper yellow, cyan and magenta, because my simple math causes smaller magnitudes for mixed colors.
Some compensation term is needed, increasing complexity. : (

I've found a solution after taking a nap, but it still needs clipping just like the traditional approaches.

Maybe someone knows a way. I want to map hue to rgb without discontinuities in the rgb values if i rotate the hue angle.
Not sure if this makes sense at all, but i can have what i want if i sacrifice some pure colors:

You see the rgb signals are all smooth, and i get a smooth rainbow. No discontinuities. I get this if i set my k to 1.

If i want a saturated rainbow, i set k to 2:

This behaves almost like a traditional color mixer, because the curves become very straight. But i have to clip rgb values in (0,1) range, and i get those ugly discontinuities in the rainbow. The magnitude of the rgb vector also can be more than 1 (the sharp spikes at the bottom), but ofc. that's needed to have saturated pastel colors.

I'm surely not the first trying to improve this somehow, so let me know if there is something related in color science.

Here is code, which at least can do hue to rgb without branches (not claiming that's faster).

			struct HCol
			{
				std::complex<float> hue = 0.f; // complex number with magnitude 1, with it's angle representing hue

				vec ToRGB (float k)
				{
					vec rgb;

					float angle = 120.f * float(PI) / 180.f;
					std::complex<float> rot (cos(angle), sin(angle));
					std::complex<float> dir(1.f, 0.f);

					rgb[0] = (std::conj(hue) * dir).real(); // dot product
					dir = dir * rot; // rotate 120 degrees
					rgb[1] = (std::conj(hue) * dir).real(); // dot product
					dir = dir * rot; // rotate 120 degrees
					rgb[2] = (std::conj(hue) * dir).real(); // dot product

					rgb = (vec(1) + rgb * k) * .5f;

					rgb[0] = min(1.f, rgb[0]);
					rgb[1] = min(1.f, rgb[1]);
					rgb[2] = min(1.f, rgb[2]);
					rgb[0] = max(0.f, rgb[0]);
					rgb[1] = max(0.f, rgb[1]);
					rgb[2] = max(0.f, rgb[2]);
					return rgb;
				}
			};

Here is the actual RGB2HSV and back again code that I use. Not many if/elses. I got it from stack exchange.


vec3 rgb2hsv(vec3 c)
{
    vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
    vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
    vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));

    float d = q.x - min(q.w, q.y);
    float e = 1.0e-10;
    return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}

vec3 hsv2rgb(vec3 c)
{
    vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
    vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
    return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
Advertisement

Don't know why you all have been trying to make something discrete into something continuous. E = h nu.

I'm trying to add fog, but it's not working quite as it should.

The fog code is:

// Do some experimental fog in an AABB
float dist_color = 0.0;
float dist_opacity = 0.0;

if(rays[i].external_reflection_ray || rays[i].external_refraction_ray)
{
	vec3 start = rays[i].origin.xyz;
	vec3 end = rays[i].origin.xyz;

	float t0 = 0.0;
	float t1 = 0.0;
	const float target_step_length = 0.1;

	if(in_aabb(rays[i].origin.xyz, aabb_min, aabb_max))
	{
		vec3 backout_pos = rays[i].origin.xyz - rays[i].direction.xyz*1000.0;

		if(BBoxIntersect(aabb_min, aabb_max, backout_pos, rays[i].direction.xyz, t0, t1))
		{
			start = rays[i].origin.xyz;
			end = backout_pos + rays[i].direction.xyz*t1;
		}
	}
	else
	{
		if(BBoxIntersect(aabb_min, aabb_max, rays[i].origin.xyz, rays[i].direction.xyz, t0, t1))
		{
			start = rays[i].origin.xyz + rays[i].direction.xyz*t0;
			end = rays[i].origin.xyz + rays[i].direction.xyz*t1;
		}
	}
	
	if(rayPayload.distance != -1.0)
	{
		if(distance(rays[i].origin.xyz, start) > distance(rays[i].origin.xyz, hitPos.xyz))
			start = hitPos.xyz;
					
		if(distance(rays[i].origin.xyz, end) > distance(rays[i].origin.xyz, hitPos.xyz))
			end = hitPos.xyz;
	}

	const int num_steps = int((distance(start, end) / target_step_length));

	if(num_steps >= 2)
	{
		const vec3 step = (end - start) / (num_steps - 1);

		vec3 curr_step = start;

		for(int j = 0; j < num_steps; j++, curr_step += step)
		{
			float colour = get_omni_radiance(10, 5, curr_step, hue, eta);
			const float trans = 1.0 - clamp(dist_opacity, 0.0, 1.0);
			dist_color += colour*trans;
			dist_opacity += colour*trans;
		}
	}
}

dist_opacity = clamp(dist_opacity, 0.0, 1.0);
rays[i].base_color = mix(rays[i].base_color, dist_color, dist_opacity);

Got the fog working. Now I'm having a different problem. I basically have reflection caustics working, but not refraction caustics.

My code to do path tracing is:

float trace_path2(out vec3 final_pos, const int steps, const vec3 origin, const vec3 direction, const float hue, const float eta)
 {
 	const RayPayload r = rayPayload;
 
 	vec3 hitPos = vec3(0.0);
 
 	float ret_colour = 0;
 
 	vec3 o = origin;
 	vec3 d = direction;
 
 	int count = 0;
 
 	
 	float energy = 100.0;
 
 	float local_colour = energy;
 
 
 	float total = 0;
 
 	for(int i = 0; i < steps; i++)
 	{
 		const float tmin = 0.001;
 		const float tmax = 10000.0;
 		traceRayEXT(topLevelAS, gl_RayFlagsOpaqueEXT, 0xff, 0, 0, 0, o.xyz, tmin, d.xyz, tmax, 0);
 
 		const vec3 mask = hsv2rgb(vec3(hue, 1.0, 1.0));
 
 		total += mask.r;
 		total += mask.g;
 		total += mask.b;
 
 		local_colour *= (rayPayload.color.r*mask.r + rayPayload.color.g*mask.g + rayPayload.color.b*mask.b);
 
 		if(rayPayload.distance == -1)
 			break;
 
 		hitPos = o + d * rayPayload.distance;
 
 		if(rayPayload.color.r == 1.0 && rayPayload.color.g == 1.0 && rayPayload.color.b == 1.0)
 			ret_colour += local_colour;
 
 
 		vec3 o_reflect = hitPos + rayPayload.normal * 0.01;
 		vec3 d_reflect = reflect(d, rayPayload.normal);
 
 		vec3 temp_o = o_reflect;
 		vec3 temp_d = d_reflect;
 
 		if(rayPayload.reflector < 1.0) // if less than fully reflective, do scattering
 		{
 			vec3 o_scatter = hitPos + rayPayload.normal * 0.01;
 			vec3 d_scatter = cosWeightedRandomHemisphereDirection(rayPayload.normal, prng_state);
 
 			temp_o = mix(temp_o, o_scatter, 1.0 - rayPayload.reflector);
 			temp_d = mix(temp_d, d_scatter, 1.0 - rayPayload.reflector);
 		}
 
 		return rayPayload.opacity;
 
 		if(rayPayload.opacity < 1.0) // if partially transparent, do refraction
 		{
 			vec3 o_transparent = vec3(0.0);
 			vec3 d_transparent = vec3(0.0);
 
 			// Incoming
 			if(dot(d, rayPayload.normal) <= 0.0)
 			{
 				o_transparent = hitPos.xyz - rayPayload.normal * 0.01f;
 				d_transparent = refract(d, rayPayload.normal, eta);
 			}
 			else // Outgoing
 			{
 				vec3 temp_dir = refract(d, -rayPayload.normal, 1.0 / eta);
 
 				if(temp_dir != vec3(0.0))
 				{
 					o_transparent = hitPos.xyz + rayPayload.normal * 0.01f;
 					d_transparent = temp_dir;
 				}
 				else
 				{
 					// total internal reflection
 					o_transparent = hitPos.xyz - rayPayload.normal * 0.01f;
 					d_transparent = reflect(d, -rayPayload.normal);
 				}
 			}
 
 			temp_o = mix(temp_o, o_transparent, 1.0 - rayPayload.opacity);
 			temp_d = mix(temp_d, d_transparent, 1.0 - rayPayload.opacity);
 		}
 			
 		o = temp_o;
 		d = normalize(temp_d);
 		
 	}
 
 	final_pos = hitPos;
 	rayPayload = r;
 	return ret_colour / total;
 }

When I make the statement return rayPayload.opacity; it returns the following image, which is what I expect:

However, if you comment out that return statement, the if statement on the next line is never run, even though opacity is obviously less than 1.0 in some parts of the image. It's the strangest problem.

The full code is at: https://github.com/sjhalayka/cornell_box_textured

Basically, I have reflection caustics working, but I cannot get refraction caustics to work yet (in the new version 2.0 of my path tracer).

Any big errors that I'm not seeing here?

Thanks for your time.

After much playing around with the code, I have caustics working in the new renderer code.

This topic is closed to new replies.

Advertisement