bsp里的面分为两种,结构化面和细节面,这两种面一般都是在建模的时候就指定好了,一般情况下结构化面主要用来分割场景的(结构化面本身也是场景的一部分),细节面单纯的是场景,把结构化面所在的平面都存储到一个数组里去,然后分割一次就把分割的平面从数组里移除,当分割到最后,结构化面都用完的时候,场景就不在分割了,计算pvs的时候,需要事先计算叶凸体,
由于节点类是从plane平面类派生出来的,所以一个节点本身也有平面的特点,比如面的法向量,这个面指的是用来将这个节点里所有 三角形一分为二的面
每个节点有一个face数组,储存的是这个节点的所有的面(包括结构化面和细节面)
每个节点有一个plane数组,储存的是这个节点的所有的结构化面所在平面
每个节点有一个leaf整形变量,储存的叶子节点的索引,如果该节点不是叶子节点,这个变量是-1,
引擎engine类本身有一个leafplane数组,这个其实是一个二维数组,这个数组按照叶子节点的leaf索引来存储每个叶子节点的所有平面,leafvert数组,这也是一个二维数组,数组里存储的是每个叶子节点构成叶凸体的顶点
关于叶凸体:
1:首先寻找构成叶凸体的平面,每个节点本身就是一个分割平面(因为节点类是从平面plane类派生出来的),从根节点开始往下遍历一直到叶子节点(有几个叶子节点就有几个叶凸体)所有分割平面构成的一个一个封闭区域就是叶凸体,这些个平面就是我们要找的构成叶凸体的平面,求出来后填充到leafplane中
2:寻找构成叶凸体的所有点,首先根据之前找出来的所有平面,求出任意三个平面的相交点,然后把这些相交的点加到数组里去(重复的不进行计算),关于求三个平面相交的点是这里求法是这样的:设这个点为p(x,y,z),然后p点乘三个平面的法向量,所得结果是远点到平面的距离,这样就列出了三个方程,就可以求出p点了,然后填充到leafvert中
3:计算入口,,在这之前要介绍另外一个类flyPolygon,这是一个多边形类,因为入口本身是个多边形,所以要对这个多边形类写一些方法,节点类有一个数组polygon,存储的就是叶凸体上的平面上的有效点构成的多边形,引擎类里还存储着一个数组leafs,这个数组里存储的是叶子节点的点线面信息,即场景信息。
对于每个叶子节点上的构成叶凸体的平面中的每个平面,从leafvert数组中找出位于这个平面上的点,然后把这些个点排序后(关于这个排序我要说一点,因为你找到一些个点,想把这些个点连成一个多边形,有时候这这些点你如果不排序一下直接连接的话可能会出现非凸多边形的出现,这个时候排序方法是先找出这些个点中心点,把这些个点和中心点都连接起来,和中心点连线形成的角度最小的两个点在最后连接成多边形的时候肯定是挨着的两个点,怎么求判断角度大小,直接求向量叉乘结果,叉乘结果最小的两个向量角度肯定是最小的),把这些个点连成多边形,放到polygon里,这样的话每个叶凸体上的所有多边形就都求出来了,下一步求入口
首先检测任意两个叶凸体i,j是否相交(通过检测两个叶子节点的多边形(这里指的是上一步计算的多边形)是否相交),如果相交的话,将两个叶凸体的所有交点存储到多边形p中,首先看看i节点的邻居节点中是否有j节点,如果有的话就跳出循环,如果没有的话,将j节点设为i节点的邻居节点(节点类里有一个数组存储的是该节点的邻居节点,一个数组存储的是该节点的所有入口,入口本身是一个多边形),将i节点设为j节点的邻居节点,将p中的点排序,然后p就是一个多边形了,将p设为i节点的一个入口,然后将p的法向量反向,将p设为j节点的一个入口,下一步测试叶凸体之间的可见性
这里测试任意两个叶凸体之间的可见性是这样的:每个叶子节点的邻居节点都是可见的,并把每一个叶子节点的邻居节点放在一个数组里list,把其他的非邻居节点放到list2数组里,如果list数组里的节点有和list2中的节点m是邻居的话,直接测试从节点leafnum到节点m的可见性,可见性是双向的,测试可见性的时候是从两个叶凸体内个取几个随机点进行测试,这样pvs就测试完了
下一步将的是关于光照贴图的:这里首先做的是为场景中的每一个三角形都生成一张纹理图,回头这个三角形就把这张纹理图作为纹理之一,怎么做呢,
1:先根据三角形的法向量(找法向量最大的两个分量比如是x,y分量是最大的两个)把三角形映射到o-xy平面,求出三角形的包围盒(这里其实是个矩形),求出包围和最大点和最小点形成的向量diag,把这个向量也映射到xy平面上,这个时候diag的xy分量大小其实代表的就是三角形映射到xy平面上的x和y方向的长度,然后根据已经定义好的世界坐标系中一个单位长度对应纹理坐标的多少个像素,分配纹理图大小(纹理图是个矩形),分配纹理图大小的时候一个细节问题是分配的时候不足一个像素的补足一个像素,还有就是纹理图大小要有个限制,比如纹理图大小不能大于64个像素,不能小于3个像素。接下来就是为三角形的每个顶点分配纹理坐标了,这里我做的处理是:根据顶点在x方向大小在该三角形包围盒x方向大小所占比例(这个比例肯定是0-1之间的)这个比例就是纹理坐标,y方向同理,这样的话每个三角形都分配了纹理图,也有了纹理坐标.,每一张纹理图有一个索引,这个索引表明这张纹理图是属于哪个三角形的纹理图,而每一个三角形faces也都有一个索引表示这个三角形的纹理图是哪张
下一步要做的是把这些小的纹理图合成几个大的纹理图,不然的话回头那么多的纹理图一个个加载的话会出大事的,当然如果所有的纹理图都何必成一个大的纹理图也不太好,会导致这个纹理图太大,这里一个折中的方法是为每个叶凸体生成一个纹理图,当然纹理图合并之后相应的纹理坐标也要修改了:先根据每个叶子节点的所有三角形找出属于这个叶子节点的所有光照图,然后把这些光照图按照大小排序,放到一个大的光照图中,然后计算纹理坐标,这样每个叶凸体的光照纹理图就都算好了,这之后要算一个东西就是求没一个光照图从纹理坐标变换到世界坐标的矩阵,这个矩阵的求法很简单,因为三角形的三个世界坐标知道,纹理坐标知道,根据这个可以求出世界坐标到纹理坐标的矩阵,然后这个矩阵的逆矩阵就是我们要求的矩阵。
关于光照图打包算法
下一步就要根据世界坐标中的光照为这些纹理图分配颜色值了。
计算颜色值这里我用的是点光源,首先根据点光源位置和半径确定点光源和哪几个叶凸体相交,这一点确定后,对于每一个灯光可以照射到的叶凸体中的每一个三角形,找出这个三角形所对应的光照贴图,然后进入到光照图类的illum函数,这个函数传入光源位置和半径,遍历光照图的每一个像素点,然后把这个像素点变换到世界坐标的一个三维点,然后计算这个三维点的最后的光照颜色,填充到这个像素点,这样的话光照贴图就都有了颜色,然后再把小光照图逐像素的复制到达光照图中,每个叶凸体的光照贴图就搞定了,回头渲染的时候直接把这个当做纹理就行了,光照图搞定。
贴一点计算光照图的代码
int flyEngineBuild::build_lmp(const char *fmpfile) { int i,j; for( i=0;i<nfaces;i++ ) { if (faces[i].facetype==FLY_FACETYPE_LARGE_POLYGON) { faces[i].lm=lm.num; flyLightMap *lightmap=build_lightmap(&faces[i]); lightmap->facenum=i; lm.add(lightmap); } else faces[i].lm=-1; } for(i=0;i<leafs.num;i++) { int ii; flyStaticMesh *sm=(flyStaticMesh*)leafs[i]->elem[0]; for(ii=0;ii<sm->objmesh->nf;ii++) { int face_lm=sm->objmesh->faces[ii]->lm; //面的光照图索引 temp_lmpic.add(lm[face_lm]); } //把每个叶凸体的所有面的光照图都打包到temp_lm里面去了 int lmpsize=leafs_lmp_size; //leafs_lmp_size表示每个叶凸体的光照图像素大小 pack_lmp(lmpsize); //把整个叶凸体的光照图打包到temp_lmpic里,这里只是计算大小,并且计算好每个temp_lmpic[]的偏移量,并没有把像素值也打包到temp_lmpic里 //计算光照图坐标(相对于大的光照图) for(ii=0;ii<sm->objmesh->nf;ii++) { if(sm->objmesh->faces[ii]->lm!=-1) { for(int ij=0;ij<sm->objmesh->faces[ii]->nvert;ij++) { sm->objmesh->faces[ii]->vert[ij].texcoord.z= (sm->objmesh->faces[ii]->vert[ij].texcoord.z*temp_lm[ii]->sizex+temp_lm[ii]->offsetx)/lmpsize; sm->objmesh->faces[ii]->vert[ij].texcoord.w= (sm->objmesh->faces[ii]->vert[ij].texcoord.w*temp_lm[ii]->sizey+temp_lm[ii]->offsety)/lmpsize; } } } memset(temp_lmpic[i]->bmp,0,temp_lmpic[i]->bytesxy); //把背景清除为黑色 j=0; for(int nk=0;nk<temp_lm.num/*这个时候temp_lm数组里的小光照图已经排好序并且计算好每个小光照图相对于大光照图的坐标偏移(这里的坐标偏移是指小的光照图左上角的点在大的光照图中坐标(相对于打光照图左上角点))了*/;nk++ ) { if (faces[temp_lm[nk]->facenum].facetype==FLY_FACETYPE_LARGE_POLYGON) temp_lm[nk]->set_base(&faces[temp_lm[nk]->facenum],temp_lmpic[i]); //计算光照图坐标系到世界坐标系的矩阵 memset(temp_lm[nk]->bmp,lmambient,temp_lm[nk]->bytesxy); //把所有的三角形对应的光照图都设置为lmambient颜色,以和背景区分开来 temp_lm[nk]->save(temp_lmpic[i]); //存储到flyLightMapPic里面,这一步是把小光照图的像素逐个复制过去了 j+=temp_lm[nk]->sizex*temp_lm[nk]->sizey; //光亮图面积,光亮图总共需要多少像素 } const char str[]="sd"; compute_lighting(str,i); //这里是根据每个光照图的坐标再找回到世界坐标,所以这个函数一定要在set_base函数之后 for(int ik=0;ik<temp_lm.num;ik++ ) temp_lm[ik]->save(temp_lmpic[i]); char name[255]; LBitmap map; map.Reset(); map.SetBMPData(temp_lmpic[i]->bmp,temp_lmpic[i]->sizex,temp_lmpic[i]->sizey); memset(name,0,255); time_t now; time(&now); strftime(name,255,"media\\%Y%m%d%H%M%S", localtime(&now)); static int bm=0; sprintf(name,"%s%d",name,bm); strcat(name,".bmp"); map.SaveCache(name); //strcat(sm->m_lmp,name); memcpy_s(temp_lmpic[bm]->path,sizeof(temp_lmpic[bm]->path),name,sizeof(name)); for(int nkk=0;nkk<temp_lm.num;nkk++) { temp_lm.remove(nkk); } temp_lm.free(); bm++; }这些个东西应该只有我能看懂的
还没写完,以后继续写