最近使用 ParticleSystem 时发现了一个问题,追查下去,才发现,常常使用的 BillboardSet 其实也并不牢靠。
ParticleSystem 中的 accurate_facing 这个属性估计没多少人会打开的,默认值是Off。
这个属性适用于 BillboardParticleRenderer, BillboardParticleRenderer 是 Ogre 下 ParticleSystem 默认的 Render。ParticleSystem 产生的 Particle 通过这个 Render 最后生成顶点数据之流,用于描画。
BillboardParticleRenderer 的实现是基于 BillboardSet 的。
如果打开 accurate_facing 这个属性,计算 BillBoard 时,会使用 Particle 中心到 Camera 这个向量来作为 BillBoard 的方向。如果不打开的话,只是使用 Camera 的方向来计算,可以节约少许运算量。
但是很不幸的,BillboardParticleRenderer 在此处被 BillboardSet 的 Bug黑了,如下
//----------------------------------------------------------------------- void BillboardParticleRenderer::_updateRenderQueue(RenderQueue* queue, std::list<Particle*>& currentParticles, bool cullIndividually) { mBillboardSet->setCullIndividually(cullIndividually); // Update billboard set geometry mBillboardSet->beginBillboards(currentParticles.size()); Billboard bb; for (std::list<Particle*>::iterator i = currentParticles.begin(); i != currentParticles.end(); ++i) { Particle* p = *i; bb.mPosition = p->position; if (mBillboardSet->getBillboardType() == BBT_ORIENTED_SELF || mBillboardSet->getBillboardType() == BBT_PERPENDICULAR_SELF) { // Normalise direction vector bb.mDirection = p->direction; bb.mDirection.normalise(); } bb.mColour = p->colour; bb.mRotation = p->rotation; // Assign and compare at the same time if (bb.mOwnDimensions = p->mOwnDimensions) { bb.mWidth = p->mWidth; bb.mHeight = p->mHeight; } mBillboardSet->injectBillboard(bb); } mBillboardSet->endBillboards(); // Update the queue mBillboardSet->_updateRenderQueue(queue); }
可以看到, 如果 p->mOwnDimensions 为 false 的话,Billboard bb的 mWidth 和 mHeight 是不确定的,因为 Billboard 的构造函数中没有初始化过。
嗯,这个问题很容易看出来,所以难免有人奇怪,为什么 Ogre 这个问题一直没人修改呢?
我们进到 BillboardSet::injectBillboard 去看:
//----------------------------------------------------------------------- void BillboardSet::injectBillboard(const Billboard& bb) { // Don't accept injections beyond pool size if (mNumVisibleBillboards == mPoolSize) return; // Skip if not visible (NB always true if not bounds checking individual billboards) if (!billboardVisible(mCurrentCamera, bb)) return; if (!mPointRendering && (mBillboardType == BBT_ORIENTED_SELF || mBillboardType == BBT_PERPENDICULAR_SELF || (mAccurateFacing && mBillboardType != BBT_PERPENDICULAR_COMMON))) { // Have to generate axes & offsets per billboard genBillboardAxes(&mCamX, &mCamY, &bb); } // If they're all the same size or we're point rendering if( mAllDefaultSize || mPointRendering) { /* No per-billboard checking, just blast through. Saves us an if clause every billboard which may make a difference. */ if (!mPointRendering && (mBillboardType == BBT_ORIENTED_SELF || mBillboardType == BBT_PERPENDICULAR_SELF || (mAccurateFacing && mBillboardType != BBT_PERPENDICULAR_COMMON))) { genVertOffsets(mLeftOff, mRightOff, mTopOff, mBottomOff, mDefaultWidth, mDefaultHeight, mCamX, mCamY, mVOffset); } genVertices(mVOffset, bb); } else // not all default size and not point rendering { Vector3 vOwnOffset[4]; // If it has own dimensions, or self-oriented, gen offsets if (mBillboardType == BBT_ORIENTED_SELF || mBillboardType == BBT_PERPENDICULAR_SELF || bb.mOwnDimensions || (mAccurateFacing && mBillboardType != BBT_PERPENDICULAR_COMMON)) { // Generate using own dimensions genVertOffsets(mLeftOff, mRightOff, mTopOff, mBottomOff, bb.mWidth, bb.mHeight, mCamX, mCamY, vOwnOffset); // Create vertex data genVertices(vOwnOffset, bb); } else // Use default dimension, already computed before the loop, for faster creation { genVertices(mVOffset, bb); } } // Increment visibles mNumVisibleBillboards++; }
如果 mAccurateFacing 这个属性不是 true 的话,显然 mWidth,mHeight 没初始化的这个问题,是不会暴露出来的,因为它会走 genVertices(mVOffset, bb) 这个流程,这个流程里面,未初始化过的 mWidth,mHeight 被丢弃了,根本不用。
看到这里,说不定你也很开心的打开了这个属性,想验证一下,是不是会有问题。结果不太乐观啊,因为这个问题居然没出现!
莫不是楼主眼光不够,其实 Ogre 在别处巧妙地规避了这个问题了呢?
那么你再尝试在这个 ParticleSystem 中加入一个 Scale Affactor。
如果是使用 Script 文件的方式的话,那么就是这样
affector Scaler
{
rate 1
}
于是问题很奇妙的出现了~
这个问题的巧妙之处在于,如果你不加入一个会改变大小的 Affactor 的话,所有的 Billboard 片都是一样大小 , mAllDefaultSize 变量为 true ,产生数据流时,bb 的 mWidth,mHeight 仍然是被无视的。
而你一旦加入了一个这样的 Affactor,mAllDefaultSize 变量就不再为 true 了,mWidth,mHeight 终于可以翻身做主人啦@!
但仅仅这样,仍然不会出问题的,因为你查看 Scale Affactor, 会发现 mWidth,mHeight 这两个值已经被 Affactor 改为新值了, p->mOwnDimensions 也变成 true 了,根本不会出现 p->mOwnDimensions 为 false 的情况!
嗯哼,同学,你忘记了 ParticleSystem的更新流程!
它是按照这个顺序来更新的
// Update existing particles _expire(timeElapsed); _triggerAffectors(timeElapsed); _applyMotion(timeElapsed); // Emit new particles _triggerEmitters(timeElapsed);
也就是说,Affacter 在 _triggerAffectors 中先动作,然后才会调用 _triggerEmitters来产生新 Particle ,这些新产生的 Particle,很不幸的没有的到 Affacter 的关照,于是它的 mOwnDimensions 仍然是默认的 false。
于是,悲剧发生了。
这其实都源于 BillboardSet 在处理加入的 Billboard 时,产生的一个逻辑错误。
比较简单的修正方法是在 BillboardSet::injectBillboard 函数中,Vector3 vOwnOffset[4]; 这一行之后,修改成如下代码。
// If it has own dimensions, or self-oriented, gen offsets if( !bb.mOwnDimensions ) { genVertices(mVOffset, bb); } else if (mBillboardType == BBT_ORIENTED_SELF || mBillboardType == BBT_PERPENDICULAR_SELF || bb.mOwnDimensions || (mAccurateFacing && mBillboardType != BBT_PERPENDICULAR_COMMON)) {
嗯,逻辑上完美无暇,至于代码有点丑陋,呵呵,反正问题解决了不是,咱们就省点脑子吧。
BTW: Ogre 帮助文档上说 在 Particle Script中使用
accurate_facing on
的格式来打开 accurate_facing 的使用。但是其实需要用
accurate_facing true
才能打开,囧~
浙公网安备 33010602011771号