How is BRDF usually implemented? - reflection

I have a small path tracer, and I am trying to figure out how to implement some basic BRDFs.
Here's a brief description of the pipeline I use (without recursion):
1) For each pixel:
1.1) For each sample:
1.1.1) I construct a path.
1.1.2) I calculate the contribution of this path.
1.1.3) I calculate the "probability" of this path.
1.1.4) Finally, I calculate the overall color value(taking into account number of samples, "probability" and contribution of the path).
1.2) Take the sum of all samples' values and write it to the pixel.
So, I calculate the direction of reflected rays in the step 1.1.1) I construct a path.
For now, I have implemented diffuse reflection, specular reflection, glossy reflection, refraction.
Now I want to implement a complex BRDF, let's say Cook-Torrance BRDF.
I see that it contains several components (diffuse reflection and specular reflection). How should I trace these rays to get the combination? Should I choose between diffuse_ray/specular_ray randomly and then accumulate the values(multiplied by some coefficients) as usual?(like, if a random value is more than 0.5 then I trace a diffuse ray, otherwise - specular) Or should I trace multiple rays from each intersection?
How is it usually implemented in physically-based renderers?
P.S. If somebody knows some good articles on this topic I would be glad to see them. I tried to read pbrt but it seems very complex and huge to me. And some things there are implemented differently, like the camera model and other stuff.

A first step might be to let your BRDF decide how the ray should bounce. If it's a combination of multiple methods, assign a probability in the BRDF to each method, and then have the BRDF pick one according to the given probabilities.
For example, suppose you want a BRDF that's a combination of specular and diffuse reflection. When you instantiate the BRDF, you might tell it that you want 60% specular and 40% diffuse. Then, when your path tracer queries the BRDF to get the reflected ray direction, the BRDF can internally calculate a specular ray 60% of the time and a diffuse ray 40% of the time.
EDIT - another, perhaps more accurate approach would be to have the BRDF use the provided probabilities to generate a reflected direction by interpolating between the two methods. In our example above, when queried, the BRDF calculates a specular ray and a diffuse ray for every intersection, and returns a new ray whose direction is a linear interpolation of 60% of the calculated specular ray and 40% of the calculated diffuse ray.

Related

Determine angle of reflection without magnitudes

I realize this question has been answered before, but not yet in terms I understand.
In code I'm bouncing two balls off each other. I know each ball's current direction in radians, and I can find the normal angle using the atan2() function.
Say I ignore one ball and focus on the other - I have an angle of incidence, and a normal angle. Is there a straightforward way to find the angle of reflection without needing magnitudes?
This isn't necessarily possible. A collision is determined by the conservation momentum and energy before/after impact, but the result differs on the type of collision (elastic/inelastic). As the equations take into account both of the balls velocity and mass before the collision, it is not possible to look at them independently and expect a correct result.
The only case that is "easy", is if the other ball is a wall and the collision (this is because the wall doesn't moveā€”it's mass is 'infinite' and the momentum will be 0 before and after collision). Then the output angle is directly opposite the incident.

Path tracing: why is there no cosine term when calculating perfect mirror reflection?

I've been looking at Kevin Beason's path tracer "smallpt" (http://www.kevinbeason.com/smallpt/) and have a question regarding the mirror reflection calculation (line 62).
My understanding of the rendering equation (http://en.wikipedia.org/wiki/Rendering_equation) is that to calculate the outgoing radiance for a differential area, you integrate the incoming radiance over each differential solid angle in a hemisphere above the differential area, weighted by the BRDF and a cosine factor, with the purpose of the cosine factor being to reduce the contribution to the differential irradiance landing on the area for incoming light at more grazing angles (as light at these angles will be spread across a larger area, meaning that the differential area in question will receive less of this light).
But in the smallpt code this cosine factor is not part of the calculation for mirror reflection on line 62. (It is also omitted from the diffuse calculation, but I believe that to be because the diffuse ray is chosen with cosine-weighted importance sampling, meaning that an explicit multiplication by the cosine factor is not needed).
My question is why doesn't the mirror reflection calculation require the cosine factor? If the incoming radiance is the same, but the angle of incidence becomes more grazing, then won't the irradiance landing on the differential area be decreased, regardless of whether diffuse or mirror reflection is being considered?
This is a question that I have raised recently: Why the BRDF of specular reflection is infinite in the reflection direction?
For perfect specular reflection, BRDF is infinite in the reflection direction. So we can't integrate for rendering equation.
But we can make reflected radiance equal the incident according to energy conservation.
The diffuse light paths are, as you suspect, chosen such that the cosine term is balanced out by picking rays proportionally more often in the direction where the cosine would have been higher (i.e. closer to the direction of the surface normal) a good explanation can be found here. This makes the simple division by the number of samples enough to accurately model diffuse reflection.
In the rendering equation, which is the basis for path tracing, there is a term for the reflected light:
Here
represents the BRDF of the material. For a perfect reflector this BRDF would be zero in every direction except for in the reflecting direction. It then makes little sense to sample any other direction than the reflected ray path. Even so, the dot product at the end would not be omitted.
But in the smallpt code this cosine factor is not part of the
calculation for mirror reflection on line 62.
By the definitions stated above, my conclusion is that it should be part of it, since this would make it needless to specify special cases for one material or another.
That's a very good question. I don't understand it fully, but let me attempt to give an answer.
In the diffuse calculation, the cosine factor is included via the sampling. Out of the possible halfsphere of incidence rays, it is more likely a priori that one came directly from above than directly from the horizon.
In the mirror calculation, the cosine factor is included via the sampling. Out of the possible single direction that an incidence ray could have come from, it is more likely a priori - you see where I'm going.
If you sampled coarse reflection via a cone of incoming rays (as for a matte surface) you would again need to account for cosine weighting. However, for the trivial case of a single possible incidence direction, sampling reduces to if true.
From formal perspective cosine factor in the integral cancels out with cosine in denominator of specular BRDF (f_r = delta(omega_i, omega_o)/dot(omega_i, n)).
In different literature, the ideal mirror BRDF is defined by
a specular albedo
dirac deltas (infinite in direction of perfect reflectance, zero everywhere else) and
and 1/cos(theta_i) canceling out the cosine term in the rendering equation.
See e.g.: http://resources.mpi-inf.mpg.de/departments/d4/teaching/ws200708/cg/slides/CG07-Brdf+Texture.pdf, Slide 12
For an intuition of the third point, consider that the differential footprint of the surface covered by a viewing ray from direction omega_r is the same as the footprint of the surface covered by the incident ray from direction omega_i. Thus, all incident radiance is reflected towards omgea_r, independent of the angle of incidence.

How do I calculate a normal vector based on multiple triangles sharing a vertex?

If I have a mesh of triangles, how does one go about calculating the normals at each given vertex?
I understand how to find the normal of a single triangle. If I have triangles sharing vertices, I can partially find the answer by finding each triangle's respective normal, normalizing it, adding it to the total, and then normalizing the end result. However, this obviously does not take into account proper weighting of each normal (many tiny triangles can throw off the answer when linked with a large triangle, for example).
I think a good method should be using a weighted average but using angles instead of area as weights. This is in my opinion a better answer because the normal you are computing is a "local" feature so you don't really care about how big is the triangle that is contributing... you need a sort of "local" measure of the contribution and the angle between the two sides of the triangle on the specified vertex is such a local measure.
Using this approach a lot of small (thin) triangles doesn't give you an unbalanced answer.
Using angles is the same as using an area-weighted average if you localize the computation by using the intersection of the triangles with a small sphere centered in the vertex.
The weighted average appears to be the best approach.
But be aware that, depending on your application, sharp corners could still give you problems. In that case, you can compute multiple vertex normals by averaging surface normals whose cross product is less than some threshold (i.e., closer to being parallel).
Search for Offset triangular mesh using the multiple normal vectors of a vertex by SJ Kim, et. al., for more details about this method.
This blog post outlines three different methods and gives a visual example of why the standard and simple method (area weighted average of the normals of all the faces joining at the vertex) might sometimes give poor results.
You can give more weight to big triangles by multiplying the normal by the area of the triangle.
Check out this paper: Discrete Differential-Geometry Operators for Triangulated 2-Manifolds.
In particular, the "Discrete Mean Curvature Normal Operator" (Section 3.5, Equation 7) gives a robust normal that is independent of tessellation, unlike the methods in the blog post cited by another answer here.
Obviously you need to use a weighted average to get a correct normal, but using the triangles area won't give you what you need since the area of each triangle has no relationship with the % weight that triangles normal represents for a given vertex.
If you base it on the angle between the two sides coming into the vertex, you should get the correct weight for every triangle coming into it. It might be convenient if you could convert it to 2d somehow so you could go off of a 360 degree base for your weights, but most likely just using the angle itself as your weight multiplier for calculating it in 3d space and then adding up all the normals produced that way and normalizing the final result should produce the correct answer.

Quaternion Interpolation w/ Rate Matching

I have an object w/ and orientation and the rotational rates about each of the body axis. I need to find a smooth transition from this state to a second state with a different set of rates. Additionally, I have constraints on how fast I can rotate/accelerate about each of the axis.
I have explored Quaternion slerp's, and while I can use them to smoothly interpolate between the states, I don't see an easy way to get the rate matching into it.
This feels like an exercise in differential equations and path planning, but I'm not sure exactly how to formulate the problem so that the algorithms that are out there can work on it.
Any suggestions for algorithms that can help solve this and/or tips on formulating the problem to work with those algorithms would be greatly appreciated.
[Edit - here is an example of the type of problem I'm working on]
Think of a gunner on a helicopter that needs to track a target as the helicopter is flying. For the sake of argument, he needs to be on the target from the time it rises over the horizon to the time it is no longer in view. The relative rate of this target is not constant, but I assume that through the aggregation of several 'rate matching' maneuvers I can approximate this tracking fairly well. I can calculate the gun orientation and tracking rates required at any point, it's just generating a profile from some discrete orientations and rates that is stumping me.
Thanks!
First of all your rotational rates about each axis should compose into a rotational rate vector (i.e. w = [w_x w_y w_z]^T). Then you can separate the magnitude of the rotation from the axis of the rotation. The magnitude is w_mag = w/|w|. Then the axis is the unit vector u = w/w_mag. You can then update your gross rotation by composing an incremental rotation using your favorite representation (i.e. rotation matrices, quaternions). If your starting rotation is R_0 and your incrementatl rotation is defined by R_inc(w_mag*dt, u) then you follow the following composition rules:
R_1 = R_0 * R_inc
R_k+1 = R_k * R_inc
enjoy.

Is the binormal of a vertex the cross between its normal and tangent?

I'm trying to find out what a binormal is in the context of graphics programming but coming up short, I saw on a site that the binormal was being calculated as the cross product between the normal and tangent (i.e. cross(normal, tangent)), is this the correct way to calculate a binormal?
Just to point out that is TOTALLY not the definition of the binormal. Thats the definition of a Bi Tangent. A Binormal is something totally different relating to the "other" normal formed by a curved surface.
People need to learn not to re-iterate that mistake (Made by someone early on in the days of normal mapping).
According to mathworld, the binormal vector is defined as cross(tangent,normal) where tangent and normal are unit normal vectors.
Note that, strictly speaking, order matters when you take cross products. cross(tangent,normal) points in the opposite direction from cross(normal,tangent). That may or may not matter depending on your application. It really doesn't matter as long as your calculations are internally consistent.
Normal, tangent and binormal vectors form an orthonormal basis to represent tangent space.
Tangent space ( sometimes called texture space ) is used in per-pixel lighting with normal maps to simulate surface detail ( imagine a wall or a golf-ball ).
The tangent and binormal vectors represent the equivalent texture UVs i.e the vectors parallel to the surface normal.
So technically speaking - as they form an orthonormal basis then binormal = cross (tangent,normal ) however in practice, since binormals and tangents are generated from the UVs in the normal map and may be averaged over several vertices then they may not be strictly orthonormal.
For a couple of good articles on the subject read
http://www.3dkingdoms.com/weekly/weekly.php?a=37
and
http://www.blacksmith-studios.dk/projects/downloads/bumpmapping_using_cg.php
Actually, no, sometimes it isn't. In 3d graphics, at least.
If a texture was stretched, then it is possible that binormal will not be perpendicular to both normal and tangent (although it should be perpendicular).
Just use whatever your exporter has calculated. If the exporter provides both tangent and binormal, it is good. If there is only tangent, then calculate binormal as a perpendicular to tangent and normal.
Get a complex object with both tangent and binormal calculated, and compare lighting when you use binormal which was provided with the lighting that you get when binormal was calculated as cross-product. There will be a difference.
Anyway, I believe that a proper way is to get both tangent and binormal calculated in exporter, and just use what exporter has provided.
Yes the Binormal or Bitangent is the cross between the normal and the tangent of a vertex. If you have any 2 vectors out of these three you can calculate the other one.
For instance if you have a tangent and a binormal (or bitangent) you can calculate the normal.
Here is a sample that can create binormal and bitangents in GLSL having just the normal:
varying vec3 normal;
varying vec4 vpos;
varying vec3 T,B;
void main()
{
gl_TexCoord[0] = gl_MultiTexCoord0;
normal = normalize(gl_NormalMatrix*gl_Normal);
gl_Position =gl_ProjectionMatrix*gl_ModelViewMatrix*gl_Vertex;
vpos = gl_ProjectionMatrix*gl_ModelViewMatrix*gl_Vertex;
T = cross(normal,vec3(-1,0,0));
B = cross(T,normal);
}
While it might not get the desired restults sometimes it should get you where you want.

Resources