什么是3d max 入门教程级3D游戏

还请各位大神评价下我的电脑配置,玩3D游戏能流畅运行不?_百度知道
还请各位大神评价下我的电脑配置,玩3D游戏能流畅运行不?
提问者采纳
1.CPU amd x2 245 入门级CPU性能般够家用般游戏2.内存2G点现<img class="word-replace" src="/api/getdecpic?picenc=0a007aG比较合适3.显卡GT220比较低入门级底层显卡性能满足般3D游戏型3D玩例型魔兽世界总体说电脑处于入门级水平玩玩般3D游戏尚型行建议升级显卡内存
提问者评价
谢谢了~!我不懂电脑,终于知道该向哪方面升级了。
其他类似问题
按默认排序
其他7条回答
显卡弱点般3D游戏游戏设置要太高基本没问题再加根内存
我建议还是换个高点的吧,3G主频,2G内存都还可以,我觉得256的显卡你的画面可能不会太好。个人观点
你这配置,win7才2G内存,
像战地3这样的游戏玩不了的。
换了吧 不行的
很一般,说不好听的,还不如现在网吧的配置。。。如果是新配的,肯定是电脑行的坑你了GT220显卡,256显存基本都是是网吧专用,245CPU也是2年前网吧配置主流。。。内存也是上一代产品了。。。 这个配置如果说玩部分3D游戏还是可以的(COD8以前的应该没啥问题),但是新出的如热血无赖,马克思佩恩3,无主之地2就比较困难了,但是效果和流畅程度肯定受到影响,不能达到很好的游戏体验,主要是看你玩游戏追求的是剧情还是效果。如果是网络游戏,基本都能玩吧,画面效果不开那么高就可以。
配置一般 玩大型3D游戏有点吃力 别开游戏特效 基本没问题
3d游戏的相关知识
您可能关注的推广
等待您来回答
下载知道APP
随时随地咨询
出门在外也不愁8001元以上笔记本大全 您已选择:笔记本分类:
更多品牌: - 回到单选
Win8系统: CPU: 显卡: 尺寸: 内存容量: 选购热点:
起 2013年12月上市加入对比起 2014年10月上市加入对比起 2014年10月上市加入对比起 2014年10月上市加入对比一月新品起 2015年01月上市加入对比
共1页 去第
笔记本选购指南本月大家买了什么价位的笔记本?本月大家买了什么品牌的笔记本?
最新笔记本
笔记本最新文章Intel在今年CES电子展上发布了第五代移动平台处理器,与此同时,各大厂商也积极推出了配备第五代Core i处理器的笔记本
“小乐徒步”有两个月没写了,倒不是因为我犯懒不想出去,而是作为一个新驴,目前实在没有适合冬天去山里闲逛的衣服,
现在主流笔记本的生命周期一般在三到五年,所以花较高预算购买一款称心如意的笔记本产品已经被越来越
&&&& 在今日凌晨微软的“新篇章”发布会上,微软公布了关于Windows 10更多的信息,也透露了用户
如果错过了今日凌晨的Windows 10发布会怎么办?观看发布会视频?--听起来有点吃力,因为整场发布会下来持续两个半小时
&&&&&在今年CES大展中一举斩获四项大奖的三星全新旗舰级ATIV Book 9 笔记本电脑强势登陆北
IT168相关链接:   
电脑整机电脑配件外设产品网络产品办公打印办公文仪
各地手机报价
本月热销笔记本购买率25% &&购买率10% &&购买率7% &&购买率5% &&购买率2% &&
01020304050607080910>>
01020304050607080910>>
总消耗: 0.2028003秒21287人阅读
本系列文章由zhmxy555(毛星云)编写,转载请注明出处。作者:毛星云&&& 邮箱: &&& 期待着与志同道合的朋友们相互交流上一节里我们介绍了在迈入DirectX 11的学习旅程之后第一个demo创建的全过程。但由于知识衔接的需要,我们的第一个demo里面涉及到的大部分知识都是关于Win32的。而为了使之前讲解的Blank Win32 Window Demo蜕变成我们期望的Direct3D的模样,我们将在这节的笔记里面对Direct3D的入门级的基础知识做一个详细的介绍,以便在下节笔记里轻车熟路地写出属于我们的第一个完整的Direct3D11 Demo。&&&&&&&入门知识的第一步当然是进行DirectX开发环境的配置,这在笔记二十五里面有详细介绍,详情请移步:&下面就开始正题,我们将分八个部分对入门级的Direct3D知识进行一个讲解。一、 Direct3D的初始化&初始化Direct3D,我们需要完成以下四个步骤:1.定义我们需要检查的设备类型(device types)和特征级别(feature levels)2.创建Direct3D设备,渲染设备(context)和交换链(swap chain)。3.创建渲染目标(render target)。4.设置视口(viewport)这里只是给大家一个框架的概念,各个部分下面会详细展开讲解。&二、驱动设备类型与特征等级&在Direct3D 11中我们能使用的设备有硬件设备(hardware device),参考设备(reference device),软件驱动设备(software driver device), 以及WARP设备 (WARP device)。硬件设备(hardware device)是一个运行在显卡上的D3D设备,在所有设备中运行速度是最快的。这将是我们日后讨论最多的一种类型。参考设备(reference device)是用于没有可用的硬件支持时在CPU上进行渲染的设备。简言之,参考设备就是利用软件,在CPU对硬件渲染设备的一个模拟。但是不幸的是,这种方式非常的低效,所以在开发过程中,没有其他可用选择的时候,我们才采用这种方式。比如新一代的DirectX发布了,市面上还没有支持这种新版本DirectX的硬件,我们在开发过程中就只能采用这种方式来跑了。&软件驱动设备(software driverdevice)是开发人员自己编写的用于Direct3D的渲染驱动软件。这种方式通常不推荐用于高性能或者对性能要求苛刻的应用程序,下面介绍的WARP设备将是更好的选择。WARP设备(WARPdevice)是一种高效的CPU渲染设备,可以模拟现阶段所有的Direct3D特性。WARP使用了Windows Vista /Windows 7/Winodws 8中的Windows Graphic 运行库中高度优化过的代码作为支撑,这让这种方式出类拔萃,相比与上文提到的参考设备(reference device)模式更加优秀。WARP设备在配置不高的机器上面可以达到化腐朽为神奇的功效。在我们的硬件不支持实时应用程序(real-time application)的情况下,用WARP设备作为替补是一个明智的选择,因为相比而言,参考设备(reference device)的执行效率实在是无法令人恭维。即便如此,WARP设备的执行效率还是不能和硬件设备同日而语,毕竟它依旧是对硬件的一种模拟,即使这种模拟是非常高效的。&注意:这不是对设备类型一个完整的列举,还有很多细枝末节的设备类型,在这里没必要一一列举&Direct3D的特征等级用于指定需要设定的设备目标。在这个专栏之中,我们将针对三种设备,第一种当然是我们的Direct3D 11设备,第二种为Direct3D 10.1设备,第三种为Direct3D 10.0设备。再这三种设备都无法支持的情况下,我们再选择WARP设备或者参考设备作为后援。&下面贴出来的代码段1为后面我们需要用到的驱动类型和特征级别的一个声明。通过创建各种类型的数组,我们可以使用循环来尝试首先创建我们最需要的设备,然后若执行失败则继续创建其他的设备类型。浅墨记得我们之前提到过,Win32宏ARRAYSIZE能够用来返回一个数组的大小,Win32函数GetClientRect可以用来计算应用程序客户区的大小。算出来的值会用于设置之后的D3D设备渲染的宽度和高度。另外,需要记住Win32应用程序是分客户区和非客户区的,我们仅能在客户区上进行渲染。代码段1 指明驱动设备类型和特征等级&RECT
GetClientRect( hwnd, &dimensions );
unsigned int width = dimensions.right - dimensions.
unsigned int height = dimensions.bottom - dimensions.
D3D_DRIVER_TYPE driverTypes[] =
D3D_DRIVER_TYPE_HARDWARE, D3D_DRIVER_TYPE_WARP,D3D_DRIVER_TYPE_SOFTWARE
unsigned int totalDriverTypes = ARRAYSIZE( driverTypes );
D3D_FEATURE_LEVEL featureLevels[] =
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0
unsigned int totalFeatureLevels = ARRAYSIZE( featureLevels );&&三、设备与交换链的创建&下一步便是创建一个交换链,交换链在Direct3D中为一个设备渲染目标的集合。每一个设备都有至少一个交换链,而多个交换链能够被多个设备所创建。一个交换目标可以为一个渲染和显示到屏幕上的颜色缓存(在后面会讨论),等等。&通常在游戏中有,有两种颜色缓存,分别叫做主缓存和辅助缓存,他们一起被称为前后台缓存组合。主缓存中的内容(前台缓存)会显示在屏幕上,而辅助缓存(后台缓存)用于绘制下一帧(真是两个好基友-o-)。&渲染的发生非常之快,屏幕的一部分可以在显示器完成显示更新之前,在先前的结果为基础上进行绘制。缓存之间的切换,可以进行一个良性的运作,前台在显示图像,后台正在为前台准备下一刻将要显示的图像,这样做可以避免很多棘手的问题,提高了效率。这种技术在计算机图形学中叫做双缓冲(doublebuffering),或者叫页面翻转(page flipping)(这种技术我们之前的一系列Win32 GDI demo中使用得比较勤,研究了之前的demo的朋友们应该已经耳濡目染了吧)。一个交换链能拥有一个或者多个这样的缓冲。&代码段2中列出了创建一个交换链的代码。一个交换链的描述用来定义和创建符合我们需要的交换链。&代码段2 对交换链的设置&DXGI_SWAP_CHAIN_DESC swapChainD
ZeroMemory( &swapChainDesc, sizeof( swapChainDesc ) );
swapChainDesc.BufferCount = 1;
swapChainDesc.BufferDesc.Width =
swapChainDesc.BufferDesc.Height =
swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferDesc.RefreshRate.Numerator = 60;
swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.OutputWindow =
swapChainDesc.Windowed =
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;
这个范例中定义了D3D的多种取样属性,多重取样(Multisampling)是一种用于采样和平衡渲染像素的创建亮丽色彩变化之间的平滑过渡的一种技术。&缓存的使用和交换链的描述有大量的成员需要设置,但这些设置都是非常简单的。缓存的对交换链的使用是设置下DXGI_USAGE_RENDER_TARGET_OUTPUT,以便交换链能够用于输出,或者换句话说,它能被渲染。&下一步是创建渲染上下文,渲染设备,以及我们拥有的交换链描述。D3D设备一般都是设备本身和硬件之间的通信,而D3D上下文是一种描述设备如何绘制的渲染设备上下文,这也包含了渲染状态和其他的绘图信息。正如我们讨论过的,交换链是设备和上下文将要绘制的渲染目标。创建设备上下文,渲染上下文和交换链所需的代码在代码段3中详细列出了,.这段代码为下次内容即将展示的Direct3D 11 BlankWindows Demo的一个片段。&代码段3 Direct3D设备,设备上下文,以及交换链的创建ID3D11Device device_;
ID3D11Context d3dContext_;
IDXGISwapChain swapChain_;
unsigned int creationFlags = 0;
#ifdef _DEBUG
creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
unsigned int driver = 0;
for( driver = 0; driver & totalDriverT ++driver )
result = D3D11CreateDeviceAndSwapChain( 0, driverTypes[driver],0,
creationFlags, featureLevels, totalFeatureLevels,
D3D11_SDK_VERSION, &swapChainDesc, &swapChain_,
&d3dDevice_, &featureLevel_, &d3dContext_ );
if( SUCCEEDED( result ) )
driverType_ = driverTypes[driver];
if( FAILED( result ) )
DXTRACE_MSG( &Failed to create the Direct3D device!&);
交换链,设备和渲染上下文可以在单独的Direct3D函数调用中被创建,或者通过特定对象的Direct3D来调用(例如用CreateSwapChain函数来专门创建一个交换链)。这个函数为D3D11CreateDeviceAndSwapChain。在代码段2中我们在每个驱动类型中循环,试图创建一个合适得设备,或为一个硬件设备,或为一个WARP设备,抑或一个参考设备(reference device)。因为如果创建失败,我们就无法初始化我们的Direct3D。D3D11CreateDeviceAndSwapChain函数中包含了特征等级作为其参数, 所以如果至少有一个这样的特征等级存在,而且若我们的设备类型也存在,这个函数才会执行成功。&其中D3D11CreateDeviceAndSwapChain函数具有如下的函数原型:HRESULT D3D11CreateDeviceAndSwapChain(
IDXGIAdapter *pAdapter,
D3D_DRIVER_TYPEDriverType,
HMODULE Software,
UINT Flags,
const D3D_FEATURE_LEVEL*pFeatureLevels,
UINT FeatureLevels,
UINT SDKVersion,
const DXGI_SWAP_CHAIN_DESC *pSwapChainDesc,
IDXGISwapChain **ppSwapChain,
ID3D11Device **ppDevice,
D3D_FEATURE_LEVEL*pFeatureLevel,
ID3D11DeviceContext**ppImmediateContext
);&&&&&& 四、 创建渲染目标视图一个渲染目标视图是一个由Output MergerStage读取的D3D资源。为了output merger能渲染一个后台缓存的交换链,我们为其创建一个渲染目标视图。由于纹理的概念说来话长,目前我们将纹理理解为一副图像就行了,后面中我们将展开讨论纹理的很多细节内容,。交换链的主缓存和辅助缓存为彩色的图像,为了获得它们的指针,我们一般会调用交换链中的函数GetBuffer。得到指向缓存的指针后,我们调用Direct3D中的函数CreateRenderTargetView,来创建一个渲染目标视图(rendertarget view.)。渲染目标视图含有ID3D11RenderTargetView类型,而CreateRenderTargetView函数将创建我们视图的2D纹理,渲染目标描述,我们创建的ID3D11RenderTargetView的对象地址为其函数变量。将渲染目标描述变量设为空给我们所有的表面的MIP映射水平都为0级,MIP映射水平也将在后面进行详细讨论。我们完成渲染目标的创建之后,就能够释放指针到交换链的后台缓存了。因为得到了COM对象的一个引用,我们必须调用COM中的Release函数来减少引用的数量。这样做会避免内存的泄露,因为我们不想应用程序退出后,系统仍然保留着这里内存,这将导致系统资源的浪费,而这种浪费是不科学的。在每次我们想渲染一个特定的渲染目标的时候,必须在所有的绘制的函数调用之前对它进行设置。这个重任就交给了我们的OMSetRenderTarget函数,这个函数隶属于output merger,在之后会讲到。&代码段4 渲染目标视图的创建和绑定&ID3D11RenderTargetView* backBufferTarget_;
ID3D11Texture2D* backBufferT
HRESULT result = swapChain_-&GetBuffer( 0, __uuidof(ID3D11Texture2D ),
( LPVOID* )&backBufferTexture );
if( FAILED( result ) )
DXTRACE_MSG( &Failed to get the swap chain backbuffer!& );
result = d3dDevice_-&CreateRenderTargetView(backBufferTexture, 0,
&backBufferTarget_ );
if( backBufferTexture )
backBufferTexture-&Release( );
if( FAILED( result ) )
DXTRACE_MSG( &Failed to create the render targetview!& );
d3dContext_-&OMSetRenderTargets( 1, &backBufferTarget_, 0);在代码段4中你会注意到我们采用了一个叫做DXTRACE_MSG的宏。这个宏用作debugging来用。在之后将进行更详细的讲解。&&&&&&&& 五、 视口&Direct3D中 的一个重点同时也是难点在于创建和设置视口。视口定义了我们渲染到屏幕上的面积。在单人或者非分割画面的多人游戏中一般都为全屏,所以我们设置视口的宽度和高度即为交换链的宽度和高度。对于分屏游戏,我们可以创建两个视口,一个视口定义在屏幕上方,另一个定义在屏幕下方。为了渲染分屏视口,我们可以分别以两位不同玩家的角度来渲染。视点的创建由填充D3D11_VIEWPORT函数和设置调用上下文的RSSetViewports函数将其设置到渲染上下文中来完成。RSSetViewports函数需要我们设置的视口数量和视口对象的列举。全屏视口的创建和设置的相关代码在代码段五中有列举,其中X和Y标明左侧和顶部屏幕的位置,最小和最大深度是0到1之间的值,表明了视口深度的最小和最大值。代码段5 &全屏视口的创建和设置&D3D11_VIEWPORT
viewport.Width = static_cast&float&(width);
viewport.Height = static_cast&float&(height);
viewport.MinDepth = 0.0f;
viewport.MaxDepth = 1.0f;
viewport.TopLeftX = 0.0f;
viewport.TopLeftY = 0.0f;
d3dContext_-&RSSetViewports( 1, &viewport );&&&&& 六、清除与显示屏幕渲染到屏幕需要几个不同的步骤。第一步通常是清除相关渲染目标的表面。在大部分游戏中这一步包含了深度缓存等一系列内容。在下一节即将呈现的demo中我们将在本章稍后实施,我们将清除渲染目标视图的颜色缓冲区到一种特定的颜色。这由调用D3D中的ClearRenderTargetView函数来完成。ClearRenderTargetView拥有如下的函数原型:void ClearRenderTargetView( ID3D11RenderTargetView*pRenderTargetView,
const FLOAT ColorRGBA[4] );注:目前的大部分商业游戏中在渲染之前清除颜色缓存并不是必须的,因为像天空这样的环境图形要确保每个像素都会被颜色缓存所覆盖着。ClearRenderTargetView函数以将被清理的渲染目标视图作为其变量。为了清除屏幕,我们设定某种颜色作为我们需要的背景阴影的颜色。这种颜色可以是红色,绿色,蓝色,和透明色Alpha数组中任意指定的0.0到1.0之间的颜色。这里0.0表示强度为0,而1.0表示完全饱满的强度。若对应于字节,1.0对应255。如果为红绿蓝颜色组合都为1.0,则会得到纯白的颜色。下一步就是绘制场景的几何形状了,最后一步是调用交换链的Present函数在屏幕上显示渲染缓冲区的内容。Present函数具有以下的声明:HRESULT IDXGISwapChain::Present( UINT SyncInterval, UINT Flags);对Present函数的参数一个简单的理解:syncinterval 同步间隔,& && flags 演示的标志。在第n个垂直空白之后,Syncinterval能被设置为0,1,2,3,4来显示。垂直空白是当前帧的最后一列更新时间与下一帧的第一列更新时间的时间差。像电脑显示器这样的设备显示更新像素为垂直的,一列一列进行更新的。Present函数的flags值可被设为0,表示输出到每一个缓冲区,设为DXGI_PRESENT_ TEST时则表示测试时不进行输出,或为DXGI_PRESENT_DO_ NOT_SEQUENCE表示不进行排序地利用垂直空白同步输出来显示输出。为达到预期的目的,我们可以只是传递0到Present函数来显示我们的渲染结果。代码段五 展示了一个清屏和显示视图的例子。在后面我们将深入探究颜色缓存,深度存,使画面流畅无比的双缓冲等等。代码段6 &清除渲染目标然后显示显得渲染场景&float clearColor[4] = { 0.0f, 0.0f, 0.25f, 1.0f };
d3dContext_-&ClearRenderTargetView( backBufferTarget_,clearColor );
swapChain_-&Present( 0, 0 );
&七、关于格式有时候我们需要创建指定的DXGI格式。格式可以用于描述一张图像的布局,每种颜色的位数,或者顶点缓存的布局(后面会讲到)。大多数情况下,DXGI格式用于描述交换链中的顶点布局。举个例子,DXGI_FORMAT_R8G8B8A8_UNORM,它表示我们定义的每一个RGBA组成部分的数据都为8位。没指名类型的格式我们称作无类型格式(typeless formats)。他们为每个部分保存相同的位数,但是并不注重包含了什么类型的数据。如DXGI_FORMAT_R32G32B32A32_TYPELESS。常用的清单类型在下面中列出了。&&常用的数据格式类型清单:DXGI_FORMAT_R32G32B32A32_TYPELESS&& &128位RGBA无类型格式DXGI_FORMAT_R32G32B32A32_FLOAT&&128位RGBA浮点型格式DXGI_FORMAT_R32G32B32A32_UINT&& 128位RGBA无符号整型格式DXGI_FORMAT_R32G32B32A32_SINT& &128位RGBA带符号整型格式DXGI_FORMAT_R8G8B8A8_TYPELESS&&32位RGBA无类型格式&DXGI_FORMAT_R8G8B8A8_UINT&&& 32位RGBA无符号整型格式DXGI_FORMAT_R8G8B8A8_SINT &&&32位RGBA带符号整型格式&当定义顶点格式的时候,比如DXGI_FORMAT_R32G32B32_FLOAT格式,就是说RGB值都支持是32位的数据类型。有时候,我们会看到特殊的为每一部分指定相同位数的格式,但是他们有不同的扩展名。举个例子,DXGI_FORMAT_R32G32B32A32_FLOAT 和DXGI_FORMAT_R32G32B32A32_UINT类型的各个部分的位数都是相同的,不同的各个位数上一个是32位的浮点型,一个是32位的无符号整型。&&&&& 八、 善后工作&&&&&& Direct3D应用程序中要做的最后一件事情,就是清除和释放我们创建的对象。举个例子,在应用程序开头,我们要创建一个D3D的设备,一个D3D的渲染上下文,一个交换链,以及一个要渲染的目标。当这个应用程序关闭的时候,我们需要释放这些对象,以将这些资源返还给系统。COM对象保持一个引用计数,告知系统什么时候从内存中移除这些对象是安全的。通过运用Release函数,我们减少了一个对象的引用数量。当引用数量达到0,系统便会回收这些资源。下面是一个释放D3D对象的范例。用首先用if条件句来确保对象不为null,然后调用Release函数。通常我们以和创建时相反的顺序来释放这些对象。&代码段7 &释放Direct3D 11 main对象if( backBufferTarget_ )backBufferTarget_-&Release( );
if( swapChain_ ) swapChain_-&Release( );
if( d3dContext_ ) d3dContext_-&Release( );
if( d3dDevice_ ) d3dDevice_-&Release( );
心得:在释放对象前,我们经常通过检查来确保DirectX对象不为null。因为试图释放一个非法的指针是非常不科学的,这会使我们游戏程序的稳定性荡然无存,经常各种无故崩溃。本篇文章到这里就结束了,谢谢欣赏。感谢一直支持【Visual C++】游戏开发笔记系列专栏的朋友们。【Visual C++】游戏开发 系列文章才刚刚展开一点而已,因为游戏世界实在是太博大精深了~但我们不能着急,得慢慢打好基础。做学问最忌好高骛远,不是吗?&浅墨希望看到大家的留言,希望与大家共同交流,希望得到睿智的评论(即使是批评)。你们的支持是我写下去的动力~&精通游戏开发的路还很长很长,非常希望能和大家一起交流,共同学习,共同进步。大家看过后觉得值得一看的话,可以顶一下这篇文章,你们的支持是我继续写下去的动力~如果文章中有什么疏漏的地方,也请大家指正。也希望大家可以多留言来和我探讨相关的问题。最后,谢谢你们一直的支持~~~&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& ——————————浅墨于日&
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:2687081次
积分:25365
积分:25365
排名:第80名
原创:112篇
转载:24篇
评论:7293条
《逐梦旅程:Windows游戏编程之从零开始》
&这是浅墨专门为热爱游戏编程的朋友们写的入门级游戏编程宝典。如果你喜欢浅墨写的【Visual C++】游戏开发系列博客文章,那么你一定会喜欢这本书。因为书中不仅涵盖了【Visual C++】游戏开发系列专栏的全部内容,而且讲解更加的细致翔实,还有很多独家的游戏编程心得和经验。可以先试读
(期待童鞋们给浅墨的书点五星好评哦,点了五星后发邮件给浅墨,浅墨会给你各种福利~)
当当网推荐语:通俗,幽默,有趣,接地气,内容新,紧跟游戏开发业界发展,游戏编程入门必读书
■ 毛星云,网络ID“浅墨,90后,喜欢IT技术的文艺青年,尤爱游戏编程,图像处理
■ 微软全球最有价值专家
■ 对浅墨的个人新闻报道:、、、、、、等几十余家主流媒体
■ 中国2013年度十大杰出IT博客作者
■ CSDN 2012年博客大赛年度博客之星
■ CSDN 2012年度十大风云专栏作者
■ 常活跃于、、等网络社区
■ 本科毕业于南京航空航天大学中国乌克兰航天联合培养班,获乌克兰国立航空航天大学与南京航空航天大学双学位
■ 现就读于南京航空航天大学航天学院(2013级硕士研究生),将于2016年三月毕业,规划踏入游戏开发行业
PS:CSDN改版后站内私信通知很鸡肋,一般很难及时看到,所以想找到浅墨,最好的方式是微博。
文章:18篇
阅读:477543
文章:57篇
阅读:1579763
(1)(3)(3)(5)(4)(5)(7)(2)(5)(3)(1)(4)(6)(7)(6)(4)(6)(1)(3)(7)(4)(8)(17)(32)(Visual C++)游戏开发笔记二十七 Direct3D 11入门级知识介绍
一、 Direct3D的初始化
初始化Direct3D,我们需要完成以下四个步骤:
1.定义我们需要检查的设备类型(device types)和特征级别(feature levels)
2.创建Direct3D设备,渲染设备(context)和交换链(swap chain)。
3.创建渲染目标(render target)。
4.设置视口(viewport)
这里只是给大家一个框架的概念,各个部分下面会详细展开讲解。
&二、驱动设备类型与特征等级
&在Direct3D 11中我们能使用的设备有硬件设备(hardware device),参考设备(reference device),软件驱动设备(software driver device), 以及WARP设备 (WARP device)。
&硬件设备(hardware device)是一个运行在显卡上的D3D设备,在所有设备中运行速度是最快的。这将是我们日后讨论最多的一种类型。
&参考设备(reference device)是用于没有可用的硬件支持时在CPU上进行渲染的设备。
简言之,参考设备就是利用软件,在CPU对硬件渲染设备的一个模拟。但是不幸的是,这种方式非常的低效,所以在开发过程中,没有其他可用选择的时候,我们才采用这种方式。比如新一代的DirectX发布了,市面上还没有支持这种新版本DirectX的硬件,我们在开发过程中就只能采用这种方式来跑了。
&软件驱动设备(software driverdevice)是开发人员自己编写的用于Direct3D的渲染驱动软件。这种方式通常不推荐用于高性能或者对性能要求苛刻的应用程序,下面介绍的WARP设备将是更好的选择。
&WARP设备(WARPdevice)是一种高效的CPU渲染设备,可以模拟现阶段所有的Direct3D特性。WARP使用了Windows Vista /Windows 7/Winodws 8中的Windows Graphic 运行库中高度优化过的代码作为支撑,这让这种方式出类拔萃,相比与上文提到的参考设备(reference device)模式更加优秀。WARP设备在配置不高的机器上面可以达到化腐朽为神奇的功效。在我们的硬件不支持实时应用程序(real-time application)的情况下,用WARP设备作为替补是一个明智的选择,因为相比而言,参考设备(reference device)的执行效率实在是无法令人恭维。即便如此,WARP设备的执行效率还是不能和硬件设备同日而语,毕竟它依旧是对硬件的一种模拟,即使这种模拟是非常高效的。
&注意:这不是对设备类型一个完整的列举,还有很多细枝末节的设备类型,在这里没必要一一列举
Direct3D的特征等级用于指定需要设定的设备目标。在这个专栏之中,我们将针对三种设备,第一种当然是我们的Direct3D 11设备,第二种为Direct3D 10.1设备,第三种为Direct3D 10.0设备。再这三种设备都无法支持的情况下,我们再选择WARP设备或者参考设备作为后援。
下面贴出来的代码段1为后面我们需要用到的驱动类型和特征级别的一个声明。通过创建各种类型的数组,我们可以使用循环来尝试首先创建我们最需要的设备,然后若执行失败则继续创建其他的设备类型。浅墨记得我们之前提到过,Win32宏ARRAYSIZE能够用来返回一个数组的大小,Win32函数GetClientRect可以用来计算应用程序客户区的大小。算出来的值会用于设置之后的D3D设备渲染的宽度和高度。
另外,需要记住Win32应用程序是分客户区和非客户区的,我们仅能在客户区上进行渲染。
代码段1 指明驱动设备类型和特征等级
GetClientRect( hwnd, &dimensions );&
unsigned int width = dimensions.right - dimensions.&
unsigned int height = dimensions.bottom - dimensions.&
D3D_DRIVER_TYPE driverTypes[] =&
D3D_DRIVER_TYPE_HARDWARE, D3D_DRIVER_TYPE_WARP,D3D_DRIVER_TYPE_SOFTWARE&
unsigned int totalDriverTypes = ARRAYSIZE( driverTypes );&
D3D_FEATURE_LEVEL featureLevels[] =&
D3D_FEATURE_LEVEL_11_0,&
D3D_FEATURE_LEVEL_10_1,&
D3D_FEATURE_LEVEL_10_0&
unsigned int totalFeatureLevels = ARRAYSIZE( featureLevels );&
GetClientRect( hwnd, &dimensions );
unsigned int width = dimensions.right - dimensions.
unsigned int height = dimensions.bottom - dimensions.
D3D_DRIVER_TYPE driverTypes[] =
D3D_DRIVER_TYPE_HARDWARE, D3D_DRIVER_TYPE_WARP,D3D_DRIVER_TYPE_SOFTWARE
unsigned int totalDriverTypes = ARRAYSIZE( driverTypes );
D3D_FEATURE_LEVEL featureLevels[] =
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0
unsigned int totalFeatureLevels = ARRAYSIZE( featureLevels );
&三、设备与交换链的创建
&下一步便是创建一个交换链,交换链在Direct3D中为一个设备渲染目标的集合。每一个设备都有至少一个交换链,而多个交换链能够被多个设备所创建。一个交换目标可以为一个渲染和显示到屏幕上的颜色缓存(在后面会讨论),等等。
通常在游戏中有,有两种颜色缓存,分别叫做主缓存和辅助缓存,他们一起被称为前后台缓存组合。主缓存中的内容(前台缓存)会显示在屏幕上,而辅助缓存(后台缓存)用于绘制下一帧(真是两个好基友-o-)。
渲染的发生非常之快,屏幕的一部分可以在显示器完成显示更新之前,在先前的结果为基础上进行绘制。缓存之间的切换,可以进行一个良性的运作,前台在显示图像,后台正在为前台准备下一刻将要显示的图像,这样做可以避免很多棘手的问题,提高了效率。
这种技术在计算机图形学中叫做双缓冲(doublebuffering),或者叫页面翻转(page flipping)(这种技术我们之前的一系列Win32 GDI demo中使用得比较勤,研究了之前的demo的朋友们应该已经耳濡目染了吧)。一个交换链能拥有一个或者多个这样的缓冲。
代码段2中列出了创建一个交换链的代码。一个交换链的描述用来定义和创建符合我们需要的交换链。
&代码段2 对交换链的设置
DXGI_SWAP_CHAIN_DESC swapChainD&
ZeroMemory( &swapChainDesc, sizeof( swapChainDesc ) );&
swapChainDesc.BufferCount = 1;&
swapChainDesc.BufferDesc.Width =&
swapChainDesc.BufferDesc.Height =&
swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;&
swapChainDesc.BufferDesc.RefreshRate.Numerator = 60;&
swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;&
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;&
swapChainDesc.OutputWindow =&
swapChainDesc.Windowed =&
swapChainDesc.SampleDesc.Count = 1;&
swapChainDesc.SampleDesc.Quality = 0;&
DXGI_SWAP_CHAIN_DESC swapChainD
ZeroMemory( &swapChainDesc, sizeof( swapChainDesc ) );
swapChainDesc.BufferCount = 1;
swapChainDesc.BufferDesc.Width =
swapChainDesc.BufferDesc.Height =
swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferDesc.RefreshRate.Numerator = 60;
swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.OutputWindow =
swapChainDesc.Windowed =
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;
这个范例中定义了D3D的多种取样属性,多重取样(Multisampling)是一种用于采样和平衡渲染像素的创建亮丽色彩变化之间的平滑过渡的一种技术。
缓存的使用和交换链的描述有大量的成员需要设置,但这些设置都是非常简单的。缓存的对交换链的使用是设置下DXGI_USAGE_RENDER_TARGET_OUTPUT,以便交换链能够用于输出,或者换句话说,它能被渲染。
下一步是创建渲染上下文,渲染设备,以及我们拥有的交换链描述。D3D设备一般都是设备本身和硬件之间的通信,而D3D上下文是一种描述设备如何绘制的渲染设备上下文,这也包含了渲染状态和其他的绘图信息。
正如我们讨论过的,交换链是设备和上下文将要绘制的渲染目标。
创建设备上下文,渲染上下文和交换链所需的代码在代码段3中详细列出了,.这段代码为下次内容即将展示的Direct3D 11 BlankWindows Demo的一个片段。
代码段3 Direct3D设备,设备上下文,以及交换链的创建
ID3D11Device device_;&
ID3D11Context d3dContext_;&
IDXGISwapChain swapChain_;&
unsigned int creationFlags = 0;&
#ifdef _DEBUG&&
creationFlags |= D3D11_CREATE_DEVICE_DEBUG;&
unsigned int driver = 0;&
for( driver = 0; driver & totalDriverT ++driver )&
result = D3D11CreateDeviceAndSwapChain( 0, driverTypes[driver],0,&
creationFlags, featureLevels, totalFeatureLevels,&
D3D11_SDK_VERSION, &swapChainDesc, &swapChain_,&
&d3dDevice_, &featureLevel_, &d3dContext_ );&
if( SUCCEEDED( result ) )&
driverType_ = driverTypes[driver];&
if( FAILED( result ) )&
DXTRACE_MSG( &Failed to create the Direct3D device!&);&
ID3D11Device device_;
ID3D11Context d3dContext_;
IDXGISwapChain swapChain_;
unsigned int creationFlags = 0;
#ifdef _DEBUG
creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
unsigned int driver = 0;
for( driver = 0; driver & totalDriverT ++driver )
result = D3D11CreateDeviceAndSwapChain( 0, driverTypes[driver],0,
creationFlags, featureLevels, totalFeatureLevels,
D3D11_SDK_VERSION, &swapChainDesc, &swapChain_,
&d3dDevice_, &featureLevel_, &d3dContext_ );
if( SUCCEEDED( result ) )
driverType_ = driverTypes[driver];
if( FAILED( result ) )
DXTRACE_MSG( &Failed to create the Direct3D device!&);
交换链,设备和渲染上下文可以在单独的Direct3D函数调用中被创建,或者通过特定对象的Direct3D来调用(例如用CreateSwapChain函数来专门创建一个交换链)。
这个函数为D3D11CreateDeviceAndSwapChain。在代码段2中我们在每个驱动类型中循环,试图创建一个合适得设备,或为一个硬件设备,或为一个WARP设备,抑或一个参考设备(reference device)。因为如果创建失败,我们就无法初始化我们的Direct3D。
D3D11CreateDeviceAndSwapChain函数中包含了特征等级作为其参数, 所以如果至少有一个这样的特征等级存在,而且若我们的设备类型也存在,这个函数才会执行成功。
其中D3D11CreateDeviceAndSwapChain函数具有如下的函数原型:
HRESULT D3D11CreateDeviceAndSwapChain(&
&& IDXGIAdapter *pAdapter,&
&& D3D_DRIVER_TYPEDriverType,&
&& HMODULE Software,&
&& UINT Flags,&
&& const D3D_FEATURE_LEVEL*pFeatureLevels,&
&& UINT FeatureLevels,&
&& UINT SDKVersion,&
&& const DXGI_SWAP_CHAIN_DESC *pSwapChainDesc,&
&& IDXGISwapChain **ppSwapChain,&
&& ID3D11Device **ppDevice,&
&& D3D_FEATURE_LEVEL*pFeatureLevel,&
&& ID3D11DeviceContext**ppImmediateContext&
HRESULT D3D11CreateDeviceAndSwapChain(
&& IDXGIAdapter *pAdapter,
&& D3D_DRIVER_TYPEDriverType,
&& HMODULE Software,
&& UINT Flags,
&& const D3D_FEATURE_LEVEL*pFeatureLevels,
&& UINT FeatureLevels,
&& UINT SDKVersion,
&& const DXGI_SWAP_CHAIN_DESC *pSwapChainDesc,
&& IDXGISwapChain **ppSwapChain,
&& ID3D11Device **ppDevice,
&& D3D_FEATURE_LEVEL*pFeatureLevel,
&& ID3D11DeviceContext**ppImmediateContext
&&&& 四、 创建渲染目标视图
一个渲染目标视图是一个由Output MergerStage读取的D3D资源。为了output merger能渲染一个后台缓存的交换链,我们为其创建一个渲染目标视图。
由于纹理的概念说来话长,目前我们将纹理理解为一副图像就行了,后面中我们将展开讨论纹理的很多细节内容,。交换链的主缓存和辅助缓存为彩色的图像,为了获得它们的指针,我们一般会调用交换链中的函数GetBuffer。
得到指向缓存的指针后,我们调用Direct3D中的函数CreateRenderTargetView,来创建一个渲染目标视图(rendertarget view.)。渲染目标视图含有ID3D11RenderTargetView类型,而CreateRenderTargetView函数将创建我们视图的2D纹理,渲染目标描述,我们创建的ID3D11RenderTargetView的对象地址为其函数变量。将渲染目标描述变量设为空给我们所有的表面的MIP映射水平都为0级,MIP映射水平也将在后面进行详细讨论。
我们完成渲染目标的创建之后,就能够释放指针到交换链的后台缓存了。因为得到了COM对象的一个引用,我们必须调用COM中的Release函数来减少引用的数量。这样做会避免内存的泄露,因为我们不想应用程序退出后,系统仍然保留着这里内存,这将导致系统资源的浪费,而这种浪费是不科学的。
在每次我们想渲染一个特定的渲染目标的时候,必须在所有的绘制的函数调用之前对它进行设置。这个重任就交给了我们的OMSetRenderTarget函数,这个函数隶属于output merger,在之后会讲到。
代码段4 渲染目标视图的创建和绑定
ID3D11RenderTargetView* backBufferTarget_;&
ID3D11Texture2D* backBufferT&
HRESULT result = swapChain_-&GetBuffer( 0, __uuidof(ID3D11Texture2D ),&
( LPVOID* )&backBufferTexture );&
if( FAILED( result ) )&
DXTRACE_MSG( &Failed to get the swap chain backbuffer!& );&
result = d3dDevice_-&CreateRenderTargetView(backBufferTexture, 0,&
&backBufferTarget_ );&
if( backBufferTexture )&
backBufferTexture-&Release( );&
if( FAILED( result ) )&
DXTRACE_MSG( &Failed to create the render targetview!& );&
d3dContext_-&OMSetRenderTargets( 1, &backBufferTarget_, 0);&
ID3D11RenderTargetView* backBufferTarget_;
ID3D11Texture2D* backBufferT
HRESULT result = swapChain_-&GetBuffer( 0, __uuidof(ID3D11Texture2D ),
( LPVOID* )&backBufferTexture );
if( FAILED( result ) )
DXTRACE_MSG( &Failed to get the swap chain backbuffer!& );
result = d3dDevice_-&CreateRenderTargetView(backBufferTexture, 0,
&backBufferTarget_ );
if( backBufferTexture )
backBufferTexture-&Release( );
if( FAILED( result ) )
DXTRACE_MSG( &Failed to create the render targetview!& );
d3dContext_-&OMSetRenderTargets( 1, &backBufferTarget_, 0);
在代码段4中你会注意到我们采用了一个叫做DXTRACE_MSG的宏。这个宏用作debugging来用。在之后将进行更详细的讲解。
&&&&&& 五、 视口
Direct3D中 的一个重点同时也是难点在于创建和设置视口。
视口定义了我们渲染到屏幕上的面积。在单人或者非分割画面的多人游戏中一般都为全屏,所以我们设置视口的宽度和高度即为交换链的宽度和高度。对于分屏游戏,我们可以创建两个视口,一个视口定义在屏幕上方,另一个定义在屏幕下方。为了渲染分屏视口,我们可以分别以两位不同玩家的角度来渲染。
视点的创建由填充D3D11_VIEORT函数和设置调用上下文的RSSetViewports函数将其设置到渲染上下文中来完成。RSSetViewports函数需要我们设置的视口数量和视口对象的列举。全屏视口的创建和设置的相关代码在代码段五中有列举,其中X和Y标明左侧和顶部屏幕的位置,最小和最大深度是0到1之间的值,表明了视口深度的最小和最大值。
代码段5& 全屏视口的创建和设置
D3D11_VIEWPORT&
viewport.Width = static_cast&float&(width);&
viewport.Height = static_cast&float&(height);&
viewport.MinDepth = 0.0f;&
viewport.MaxDepth = 1.0f;&
viewport.TopLeftX = 0.0f;&
viewport.TopLeftY = 0.0f;&
d3dContext_-&RSSetViewports( 1, &viewport );&
D3D11_VIEWPORT
viewport.Width = static_cast&float&(width);
viewport.Height = static_cast&float&(height);
viewport.MinDepth = 0.0f;
viewport.MaxDepth = 1.0f;
viewport.TopLeftX = 0.0f;
viewport.TopLeftY = 0.0f;
d3dContext_-&RSSetViewports( 1, &viewport );
&&&& 六、清除与显示屏
渲染到屏幕需要几个不同的步骤。第一步通常是清除相关渲染目标的表面。在大部分游戏中这一步包含了深度缓存等一系列内容。在下一节即将呈现的demo中我们将在本章稍后实施,我们将清除渲染目标视图的颜色缓冲区到一种特定的颜色。这由调用D3D中的ClearRenderTargetView函数来完成。ClearRenderTargetView拥有如下的函数原型:
void ClearRenderTargetView( ID3D11RenderTargetView*pRenderTargetView,&
const FLOAT ColorRGBA[4] );&
void ClearRenderTargetView( ID3D11RenderTargetView*pRenderTargetView,
const FLOAT ColorRGBA[4] );
注:目前的大部分商业游戏中在渲染之前清除颜色缓存并不是必须的,因为像天空这样的环境图形要确保每个像素都会被颜色缓存所覆盖着。
ClearRenderTargetView函数以将被清理的渲染目标视图作为其变量。为了清除屏幕,我们设定某种颜色作为我们需要的背景阴影的颜色。这种颜色可以是红色,绿色,蓝色,和透明色Alpha数组中任意指定的0.0到1.0之间的颜色。这里0.0表示强度为0,而1.0表示完全饱满的强度。若对应于字节,1.0对应255。如果为红绿蓝颜色组合都为1.0,则会得到纯白的颜色。下一步就是绘制场景的几何形状了,最后一步是调用交换链的Present函数在屏幕上显示渲染缓冲区的内容。
Present函数具有以下的声明:
HRESULT IDXGISwapChain::Present( UINT SyncInterval, UINT Flags);&
HRESULT IDXGISwapChain::Present( UINT SyncInterval, UINT Flags);
对Present函数的参数一个简单的理解:syncinterval 同步间隔,
&&&&&&&&& &flags 演示的标志。
在第n个垂直空白之后,Syncinterval能被设置为0,1,2,3,4来显示。垂直空白是当前帧的最后一列更新时间与下一帧的第一列更新时间的时间差。像电脑显示器这样的设备显示更新像素为垂直的,一列一列进行更新的。
Present函数的flags值可被设为0,表示输出到每一个缓冲区,设为DXGI_PRESENT_ TEST时则表示测试时不进行输出,或为DXGI_PRESENT_DO_ NOT_SEQUENCE表示不进行排序地利用垂直空白同步输出来显示输出。为达到预期的目的,我们可以只是传递0到Present函数来显示我们的渲染结果。
代码段五 展示了一个清屏和显示视图的例子。在后面我们将深入探究颜色缓存,深度存,使画面流畅无比的双缓冲等等。
代码段6& 清除渲染目标然后显示显得渲染场景
float clearColor[4] = { 0.0f, 0.0f, 0.25f, 1.0f };&
d3dContext_-&ClearRenderTargetView( backBufferTarget_,clearColor );&
swapChain_-&Present( 0, 0 );&
float clearColor[4] = { 0.0f, 0.0f, 0.25f, 1.0f };
d3dContext_-&ClearRenderTargetView( backBufferTarget_,clearColor );
swapChain_-&Present( 0, 0 );
七、关于格式
有时候我们需要创建指定的DXGI格式。格式可以用于描述一张图像的布局,每种颜色的位数,或者顶点缓存的布局(后面会讲到)。大多数情况下,DXGI格式用于描述交换链中的顶点布局。
举个例子,DXGI_FORMAT_R8G8B8A8_UNORM,它表示我们定义的每一个RGBA组成部分的数据都为8位。
没指名类型的格式我们称作无类型格式(typeless formats)。他们为每个部分保存相同的位数,但是并不注重包含了什么类型的数据。如DXGI_FORMAT_R32G32B32A32_TYPELESS。在常用的清单类型在下面中列出了。
常用的数据格式类型清单:
DXGI_FORMAT_R32G32B32A32_TYPELESS&&& 128位RGBA无类型格式
DXGI_FORMAT_R32G32B32A32_FLOAT& 128位RGBA浮点型格式
DXGI_FORMAT_R32G32B32A32_UINT&& 128位RGBA无符号整型格式
DXGI_FORMAT_R32G32B32A32_SINT&& 128位RGBA带符号整型格式
DXGI_FORMAT_R8G8B8A8_TYPELESS& 32位RGBA无类型格式
&DXGI_FORMAT_R8G8B8A8_UINT&&& 32位RGBA无符号整型格式
DXGI_FORMAT_R8G8B8A8_SINT&&& 32位RGBA带符号整型格式
&当定义顶点格式的时候,比如DXGI_FORMAT_R32G32B32_FLOAT格式,就是说RGB值都支持是32位的数据类型。有时候,我们会看到特殊的为每一部分指定相同位数的格式,但是他们有不同的扩展名。
举个例子,DXGI_FORMAT_R32G32B32A32_FLOAT 和DXGI_FORMAT_R32G32B32A32_UINT类型的各个部分的位数都是相同的,不同的各个位数上一个是32位的浮点型,一个是32位的无符号整型。
&八、 善后工作
&&&&&& Direct3D应用程序中要做的最后一件事情,就是清除和释放我们创建的对象。举个例子,在应用程序开头,我们要创建一个D3D的设备,一个D3D的渲染上下文,一个交换链,以及一个要渲染的目标。当这个应用程序关闭的时候,我们需要释放这些对象,以将这些资源返还给。
COM对象保持一个引用计数,告知系统什么时候从内存中移除这些对象是安全的。通过运用Release函数,我们减少了一个对象的引用数量。当引用数量达到0,系统便会回收这些资源。
下面是一个释放D3D对象的范例。用首先用if条件句来确保对象不为null,然后调用Release函数。通常我们以和创建时相反的顺序来释放这些对象。
& 代码段7& 释放Direct3D 11 main对象
if( backBufferTarget_ )backBufferTarget_-&Release( );&
if( swapChain_ ) swapChain_-&Release( );&
if( d3dContext_ ) d3dContext_-&Release( );&
if( d3dDevice_ ) d3dDevice_-&Release( );&
if( backBufferTarget_ )backBufferTarget_-&Release( );
if( swapChain_ ) swapChain_-&Release( );
if( d3dContext_ ) d3dContext_-&Release( );
if( d3dDevice_ ) d3dDevice_-&Release( );
心得:在释放对象前,我们经常通过检查来确保DirectX对象不为null。因为试图释放一个非法的指针是非常不科学的,这会使我们游戏程序的稳定性荡然无存,经常各种无故崩溃。
&作者:zhmxy555
您对本文章有什么意见或着疑问吗?请到您的关注和建议是我们前行的参考和动力&&
您的浏览器不支持嵌入式框架,或者当前配置为不显示嵌入式框架。

我要回帖

更多关于 3d max 入门教程 的文章

 

随机推荐