Flex library
所有粒子和约束以flat-array的形式一次同时传入解算器,使用SOA数据布局。
SOA是什么?
struct {
uint8_t r, g, b;
} AoS[N];
struct {
uint8_t r[N];
uint8_t g[N];
uint8_t b[N];
} SoA;
Code Demo
#include <NvFlex.h>
NvFlexLibrary* library = NvFlexInit();
NvFlexSolver* solver = NvFlexCreateSolver(library);
// param2:element count
// param3:stride
NvFlexBuffer* particleBuffer = NvlexAllocBuffer(library, n, sizeof(Vec4), eNvFlexBufferHost);
while(true){
// map buffers for reading / writing
// get the address whitch can be accessed by CPU
// this call will be blocked until other transfers
float4* particles = (float4*)NvlexMap(particles, eNvFlexMapWait);
// spawn (user method)
SpawnParticles(particles, velocities, phases);
// render (user method)
RenderParticles(particles, velocities, phases);
// unmap buffers,after CPU has finished writing buffers
NvFlexUnmap(particleBuffer);
// write to device (async)
NvFlexSetParticles(particleBuffer, n);
// tick update the solver
NvFlexUpdateSolver(solver, dt, 1, false);
// read back (async) retreve data from FLEX
NvFlexGetParticles(particleBuffer, n);
}
NvFlexFreeBuffer(particleBuffer);
NvFlexDestroySolver(solver);
NvFlexShutdown(library);
Particles
通过位置、速度、质量以及所处阶段来对粒子进行描述。质量存储为质量的倒数,并和位置存储在一起[x, y, z, 1/m]
。
spawn particles
for (int i=0; i < n; ++i)
{
particles[i] = RandomSpawnPosition();
velocities[i] = RandomSpawnVelocity();
phases[i] = NvFlexMakePhase(0, eNvFlexPhaseSelfCollide);
}
对数据进行初始化需要按照下图所示的过程:
Radius
使用NvFlexParams::rasius
进行设置。
Phase
一个用32位掩码进行表示的量,由组ID和各种粒子标记组成:通过NvFlexMakePhase
设置
eNvFlexPhaseGroupMask 低24位
不同组的粒子总是会进行碰撞解算,而且默认情况下同一组粒子不会进行碰撞。
eFlexPhaseSelfCollide
使同一组的粒子可以进行碰撞 self collision
eNvFlexPhaseFluid
使粒子产生流体物理属性例如:fluid density constraints, cohesion, surface tension effects
粒子的碰撞状态通过NvFlexMakePhase
函数设置,可以设置为一下几个:
- eNvFlexPhaseSelfCollide:粒子将会与同一组的粒子发生碰撞
- eNvFlexPhaseSelfCollideFilter:忽略小于一定半径之内的碰撞
- eNvFlexPhaseFluid:生成流体密度约束
Active Set
每一个解算器solver有一个支持粒子的数量上限,可以选择部分粒子处于激活状态,而处于非激活状态的粒子只会消耗很小的计算量。
使用NvFlexSetActive(solver, activeBuffer, 10)
设置激活组。
运行原理
- 如何解算
flex中的解算并不是将每个约束单独计算,而是全部设置在NvFlexParams
中,再与solver绑定,GPU根据params和分别设置buffer进行解算,例如://CPU端写入数据 mapbuffer(); gbuffer.position => set in CPU unmapbuffer(); //设置进flex NvFlexSetParticles(g_solver, g_buffers->positions.buffer, NULL); NvFlexSetVelocities(g_solver, g_buffers->velocities.buffer, NULL); NvFlexSetPhases(g_solver, g_buffers->phases.buffer, NULL); NvFlexSetActive(g_solver, g_buffers->activeIndices.buffer, NULL); NvFlexSetActiveCount(g_solver, g_buffers->activeIndices.size()); //设置状态 NvFlexSetParams(g_solver, &g_params); //进行FLex模拟 NvFlexUpdateSolver(g_solver, g_dt, g_numSubsteps, g_profile; //取回数据 NvFlexGetParticles(g_solver, g_buffers->positions.buffer, NULL); NvFlexGetVelocities(g_solver, g_buffers->velocities.buffer, NULL);
Constraints
首先需要记住的一点是,FLEX中除了静态碰撞,其余都是粒子,所以position,velocity这些属性是,所有粒子都会记录的,但是还会附加其他的一些buffer来记录部分粒子的特殊性质,这些buffer的标记方式,通过记录粒子的indice来标记。例如之后的rigidbody。
Fluid
设置NvFlexParams::radius
和NvFlexParams::fluidRestDistance
,保持二者之比为2:1,一下为流体的四个重要属性
- Cohesion凝聚力:内部相邻分子之间的相互吸引力
- Surface Tension表面张力 :由内聚力而产生
- Vorticity Confinement向粒子增加旋转力
- Adhesion粘着力
- Viscosity阻尼
- Solid Pressure增加粒子收到的固体表面的力
- Surface Drag
- Buoyancy浮力
- Anisotropy Scale
- Smoothing
流体并不需要额外设置参数(在早些版本中可以在
NvFlexParams
中存在标记fluid的变量,之后的版本中没有了)
如何设置数据
- 设置粒子的positions,velocities,phases,activeIndices(对应position中粒子的索引,这里所有的buffer相同的索引对应的都是同一个粒子的属性)
- 获取粒子数目,设置可支持的最大粒子数
- 设置solverDesc
string
在设置完数据之后设置NvFlexSetSprings
如何设置数据
- 设置string的索引(两两一对),设置长度和硬度(长度是索引数的0.5倍)
- 注意设置索引的基础上是position数组已经被设置了
cloth
可以使用string网格完成约束,但是在EXT库中提供了
NvFlexExtCreateClothFromMesh
进行设置。使用NvFlexSetDynamicTriangles
可以增加更多的cloth属性 - lift/drag
- wind
- smooth normal: 可以通过
NvFlexGetNormals
获得
rigid body
也是通过粒子进行粒子的模拟。可以通过三角网格获取粒子集合(参考函数NvFlexExtCreateRigidFromMesh
)。通过NvFlexSetRigids
添加rigid粒子约束。
当模拟时间步长过大时,会产生interlocked现象。 PBD则使用SDF来解决这个问题
NvFlexSetRigids(g_solver,
g_buffers->rigidOffsets.buffer, //记录各个rigid在rigidIndices中的末尾边界
g_buffers->rigidIndices.buffer, //position中对应粒子的索引
g_buffers->rigidLocalPositions.buffer,
g_buffers->rigidLocalNormals.buffer,
g_buffers->rigidCoefficients.buffer,
g_buffers->rigidPlasticThresholds.buffer, //设置plastic 属性
g_buffers->rigidPlasticCreeps.buffer,
g_buffers->rigidRotations.buffer,
g_buffers->rigidTranslations.buffer,
g_buffers->rigidOffsets.size() - 1, //因为只要存在就会预先push进一个0,所以实际长度要-1
g_buffers->rigidIndices.size());
- 与其他的效果结合
1.1 在cloth上增加rigid约束
1.2 在fluid上增加rigid约束
在较早版本的flex中,支持从shape创建粒子CreateParticleShape
,接受mesh,体素化后转化为粒子。新的版本中也有这样的函数。参数中可以设置是否使其具有rigid的性质,可以设置rigid的rigidStiffness,理解为恢复为原本shape的速度(弹性程度)
collision
Flex支持静态碰撞几何(球,胶囊,平面,凸体,三角网格,有向距离场)
union NvFlexCollisionGeometry
{
NvFlexSphereGeometry sphere;
NvFlexCapsuleGeometry capsule;
NvFlexBoxGeometry box;
NvFlexConvexMeshGeometry convexMesh;
NvFlexTriangleMeshGeometry triMesh;
NvFlexSDFGeometry sdf;
};
通过NvFlexSetShapes
生成静态碰撞体。
虽然是静态碰撞体,但是在更新的时候还是可以通过访问
g_buffers->shapeGeometry.buffer,
g_buffers->shapePositions.buffer,
g_buffers->shapeRotations.buffer,
g_buffers->shapePrevPositions.buffer,
g_buffers->shapePrevRotations.buffer,
g_buffers->shapeFlags.buffer,
这些字段来进行位置的修改,修改完成后,一定要再次使用NvFlexSetShapes
上传到flex
code demo
//定义四个缓冲
NvFlexCollisionGeometry* geometry = (NvFlexCollisionGeometry*)NvFlexMap(geometryBuffer, 0);
float4* positions = (float4*)NvFlexMap(positionsBuffer, 0);
quat* rotations = (quat*)NvFlexMap(rotationsBuffer, 0);
int* flags = (int*)NvFlexMap(flagsBuffer, 0);
// 第一个缓冲中定义一个球儿
flags[0] = NvFlexMakeShapeFlags(eNvFlexShapeSphere, false);
geometry[0].sphere.radius = radius;
positions[0] = float3(0.0f);
rotations[0] = quat(0.0f);
// 创建一个mesh
NvFlexTriangleMeshId mesh = NvFlexCreateTriangleMesh();
//绑定数据到id
NvFlexUpdateTriangleMesh(lib, mesh, vertices, indices, numVertices, numTriangles, NULL, NULL);
// 在第二个缓冲中创建一个三角网格
flags[1] = NvFlexMakeShapeFlags(eNvFlexShapeTriangleMesh, false);
geometry[1].triMesh.mesh = mesh;
geometry[1].triMesh.scale[0] = 1.0f;
geometry[1].triMesh.scale[1] = 1.0f;
geometry[1].triMesh.scale[2] = 1.0f;
positions[1] = float3(0.0f, 100.0f, 0.0f);
rotations[1] = quat(0.0f);
// unmap buffers
NvFlexUnmap(geometryBuffer, 0);
NvFlexUnmap(positionsBuffer, 0);
NvFlexUnmap(rotationsBuffer, 0);
NvFlexUnmap(flagsBuffer, 0);
// send shapes to Flex
NvFlexSetShapes(solver, geometry, positions, rotation, NULL, NULL, flags, 2);
SDF 有向距离场
通过NvFlexCreateSDF
生成。
NvFlexExtAsset
记录了一批粒子和约束信息
struct NvFlexExtAsset
{
// particles
float* particles; //!< Local space particle positions, x,y,z,1/mass
int numParticles; //!< Number of particles
int maxParticles; //!< Maximum number of particles, allows extra space for tearable assets which duplicate particles
// springs
int* springIndices; //!< Spring indices
float* springCoefficients; //!< Spring coefficients
float* springRestLengths; //!< Spring rest-lengths
int numSprings; //!< Number of springs
// shapes
int* shapeIndices; //!< The indices of the shape matching constraints
int numShapeIndices; //!< Total number of indices for shape constraints
int* shapeOffsets; //!< Each entry stores the end of the shape's indices in the indices array (exclusive prefix sum of shape lengths)
float* shapeCoefficients; //!< The stiffness coefficient for each shape
float* shapeCenters; //!< The position of the center of mass of each shape, an array of vec3s mNumShapes in length
int numShapes; //!< The number of shape matching constraints
// plastic deformation
float* shapePlasticThresholds; //!< The plastic threshold coefficient for each shape
float* shapePlasticCreeps; //!< The plastic creep coefficient for each shape
// faces for cloth
int* triangleIndices; //!< Indexed triangle mesh indices for clothing
int numTriangles; //!< Number of triangles
// inflatable params
bool inflatable; //!< Whether an inflatable constraint should be added
float inflatableVolume; //!< The rest volume for the inflatable constraint
float inflatablePressure; //!< How much over the rest volume the inflatable should attempt to maintain
float inflatableStiffness; //!< How stiff the inflatable is
};
以rigidbody为例:
调用NvFlexExtCreateRigidFromMesh
后,shapes 就代表了粒子集合信息(不要和静态碰撞体的shape搞混)。
- numShape: rigid的个数
- numShapeIndices: rigid代表的粒子索引数组的长度
- shapeIndices:存储粒子索引数组的首地址
- shapeOffsets:这一个shape占据的偏移量(之后会累加在总粒子数组上,得到这个rigid的粒子数组边界)
- shapeCoefficients: 物理参数
- shapeCenters: 中心
以上这些参数,都需要在手动添加到position,rigidOffset,rigidTranslation,等参数中,再调用flexSetRigidBody.
注意
- 一定要区分rigid body和shape 的关系
- 向FLEX中设置参数的例子
```c NvFlexSetParams(g_flex, &g_params); NvFlexSetParticles(g_flex, g_buffers->positions.buffer, numParticles); NvFlexSetVelocities(g_flex, g_buffers->velocities.buffer, numParticles); NvFlexSetNormals(g_flex, g_buffers->normals.buffer, numParticles); NvFlexSetPhases(g_flex, g_buffers->phases.buffer, g_buffers->phases.size()); NvFlexSetRestParticles(g_flex, g_buffers->restPositions.buffer, g_buffers->restPositions.size());
NvFlexSetActive(g_flex, g_buffers->activeIndices.buffer, numParticles);
// springs if (g_buffers->springIndices.size()) { assert((g_buffers->springIndices.size() & 1) == 0); assert((g_buffers->springIndices.size() / 2) == g_buffers->springLengths.size());
NvFlexSetSprings(g_flex, g_buffers->springIndices.buffer, g_buffers->springLengths.buffer, g_buffers->springStiffness.buffer, g_buffers->springLengths.size()); }
// rigids if (g_buffers->rigidOffsets.size()) { NvFlexSetRigids(g_flex, g_buffers->rigidOffsets.buffer, g_buffers->rigidIndices.buffer, g_buffers->rigidLocalPositions.buffer, g_buffers->rigidLocalNormals.buffer, g_buffers->rigidCoefficients.buffer, g_buffers->rigidRotations.buffer, g_buffers->rigidTranslations.buffer, g_buffers->rigidOffsets.size() - 1, g_buffers->rigidIndices.size()); }
// inflatables if (g_buffers->inflatableTriOffsets.size()) { NvFlexSetInflatables(g_flex, g_buffers->inflatableTriOffsets.buffer, g_buffers->inflatableTriCounts.buffer, g_buffers->inflatableVolumes.buffer, g_buffers->inflatablePressures.buffer, g_buffers->inflatableCoefficients.buffer, g_buffers->inflatableTriOffsets.size()); }
// dynamic triangles if (g_buffers->triangles.size()) { NvFlexSetDynamicTriangles(g_flex, g_buffers->triangles.buffer, g_buffers->triangleNormals.buffer, g_buffers->triangles.size() / 3); }
// collision shapes if (g_buffers->shapeFlags.size()) { NvFlexSetShapes( g_flex, g_buffers->shapeGeometry.buffer, g_buffers->shapePositions.buffer, g_buffers->shapeRotations.buffer, g_buffers->shapePrevPositions.buffer, g_buffers->shapePrevRotations.buffer, g_buffers->shapeFlags.buffer, int(g_buffers->shapeFlags.size())); } ```
- rigidbody约束中,rigidOffsets的数量是rigidbody的数量加一,并且第一个必须是0,每一个表示rigidbody中包含的粒子数。indice数组则记录了所有构成rigidbody的粒子在position中的索引(全局索引)。相当于,offset指定了这个rigidbody的粒子在indice中的区间,再从这个区间中得到在position中的索引,二级索引