上一篇文章我们已经可以求出由SH基函数组成特定复合函数的因子,现在我们就来实现三维空间的SHL,光源来自HDR图像。
该算法使用上一篇文章的SH因子计算过程、HDR图像导入过程,同时也使用了光线/物体相交过程。
这里新引进几个结构体。
SHRay-使用两个SHVector3d表示射线的起点和方向。
SHRGBColor-表示RGB颜色值,每个分量都是double精度的浮点数。
SHCoeff-为每一个颜色通道容纳SH因子。
SHMaterial-包含物体表面属性:ambient,diffuse,specular,specular power,最后一个分量作为物体的uid。
SHMaterialLookup-一个查找表。因为3DS文件使用名字表示材质,而不是一个uid。这个结构体有两个成员:一个string代表材质名称,一个整数表示和SHMaterial结构体中uid相对应的uid。这个uid作为材质数组的索引。
SHFace3d-容纳场景中给定三角形的特征。成员有:附着在三角形上的材质uid,一个代表法线的向量,含有三个索引的数组(这三个索引分别指向三个顶点),常数(用来搞笑检测交点),两个主轴索引(在求交点过程中三角形投影平面的主轴)。
SHMesh3d-场景中物体的特征,成员有:顶点个数,三角面片个数,两个向量代表包围盒的范围(用来加速求交),三角形数组,顶点数组,每个顶点的法线数组,每一个转移方程的SH因子(不考虑自阴影、自阴影、全局光照)。
SHCamera3d-包含摄像机的属性,成员有:位置(SHVector3d),目标向量(SHVector3d),视野范围(FOV)。
SHScene3d-包含3D场景中的一切东西:摄像机的数目,光源数目,材质数目,物体数目,三角面片总数,场景物体数组,默认材质,材质查找表,背景色,摄像机数组,光源数组。
1、SH Diffuse Unshadowed Light
/*
point:要计算SH因子的当前顶点
normal:当前顶点的法线
color:当前顶点的颜色
sphericalSamples:在所有SH计算中都要使用的样本
result:结果因子
numSamples:样本数
numCoeffs:因子数
*/
void SHProjectDiffuseUnshadowedVertex(SHVector3d point,SHVector3d normal,SHRGBColor color,SHSample* sphericalSamples,SHRGBColor* result,int numSamples,int numCoeffs)
{
//反射率
SHRGBColor albedo;
//dot product and scale coefficient
double H,value;
//weighting factor
double dWeight=4.0*PI;
double factor=dWeight/numSamples;
//indexes
int i,n;
//loop through all the spherical samples
for(i=0;i<numSamples;++i)
{
//the transfer function is the cosine item
VecDot(sphericalSamples[i].vec,normal,H);
//calculate the albedo
RGBScale(color,ONE_OVER_PI,albedo);
//calculate only if cosine positive
if(H>0.0)
{
//calculate the coefficient
for(n=0;n<numCoeffs;++n)
{
value=H*sphericalSamples[i].coeff[n];
result[n].r+=albedo.r*value;
result[n].g+=albedo.g*value;
result[n].b+=albedo.b*value;
}
}
}
//scale the SH coefficient
for(i=0;i<numCoeffs;++i)
{
result[i].r*=factor;
result[i].g*=factor;
result[i].b*=factor;
}
}
以上代码是求给定点转移方程的SH投影(即求系数),是预处理的一步。
2、SH Diffuse Shadowed Light
转移方程包含可见项,需要检测和场景中所有其他三角面片的相交情况,需要使用光线追踪的算法。对于一个给定的顶点,我们不去检测它和场景中所有物体的所有三角面片的相交情况,而是我们首先检测它和每个物体包围盒的相交情况,如果相交,则继续检测它和该物体所有三角面片的相交情况。
/*
aScene:被照亮的场景
meshIndex:当前顶点属于的mesh
faceIndex:当前顶点属于的三角面片
*/
void SHProjectDiffuseShadowedVertex(SHScene3d* aScene,int meshIndex,int faceIndex,SHVector3d point,SHVector3d normal,SHRGBColor color,SHSample* sphericalSamples,SHRGBColor* result,int numSample,int numCoeffs)
{
//反射率
SHRGBColor albedo;
//dot product and scale coefficient
double H,value;
//weighting factor
double dWeight=4.0*PI;
double factor=dWeight/numSamples;
//indexes
int i,n;
//ray used for the visibility term
SHRay ray;
//origin of the ray is the current vertex
VecCopy(point,ray.origin);
//loop through all the spherical samples
for(i=0;i<numSamples;++i)
{
VecDot(sphericalSamples[i].vec,normal,H);
RGBScale(color,ONE_OVER_PI,albedo);
if(H>0.0)
{
//the direction is the spherical sample direction
VecCopy(sphericalSamples[i].vec,ray.direction);
//determine the visibility for shadowing
if(!intersectScene(&ray,aScene,faceIndex,meshIndex))
{
for(n=0;n<numCoeffs;++n)
{
value=H*sphericalSamples[i].coeff[n];
result[n]+=albedo*value;
}
}
}
}
for(n=0;n<numCoeffs;++n)
result[n]*=factor;
}
int intersectScene(SHRay* ray,SHScene3d* aScene,int faceIndex,int meshIndex)
{
//the current mesh
SHMesh3d* mesh;
//indexes
int i,j;
//go through each object of the model
for(i=0;i<aScene->numMeshes;++i)
{
//assign current mesh
mesh=&aScene->meshes[i];
//check if ray intersects the mesh's bounding box
if(!boxIntersect(ray,mesh->min,mesh->max))
continue;
//go through all of the faces of the object
for(j=0;j<mesh->numFaces;++j)
{
//skip triangles from which the ray orginated
if(i==meshIndex && j==faceIndex)
continue;
//test intersection,returns if intersection
if(triangleIntersect(&mesh->faces[j],mesh->points[mesh->faces[j].pointIndex[0]],mesh->points[mesh->faces[j].pointIndex[1]],
mesh->points[mesh->faces[j].pointIndex[2]].ray))
return 1;
}
}
//returns 0 if no intersection found
return 0;
}
下面的程序实时计算每一个顶点的最终颜色值,用到SH基函数的性质2
SHRGBColor SHLighting(SHRGBColor* light,SHRGBColor* vertexSH,
int numCoeffs,double dScale)
{
.//the color returned
SHRGBColor result;
//index
int i;
//initialize the color
result.r=result.g=result.b=0.0;
//perform the dot product of the SH coefficients
for(i=0;i<numCoeffs;++i)
{
result+=light[i]*vertexSH[i]*dScale;
}
return result;
}