If, by any chance, you get a 404 on the video above (I got one report of such an event taking place),
download it as a zip from here:
http://deeplayer.com/cinemut/0001_0031.zip
Or if you can't play ogg, here's an mpeg version, but it looks really crappy...
http://deeplayer.com/cinemut/test7777.mpeg
Here's an update of the routine, and a full explanation I emailed to Klauss:
Code: Select all
float prt_spec_shadow( in vec3 dir, in vec3 nor, in vec4 prt )
{
vec3 VUT = normalize( dir );
vec3 NOR = normalize( nor );
vec3 PRT = prt.rgb;
float ao = prt.a;
vec3 NPRT = normalize( PRT );
vec3 temp1 = cross( VUT, NOR );
vec3 temp2 = cross( NPRT, NOR );
float coplanarity_relevance = dot( temp1, temp1 ) * dot(temp2,temp2) * ao;
float coplanarity = dot( normalize(temp1), normalize(temp2) );
coplanarity *= coplanarity;
float noncoplanarity = 1.0-coplanarity;
noncoplanarity *= noncoplanarity; //less than full squaring makes weird things
noncoplanarity *= sqrt(coplanarity_relevance);
noncoplanarity += (0.1*(1.0-sqrt(dot(temp2,temp2))));
float vutocclusion = 0.6366*asin(dot(PRT,VUT));
vutocclusion += (sqrt(ao)+noncoplanarity-0.03);
vutocclusion *= vutocclusion;
vutocclusion *= vutocclusion;
vutocclusion *= vutocclusion;
vutocclusion *= vutocclusion;
vutocclusion *= vutocclusion;
vutocclusion *= vutocclusion;
vutocclusion *= vutocclusion;
vutocclusion *= vutocclusion;
vutocclusion *= vutocclusion;
return clamp( vutocclusion, 0.0, 1.0 );
}
Let's take it piece by piece:
Code: Select all
vec3 VUT = normalize( dir );
vec3 NOR = normalize( nor );
These are probably unnecessary; I just normalized them just in case.
Code: Select all
vec3 PRT = prt.rgb;
float ao = prt.a;
I made the prt_decode routine output a vec4 "prt", where prt.rgb = prtp-prtn and prt.a is the same uniform ao that comes with the prt texture's alpha channel.
A normalized PRT will be needed for the coplanarity computations.
Code: Select all
vec3 temp1 = cross( VUT, NOR );
vec3 temp2 = cross( NPRT, NOR );
To compute coplanarity, I need two vectors showing azimuth rotations of the VUT (Vector Under Test) and the PRT (bent normal) vector.
What is coplanarity?
Well, if you're looking at a point at a surface in front of you, and the PRT vector is pointing to the left, it means there's an occluder to the right, and such an
occluder does not occlude your reflection vector, no matter how big it is. So, coplanarity is a computation of how closely in the same plane are your view,
reflection and prt vectors.
So, what I'm doing is computing cross products between the normal and the reflection (or light) --i.e. VUT-- and between the normal and the PRT bent normal.
If the vectors are parallel OR opposite, the VUT and PRT are co-planar. If they are at 90 degrees, they are maximally non-coplanar.
Code: Select all
float coplanarity_relevance = dot( temp1, temp1 ) * dot(temp2,temp2) * ao;
float coplanarity = dot( normalize(temp1), normalize(temp2) );
Coplanarity relevance is a bit long to explain, but pretty simple:
Suppose the PRT bent normal and the normal are parallel: Their cross product is zero. They don't define a plane at all, so the coplanarity test is not applicable.
Similarly, if your view/reflection/light... VUT vector is parallel to the normal, coplanarity is a non-issue. So, relevance is essentially the product of the lengths
of the cross product vectors. Now, I'm also multiplying by the ao, and there's a reason for that. Non-coplanarity can make a strong force to negate occlusion;
but in very hidden places that can be a bad thing. There were lights appearing all over in the fins on the cylinders until I multiplied by the ao.
Note that we cannot simply leave the cross products un-normalized and let them carry their own relevance. I tried that first. Problem is, we need relevance
to apply to NON-coplanarity. See below.
So. now, coplanarity is simply the dot product of the two normalized cross-products. If coplanarity is close to 1 or -1, the VUT and PRT are coplanar with the
normal. If the dot product is close to zero, they are maximally non-coplanar, and we will avoid occluding.
Should be obvious: Coplanarity is at +1 OR -1, so obviously the true measure is the square of the dot product.
Code: Select all
float noncoplanarity = 1.0-coplanarity;
Code: Select all
noncoplanarity *= noncoplanarity; //less than full squaring makes weird things
This is a discovery item during testing. It needed squaring; though I'm not exactly sure why. Multiplying by its square root was not enough.
Code: Select all
noncoplanarity *= sqrt(coplanarity_relevance);
NON-coplanarity multiplied by relevance. Well, by its square root...
Here again, this was a bit hackish, if you want to call it that. The relevance I arrived at was too strong; it made coplanarity detection irrelevant too readily.
I tried a million things. No; can't get rid of the ao; or any of the other terms; but square-rooting the whole thing is okay.
Code: Select all
noncoplanarity += (0.1*(1.0-sqrt(dot(temp2,temp2))));
This is the biggest hack of all; but it can still be explained: The lack of it caused very strange, vein-like or thread-like, or river-like artifacts.
The reason for the artifacts, after a lot of experimentation, was not, as I initially suspected, a problem with the relevance computation.
One typical artifact location was along the middle of the oval depression on the air filter unit. What happened along that middle was that PRT became
parallel to the normal, producing a zero length cross product, which got normalized to some random direction. So, along the center of the oval, there
was a river of points that produced totally random coplanarities. The relevance factor should have taken care of it, but for some reason it didn't.
Well, I know the reason: The vectors are normalized before dotting, so they produce stron coplanarity signals, whereas the relevance factor is
an analog signal that only goes down gradually. The artifact was not strong; only visible at shallow angles, but I wanted to get rid of it, and so the
the temp2 term came handy, which is already the PRT dot normal. By adding a bit of its inverse to non-coplanarity, the problem was fixed.
Code: Select all
float vutocclusion = 0.6366*asin(dot(PRT,VUT));
vutocclusion += (sqrt(ao)+noncoplanarity-0.03);
This is really the heart of the algorithm:
First let's discuss the rough terms and then refine it:
If PRT dot VUT were zero, we know the ray is occluded, UNLESS the AO is exactly 1.0.
If the AO is zero, we know the ray is occluded unless PRT dot VUT is exactly 1.0.
See a pattern?
If the sum of the two is 1.0 or greater, the ray is unoccluded. If their sum is less than 1.0, the ray is occluded.
At least that's what I thought: My first attempt was to write vtocclusion = dot(PRT,VUT) + ao.
But the linearity was not right.
So, what should the linearity be?
Well, the AO we pack with the PRT's is a uniform ao, for good reasons. You can say that the ao value represents the un-occluded solid angle.
Now, the dot product of PRT and VUT had to be converted back to an angle. The AO represents solid angles where 1.0 is a hemisphere; but so
the square root of the ao represents regular angle in 0-1 representing 0-90 degrees. But asin returns radians, so I had to multiply by 2/pi to get
to 0-1 representing 0-90 degrees.
So, the heart of ray occlusion is really 0.6366*asin(PRT,VUT)+sqrt(ao), where the threshold is 1.0
The non-coplanarity signal is added so that it negates occlusion to the extent that the VUT and PRT are not coplanar.
Then, the 0.03 subtracted is a hack.
Code: Select all
vutocclusion *= vutocclusion;
vutocclusion *= vutocclusion;
vutocclusion *= vutocclusion;
vutocclusion *= vutocclusion;
vutocclusion *= vutocclusion;
vutocclusion *= vutocclusion;
vutocclusion *= vutocclusion;
vutocclusion *= vutocclusion;
vutocclusion *= vutocclusion;
return clamp( vutocclusion, 0.0, 1.0 );
}
And the multiple squarings simply make a smooth boolean
If it's greater than one, it's unoccluded. If it's even slightly less than one it quickly becomes occluded.