投影贴图
声明:本系列手札是对中级教程的摘要,本节已经是中级教程的第六节了,详细的原材料,请到OGRE3D开放资源地带下载


开始前请将这两幅(右边的也是一幅图啊)图放到media/materials/textures目录下。
照例的框架程序
#include "ExampleApplication.h"
// A FrameListener that gets passed our projector node and decal frustum so they can be animated
class ProjectiveDecalListener : public ExampleFrameListener
{
public:
ProjectiveDecalListener(RenderWindow* win, Camera* cam, SceneNode *proj, Frustum *decal)
: ExampleFrameListener(win, cam), mProjectorNode(proj), mDecalFrustum(decal), mAnim(0)
{
}
bool frameStarted(const FrameEvent& evt)
{
return ExampleFrameListener::frameStarted(evt);
}
protected:
SceneNode *mProjectorNode;
Frustum *mDecalFrustum;
float mAnim;
};
class ProjectiveDecalApplicati
on : public ExampleApplication
{
protected:
SceneNode *mProjectorNode;
Frustum *mDecalFrustum;
Frustum *mFilterFrustum;
void createScene()
{
// Set ambient light
mSceneMgr->setAmbientLight(ColourValue(0.2, 0.2, 0.2));
// Create a light
Light* l = mSceneMgr->createLight("MainLight");
l->setPosition(20,80,50);
// Position the camera
mCamera->setPosition(60, 200, 70);
mCamera->lookAt(0,0,0);
// Make 6 ogre heads (named head0, head1, etc.) arranged in a circle
Entity *ent;
for (int i = 0; i < 6; i++)
{
SceneNode* headNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
ent = mSceneMgr->createEntity("head" + StringConverter::toString(i), "ogrehead.mesh");
headNode->attachObject(ent);
Radian angle(i * Math::TWO_PI / 6);
headNode->setPosition(75 * Math::Cos(angle), 0, 75 * Math::Sin(angle));
}
}
// The function to create our decal projector
void createProjector()
{
}
// A function to take an existing material and make it receive the projected decal
void makeMaterialReceiveDecal
(const String &matName)
{
}
// Create new frame listener
void createFrameListener(void)
{
mFrameListener= new ProjectiveDecalListener(mWindow, mCamera, mProjectorNode, mDecalFrustum);
mRoot->addFrameListener(mFrameListener);
}
};
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT)
#else
int main(int argc, char **argv)
#endif
{
// Create application object
ProjectiveDecalApplicati
on app;
try {
app.go();
} catch(Exception& e) {
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
MessageBoxA(NULL, e.getFullDescription().c_str(),
"An exception has occurred!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
std::cerr << "An exception has occurred: " << e.getFullDescription();
#endif
}
return 0;
}
#ifdef __cplusplus
}
#endif
运行这个程序,你应该可以看见六个Ogre头颅。
下面开始添加代码:
一、投影贴图
平截头体(Frustums)
一个平截头体(Frustums)表示一个头部被截取的棱椎,代表了一个可视区域或是一个投影。Ogre使用它来表示一个camera(Camera类直接继承了Frustum类)。在这一课里,我们将使用一个平截头体来把贴花投影到场景中的网格(mesh)上。
创建一个投影器(projector),即创建代表它的平截头体,并绑定到场景节点上。
createProjector函数中添加代码:
mDecalFrustum = new Frustum();
mProjectorNode = mSceneMgr->getRootSceneNode()->createChildSceneNode
("DecalProjectorNode");
mProjectorNode->attachObject(mDecalFrustum);
mProjectorNode->setPosition(0,5,0);
这样一个投影器就创建好了,当你离得越远时,这个贴图会变得更大.
如果你想不论多远它总是保持恒定的大小和形状,请添加如下代码:
// 这里无需添加,这两句通过设置正射投影的视线范围、横纵比、近截取距离,决定了这个帖图
// 的大小和形状,使得无论投影器有多远,它总是保持恒定。
mDecalFrustum->setProjectionType(PT_ORTHOGRAPHIC); mDecalFrustum->setNearClipDistance(25);
请先搞清楚我们的平截头体将要把贴图投影到的地点。在这个程序中,有一圈Ogre头颅,而这个平截头体处于它们的正中心(虽然微微向上抬起了5个单位),并指向-Z方向(由于我们没有改变朝向,是一个默认值)。这就意味着,最终我们运行程序时,贴图将投向后面的Ogre人头。
二、 修改材质
为了让贴图最终显示在物体上,它使用的材质必须能够接收贴图。为此我们创建一个新的通路,在常规纹理上面渲染贴图。这个平截头体决定了这个投影贴图的位置、大小和形状。在这个Demo里面,我们将直接修改这个材质来接收贴图,但在实际的应用中,你应该创建这个材质的一个拷贝,再修改它,这样你就可以将材质切换回原来的。
makeMaterialReceiveDecal中添加代码:
//获得这个材质
MaterialPtr mat = (MaterialPtr)MaterialManager::getSingleton().getByName(matName);
Pass *pass = mat->getTechnique(0)->createPass();//为它创建一个新的通路
//设置混合和光照
新添加的纹理必须与当前的纹理正确地混合。为此我们将把场景混合(scene blending)设置成alpha透明
把深度偏移(depth bias)设置成1(也就是贴图不透明)。
最后我们把材质的光照关闭,这样不论场景使用何种光照,它总是显示的。如果你想在程序里让这个贴图受光照的影响,你就不必加上最后的函数调用:
pass->setSceneBlending(SBT_TRANSPARENT_ALPHA);
pass->setDepthBias(1);
pass->setLightingEnabled(false);
//用我们的decal.png图像来创建一个新的纹理单元状态
TextureUnitState *texState = pass->createTextureUnitState("decal.png");
texState->setProjectiveTexturing(true, mDecalFrustum);//打开了投影纹理
texState->setTextureAddressingMode(TextureUnitState::TAM_CLAMP);//设置过滤和寻址模式
//把纹理的寻址模式设置成clamp,这样贴图就不会在物体上不停地循环了
texState->setTextureFiltering(FO_POINT, FO_LINEAR, FO_NONE);
//当对象放大时,使用标准线性滤波,但对于缩小的情况,我们只是关闭过滤
//并且完全关闭mip贴图。这样避免了当缩小时,贴图的边缘不能很好地融入纹理的其它部分。
三、调用函数
我们已经建立好了函数,为了设置投影器和材质还必须调用它们
createScene最后面添加代码:
createProjector();
for (unsigned int i = 0; i < ent->getNumSubEntities(); i++)
makeMaterialReceiveDecal
(ent->getSubEntity(i)->getMaterialName());
注意,在前面的循环里,ent变量已经保存有了Ogre头颅。由于所有的Ogre头颅使用相同的材质,我们只需要随机选择他们中的一个来获取材质名称。
四、消除反向投影
为了过滤掉反向投影,我们需要一个新的平截头体,让它指向我们想要过滤的方向。
在createProjector方法中添加以下代码:
mFilterFrustum = new Frustum();
mFilterFrustum->setProjectionType(PT_ORTHOGRAPHIC);
SceneNode *filterNode = mProjectorNode->createChildSceneNode("DecalFilterNode");
filterNode->attachObject(mFilterFrustum);
filterNode->setOrientation(Quaternion(Degree(90),Vector3::UNIT_Y));
唯一的区别就是我们把这个节点旋转了90度以朝向背面
修改材质
下面我们添加另一个纹理状态到材质的通路上。
makeMaterialReceiveDecal中添加代码:
texState = pass->createTextureUnitState("decal_filter.png");
texState->setProjectiveTexturing(true, mFilterFrustum);
texState->setTextureAddressingMode(TextureUnitState::TAM_CLAMP);
texState->setTextureFiltering(TFO_NONE);
注意,我们正在使用过滤纹理,过滤平截头体,并关闭了过滤。编译并运行程序。你应该可以看到只有朝向前面的投影贴图。
简单的旋转
旋转这个投影并更新它的视线范围(Field of View)。
为了旋转这个投影器,只需要把下面几行代码添加到frameStarted方法里:
mProjectorNode->rotate(Vector3::UNIT_Y, Degree(evt.timeSinceLastFrame * 10));
编译并运行程序。你将会看到贴图沿着一个圈被投影到Ogre人头。
修改视线范围
接下来,我们将修改这个投影器的视线范围(field of view)。由于我们不使用正射投影器,我们可以修改视力范围来增加或缩小投影的大小。作为演示,我们将把FOVy(field of view Y)设置成15到25度的夹角。下面的代码将增加或缩小贴图的大小(添加到frameStarted方法):
mAnim += evt.timeSinceLastFrame / 2;
if (mAnim >= 1)
mAnim -= 1;
mDecalFrustum->setFOVy(Degree(15 + Math::Sin(mAnim * Math::TWO_PI) * 10));
编译并运行程序。
最后要注意的
最后要注意到是,如果你在程序里使用贴图,必须保证贴图的边缘像素都是完全透明的(alpha为0)。否则,出于纹理clamping的工作机制,贴图会拖泥带水。
这些东西什么时候会用到呢》?程序添加的代码十分简单,就不提供终级代码了,不过贴一张运行的贴图吧

这张是由于没有将那两张图片放到texture下的缘故

E,有点过于艺术了!