Skip to main content
GameDev.net gamedev.net
🔒 Locked

Cosine-weighted vector

Started by taby Oct 19, 2025 at 8:03 PM 10 replies 2.6k views
JoeJ
JoeJ

taby said:
What do you know about cosine-weighted vectors, and how have you applied them in a graphics context (e.g. path tracing)?

It's probably the simplest form of importance sampling, working for diffuse reflection.

The idea is, either you take N uniformly distributed samples, and weight their contribution by the dot product of sample direction and normal.

Or you take M samples matching a cosine weighted distribution, using a weight of one for each.

Results are the same, but for the same accuracy M can be smaller than N, so you need less rays.

Here an image of a wall on the left, showing the sample directions we might use to accumulate incoming lighting:

The top side in red shows uniform distribution. The angle between each ray is the same.

The bottom side in green shows importance sampled distribution. We get more rays near the normal because those direction contribute more to the result. So this is more efficient.

The blue circle shows the contribution of each direction, which forms indeed a sphere for a perfect Lambert diffuse material model.

For more complex materials the shape illustrating contribution becomes complex. Typically yo get a lobe for glossy reflections.

Often there is no easy way to calculate this shape analytically, and precise importance sampling becomes impossible.
In those cases you can do a combination of both, using a simple lobe shape pointing along the reflection vector, and weighting the sample by 1/lobe * weight of the actual complex material function.

Some code i have used to generate such sample direction:

static vec RandomDirCosineWeighted (int seed = 0)
			{ 
				float r0 = rand();
				float r1 = rand();
				float r = sqrt(r1);
				float a = r0 * float(PI*2);
				vec d(cos(a) * r, sin(a) * r, 0);
				d[2] = sqrt (1 - r*r);
				return d;
			}

			static vec RandomDir (int seed = 0)
			{ 
				float r0 = rand();
				float r1 = rand();
				float sinTheta = sqrt(1 - r0*r0); 
				float phi = float(PI*2) * r1; 
				float x = sinTheta * cos(phi); 
				float y = sinTheta * sin(phi); 
				return vec(x, y, r0); 
			}

Seed is unused and you need to rotate the direction from z to the material normal.

JoeJ
JoeJ
static float rand ()
			{
				return float (::rand()) / float(RAND_MAX);
			}
taby
taby

Thank you so much JoeJ. My code is not quite perfect yet, but I'm getting close. Thank you!

taby
taby

P.S. I use the Mersenne Twister, which has a period much larger than the number of rays being fired.

JoeJ
JoeJ

I usually use cheap hash functions for random numbers. But for PT it caused noticeable patterns.
rand() still showed patterns some times, so MT is good for a reference, but you would want something faster at some point.
Idk how people solve this in practice, but i guess hash from hash would be good enough.

However, currently the most popular seems to use precomputed blue noise textures, which gives them faster convergence, probably because distribution is coarsely regular and predictable, allowing for some more control and optimization. But idk any details.

taby
taby

Yes, the AIs all used a hash-based PRNG when converting the code to compute shaders.

alvaro
alvaro

If you have access to 64-bit arithmetic, I think it should be very easy to make a hash function with no artifacts. Just combine a few of these:

hash ^= A_64_BIT_NUMBER;

hash *= A_64_BIT_ODD_NUMBER;

hash ^= hash >> 32;

If you are hashing a single counter, three rounds of those operations in that order with random constants produces something indistinguishable from random by statistical tests. For hashing multiple inputs, you can probably get away with a random linear combination of your inputs and then the same three rounds of those operations.

taby
taby

Thank you alvaro!

oguz1ak1
oguz1ak1

Basically, they just bias directions toward the normal, which is perfect for diffuse lighting in path tracing. Makes sampling feel way more “natural” than uniform, and your render converges faster. I’ve used them for AO, Lambertian surfaces, and just general importance sampling always smooths things out and reduces noise. In your physics setup, that π/2 factor doesn’t surprise me; mapping from random numbers to a hemisphere is always a little tricky.

taby
taby

So it’s not too shocking to me that gravitation uses a diffuse process.

Thank you oguz1ak1!

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.