标题
[d3d/opengl]在程序中怎么回放3DMax建立的3D[zt]
clq
浏览(0) +
2009-01-20 10:07:26 发表
编辑
关键字:
在程序中怎么回放3DMax建立的3D模型收藏
新一篇: XML相关资源 | 旧一篇: Bugzilla 的安装笔记
将3dmax文件回放出来有几种方式:
1、3dmax文件可以导出为3DS格式的文件。再用DIRECTX提供的工具conv3ds.exe或接口将3DS文件转成.x文件,最后用DIRECTX导入.x文件
conv3ds.exe下载:http://www.microsoft.com/downloads/details.aspx?FamilyID=26fca7ce-6c37-4d9b-9b20-5f71b7bd369c&displaylang=en
.x文件回放可以参考:http://www.frontfree.net/view/article_772.html
2、3dmax文件可以导出为3DS格式的文件。再用LIB3DS库解析3ds文件,一般OPENGL下面可以这样用
3、为3dmax写插件或脚本,将3dmax模型导出成自定义格式文件。再用D3D或OPENGL回放。这种方法导出的数据可以最完整,但是过程比较复杂。要研究3dmax的SDK或者Script.
4、将MAX文件导出成ASE文件(文本),再读取ASE文件信息。
clq
http://www.frontfree.net/view/article_772.html
如何将3D作品用于程序
原创:XuanTZ 2003年8月7日
相信大家都玩过或见过一些绚丽的3D游戏,也都知道其中的人物和场景是使用各种3D软件——诸如3DSMAX、MAYA制作完成的。那这些作品是如何被用在程序中的呢?我们熟知的Direct3D就是用来做这件事的。本篇将简要介绍如何通过D3D 来运用3D作品。不要怕没有接触过D3D就不看了,笔者将从最简单的——如何创建3D设备谈起。
下载源代码
注意,除了DirectX9.0SDK外,我们还需要一个conv3ds转换器,在D3D的插件工具包里。D3D中一个3D物体——包含了顶点信息、材质、贴图、动画桢的3D成品被叫做mesh物体。conv3ds就是用来把3D作品转换为标准mesh物体文件(*.x)的。
言归正传,首先你要有一个像样的3D作品。
打开你的3DSMAX(或MAYA等其他3D软件要导出D3D能用的文件,还需要另外的插件,这里就不多说了),尽情发挥你的才智来制作任何你想要的模型。不过丑话说在前面,熟悉3D制作的朋友都清楚,精致的3D作品要靠大量的各种类型的贴图来完成,但D3D只认Diffuse(表面色)一个通道。尽管它也支持Bump(凹凸)Specular Color(高光色)等另外的一些,但都不能直接用3D软件来完成,另需代码。这也就是为什么相对3D作品来说,游戏的渲染速度要快的多(当然还有其他原因),也是为什么游戏的画质都不够传神。好吧,尽量做得好看吧。还要注意,贴图的长和宽一定要是2的幂次像素大小,并且和你的程序在同一目录下。笔者制作了一个放飞的标志作为例子,存为"C:\frontfree.3ds",并使用了一张名为"C:\larglog.bmp"和一张名为"C:\Lakerrem2.jpg"的贴图。
在菜单file中用export(导出整个场景)或export selecte(只导出所选物体)将其导出为*.3ds文件。然后用命令行方式运行conv3ds把它转*.x。这样就有了一个可用的3D作品。 conv3ds有很多有用的参数,读者朋友可以在包里找到它的自述文件,这里只介绍一个-x(小写)。默认的*.x都是二进制的,这样写就生成文本式的*.x:conv3ds -x *.x,用UtralEdit等打开,你会直接看到mesh物体是如何构成的,当然,这样的文件要大一些。
该开始写程序了。一般来说,D3D程序要用到以下头文件:D3DX9.H,D3D9.H,D3DTYPES.H,以及相应的库文件,还有个WINMM.LIB。
不管要做什么,创建一个窗口,主事件循环等等总是Windows程序首要的且一成不变的。下面的代码完成了这些工作:
LRESULT CALLBACK WinProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch ( msg )
{
case WM_DESTROY:
//...这里将调用一个是收尾函数来释放所有界面指针
PostQuitMessage(0);
DestroyWindow(hWnd);
break;
default:
break;
}
return ( DefWindowProc( hWnd, msg, wParam, lParam ) );
}
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd )
{
//create a main window:
WNDCLASSEX mainwnd;
mainwnd.cbSize=sizeof(WNDCLASSEX);
mainwnd.cbClsExtra=0;
mainwnd.cbWndExtra=0;
mainwnd.hbrBackground=NULL;//这将是一个没有背景
mainwnd.hCursor=NULL;//鼠标是沙漏
mainwnd.hIcon=NULL;//没有图标
mainwnd.hIconSm=NULL;
mainwnd.hInstance=hInstance;
mainwnd.lpfnWndProc=WinProc;//没有标题栏
mainwnd.lpszClassName="main window";
mainwnd.lpszMenuName=NULL;//没有菜单
mainwnd.style=CS_OWNDC;//的窗口
RegisterClassEx(&mainwnd);
HWND hWnd = CreateWindowEx( NULL,"main window","only a demo",
WS_POPUP|WS_VISIBLE, 0, 0, 800, 600, NULL, NULL, hInstance, NULL);
//在此初始化D3D设备,物体
//Init_D3D();
//主事件循环:
MSG msg;
ZeroMemory(&msg,sizeof(msg));
while (msg.message!=WM_QUIT)
{
//proc message:
if ( PeekMessage( &msg, hWnd, 0, 0, PM_REMOVE ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
else
{
//每帧一次的渲染就在这里
//Render();
}
}
//UnRegiste the window:
UnregisterClass( "main window", mainwnd.hInstance );
return ( msg.wParam);
}//end winmain
Direct3D是DirectX中极其重要的组建,提供了一套完整的用来设置场景、操作3D物体、渲染画面的方法。来复习一下D3D程序的一般流程:首先得有个要渲染的物体,你得给出它的顶点、贴图、材质信息。然后把它的坐标(3D的)转换为屏幕坐标,再添加一些灯光信息,然后调用一个渲染方法,它所做的就是根据各个顶点的位置(已是屏幕坐标),用灯光和材质信息计算亮度,把贴图“填”进去,这样你看起来就是个立体的东西。渲染方法函数把图画在后备缓冲,因此最后别忘了把它换到前缓冲。你要有个Dirext3D的接口作为创建一切的基础。像这样:
LPDIRECT3D9 g_lpd3d9;//全局的
g_lpd3d9 = Direct3DCreate9( D3D_SDK_VERSION );//参数代表了你的D3D SDK版本。必须要这样写
然后通过它来创建一个Direct3D设备接口,其他所有创建物体、灯光,以及渲染等方法都要通过这个设备接口指针来调用:
LPDIRECT3DDEVICE9 g_lpd3ddevice;//全局的
HRESULT InitD3D(HWND hWnd)
{
D3DPRESENT_PARAMETERS m_d3dpp;
//我们要把当前适配器(显卡)的信息读到这个结构体里,它将被用于创建设备
D3DDISPLAYMODE m_d3ddm;
g_lpd3d9->GetAdapterDisplayMode(D3DADAPTER_DEFAULT,&m_d3ddm);
//D3DADAPTER_DEFAULT表示默认显卡
ZeroMemory(&m_d3dpp,sizeof(m_d3dpp));
m_d3dpp.Windowed=false;//要建立一个全屏程序,则置此项为FALSE
m_d3dpp.BackBufferWidth=m_d3ddm.Width;
m_d3dpp.BackBufferHeight=m_d3ddm.Height;
m_d3dpp.BackBufferFormat=m_d3ddm.Format;//此项记录色深信息
m_d3dpp.SwapEffect=D3DSWAPEFFECT_DISCARD;
m_d3dpp.EnableAutoDepthStencil = TRUE;//让D3D管理深度缓冲
m_d3dpp.AutoDepthStencilFormat=D3DFMT_D16;//设置深度缓冲为16位
m_d3dpp.hDeviceWindow=hWnd;
//create the device:
g_lpd3d9->CreateDevice(D3DADAPTER_DEFAULT,//适配器标识符,大多数情况使用默认的
D3DDEVTYPE_HAL,//硬件抽象层
hWnd,//这是一个当前窗口的句柄,使用刚才CreateWindowEx()的返回值就行了
D3DCREATE_HARDWARE_VERTEXPROCESSING,//顶点处理类型,这里使用硬件处理
&m_d3dpp,//描述设备的结构体
&g_lpd3ddevice);//最后给一个要被创建的设备的指针
//启用Z缓冲
g_lpd3ddevice->SetRenderState(D3DRS_ZENABLE,TRUE);
return S_OK;
}
Z缓冲是深度缓冲的一种。深度缓冲是一块与屏幕大小相同的区域,记录着屏幕上每个点的“最近像素”有多“近”。比如说程序要画一个三角形中的某个点,但它发现深度缓冲中记录的距离比这个点要近,也就是说其他物体的某点挡在它前面,自然也就不会去画它;否则就画它,然后把该点的深度信息写进缓冲。现在我们把Z-Buffer打开,就是把这些事交给系统处理。调节缓冲数据位数是在较精确的数据与较大的空间之间作个权衡。
该用到刚刚做完的那个东西了。在D3D中,可以说所有物体的外形位置由顶点构成;材质决定着物体如何反射光线;贴图的作用自不言而喻。任何3D程序首先要做的就是创建一套这些信息。刚才说道,现在这些信息都包含在现成的mesh物体中,我们要做的就是把它从文件中提取出来。D3D中的 IDirect3DMesh界面提供了这些方法。
LPD3DXMESH g_lpmesh;//全局的
mesh物体的定点信息都存放在g_lpmesh里,而材质和贴图还要单独提取出。D3D里,材质和贴图分别用D3DMATERIAL9结构体和ID3DTexture9界面描述。一般来说,mesh中都含有多种贴图和材质,我们要将其声明为数组。
D3DMATERIAL9 *g_meshmtrl;
LPDIRECT3DTEXTURE9 *g_meshtex;
数组的大小事先并不知道,取决于mesh物体的SebSet的数量。在一个mesh物体中,拥有相同材质贴图属性的一组三角形叫它的一个 SubSet。因为转换当前材质和贴图是十分耗时的,因此在D3D中是按照一个SubSet组接着另一个去渲染的,这样就避免了材质贴图的频繁转变。我们用一个DWOD型的全局变量g_meshmtrlnum记录这个mesh中的SubSet数量。
HRESULT InitGeometry()
{
LPD3DXBUFFER lpmeshmtrlbuffer;//材质贴图的临时缓冲
//从文件装载一个mesh物体:
D3DXLoadMeshFromX("c:\\", //文件路径
D3DXMESH_SYSTEMMEM,//g_lpmesh被创建的位置,这里它被创建在系统内存而非显示内存
g_lpd3ddevice,NULL,&lpmeshmtrlbuffer,NULL,
&g_meshmtrlnum,//一个用于存放SubSet数量的变量指针
&g_lpmesh);//最后是要被创建的mesh指针
//我们要一次性读取出全部材质和贴图信息,并都存放在这里:
D3DXMATERIAL *pd3dxmtrl=(D3DXMATERIAL*)lpmeshmtrlbuffer->GetBufferPointer();
//然后再细分:
g_meshmtrl=new D3DMATERIAL9[g_meshmtrlnum];
g_meshtex=new LPDIRECT3DTEXTURE9[g_meshmtrlnum];
//对每个SubSet,我们分别提取它的材质和贴图
for(DWORD i=0;i {
g_meshmtrl[i]=pd3dxmtrl[i].MatD3D;
//实际上,pd3dxmtrl[i].pTextureFilename只是个文件名
TCHAR texname[512];//存放完整的贴图文件路径
strcpy(texname,"c:\\");
strcat(texname, pd3dxmtrl[i].pTextureFilename);
D3DXCreateTextureFromFile(g_lpd3ddevice,texname,&g_meshtex[i]);
}
lpmeshmtrlbuffer->Release();
lpmeshmtrlbuffer = NULL;
return S_OK;
}
打开*.x文件(如果是文本模式的)你会看到贴图在文件中只被记录该图片的文件名。而CreateTextureFromFile()所需要的参数是一个完整路径,所以如果这些图不在程序同一目录下,就要在代码中做些处理。否则我敢保证你什么也看不见。这里,我只是简单的使用了绝对路径。
好,所有信息都被从文件中摘抄了出来,但现在还不是渲染的时候。没有进行坐标转换,目前所有顶点使用的,都是其局部的3D坐标,并非渲染函数能够直接使用的屏幕坐标。通常需要经过以下三种坐标转换:
全局转换 将所有的物体转换为统一的全局坐标,你还可以在这里完成各种对物体位置的操作。
视图转换 转为从观察者(玩家)角度看到的全局坐标。先在全局坐标系的指定位置架一台摄影机,指定一个注视点和一个参考点。坐标将被转换到以摄影机为原点,从原点到注视点为Z轴,再加上参考点,所确定的平面为Y-Z平面的坐标系中。实际上就是把全局坐标拧了个儿。
透视转换 前两种坐标转换并无本质区别,有些情况就把它们合为一个。而经过透视转换,各顶点的X,Y值将表示实际的屏幕坐标,而Z值就是Z-Buffer里用到的深度信息。
任何的基本的坐标转换(平移、旋转、放缩),在3D程序里,都是将该点(就是一个列向量)乘以一个4x4矩阵。上面的三种转换实际就是一系列的基本转换。矩阵不用读者自己填写,D3D会完成这些。然后还要调用相应的三个函数告诉D3D:你要用这个矩阵完成全局转换,然后用那个矩阵完成视图转换等等。代码如下:
HRESULT SetMatrix()
{
D3DXMATRIX matWorld;
//以下用来让物体转起来:
UINT iTime = timeGetTime() % 10000;
FLOAT fAngle = iTime * (2.0f * D3DX_PI) / 10000.0f;
D3DXMatrixRotationX( &matWorld,fAngle);//填充matWorld为一个旋转矩阵,fAngle是旋转角度
g_lpd3ddevice->SetTransform(D3DTS_WORLD,&matWorld);//将matWorld设置为世界转换矩阵
D3DXMATRIX matView;
D3DXVECTOR3 d3dvEye(0,100,-500);//玩家所在位置,用一个D3DXVECTOR3描述
D3DXVECTOR3 d3dvLA(0,0,0);//视点
D3DXVECTOR3 d3dvUp(0,1,0);//参考点
D3DXMatrixLookAtLH( &matView,&d3dvEye,&d3dvLA,&d3dvUp );//填充matView矩阵
g_lpd3ddevice->SetTransform(D3DTS_VIEW,&matView);//设置为试图转换矩阵
D3DXMATRIX matProj;D3DXMatrixPerspectiveFovLH( &matProj,
D3DX_PI/4,//视角的范围,一般是45度
float(1280)/float(1024),//这一项是视图的长宽比
1.0f,10000.0f);//这两个数是最近与最远的可见范围
g_lpd3ddevice->SetTransform(D3DTS_PROJECTION,&matProj);//设置为透视转换矩阵
return S_OK;
}
可以渲染了,不过最好先来放置两盏灯。灯在D3D里用D3DLIGHT9结构体表示。熟悉3D制作的读者不难猜出这些结构的成员。我设置了一盏白色的主光和一盏黄色的副光,注意,灯光并不会和物体一起转动,全局转换只是针对物体的顶点。
HRESULT SetLight()
{
D3DLIGHT9 light_r;
ZeroMemory(&light_r,sizeof(light_r));
light_r.Type=D3DLIGHT_DIRECTIONAL;
//D3D里支持泛光灯,聚光灯和平行光三种光源,这里用了平行光
light_r.Diffuse.r=light_r.Diffuse.g=light_r.Diffuse.b=1;
light_r.Specular.r=light_r.Specular.g=light_r.Specular.b=1;
light_r.Direction=D3DXVECTOR3(0.5f,-0.5f,0.5f); //平行光的方向
g_lpd3ddevice->SetLight(0,&light_r);
//你可以设置很多的灯,第一个参数就是你要设置的灯的索引号,第二个参数就是描述这盏灯的结构体
lpd3ddevice->LightEnable(0,TRUE);
//然后让这盏灯起作用,就好像把它打开
D3DLIGHT9 light_b;
ZeroMemory(&light_b,sizeof(light_b));
light_b.Type=D3DLIGHT_DIRECTIONAL;
light_b.Diffuse.r=0.5;
light_b.Diffuse.g=1;
light_b.Direction=D3DXVECTOR3(-0.5F,-0.5F,0.5F);
g_lpd3ddevice->SetLight(1,&light_b);
g_lpd3ddevice->LightEnable(1,TRUE);
g_lpd3ddevice->SetRenderState(D3DRS_LIGHTING,TRUE);
return S_OK;
}
D3D中,物体的高光色并不依赖于其表面色,因此表面色和高光色可以分别设置。灯光也是这样。我把第一盏灯的表面色和高光色都设置呈白色,而第二盏灯不参与高光运算,就好像环境反射光。光的颜色通过rgb来设置,每种颜色的范围是0-1。
最后的渲染实际很简单。首先要调用Clear()清除画面和深度缓冲,把所有的SebSet画到后备缓冲,然后调用Present()换到前缓冲,就可以出现在显示器上了。
HRESULT Render()
{
g_lpd3ddevice->Clear(0,NULL,D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,
//需要渲染的表面,这里是目标画面和深度缓冲
0x00000000,//用来清屏的颜色值,这里是用黑色
1,0);
g_lpd3ddevice->SetTextureStageState(0,D3DTSS_COLOROP,D3DTOP_MODULATE);
g_lpd3ddevice->BeginScene();//每次画图形之前,一定要调用这个方法
SetMatrix();
for(DWORD i=0;i g_lpd3ddevice->SetMaterial(&g_meshmtrl[i]);//参数就是所需的材质
g_lpd3ddevice->SetTexture(0,
//D3D支持多重贴图,这个参数是要设置的贴图序号,这里只用第一重
g_meshtex[i]);
g_lpmesh->DrawSubset(i);//这个方法才开始真正的画图,参数就是mesh的子物体序号
}
g_lpd3ddevice->EndScene();//这是与BeginScene()对应的
g_lpd3ddevice->Present(NULL,NULL,NULL,NULL);//翻页,一般来说,四个参数都是NULL
return 0;
}
这只是一个很简单的例子,代码极其简陋,没有错误校验,没有释放界面指针(读者一定要想着释放,否着你的程序并不会真正结束),没有用户操作,关闭是Alt+F4。效果也并不复杂:
这个例子只是想告诉读者一些基本的做法。由于笔者水平有限,很多问题没也有叙述清楚。欢迎各位读者朋友与我交流经验。
NEWBT官方QQ群1: 276678893
可求档连环画,漫画;询问文本处理大师等软件使用技巧;求档softhub软件下载及使用技巧.
但不可"开车",严禁国家敏感话题,不可求档涉及版权的文档软件.
验证问题说明申请入群原因即可.