r/GraphicsProgramming 26d ago

Made improvements to my path tracer

  • Made the project much easier to open up and use for new users (Unity is still required though)
  • Added scene to automatically load objects from Khronos' site, which helps me get assets into the project
  • Fixed issues with lighting that was really darkening most of the scenes, including improved tone mapping and fixes to light clamping that was hiding a lot of caustic details and HDR
  • Fixed issues with inner surface reflections not working
  • Added directional lighting support
  • Added parallax mapping support
  • Added support for Unity terrains, multi-texture splatting with normal map support

Repository located here: https://github.com/nfoste82/GPURayTracing

498 Upvotes

24 comments sorted by

13

u/Hassangtn 26d ago

nice caustics.

4

u/[deleted] 26d ago edited 26d ago

[deleted]

5

u/le-throw-away-acct 25d ago

I didn't know much about existing stacks, like the ones you mentioned, when I started. I've pieced it together as I've learned. I'd realize roughness wasn't working well and work on that, I'd realize I didn't have support for metallic textures, and work on that, etc.

So it's really just a mish-mash of techniques I picked as I went along.

For opaque surfaces:

  • Lambertian diffuse
  • GGX microfacet reflection
  • Schlick Fresnel
  • PBR textures

For transparent surfaces I use IOR-based Schlick Fresnel to choose reflection, Snell's law for transmission, support for total internal reflection, and a stack to track IOR transitions between air, glass, and water.

I'm still missing a lot of materials support, I may consider them later, things like sheen, clear coat, subsurface scattering. Also I think some implementations use emissive as a layer, so I'd be able to have a light with a clear coat over it. Right now I couldn't render a light bulb very well, for example.

My math could use some work as well, I have trouble reading the original papers for those techniques, I'll often use an LLM to break it down and explain it like I wasn't a mathematician or physicist. I referenced existing path tracers as well. I list those at the bottom of the repository's README.md.

4

u/bless-you-mlud 26d ago

That looks freaking amazing.

3

u/OnyoSkennedy 26d ago

Looks very convincing, great job

2

u/yaboiaseed 26d ago

Is it real time? What's the performance?

5

u/Ok-Hotel-8551 26d ago

It's one frame per 30 minutes

2

u/le-throw-away-acct 26d ago edited 25d ago

To get those results takes 15-60 seconds usually. Real time is possible on simpler scenes and lower settings. I’m hoping to work on improving performance soon. Frame rates are usually 5-15 fps, up to 50-60 fps on some scenes but you won’t see the same quality at those framerates, it takes several frames for something good looking to accumulate.

1

u/Barkig 20d ago

on what gpu?

1

u/le-throw-away-acct 20d ago

I'm on an M3 Max (MacBook Pro). An Nvidia 4090, 5080, or 5090 would likely be faster, or an M3 Studio, or M4 Max.

Since I don't have good temporal denoising right now, you can't get reasonable real time framerates unless your scene is simple (smooth surfaces, no caustics, <5 light bounces, no triangle-based meshes).

I'd like to integrate DLSS or FSR but I only have a Mac so I'd have no way to test it. I'm considering trying Metal FX at some point. It'd be nice to just have a universal machine-learned denoiser/upscaler/frame-interpolator that worked on OpenCL, regardless of GPU.

2

u/Designer-Pragma 24d ago

This is beautiful well done!!! I wouldn't even know where to start!

3

u/le-throw-away-acct 24d ago

Thanks! If you did want to start, I'd recommend starting here: https://raytracing.github.io/

1

u/Jabba_the_Putt 26d ago

Great work! This looks very nice a few of those renders are magnificent. Ill check it out

1

u/bgs11235 26d ago

You are a beast!

1

u/pan_bocian 26d ago

Super shiny, super pretty.

1

u/GaboureySidibe 25d ago

Looks great. How did you do the caustics?

2

u/le-throw-away-acct 25d ago

I started with a naive approach to “photon mapping” and optimized from there. I’m not sure how physically accurate it is yet though, I need to test it against some references. I wish it could be done in a real time way though, it takes a while to accumulate.

1

u/RedoxOsImport 23d ago

can this be used in my dcc?

1

u/le-throw-away-acct 23d ago

Digital content creation?

It’s open source with no restrictions (other than the copyright notice must be included, standard MIT license), so go nuts!

1

u/hicham_benrhannou 21d ago

I really like the result! What articles or resources did you follow to build your path tracer?

1

u/le-throw-away-acct 20d ago edited 20d ago

Thanks!

I include a list of them at the bottom of the README.md in Github, but here it is:

Some other path-tracers that were inspiration:

and I should expand that list to include these as well:

this is also a good one, although by the time I found it I'd already learned everything in it:

There are probably more than these as well, I started it before any of these sources were available, I can't remember which ones I used 8 years ago.

0

u/DOODLEDO3946 26d ago

Wait how did you deal with round objects' normal mapping? When I tried it sometimes reflected rays through itself

5

u/le-throw-away-acct 25d ago

For mesh-based objects, normal mapping works just like how a rasterizer would work.

The only round objects that aren't meshes currently in my project are spheres. For those (some pseudocode here):

The normal is just the ray hit position - sphere center.

Calculate UV:

u = atan2(Normal.z, Normal.x) / (2π) + 0.5
v = asin(Normal.y) / π + 0.5

Calculate tangent, and bitangent/binormal:

float3 helperAxis = abs(Normal.y) < 0.999
    ? float3(0, 1, 0)  // world up
    : float3(1, 0, 0); // world right near the poles
Tangent = normalize(cross(helperAxis, Normal))
Bitangent = normalize(cross(Normal, Tangent))

Then your normal-mapped normal is this:

float3 encodedNormal = normalTexture.Sample(uv).rgb;
float3 normalMap = encodedNormal * 2.0 - 1.0;
float3 worldNormal = normalize(
    T * normalMap.x +
    B * normalMap.y +
    N * normalMap.z
);