我要发帖 回复

中级会员

5

主题

128

积分

0

专家分

初学者

:

私信
发表时间 : 2007-10-4 09:01:55 | 浏览 : 6923    评论 : 6
INTRODUCTION


Written by Sphinkie (Sphinkie's website).
这个摘录列表是Oogst写的“HOWTO tutorial”的后续。



目录







2.4 ST_EXTERIOR_FAR (aka "Nature")





2.8 iPLSM

















§
15.2.1 例子







如何设置光的范围/衰减


光的衰减遵循下面的公式: A = p0 + p1xR + p2xR²,这里R是到光源的距离,p0,p1p2,三个参数允许设置光会如何衰减。光的亮(发光)度为:L=1/Attenuation

示例 1:
如果你想让一个光在1000单位远的地方失去50%的亮度,等式将会是<math>\frac{1}{A}</math>

参数分别如下:


p0=1


p1=<math>\frac{1}{1000}</math>


p2=0



mLight->setAttenuation(1000, 1, 0.001, 0) ;


Example 2:
2
次衰减会更迅速。比如,你想让一个光在1000单位远的地方失去66%的亮度,这些参数为:


p0=1


p1=1/1000


p2=1/(1000x1000)



mLight->setAttenuation(1000, 1, 0.001, 0.000001) ;


如何选择场景管理器


你可以在这里找到关于这个主题的更多详细信息:SceneManagersFAQ.

chooseScenemanager()函数中,用你想为你的应用程序使用的场景管理器类型来替换ST_GENERIC


Virtual void chooseSceneManager(void)


{


mSceneMgr = mRoot->createSceneManager(ST_GENERIC);


}


你可以在下面的场景类型中选择:

ST_GENERIC

这个场景是基于有objects,但没地面(ground)3D世界。世界基于octree(不渲染远处的objects)渲染。这个场景适合室外的空间等。

DotSceneManager插件也适用于这个场景类型。因此,你不能同时使用OctreeSceneManagerDotSceneManager两种类型的场景。

ST_INTERIOR

地面是基于Quake III BSP文件的。

ST_EXTERIOR_CLOSE ( aka "Terrain")

这个场景基于有3D objects和一个基本地形(terrain)作为地面的世界。这个场景有简单的LOD

l
地面基于1个描述文件和3张图象:

n
一张图象作为高度贴图(height-map)(尺寸: 2n+1 x 2n+1)

n
一张图象作为地形纹理(尺寸: 2n x 2n)

n
一张图象作为细节地形纹理(当接近相机时)

这个场景适合小贴图(257x257513x513)。用setWorldGeometry(“terrainfile.cfg”)函数来加载描述文件。参考TerrainSceneManager。参考demo_terrain.exe

ST_EXTERIOR_FAR (aka "Nature")

在各个模式在Ogre 1.0已经不存在了。用“Terrain”,或者“Paging Landscape”代替。

ST_EXTERIOR_REAL_FAR (plug-in)

这个插件包括三种场景管理器:PLSM1PLSM2iPLSM。它由使用3种不用插件库和3种不同配置文件的3种场景管理器组成。更多信息请查看Ogre论坛。

ST_EXTERIOR_REAL FAR (aka "PLSM1" or "Paging Landscape Scene Manager 1")

这个场景基于有3D objects和基本分页地形(paged terrain)作为地面的世界。这个场景使用一个合适的LOD。地面被分成pages。每个page基于images (from multiple sources heighfield, heighfieldTC and spline)textures。这个场景适合非常大的地形。注意:你须安装paging landscape插件并编译它,并须调整plugin.cfg文件。参看demo_pagingLandscape.exe

ST_EXTERIOR_REAL FAR (aka "PLSM2" or "Paging Landscape Scene Manager 2")

PLSM1,但是textures可被用于height,并且添加了splatting (阴影(shader)
多重纹理(multi-texture),多通道(multi-pass))lighting,实时渲染(real-time texturing),变形(deformation)以及geomipmaping LOD。此场景适合非常广阔的地形:参见Paging_Scene_Manager

iPLSM

工作正在进行中。

DISPLACEMENT MAPPING (plug-in)


一个基于高度贴图(height-map)的场景并需要定点着色(vertex shader)

如何获得调试信息


对于实时(real time)调试信息,你可以用下面的代码在主窗口用一行来显示:


mWindow->setDebugText("debug information");




int value;


mWindow->setDebugText("Value = " + StringConverter::toString(value));


对于不同时(differed time)的调试信息,你可以把事件记录到默认的日记文件中(ogre.log)


LogManager::getSingleton().logMessage(LML_NORMAL, "text 1",
"text 2");



如果你想使用一个特殊的日记文件(取代默认的ogre.log)


Log* mylogfile, ogrelogfile;




ogrelogfile = LogManager::getSingleton().getDefaultLog();


mylogfile
= LogManager::getSingleton().createLog("mylogfile.log");




LogManager::getSingleton().setDefaultLog(mylogfile);


LogManager::getSingleton().logMessage(LML_NORMAL, "write in mylogfile.log");




LogManager::getSingleton().setDefaultLog(ogrelogfile);


LogManager::getSingleton().logMessage(LML_NORMAL, "write in ogre.log");


如何保证相机在地面之上


你可以使用不同的方法,取决于“floor”的类型。地面(floor)可以是一个地形(terrain),一个mesh对象,一个planeEntity,等等……

首先,从你的相机向地面发射一条射线(Ray)


Vector3 pos = mCamera->getPosition();




float
floorD = 0;




RaySceneQuery* mRayQuery;


SceneManager*
mSceneMgr;




mSceneMgr = mCamera->getSceneManager();


mRayQuery = mSceneMgr->createRayQuery(Ray(pos, Vector3::NEGATIVE_UNIT_Y));




RaySceneQueryResult& queryResult = mRayQuery->execute();


得到与射线相交的对象的列表。取列表的第一条和最后一条的索引。


RaySceneQueryResult::iterator i;


RaySceneQueryResult::iterator i_final;




i_final = queryResult.end();


i = queryResult.begin();


如果地面是一个terrain,你应在列表中搜寻world fragment


if ( i!=queryResult.end() && i->worldFragment )


{


SceneQuery::WorldFragment* wf = i->worldFragment;


mWindow->setDebugText("Detected: " + StringConverter::toString(i->worldFragment->


singleIntersection.y));


}


如果地面是一个object,你应搜索找到的第一个object,或一个指定的object。注意如果objects是复合的(complex)或重新调节的(rescaled),这个方法有点不准确;并且如果objects是凸的(convex)或凹的(hollow)这个方法不起作用。此例中,我们寻找一个名字为《floor》的plane entity



for ( i=queryResult.begin(); i!=i_final; ++i)


{


MovableObject* wf = i->movable;


Real distance

= i->distance;


String detected ;


detected
= "movable found " + wf->getName();


detected += " at "+ StringConverter::toString(distance);




if (wf->getName() == "floor")


{


mWindow->setDebugText(detected);


floorD=distance;


}


}


最后,销毁查询。


mCamera->getSceneManager()->destroyQuery(mRayQuery);


并调整相机的Y位置(这是一个非常低级的例子)


// keep camera not too close of the ground


if (floorD < MIN_Y_ALLOWED)


{


pos.y = floorY+ MIN_Y_ALLOWED;


mCamera->setPosition(pos);


}


// make the camera falling if too far of the ground


if (floorD > MIN_Y_ALLOWED)


{


pos.y = pos.y - 4;


mCamera->setPosition(pos);


}


如何使得观察者面向目标


(代码还未检验)


Vector3 lookAt = target->getPosition() - viewer->getPosition();


Vector3 axes[3];




viewer->getOrientation().ToAxes(axes);


Quaternion rotQuat;




if (lookAt == axes[2])


{


rotQuat.FromAngleAxis(Math::PI, axes[1]);


}


else


{


rotQuat = axes[2].getRotationTo(lookAt);


}


viewer->setOrientation(rotQuat* viewer->getOrientation());


如何进行截屏


这里有两个Ogre函数来进行截屏(screenshot)writeContentsToFile()writeContentsToTimestampedFile(),你只需要调用它就行了。它们有两种方法来命名截图文件。

如果你想让文件名为screenshot_1.pngscreenshot_2.png,你可以用:


unsigned int indice = 1;


char filename[30] ;


sprintf (filename, “screenshot_%d.png”, ++indice);


mWindow->writeContentsToFile(filename);


或者,如果你想在当前程序目录下获得有时间标记的(time-stamped)文件(名字为screenshot_MMDDYYY_HHMMSSmmm.jpg),你可以仅使用:


mWindow->writeContentsToTimestampedFile(“screenshot”,”.jpg”);


注意:MM是从011YYY(year-1900) (比如:2004=104)
注意:
你指定的扩展名(.png”或“.bmp”或“.jpg”,等等)能决定生成的文件的类型。

如何获得雾



mSceneMgr->setFog (FOG_LINEAR, ColourValue(0.25,1.1,0.2), 0.5, 200, 500);


参数为:


雾的类型: FOG_LINEAR, FOG_EXP, FOG_EXP2, FOG_NONE


颜色


扩展度(Exp_density) 0..1 (数值越小意味着没有雾的区域越大)


初始密度(Initial density) (for linear fog)


最终密度(Final density) (for linear fog)

注意透明物体在雾中会变得不透明。你应该在material文件中的pass{}片段中用指令fog_override把它们从雾中排除。

如何在wire-frame中渲染世界


为能在solidwire-frame模式下渲染场景,使用下面的命令:


mCamera->setDetailLevel (SDL_WIREFRAME);


mCamera->setDetailLevel (SDL_SOLID);


你只能对整个场景做这些,而不能对某个单独的物体这样做。

注意上面的方法在Ogre 1.2中好像不能运行

为在Ogre 1.2及其以上版本中在solidwire-frame模式下渲染场景:


mCamera->setPolygonMode(Ogre::PolygonMode::PM_WIREFRAME);
/* wireframe */


mCamera->setPolygonMode(Ogre::PolygonMode::PM_SOLID);
/* solid */


如何在配置文件中获得并设置参数


你可以在配置文件中读取配置参数。配置文件是这样子(可以用“;”、“=”或“TAB”作为分隔符)


language=french


使用它的代码为(比如:读取语言参数)


ConfigFile setupfile;


setupfile.load("myconfig.cfg");




String Lang = setupfile.getSetting(“language”);


如何获得单独的配置程序


你可以用配置对话框(configuration dialog box)制作单独的配置 exemRoot->showConfigDialog();

这个函数会显示配置对话框,并且保存配置到ogre.cfg文件中。

在你的主程序中,你只须调用这个函数:

mRoot->restoreConfig();

mWindow = mRoot->initialise(true);



它会自动加载并应用这个配置(如果这个文件存在)

为了设置其他的配置参数(音频、控制等),假如我不想在一个标准的MFC程序中去调用showconfigDialog()函数,因此我不得不再写一个配置 exe(如果有人知道怎么做……)

How to(2).rar

30.66 KB, 下载次数: 202

最近VR访客

freezing 评论于2007-10-4 09:03:54
如何管理世界单元

当你用一个模型(modeler)创建你的3D objects的时候,知道你的世界单元(world unit)的值是用处的。于是你可以为你所有的objects设置相同的尺度,并且你不需在你的代码中用setScale()函数分别对每个object进行调节。
为确定你的场景的世界单元,并没有这样的一个Ogre函数;你须在你的场景中行走,直观地估计从一点到另一点的距离,并检查这两个点的坐标。如此,在Ogre demo程序中,你会发现15m=1.500wu。即:1wu=1cm。并且,在ExampleFrameListener中,你会发现速度被设置为100wu每秒(=1m/s=人的行走速度)
如果你想要一个不同的设置,比如1wu=1m,你只需要在FrameListener中调节你相机的速度(让我们假定为1wu每秒),并在最后调节你的3D objects的大小。你还可以用函数setFOVy(angle)调整相机focal angle(可以理解为视锥体在Y方向上的角度,具体见Ogre SDK关于setFOVy函数的说明——译者注)的大小(4560之间)
注意:
你最好早些在你的工程中选择好这个,否则,你你将不得不重新调整在你的代码和脚本中已经定义过的所有的objects,相机,粒子等(尺寸和速度)
如何在工程中添加插件或库

Visual C++环境中,做如下调整:
Project Setting
C++
preprocessor
additional include directory
添加头文件(.h)所在的目录(以‘,’为分隔符)(如果你想让它成为所有工程的固定设置,也可以调整Tools-option-directory-include files)
然后调整:
Project Setting
Link
input
additional library path
添加库文件(.lib)所在的目录(以‘,’为分隔符)(如果你想让它成为所有工程的固定设置,也可以调整Tools-option-directory-library files)
接下来调整:
Project Setting
Link
input
“objects/library module”
添加库文件的名字(.lib)(分隔符为‘
)
如果这是一个Ogre插件,你还需调整plugin.cfg文件。
如何管理多个物体运动

通常的方法是为每一个object写两个类:比如一个继承自UserDefinedObject的类Rocket和一个继承自FrameListener的类RocketListener
class Rocket  : public UserDefinedObject
{
public:

Rocket ();

virtual ~ Rocket ();
}
class RocketListener  : public FrameListener
{
public:

RocketListener ();

virtual ~ RocketListener ();

bool
frameStarted(const FrameEvent& evt);
}

Rocket的代码中,你必须调用一次(在构造函数或别的地方)
mRoot->addFrameListener(…);

在析构函数(或在别处)
mRoot->removeFrameListener(…);

然后函数RocketListener::frameStarted会在每一帧开始的时候被Ogre调用。
用这个方法,你可以获得N个“Rocket objects,和一个“RocketListenerobject。此RocketListener会令注册到它上面的rocket运动。(注册函数由你来完成)
其他方法:
另一个方法是让一个类继承UserDefinedObjectFrameListener
class Robot  : public UserDefinedObject, public FrameListener
{
public:

Robot();

virtual ~Robot();

bool
frameStarted(const FrameEvent& evt);
}

Robot代码中,我调用一次:
mRoot->addFrameListener(this);

当这个object不再需要移动时:
mRoot->removeFrameListener(this);

frameStared函数在每一帧开始时被Ogre调用。frameStarted函数还可以直接访问所有的成员变量(member variable)
注意如果你有许多这个类的object,这个方法会影响性能(因为Ogre会在每一帧调用NframeStared(),而不是只调用一次)。如果是那样的话,最好使用第一种方法。
如何产生透明

.material文件中,你可以设置scene_blend参数为addmodulatealpha_blend
scene_blend add

你可以使用JPEGPNG图象作为纹理。

图片的暗部(dark part)会变得透明(black=完全透明)

它用与爆炸、闪光、灯光等
scene_blend modulate

你可以使用JPEGPNG图象文件作为纹理。这个纹理会增加背景。通常它会改变图片的颜色并使它边暗。纹理的暗部会变得透明。

它用于烟色玻璃、半透明物体等
scene_blend alpha_blend

你应该使用PNG图片文件用做纹理。为了在PNG文件中实现透明,你应用Photoshop处理这个文件:把背景层送入垃圾桶,在其他层上使用橡皮。(因对Photoshop不熟悉,恐此处翻译不准确,故附上原文:To have transparency in your PNG file, you should prepare the file with Photoshop, by sending the background layer to the trashcan, and use the eraser on the other layers. )

擦除过的部分会变得透明。
如何在场景中获得阴影

有两种阴影类型:物体的阴影面,以及物体投射的阴影。
有阴影的面:
为看到物体的阴影面,你须在material描述中设置适当的参数。
l
createScene函数中,不要使用环境光(或很低的光)
l
createScene函数中,创建一个light(见上面章节)
l
.material文件中,在pass{}部分设置如下参数。
n
如果lightingOFF,对象完全被点亮,那么使用ON
n
注意:000是黑,111为白。
n
Ambient是全局的环境光。它使得被照亮面和阴影面之间产生强的或弱的对比。
n
Diffuse是被照亮面反射的颜色。你可以把它想象为一个发光体前面的颜色过滤器。
n
Specular是被照亮面的最亮的部分反射的颜色。它能给物体一种金属质感的外表。为能看到这个,你须降低环境光,并为你的Light创建setSpecularColor
lighting on
ambient
1 1 1
diffuse
1 1 1
// specular 1 1 1 12

注意透明纹理,以及布告板不能接受阴影。
投射的阴影:
实体能够投射阴影。默认情况下,lights不投射阴影,你须使之能够投射。
阴影渲染有四种技术:STENCILTEXTURE,及ADDITIVEMODULATIVE(The more “cpu friendly” is SHADOWTYPE_TEXTURE_MODULATIVE).
模版阴影(STENCIL SHADOWS):
l
这个技术使用CPU来渲染。
l
为保持良好的帧率你应设置一个最大距离(setShadowFarDistance)
*The mesh (which cast shadows) should be completely manifold (ie: closed / no holes).(not anymore in Ogre 1.0: TBC)
l
避免长阴影(比如:黎明时)
l
避免物体离光源太近。
l
这个技术允许自投影。
l
阴影有僵硬的边缘(阴影和光亮处的过渡僵硬)
纹理阴影(TEXTURE SHADOWS):
l
这个技术使用图形显示卡进行渲染。
l
这个技术工作于有向光光源和点光源。
l
这个技术不允许自投影。
l
Meshes应定义为“阴影投射体(shadow caster)”或“阴影接收体(shadow receiver)”。
l
避免长阴影(比如黎明)
l
避免物体太接近光源。
l
阴影有平滑的边缘。
l
阴影的颜色和清晰度可以定义。
更多细节,参考demo_shadows.exe,以及指南的chapter 7 “Shadows”
例子:
mSceneMgr->setShadowsTechnique(SHADOWTYPE_TEXTURE_MODULATIVE);
mSceneMgr->setShadowColour( ColourValue(0.5, 0.5, 0.5) );
...
mLight->setCastShadows(true);
...
mCastingObject->setCastShadows(true);
mReceivingObject->setCastShadows(false);
...
myMaterial->setReceiveShadows(true);
...

注意myMaterial->setReceiveShadows指令可以在material文件中由receive_shadows on替代,在顶级(此处不理解,我理解应该是在material脚本文件中的techniquepass中的部分,后附原文),或忽略,因为默认的设置就是ON(原文:Note that the instruction myMaterial->setReceiveShadows(true) can be replaced by receive_shadows on in the material file, at top level, or be omitted, as the default setting for material is ON.)你可以使用这条指令设置不应该接收阴影的材质的值为OFF。这可以节省一些帧率。
如何播放动画mesh

首先,你需要有:
l
一个mesh文件(它包含mesh的描述,以及顶点和骨骼之间的连接)
l
一个skeleton文件(它包含骨骼的描述,以及动画过程中骨骼的运动)
这两周文件可由Ogre输出设备产生。
加载动画:
Animation::setDefaultInterpolationMode(Animation::IM_SPLINE);
AnimationState *mAnimState = mEntity->getAnimationState("move");
mAnimState->setEnabled(true);
mAnimationSpeed = 1;

播放动画:(frameStarted())
mAnimState->addTime(evt.timeSinceLastFrame * mAnimationSpeed);

停止动画:
if (mAnimState->getTimePosition() >= mAnimState->getLength())

{

// animation is over
}

或者,当你加载动画时,你可以设置:
mAnimState->setLoop(false);

然后播放动画:
mAnimState->addTime(evt.timeSinceLastFrame * (-mAnimationSpeed));

如何获得调试控制台

这个代码来自于johnhpus
为了完全利用调试控制台,你需要在窗口模式下运行你的程序(否则,控制台是隐藏的)。在winmain.cpp项目中创建控制台窗口:
AllocConsole();
try { app.go(); … }
FreeConsole();
return 0;

在调试控制台写调试信息:
#include "conio.h"
_cprintf("Thanks John");

如何获得调试控制台(Alternative)

来自Willi >.>
在连接器中应用这些工程设置,工程设置中找到它们(Linker->System->SubSystem Linker->Advanced->Entry Point),或在命令行手动为MSVS设置它们
/SUBSYSTEM:CONSOLE /ENTRY:WinMainCRTStartup

可以自由使用你想使用的任何输出..比如
#include <iostream>
...
std::cout << "Debug Message";

注意用"int main()"替代"int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)",并设置子系统为auto(默认)也将会工作,但是你将得不到hInstance……
如何获得调试控制台(Easy and powerful Alternative)

这个代码由Kristian Mortensen发布在Ogre论坛上——多谢!
把这个代码添加到你的main.cpp中:
#include <stdio.h>
#include <fcntl.h>
#include <io.h>
#include <iostream>
#include <string>
void showWin32Console()
{

static const WORD MAX_CONSOLE_LINES = 500;

int hConHandle;

long lStdHandle;

CONSOLE_SCREEN_BUFFER_INFO coninfo;

FILE *fp;

// allocate a console for this app

AllocConsole();

// set the screen buffer to be big enough to let us scroll text

GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo);

coninfo.dwSize.Y = MAX_CONSOLE_LINES;

SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE),

coninfo.dwSize);

// redirect unbuffered STDOUT to the console

lStdHandle = (long)GetStdHandle(STD_OUTPUT_HANDLE);

hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);

fp = _fdopen( hConHandle, "w" );

*stdout = *fp;

setvbuf( stdout, NULL, _IONBF, 0 );

// redirect unbuffered STDIN to the console

lStdHandle = (long)GetStdHandle(STD_INPUT_HANDLE);

hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);

fp = _fdopen( hConHandle, "r" );

*stdin = *fp;

setvbuf( stdin, NULL, _IONBF, 0 );

// redirect unbuffered STDERR to the console

lStdHandle = (long)GetStdHandle(STD_ERROR_HANDLE);

hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);

fp = _fdopen( hConHandle, "w" );

*stderr = *fp;

setvbuf( stderr, NULL, _IONBF, 0 );

// make cout, wcout, cin, wcin, wcerr, cerr, wclog and clog

// point to console as well

std::ios::sync_with_stdio();
}

然后只须做这个:

SET_TERM_HANDLER;

showWin32Console();

try {

app.go();
} catch( Ogre::Exception& e ) {
...
}

FreeConsole();

return 0;
}

现在你可以使用coutprintf等所有你想使用的——它会指向你的控制台。
并且不需更改工程设置。;-))
如何计算一个场景结点和一点间的角度

这个函数的返回值为snFrom为看向vecTo需要yaw()的角度。
Real angleBetween(SceneNode &snFrom, Vector3 &vecTo)
{

Radian r;

Vector3 vsrc = snFrom.getOrientation( ) * Vector3::NEGATIVE_UNIT_Z;

Vector3
vDiff = vecTo - snFrom.getPosition();

vDiff.normalise();

r = Math::ACos(vsrc.dotProduct(vDiff));

return r.valueDegrees();
}

如何使NVPerfHUD能用

这个代码来自于Istari
首先,下载并安装NVPerfHUD。下来要在代码上做些改变,打开RenderSystem_Direct3D9工程。
D3D9RenderWindow::createD3DResources中遭到下面的代码行:

hr = pD3D->CreateDevice( mDriver->getAdapterNumber(), devType, mHWnd,

D3DCREATE_HARDWARE_VERTEXPROCESSING | extraFlags, &md3dpp, &mpD3DDevice );

并把他们改作这样:
#ifdef NV_PERFHUD

D3DADAPTER_IDENTIFIER9 Identifier;

hr = pD3D->GetAdapterIdentifier(mDriver->getAdapterNumber(), 0, &Identifier);

if (strcmp(Identifier.Description, "NVIDIA NVPerfHUD") == 0)

{


hr = pD3D->CreateDevice( mDriver->getAdapterNumber(), D3DDEVTYPE_REF, mHWnd,

D3DCREATE_HARDWARE_VERTEXPROCESSING | extraFlags, &md3dpp, &mpD3DDevice );

}

else

{
#endif

hr = pD3D->CreateDevice( mDriver->getAdapterNumber(), devType, mHWnd,

D3DCREATE_HARDWARE_VERTEXPROCESSING | extraFlags, &md3dpp, &mpD3DDevice );
#ifdef NV_PERFHUD

}
#endif

现在你需要做的就是在建立dll前定义NV_PERFHUD使之可用。
现在当你用NVPerfHUD运行执行文件你需选择NVINIA NVPerfHUD渲染设备。Retrieved from "http://www.ogre3d.org/wiki/index.php/HowTo_%28part_II%29"


[ 本帖最后由 freezing 于 2007-10-4 09:06 AM 编辑 ]
obuil 评论于2007-10-4 20:37:32
支持一下 ,我还没好好看 一会
wuliao009 评论于2007-10-20 16:41:28
挺好。
yaren 评论于2008-8-22 15:11:14
收藏一下
sanjinyu 评论于2009-3-3 09:36:07
好帖,支持支持……
masmprogram 评论于2012-8-1 16:56:20
初学OGRE,非常感谢楼主的分享!

手机版|VR开发网 统计 津ICP备18009691号
网安备12019202000257

GMT+8, 2021-10-29 03:53 AM

返回顶部