FLEX使用教程

Nvidia 位置动力学模拟工具

Posted by infinityyf on February 29, 2020

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函数设置,可以设置为一下几个:

  1. eNvFlexPhaseSelfCollide:粒子将会与同一组的粒子发生碰撞
  2. eNvFlexPhaseSelfCollideFilter:忽略小于一定半径之内的碰撞
  3. eNvFlexPhaseFluid:生成流体密度约束

Active Set

每一个解算器solver有一个支持粒子的数量上限,可以选择部分粒子处于激活状态,而处于非激活状态的粒子只会消耗很小的计算量。 使用NvFlexSetActive(solver, activeBuffer, 10)设置激活组。

运行原理

  1. 如何解算
    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::radiusNvFlexParams::fluidRestDistance,保持二者之比为2:1,一下为流体的四个重要属性

  1. Cohesion凝聚力:内部相邻分子之间的相互吸引力
  2. Surface Tension表面张力 :由内聚力而产生
  3. Vorticity Confinement向粒子增加旋转力
  4. Adhesion粘着力
  5. Viscosity阻尼
  6. Solid Pressure增加粒子收到的固体表面的力
  7. Surface Drag
  8. Buoyancy浮力
  9. Anisotropy Scale
  10. Smoothing 流体并不需要额外设置参数(在早些版本中可以在NvFlexParams中存在标记fluid的变量,之后的版本中没有了)

如何设置数据

  1. 设置粒子的positions,velocities,phases,activeIndices(对应position中粒子的索引,这里所有的buffer相同的索引对应的都是同一个粒子的属性)
  2. 获取粒子数目,设置可支持的最大粒子数
  3. 设置solverDesc

string

在设置完数据之后设置NvFlexSetSprings

如何设置数据

  1. 设置string的索引(两两一对),设置长度和硬度(长度是索引数的0.5倍
  2. 注意设置索引的基础上是position数组已经被设置了

    cloth

    可以使用string网格完成约束,但是在EXT库中提供了NvFlexExtCreateClothFromMesh进行设置。使用NvFlexSetDynamicTriangles可以增加更多的cloth属性

  3. lift/drag
  4. wind
  5. 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.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搞混)。

  1. numShape: rigid的个数
  2. numShapeIndices: rigid代表的粒子索引数组的长度
  3. shapeIndices:存储粒子索引数组的首地址
  4. shapeOffsets:这一个shape占据的偏移量(之后会累加在总粒子数组上,得到这个rigid的粒子数组边界)
  5. shapeCoefficients: 物理参数
  6. shapeCenters: 中心
    以上这些参数,都需要在手动添加到position,rigidOffset,rigidTranslation,等参数中,再调用flexSetRigidBody.

注意

  1. 一定要区分rigid body和shape 的关系
  2. 向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())); } ```

  1. rigidbody约束中,rigidOffsets的数量是rigidbody的数量加一,并且第一个必须是0,每一个表示rigidbody中包含的粒子数。indice数组则记录了所有构成rigidbody的粒子在position中的索引(全局索引)。相当于,offset指定了这个rigidbody的粒子在indice中的区间,再从这个区间中得到在position中的索引,二级索引