🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Path tracing in Vulkan

Started by
116 comments, last by taby 8 months, 3 weeks ago

Now…. How to do caustics.

Advertisement

taby said:
How to do caustics.

You need a bidirectional path tracer with multiple importance sampling to do that with reasonable speed. I recommend this paper for MIS implementation, much simpler than other ways. Before that you need to make your existing path tracer better by adding light sampling (next event estimation), and better energy-conserving BRDFs. You need to verify that the unidirectional tracer is accurate, so that you can check the BDPT result against it, since there are many more things that can go wrong.

With much better contrast:

@taby That looks worse to me, it's too dark. Not enough bounces? The dark areas should be more illuminated by indirect light.

Double OK. I have codes to study. Namely, those by Sascha Willems and JoeJ.

I'm trying to make the lighting more physically accurate. I was given a code that uses cosine-weighted rays.

https://www.shadertoy.com/view/4tl3z4

However, when I go to use the function, it gives me garbage.

The code is:

float stepAndOutputRNGFloat(inout uint rngState)
{
  // Condensed version of pcg_output_rxs_m_xs_32_32, with simple conversion to floating-point [0,1].
  rngState  = rngState * 747796405 + 1;
  uint word = ((rngState >> ((rngState >> 28) + 4)) ^ rngState) * 277803737;
  word      = (word >> 22) ^ word;
  return float(word) / 4294967295.0f;
}

// See: https://github.com/nvpro-samples/vk_mini_path_tracer/blob/main/vk_mini_path_tracer/shaders/raytrace.comp.glsl#L26
vec3 RandomUnitVector(inout uint state)
{
    float z = stepAndOutputRNGFloat(state) * 2.0f - 1.0f;
    float a = stepAndOutputRNGFloat(state) * TWO_PI;
    float r = sqrt(1.0f - z * z);
    float x = r * cos(a);
    float y = r * sin(a);
    return vec3(x, y, z);
}

// ...

float hash1(inout float seed) {
    return fract(sin(seed += 0.1)*43758.5453123);
}

vec2 hash2(inout float seed) {
    return fract(sin(vec2(seed+=0.1,seed+=0.1))*vec2(43758.5453123,22578.1459123));
}

vec3 hash3(inout float seed) {
    return fract(sin(vec3(seed+=0.1,seed+=0.1,seed+=0.1))*vec3(43758.5453123,22578.1459123,19642.3490423));
}


vec3 cosWeightedRandomHemisphereDirection( const vec3 n, inout float seed )
{
  	vec2 r = hash2(seed);
    
	vec3  uu = normalize( cross( n, vec3(0.0,1.0,1.0) ) );
	vec3  vv = cross( uu, n );
	
	float ra = sqrt(r.y);
	float rx = ra*cos(6.2831*r.x); 
	float ry = ra*sin(6.2831*r.x);
	float rz = sqrt( 1.0-r.y );
	vec3  rr = vec3( rx*uu + ry*vv + rz*n );
    
    return normalize( rr );
}

vec3 randomSphereDirection(inout float seed) {
    vec2 h = hash2(seed) * vec2(2.,6.28318530718)-vec2(1,0);
    float phi = h.y;
	return vec3(sqrt(1.-h.x*h.x)*vec2(sin(phi),cos(phi)),h.x);
}

vec3 randomHemisphereDirection( const vec3 n, inout float seed ) {
	vec3 dr = randomSphereDirection(seed);
	return dot(dr,n) * dr;
}



float trace_path(const int iterations, const vec3 origin, const vec3 direction, out vec3 hitPos, const int channel)
{
	float ret_colour = 0;

	vec3 o = origin;
	vec3 d = direction;

	const int samples = 100;

	for(int s = 0; s < samples; s++)
	{
		float local_colour = 0;

		for(int i = 0; i < iterations; i++)
		{
			float tmin = 0.001;
			float tmax = 1000.0;
			traceRayEXT(topLevelAS, gl_RayFlagsOpaqueEXT, 0xff, 0, 0, 0, o.xyz, tmin, d.xyz, tmax, 0);

			if(channel == red_channel)
				local_colour += rayPayload.color.r;
			else if(channel == green_channel)
				local_colour += rayPayload.color.g;
			else
				local_colour += rayPayload.color.b;

			if(rayPayload.color.r == 1.0 && rayPayload.color.g == 1.0 && rayPayload.color.b == 1.0)
			{
				local_colour += 1;
				ret_colour += local_colour;
	
				continue;//return ret_colour;
			}

			if(rayPayload.distance == -1)
			{
				ret_colour += local_colour;
				break;
			}

			hitPos = o + d * rayPayload.distance;

			o = hitPos + rayPayload.normal*0.01;

			//vec3 rdir = RandomUnitVector(prng_state);

			vec3 rdir = cosWeightedRandomHemisphereDirection(normalize(rayPayload.normal), prng_state_float); // this does not work
			//vec3 rdir = randomSphereDirection(prng_state_float); // this works

			// Stick to the correct hemisphere

			if(dot(rdir, normalize(rayPayload.normal)) < 0.0)
				rdir = -rdir;
			
			d = rdir;

			




		}

		o = origin;
		d = direction;

		ret_colour += local_colour;
	}

	return ret_colour / samples / iterations;
}

taby said:
However, when I go to use the function, it gives me garbage.

It's not garbage. This is considered broken shader art. My hope is that one day, we will outsell ‘black square on white canvas’ with this. Jokes aside - keep these images, bugs tend to be fun when shared in blog posts, articles or just among friends.

Hints from quickly glancing at code:

  • Check that your rayPayload.normal is correct (and normalized!), for starters just do a single iteration (which should give you primary ray and intersection results only), ensure that it indeed is a normal
  • Does prng_state_float need to be declared, initialized or not? I don't see its initialization anywhere. It should be done within the trace_path I assume. I've mostly initialized my prng state value from some value different per each pixels, make sure to do that
  • Why do you need to dot normal against cosine weighted random hemisphere direction? It should always be generated along your normal!

My current blog on programming, linux and stuff - http://gameprogrammerdiary.blogspot.com

The full raygen shader is at https://github.com/sjhalayka/cornell_box_textured/blob/main/raygen.rgen

nothing is set in stone. I’m basically just trying everything, until something works out lol

thanks!

I got the cosine-weighted rays working:

This topic is closed to new replies.

Advertisement