PDA

View Full Version : reflection mapping hair


mjs1
07-22-2005, 12:38 AM
Has anyone reflection-mapped hair made out of RiCurves? When you have an RiCurve can determine a tangent vector to the hair. The surface normal (which you need for regular reflection mapping) would be perpendicular to this tangent vector. Knowing JUST the tangent vector gives you a disk perpendicular to the hair and the normal vector could potentially be anywhere on this disk.

You need another known vector to cross the tangent vector with to get a vector perpendicular to the tangent vector, but as far as I can tell, the tangent vector is all you really have. You could use any old vector, like the surface normal, to cross with the tangent vector, but this is a complete hack and unstable, since the cross product would be 0 if the hair were aligned with the surface normal.

arnecls
07-22-2005, 09:30 AM
Just an idea from creating cameras with only a directon vector for raytracers.

Define an "up" vector, e.g. 0,1,0
Do the following calculations:
normalize(tangent) x up = right
right x tangent = normal

the normal is automatically normalized in this example.
However - this will not work when the tangent points straight down (because the cross product will produce 0,0,0). In that case I would suggest to use the surface normal, as this might be the closest one to the "right" normal.
A better way would be to take 2 normals before and after this point and interpolate.

That's just an idea, I haven't worked with hair or RiCurves before (btw. what are RiCurves?), so there might be better ways.

gga
07-24-2005, 02:31 PM
Has anyone reflection-mapped hair made out of RiCurves? When you have an RiCurve can determine a tangent vector to the hair. The surface normal (which you need for regular reflection mapping) would be perpendicular to this tangent vector. Knowing JUST the tangent vector gives you a disk perpendicular to the hair and the normal vector could potentially be anywhere on this disk.


You kind of answered your own question. To map/shade an infinitely thin cylinder such as hair that is subpixel or even only a couple of pixels in screen space, you really have to consider the normals of all directions around that disk perpendicular to the tangent of the point you are shading (or, better said, if your case, all the reflections of all the normals of the half disk facing towards the camera). The integration (read: sum) of all those values should also potentially be done weighting things such as fresnel effects, as well as the diminishing area of the surface (cylinder) at glancing angles.
AFAIK, doing this without multiple sampling the reflection is not possible.

You can, however, try to cheat. So... let's answer your second question. How to obtain a normal from the tangent?
You will see that nothing else is needed.
Overall, we mentioned that we will be shading the half disk facing the camera. So, our normal or normals should really be facing the camera. So, we can really start with the view vector ( ie. normalize(-I) ) which we *know* is facing towards the camera.
Now... the way prman creates hair, the renderer will create a strip of micropolygons facing the camera. Thus, you might find that if you look at N, it may already have the value of normalize(-I). If it does, use that and then you save some minor calculations.
Anyway, more than likely this view vector does not lie within the plane of the disk perpendicular to the hair tangent. So, what you need to do is project the view vector onto that perpendicular plane of the hair.
If you see it as bad ASCII art, we then have:


T
| View vector
| / |
| / | projection
|/ v
-----*----- vector in plane of hair tangent (we'll call it Nh)


Now, when drawn as a simple 2d drawing, you will see that the view vector, when projected onto the plane of all the cylinder normals perpendicular to the hair tangent creates a triangle. Fortunately for us, these sort of triangles have been studied since the days of Pythagoras.
And... if we know the value of T dot V, we can easily calculate the projection. The value of Nh dot V is simple the opposite of T dot V. That is, the sine of T and V.
To save some calculations, the expression "sin(a,b)" (where a and b are normalized vectors),
can also be written as sqrt(1.0-dot(a,b)^2).
Thus, we have:


vector V = normalize(-I); // or just vector V = N;
vector T = normalize(dPdv);
float TdV = T.V;
float sign = 1;
if (TdV < 0) sign = -1;
normal Nh = normalize( V - T * sign * sqrt(1 - TdV * TdV) );


The sign variable is just there to handle the case where the tangent and view vector face in opposite directions in which case you need to project in the opposite direction.
And voila... you have now a normal corresponding to the projection of the view vector, which roughly corresponds to the visible "center" of the hair.

To get other vectors around the circumference of the hair, you can take advantage of the "s" variable in renderman. In hair, the s variable will go from 0 to 1 around the (visible) half circunference of the hair. Thus, if you are seeing the hair really, really close (where the hair width is several pixels), you can rotate Nh by the value of this variable.
Or, if you are seeing the hair from afar, you can then shoot several reflection rays based on sampling R several times with different rotated normals. Or use it to call environment() several times in case of just doing reflection mapping.

mjs1
08-04-2005, 03:46 AM
Thanks, Arne and Gonzalo!

I have something working now, thanks to your help. The key was using the view vector. I took Gonzalo's idea and ran it by some others and we fleshed it out into something that works pretty well. This is what is I have working:

N should point toward the camera, which means that T, V, and N will all be in the same plane.
I labeled the normal vector as Nscaled instead of N because it is the projection of V into the disk of N vectors. Its length will need to be normalized to one before it is the true normal unit vector N.

I have also added a vector called Tscaled. This is a vector parallel to the tangent vector T with the length needed to reach from the end of the view vector to its projection into the normal disk.

By vector addition, we can write


Nscaled = V + Tscaled


Because the view vector V is known, we just need to find the length |Tscaled|, multiply it by the tangent unit vector T, and we will have the vector Nscaled.

Nscaled = V + |Tscaled| * T


The sine of an angle in a right triangle is equal to the length of the opposite side divided by the length of the hypotenuse. Because qTV can be found from the dot product of unit vectors T and V, the angle qTV -90 is also known, as is its sine. We can use this to find the length |Tscaled|.

sin(qTV - 90) = opp/hyp = |Tscaled|/|V| = |Tscaled|/1 = |Tscaled|

By a trigonometric identity, we know that sin(qTV - 90) = -cos(qTV). So that gives us

|Tscaled| = sin(qTV - 90) = -cos(qTV)
Nscaled = V - cos(qTV) * T


This also works if the view vector points up like the tangent vector. Because the Tscaled vector still points up like T, the formula for Nscaled becomes

Nscaled + Tscaled = V
Nscaled = V - Tscaled


Because the angle between V and Nscaled is now 90 - qTV instead of qTV 90, the appropriate trig identity is sin(90 - qTV) = cos(qTV). So

Nscaled = V - cos(qTV) * T

Because the dot product can be written T . V = |T|*|V|*cos(qTV), and because T and V are unit vectors (|T| = |V| = 1), we can find the vector Nscaled using the formula

Nscaled = V - (T . V) * T

CGTalk Moderation
08-04-2005, 03:46 AM
This thread has been automatically closed as it remained inactive for 12 months. If you wish to continue the discussion, please create a new thread in the appropriate forum.