中级教程六
投影贴图 目录
[隐藏]
1 介绍
2 准备开始
3 起始代码
4 投影贴图
4.1 平截头体(Frustums)
4.2 修改材质
4.3 调用函数
5 消除反向投影
5.1 介绍
5.2 修改投影器
5.3 修改材质
6 炫耀一下投影
6.1 简单的旋转
6.2 修改视线范围
7 最后要注意的
[编辑] 介绍
在这一课里,我们将介绍如何为场景中的一个物体添加投影贴图。投影纹理是非常有用的,比如你想要一个类似地面上的选择指示器,或者你正在瞄准的瞄准镜,或者投射在某个物体上的其它贴图。下面的截图是一个大家都喜爱的ogre头像,它被投影了一个瞄准器:
[[1]]
你能在这里找到本课的代码。当你学习本课时,你应该逐个地往你的工程里添加代码,编译并观察结果。
[编辑] 准备开始
开始之前,我们需要两张新图。右击这两个链接,并保存到Ogre可以找到的地方:[decal.png] [decal_filter.png]
最好放在media/materials/textures目录下(对于大多数人,是在OgreSDK目录里)。
[编辑] 起始代码
用你喜爱的IDE创建一个cpp文件,并添加如下代码:
#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 ProjectiveDecalApplication : 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
ProjectiveDecalApplication 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);
texState->setTextureFiltering(FO_POINT, FO_LINEAR, FO_NONE);
我们已经把纹理的寻址模式设置成clamp,这样贴图就不会在物体上不停地循环了。对于过滤选项,当对象放大时,使用标准线性滤波,但对于缩小的情况,我们只是关闭过滤,并且完全关闭mip贴图。这样避免了当缩小时,贴图的边缘不能很好地融入纹理的其它部分。如果我们不这样做的话,贴图投影区域的边缘会非常的难看。
对于设置材质,以上是所有你要做的。
[编辑] 调用函数
我们已经建立好了函数,为了设置投影器和材质还必须调用它们。在createScene方法的最后面,加上以下代码:
createProjector();
for (unsigned int i = 0; i < ent->getNumSubEntities(); i++)
makeMaterialReceiveDecal(ent->getSubEntity(i)->getMaterialName());
注意,在前面的循环里,ent变量已经保存有了Ogre头颅。由于所有的Ogre头颅使用相同的材质,我们只需要随机选择他们中的一个来获取材质名称。
[编辑] 消除反向投影
[编辑] 介绍
也许你已经注意到了,当运行程序里,存在两个投影贴图。一个是投射在-Z方向,也就是我们的平截头体的朝向,另一个投射在+Z方向,在我们创建的平截头体的后面的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的工作机制,贴图会拖泥带水。