流体模拟(三)
Marching Cube算法(2)
我们在之前的流体系统类里新加入一些函数和成员,用来引用我们的MC类,便可以获得生成有表面的流体模型了,效果如图:
添加简单的天空盒以及Phong氏光照模型边有效果:
流体系统类的更改:
class FluidSystem{
public:
......
//复制缓存
void CuCopyArrayToDevice(float* device, const float* host, int offset, int size);
// 隐式函数值计算
virtual double GetImplicit(double x, double y, double z);
virtual void CalImplicitField(int n[3], glm::vec3 minp, glm::vec3 d, float *hF);
virtual void CalImplicitFieldDevice(int n[3], glm::vec3 minp, glm::vec3 d, float *dF);
// 使用metaball隐式函数值
double CalColorField(double x, double y, double z);
float* getPointPosBuf(){return &posData[0];} //获取点位置缓存
float* getPolygonBuf();//获取三角面
float* getSufPosBuf(){return &m_vrts[0].x;}//获取表面点
void clearSuf(){m_nrms.clear();m_nrms.clear();m_face.clear();}
int getSufVrtBufNum(){return (int)m_vrts.size();}
int getPolyNum();
private:
......
rxMCMesh m_mcMesh;//mc类型的成员
float* m_field;//密度场
std::vector<float> m_polyBuf;//三角面缓存
//表面信息
vector<glm::vec3> m_vrts;//表面点坐标
vector<glm::vec3> m_nrms;//点法线
vector<rxFace> m_face;//表面三角面
float m_thre;// 隐函数阈值
}
我们用m_mcMesh来控制我们的表面生成,m_field保存密度场,m_thre则为隐函数阈值(阈值内为表面内点,阈值外为表面外点)。还用了m_vrts,m_nrms,m_face来保存表面信息。添加的函数中,主要用函数GetImplicit,CalImplicitField,CalImplicitFieldDevice去计算我们的网格密度值,获取到所有网格的密度值,便可以用mc类获取表面信息。
函数实现如下:
void FluidSystem::tick(){
m_gridContainer.insertParticles(&m_pointBuffer);//每帧刷新粒子位置
glm::vec3 tem=m_sphWallBox.min;
CalImplicitFieldDevice(m_rexSize, tem, glm::vec3(0.125/m_gridContainer.getDelta()), m_field);
clearSuf();//清空表面数据
m_mcMesh.CreateMeshV(m_field, tem, 0.125/m_gridContainer.getDelta(), m_rexSize, m_thre, m_vrts, m_nrms, m_face);
_computerPressure();
_computerForce();
_advance();
}
void FluidSystem::_init(unsigned short maxPointCounts, const fBox3 &wallBox, const fBox3 &initFluidBox, const glm::vec3 &gravity){
m_pointBuffer.reset(maxPointCounts);
m_sphWallBox=wallBox;
m_gravityDir=gravity;
m_pointDistance=pow(m_pointMass/m_restDensity, 1.0/3.0);//计算粒子间距
_addFluidVolume(initFluidBox, m_pointDistance/m_unitScale);
m_mcMesh=rxMCMesh();
m_gridContainer.init(wallBox, m_unitScale, m_smoothRadius*2.0, 1.0,m_rexSize);//设置网格尺寸(2r)
//初始化标量场
m_field=new float[(m_rexSize[0]+1)*(m_rexSize[1]+1)*(m_rexSize[2]+1)]();
posData=std::vector<float>(3*m_pointBuffer.size(),0);
}
//-----------------------------------------------------------------------------
// MARK:隐含的函数值
//-----------------------------------------------------------------------------
double FluidSystem::GetImplicit(double x, double y, double z)
{
return CalColorField(x, y, z);
}
/*!
*从粒子计算网格的隐含函数值
* @param [in] n网格数
* @param [in] minp网格的最小坐标
* @param [in] d网格宽度
* @param [out] hF隐含函数值(nx×ny×nz的数组)
*/
void FluidSystem::CalImplicitField(int n[3], glm::vec3 minp, glm::vec3 d, float *hF)
{
int slice0 = n[0]+1;
int slice1 = slice0*(n[1]+1);
for(int k = 0; k < n[2]; ++k){
for(int j = 0; j < n[1]; ++j){
for(int i = 0; i < n[0]; ++i){
int idx = k*slice1+j*slice0+i;
glm::vec3 pos = minp+glm::vec3(i, j, k)*d;
hF[idx] = GetImplicit(pos[0], pos[1], pos[2]);
}
}
}
}
/*!
*从粒子计算网格的隐含函数值
* @param [in] pnx,pny,pnz的网格数
* @param [in] minp网格的最小坐标
* @param [in] d网格宽度
* @param [out] hF隐含函数值(nx×ny×nz的数组)
*/
void FluidSystem::CalImplicitFieldDevice(int n[3], glm::vec3 minp, glm::vec3 d, float *dF)
{
float *hF = new float[(n[0]+1)*(n[1]+1)*(n[2]+1)]();
CalImplicitField(n, minp, d, hF);
CuCopyArrayToDevice(dF, hF, 0, (n[0]+1)*(n[1]+1)*(n[2]+1)*sizeof(float));
delete [] hF;
}
//计算颜色场(密度场)
double FluidSystem::CalColorField(double x, double y, double z)
{
// MRK:CalColorField
float c = 0.0;
glm::vec3 pos(x, y, z);
if(pos[0] < m_sphWallBox.min[0]) return c;
if(pos[0] > m_sphWallBox.max[0]) return c;
if(pos[1] < m_sphWallBox.min[1]) return c;
if(pos[1] > m_sphWallBox.max[1]) return c;
if(pos[2] < m_sphWallBox.min[2]) return c;
if(pos[2] > m_sphWallBox.max[2]) return c;
float h = m_smoothRadius;
int cell[8];
m_gridContainer.findCells(pos, h/m_unitScale, cell);
// 近傍粒子(各向同性)
for(int i=0;i<8;i++){
if(cell[i] < 0) continue;
int pndx=m_gridContainer.getGridData(cell[i]);
while(pndx!=-1){
Point* p=m_pointBuffer.get(pndx);
float r = glm::distance(pos,p->pos)*m_unitScale;
float q = h*h-r*r;
if(q>0){
c += m_pointMass*m_kernelPoly6*q*q*q;
}
pndx=p->next;
}
}
return c;
}
//获取三角片面缓存
float* FluidSystem::getPolygonBuf(){
m_polyBuf.clear();
for(int i=0;i<m_face.size();i++){
for(int j=0;j<m_face[i].vert_idx.size();j++) {
glm::vec3 posTem=m_vrts[m_face[i].vert_idx[j]];
m_polyBuf.push_back(posTem.x);
m_polyBuf.push_back(posTem.y);
m_polyBuf.push_back(posTem.z);
}
}
return &m_polyBuf[0];
}
//获取顶点个数
int FluidSystem::getPolyNum(){
int tem=0;
for(int i=0;i<m_face.size();i++){
for(int j=0;j<m_face[i].vert_idx.size();j++){
tem++;
}
}
return 3*tem;
}
//保存所有网格隐函数值
void FluidSystem::CuCopyArrayToDevice(float* device, const float* host, int offset, int size){
memcpy(device+offset, host, size);
}
}
可以看到我们的密度场由CalColorField函数计算,它依旧用了公式3.3:
获取所有网格的密度值。我们在_init函数里添加了对标量场的初始化。在tick函数里多了CalImplicitFieldDevice操作,用来先计算所有网格的密度值,然后调用clearSuf函数清除所有表面数据,然后根据m_mcMesh.CreateMeshV函数,用密度场重新计算新的表面数据,以生成表面。
当所有的计算完成时,我们的m_vrts,m_nrms以及m_face都有了新的数据,在opengl中将其传入VBO便可以绘制出有表面的粒子和网格效果如下:
加上天空盒和简单的Phong氏光照后,效果勉强还过得去:
完成了表面绘制后,其实这里的流体表面还是有一些问题,较为明显的则是表面不够光滑,如上图就可以看出流体表面有强烈的颗粒感,Yu, Jihun 等人的“Reconstructing surfaces of particle-based fluids using anisotropic kernels”这篇文章则非常好的解决了这个问题,他利用了给每个粒子构建了各项异性的缩放旋转矩阵,能够使流体达到一个非常光滑的形态,该算法下节会详细介绍。