本文是optix自带的文档的翻译,本人初学者,英文水平很有限,仅作为自学与交流之用,如有大神能指正其中错误,本人将感激不尽。
一点四
到目前为止,我们还没有创建任何不能被opengl轻松创建的图像。然而,光线跟踪技术一个十分强悍的特征就是我们可以很轻松地添加十分复杂的光照效果(比如阴影和反射)。为了修改之前的例子使它可以支持阴影效果,我们添加了几行代码来跟踪另外的光线。在这个例子中新的光线(叫做阴影光线)将会从阴影点的表面发出,指向光源的位置。
...
if( nDl > 0.0f ){
// cast shadow ray
PerRayData_shadow shadow_prd;
shadow_prd.attenuation = 1.0f;
float Ldist = length(light.pos - hit_point);
optix::Ray shadow_ray(hit_point, L, shadow_ray_type,
scene_epsilon, Ldist );
rtTrace(top_shadower, shadow_ray, shadow_prd);
float light_attenuation = shadow_prd.attenuation;
if( light_attenuation > 0.0f ){
float3 Lc = light.color * light_attenuation;
color += Kd * nDl * Lc;
float3 H = normalize(L - ray.direction);
float nDh = dot( ffnormal, H );
if(nDh > 0)
color += Ks * Lc * pow(nDh, phong_exp);
}
}
...
这段代码构建了一个新的光线,跟针孔相机类似。需要指出的是第三个参数shadow_ray_type与针孔相机里的同名参数是不同的。这些光线类型只是些int类型的变量,是由host提供给optix的以方便optix可以分别处理不同类型的光线。同样还要指出的是与相机发出的光线相比,阴影光线拥有不同类型的ray payload,也就是PerRayData_shadow,因为阴影光线不需要包含额外的数据,当然除了遮挡信息之外,这里的这个参数代表了一个衰减因素,其范围是从零到一。
我们初始化这条光线的衰减系数为1.0,并调用相机代码中的rtTrace。指出一点,optix的递归程序是很高效的;这个调用会在相机函数的代码中深度递归执行(不会翻了)。现在我们将会限制到不透明的物体上,所以跟物体相交的阴影光线是全黑的。
阴影光线不需要求最近的交点,因为我们不关心阴影光线与什么物体相交。因此,我们将不会使用closest hit函数,取而代之的是any hit函数。Any hit函数会在任何一个光线与物体的交点处被optix调用。如果在这个光线上有多个交点,那么这些交点调用any hit函数的顺序是不一定的。
RT_PROGRAM void any_hit_shadow()
{
// this material is opaque, so it fully attenuates all
// shadow rays
prd_shadow.attenuation = 0.0f;
rtTerminateRay();
}
如果一个阴影射线的交点被找到,我们将这条光线的有效信息中的attenuation(衰减)值设置为0,用来指示说明没有光线到达这个物体。此外,我们不需要查找更多的交点。于是我们调用rtTerminateRay(终止光线)函数,这个函数会立即将控制权最近一次调用rtTrace的函数(这里,closesthit函数在上面展示过了)。
只有在阴影没有挡在物体和光源之间时,closesthit 函数才会把这个光源的贡献值加上。此外,光照的贡献值是根据返回的衰减值累加的,这一点使得我们可以在透明的物体下创建有颜色的阴影,这样的物体将会在后面的例子中提到。另外,这一部分使用的是和之前一样的phong光照模型。
一点五
在光线跟踪系统中添加完美的镜面反射是很容易的;这样的特效使得光线跟踪成为更加灵活和综合的方法。这一节我们主要研究地板的材质。回想一下,每一个物体的材质都可以用过closesthit函数在host代码中单独设置。
为了使得地板的材质具有反射属性,我们在之前产生阴影的表面创建了新的光线,方向正是产生镜面反射的方向。一个标准的光线跟踪教程会告诉你如何确定反射的方向,但是在这里我们使用一个叫做reflect的optix内置函数隐藏了这个实现细节。我们创建了一个新的辐射光线(再一次指出光线构造器的第三个参数?)并跟踪它。返回的颜色是由反射参数(由host端提供)以及之前计算得到的加到这个表面的颜色共同构成的。
RT_PROGRAM void floor_closest_hit_radiance4()
{
// Calculate direct lighting using Phong shading, as
// shown above
...
float3 R = reflect( ray.direction, ffnormal );
optix::Ray refl_ray( hit_point, R, radiance_ray_type,
scene_epsilon );
rtTrace(top_object, refl_ray, refl_prd);
color += reflectivity * refl_prd.result;
prd_radiance.result = color;
}
和阴影射线一样,这个函数也是递归的---计算一个反射光线也许会发送另一个反射光线,阴影光线等等。Optix在将控制权交换给这个点之前会使用一个小函数呼叫stack计算所有的返回值。这里强调一个潜在的终止将会使得optix的call stack溢出(想象一个镜子构成的大厅,光线会在其中永不停止的反射反射,无限循环下去),调用的结果是上面提到的异常处理程序。
为了解决上述的问题,我们需啊哟修改反射程序的代码,使得反射在进行一定的次数之后停止。我们使用一个叫做depth的变量把它放到光线的payload中,来跟踪递归的深度。一旦递归深度超过了用户设置的界限,我么就不再发送光线。在本教程中我们将最大递归深度设为100,这个值应该可以满足几乎所有的场景。
RT_PROGRAM void floor_closest_hit_radiance4()
{
// Calculate direct lighting using Phong shading, as
// shown above
...
if(prd_radiance.depth < max_depth) {
PerRayData_radiance refl_prd;
refl_prd.depth = prd_radiance.depth+1;
float3 R = reflect( ray.direction, ffnormal );
optix::Ray refl_ray( hit_point, R, 0, scene_epsilon );
rtTrace(top_object, refl_ray, refl_prd);
color += reflectivity * refl_prd.result;
}
prd_radiance.result = color;
}
另一个简单的修改可以提高大多数场景的效果。除了跟踪递归深度,我们还会跟踪光线的“重要性”。重要性跟踪主要是跟踪颜色中的能量最后会有多少被添加到最终的颜色中。为了跟踪这个值,我们在光线的payload中添加另一个变量,并初始化为1.0,并在每一次反射时都计算它。然后,我们添加最后一个状态到反射代码中,这样可以在光特别暗的时候避免再发射反射光线。另一个函数luminance,是用来计算颜色的亮度值的,这个亮度值可用于计算“重要性”。