上一篇文章我们已经可以求出由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; }