热门标签 | HotTags
当前位置:  开发笔记 > 后端 > 正文

第17章他山之石可攻玉——三维游戏模型的载入

17.1网格模型技术的前生今世网格模型是一种将物体模型的顶点数据、纹理、材质等信息存储在一个外部文件中的3D物体模型。对于那些简单的图元描述的图形,比如点、线、三角形等等,我们可以

17.1 网格模型技术的前生今世

网格模型是一种将物体模型的顶点数据、纹理、材质等信息存储在一个外部文件中的3D 物体模型。对于那些简单的图元描述的图形,比如点、线、三角形等等,我们可以通过写代码指定顶点数据、索引数据、法线向量、纹理和材质等信息。但对于复杂的3D 物体的话, 采用这种方式显然是不现实的。因此,Direct3D 提供了一种称作网格模型的技术,可以从各种特定的文件格式中读取和绘制3D 图形,极大地方便了游戏的开发。

使用网格模型最普遍的方式是从外部的3D 模型文件中加载一个网格。而这些3D 模型通常都是由3D 建模软件生成的,比较复杂的网格数据。目前市面上主流的3D 建模软件有3DS Max 和Maya。而目前流行的3D 模型文件格式有.3ds 、.max 、.obj 以及.mb 。其中.3ds 、. max 为3DS Max常用的格式, .mb 为Maya 常用的格式, 而.obj 为3DSMax 和Maya 通用的文件格式。

17.2 认识三维建模软件3DS Max 和Maya

再不厌其烦地说一遍,我们在通常的三维游戏开发中, 常常要涉及到非常复杂的三维物体数据模型,如果用我们之前讲的知识,顶点缓存索引缓存,通过写代码来构造这些三维模型, 显然是不合实际的。
这些复杂物体的模型通常需要利用专业的三维建模软件来制作。目前市面上主流的3D 建模软件有3DS Max 和Maya。这两款在三维建模行业作为竞争对手的软件,目前同为Autodesk 公司所有, 这倒是有些令人匪夷所思。下面我们先来分别介绍一下当前市场上主流的三维建模软件(当然,不仅仅是用于三维建模这么简单) 3DS Max 和Maya。

1. 3DS Max 软件简介
"D"\Program Files\Autodesk\3ds Max 2012\plugins" 目录中添加dle 插件文件;
然后, 重启一下3DS Max 2012 ,我们就可以发现, 导出选项中有了X 文件导出项了。
第三个要素, 也就是导出X 文件的具体步骤了。
首先, 正所谓巧妇难为无米之炊,我们需要有一个导出的对象,也就是一个三维的模型。模型嘛, 可以自己用3DS Max现场做,也可以自己去网上下载,这里推荐一个论坛, 有大量的3D 模型资源:http://www.cgmodel.com/
最后经过3DS Max 的处理,我们就发现在我们设定的保存路径下多了X 文件以及配套的纹理了。

如果我们想要在我们写的游戏程序中使用这个三维模型,把这两个文件一起放到我们工程中相应的地方就可以了。
另外提一点, Maya 中可以用cvXporter 插件来导出X 文件,对应于3DS Max 中的Panda 插件。
之前讲了那么多元非是在为X 文件的诞生造势,下面就开始隆重介绍如何在Direct3D 程序中载入X 文件的具体知识吧。
想利用X 文件来在游戏程序中载入三维模型的话,首先就需要将X 文件中的各种数据分别加载到内存中,而这些数据主要包括顶点数据、材质数据和纹理数据等等。首先,我们需要介绍一下与网格模型相关的一个重要的接口——ID3DXMESH 。

17.5 网格模型接口ID3DXMESH

在Direct3D 中,微软为我们提供了ID3DXMesh 接口表示网格,这个接口继承自ID3DXBaseMesh 接口。网格模型接口ID3DXMesh 实际上是三维物体的顶点缓存的集合,他将为我们创建顶点缓存、定义灵活顶点格式和绘制顶点缓冲区等功能封装在一个COM 对象中,这样复杂三维物体的绘制就显得非常简便了。
其中, ID3DXMESH 接口中的D3DXCreateMesh()可用于创建一个Direct3D 网格模型对象,我们可以在MSDN 中查到该函数声明是这样的:

 HRESULT  D3DXCreateMesh(
  __in   DWORD NumFaces,
  __in   DWORD NumVertices,
  __in   DWORD Options,
  __in   const LPD3DVERTEXELEMENT9 *pDeclaration,
  __in   LPDIRECT3DDEVICE9 pD3DDevice,
  __out  LPD3DXMESH *ppMesh
);

这个函数的参数说明如下。
  •  第一个参数, DWORD 类型的NumFaces ,表示创建网格模型的多边形数目。
  •  第二个参数, DWORD 类型的NumVertices ,表示创建网格的顶点数目。
  •  第三个参数, DWORD 类型的Options ,表示创建网格时的附加选项,他的取值为D3DXMESH枚举体中的一个或者多个值。这个枚举体定义如下:
typedef enum D3DXMESH {
  D3DXMESH_32BIT                   = 0x001,
  D3DXMESH_DOnOTCLIP= 0x002,
  D3DXMESH_POINTS                  = 0x004,
  D3DXMESH_RTPATCHES               = 0x008,
  D3DXMESH_NPATCHES                = 0x4000,
  D3DXMESH_VB_SYSTEMMEM            = 0x010,
  D3DXMESH_VB_MANAGED              = 0x020,
  D3DXMESH_VB_WRITEOnLY= 0x040,
  D3DXMESH_VB_DYNAMIC              = 0x080,
  D3DXMESH_VB_SOFTWAREPROCESSING   = 0x8000,
  D3DXMESH_IB_SYSTEMMEM            = 0x100,
  D3DXMESH_IB_MANAGED              = 0x200,
  D3DXMESH_IB_WRITEOnLY= 0x400,
  D3DXMESH_IB_DYNAMIC              = 0x800,
  D3DXMESH_IB_SOFTWAREPROCESSING   = 0x10000,
  D3DXMESH_VB_SHARE                = 0x1000,
  D3DXMESH_USEHWOnLY= 0x2000,
  D3DXMESH_SYSTEMMEM               = 0x110,
  D3DXMESH_MANAGED                 = 0x220,
  D3DXMESH_WRITEOnLY= 0x440,
  D3DXMESH_DYNAMIC                 = 0x880,
  D3DXMESH_SOFTWAREPROCESSING      = 0x18000 
} D3DXMESH, *LPD3DXMESH;

一般情况下,我们都把这个Options 取为D3DXMESH_ SYSTEMMEM 或者D3DXMESH_MANAGED , 表示对Direct3D 顶点缓冲区和索引缓冲区使用D3DPOOL_SYSTEMMEM 或者D3DPOOL_MANAGED 内存。
  •  第四个参数, const LPD3DVERTEXELEMENT9 类型的*pDeclaration , 表示顶点包含哪些信息。这个参数的作用类似于我们之前一直在用的灵活顶点格式(FVF ),表示顶点包含了哪些具体数据,但是它却高于灵活顶点格式。它的类型LPD3DVERTEXELEMENT9 表示顶点元素, 主要用于我们来没讲到的可编程渲染流水线之中,在此我们暂且不用去多做考虑。
  • 第五个参数, LPDIRECT3DDEVICE9 类型的pD3DDevice,就是我们的金钥匙, Direct3D设备的指针了。
  •  第六个参数,LPD3DXMESH 类型的*ppMesh,指向我们创建好的网格模型对象指针的地址,用于返回创建好的网格模型对象。可以说我们调用D3DXCreateMesh 就是为了创建并得到这个指针地址。后面关于我们创建好的网格模型的访问,都靠这个ppMesh 参数了。
需要注意的是,这个创建好网格模型对象的D3DXCreateMesh 函数我们通常很少直接去用它,而且直接用它往往也意义不大。它总是“ 真人不露相”,被封装在了其他Direct3D 函数之中,默默地为我们服务。
介绍完网格模型接口ID3DXMESH 相关的知识,下面就来看看从X 文件载入模型的具体步骤。

17.6 文件模型载入三步曲

17.6.1 三步曲之一:通过X文件加载网格模型

上面讲到了D3DXCreateMesh 函数很少去直接应用,因为我们常常都是通过载入X 文件来生成网格模型的,用到的函数是D3DXLoadMeshFromX 。我们可以在MSDN 中查到这个函数的声明如下:
 HRESULT  D3DXLoadMeshFromX(
  __in   LPCTSTR pFilename,
  __in   DWORD Options,
  __in   LPDIRECT3DDEVICE9 pD3DDevice,
  __out  LPD3DXBUFFER *ppAdjacency,
  __out  LPD3DXBUFFER *ppMaterials,
  __out  LPD3DXBUFFER *ppEffectInstances,
  __out  DWORD *pNumMaterials,
  __out  LPD3DXMESH *ppMesh
);
  • 第一个参数, LPCTSTR 类型的pFilename,显然就是一个指向我们需要加载的X 文件的磁盘路径和文件名的字符串了。
  • 第二个参数, DWORD 类型的Options ,表示创建网格时的附加选项,他的取值为D3DXMESH 枚举体中的一个或者多个值。这个参数我们刚才在讲D3DXCreateMesh 时已经讲过了,具体参看D3DXCreateMesh的第三个参数,在这里就不赘述了。
  • 第三个参数,LPDIRECT3DDEVICE9 类型的pD3DDevice,也就是我们的金钥匙, Direct3D设备的指针。
  • 第四个参数, LPD3DXBUFFER 类型的*ppAdjacency ,用于保存加载网格的邻接信息,也就是包含每个多边形周围的多边形信息的缓冲区的内存地址。
  • 第五个参数, LPD3DXBUFFER 类型的*ppMaterials ,用于保存网格的所有子集的材质,指向用于存储模型材质和纹理文件名的缓冲区的地址,而材质的数目存在之后第七个参数pNum扣laterials 中了。
  • 第六个参数,LPD3DXBUFFER 类型的*ppEffectlnstances ,用于存储网格模型的特殊效果,指向用于存储模型效果实例的缓冲区的内存地址,这个参数通常设为NULL 就可以了。
  • 第七个参数,DWORD 类型的*pNumMaterials,它配合着第五个参数,用于存储所有子集材质的数目。
  • 第八个参数,LPD3DXMESH 类型的*ppMesh ,指向我们从文件生成的Direct3D 网格模型指针的地址。可以说我们调用D3DXLoadM eshFrornX 就是为了从文件加载X 文件的模型信息并进行模型的创建,从而能得到这个指向创建好的模型的指针地址。后面关于我们创建好的网格模型的访问,都靠这个ppMesh 参数了。
可以发现,在上面我们的D3DXLoadMeshFromX 函数中引入了一个新的Direct3D 类型, 它就是LPD3DXBUFFER。LPD3DXBUFFER 因数据操作的方便性而诞生,我们称它为泛型数据结构。它的好处是可以存储顶点位置坐标、材质、纹理等多种类型的Direct3D 数据,而不必对每种数据都去声明一种函数接口类型。可使用接口函数ID3DXBuffer::GetBufferPointer()获取缓冲区中的数据, 使用ID3DXBuffer::GetBufferSize()获得缓冲区数据大小,这两个接口函数的声明如下:
LPVOID GetBufferPointer();
SIZE_T GetBufferSize();
没错,这就是原型声明,因为这两个函数都没有参数,所以它们的身子显得非常单薄。
比如,我们要从网格模型中提取材质属性和纹理文件名,那么代码就是像这样写:
// 读取材质和纹理数据
D3DXMATERIAL *pMtrls = (D3DXMATERIAL*)pMtrlBuffer->GetBufferPointer(); 

17.6.2 三步曲之二:载入材质和纹理

如果之前的D3DXLoadMeshFromX 函数调用成功的话,那么参数ppMaterials 就会获得.x 文件中三维模型的材质和纹理等信息,而pNumMaterials 参数就会获得材质的数目。
X文件中的材质信息是以D3DXMATERIAL 结构类型的数组形式储存的。其中,该结构定义了D3DMATERIAL9 结构类型的成员和一个指向以NULL 结尾的字符串指针, 而该字符串用于指定与网格子集相关的纹理贴图文件名。
我们可以在MSDN 中查到D3DXMATERIAL 结构体的定义如下:
typedef struct D3DXMATERIAL {
  D3DMATERIAL9 MatD3D;
  LPSTR        pTextureFilename;
} D3DXMATERIAL, *LPD3DXMATERIAL;
当我们加载X 文件后,需要遍历整个D3DXMATERIAL 结构类型的数组,用于取出保存在ID3DXBuffer 接口对象中的材质信息。由于X 文件中并未存储具体的纹理数据, 它只包含纹理贴图的文件名,因此需要我们自己根据该文件名创建相应的纹理对象。
就像这样:
	// 从X文件中加载网格数据
	LPD3DXBUFFER pAdjBuffer  = NULL;
	LPD3DXBUFFER pMtrlBuffer = NULL;

	D3DXLoadMeshFromX(L"miki.X", D3DXMESH_MANAGED, g_pd3dDevice, 
		&pAdjBuffer, &pMtrlBuffer, NULL, &g_dwNumMtrls, &g_pMesh);

	// 读取材质和纹理数据
	D3DXMATERIAL *pMtrls = (D3DXMATERIAL*)pMtrlBuffer->GetBufferPointer(); //创建一个D3DXMATERIAL结构体用于读取材质和纹理信息
	g_pMaterials = new D3DMATERIAL9[g_dwNumMtrls];
	g_pTextures  = new LPDIRECT3DTEXTURE9[g_dwNumMtrls];

	for (DWORD i=0; i 
  

17.6.3 三步曲之三:绘制网格模型

完成前两步做好准备工作之后,也就是生成X文件网格以及材质和纹理的读取之后,接下来就是把我们准备的内容绘制出来就行了。我们依然是用ID3DXMesh 接口的DrawSubset 方法绘制网格中的每个子集的。但是由于绘制的部分比较多,对每个部分的绘制,我们都需要专门为其进行材质和纹理的设置,然后才进行绘制,所以一般我们在绘制从X 文件读取的三维模型的时候, 一般用一个for 循环来进行绘制,就像这样:
	g_pd3dDevice->BeginScene();                     // 开始绘制
	
	// 用一个for循环,进行网格各个部分的绘制
	for (DWORD i = 0; i SetMaterial(&g_pMaterials[i]);
		g_pd3dDevice->SetTexture(0, g_pTextures[i]);
		g_pMesh->DrawSubset(i);
	}
	g_pd3dDevice->EndScene();                       // 结束绘制

一些关于使用模型的小技巧:
 用三维建模工具制作的三维模型通常比较复杂,多边形数量很多。而多边形数量越多,图形的渲染速度就越慢,所以在制作模型时,在不明显影响视觉效果的情况下,应尽量减少多边形的数量。
 三维模型自身的尺寸在模型制作时就确定好的, 不同的模型的尺寸可能是千差万别,因此在渲染网格模型时要针对模型进行适当的缩放。当然,最好是在制作或者导出模型时,就在建模软件中进行适当的缩放调整。
 渲染网格模型时,通常需要进行光照处理和纹理映射,所以需要对光照和纹理进行相关的设置。对于纹理,如果我们忘了进行相关的设直,Direct3D 也会自动使用默认设置,而Direct3D 中是没有设置默认光源的,所以如果忘记设置光源的话,通常会造成模型的显示不正确。所以大家记得养成好的习惯,在渲染前设直光源,需要长点心了。
 如果程序在读取X 文件时内存报错,看看是否把所有的纹理素材都放到X 文件所在的路径下了, 如果不行。把纹理全部取NULL,即g_pTextures[i]=NULL,然后注释掉接下来的
D3DXCreateTextureFromFileA(g_pd3dDevice, pMtrls[i].pTextureFilename, &g_pTextures[i]);再试试。
 模型的旋转是绕自身坐标轴的旋转,而不是绕世界坐标系的三个坐标轴进行旋转,而且在旋转模型的时候,它的自身坐标轴也在不断改变,大家需要注意.
 在从网格模型数据中提取纹理文件名为网格模型创建纹理对象的时候,需要注意的是,提取的文件名可能是纹理文件的绝对路径。这个绝对路径常常是在利用三维建模软件时指定的路径,这个路径与当前纹理文件的路径一般是不一样的。因此在创建纹理的时候,常常就因为找不到纹理文件而报内存溢出类的错误.这个时候,我们就应该对提取到的纹理文件的绝对路径进行处理,即删除路径部分,只保留纹理文件名。这样在创建纹理对象时就可以从当前的路径搜索纹理文件。示例代码如下:
// Desc : 从绝对路径中提取纹理文件名
//------------------------------------------------------------------------
 voi d RemovePathFromFileName(LPSTR fullPath , LPWSTR fileName )
 {
   //先将full Path 的类型变换为LPWSTR
   WCHAR wszBuf [MAX_PATH] ;
   MultiByteToWideChar ( CP_ACP, 0, fullPath , -1, wszBuf, MAX_PATH ) ;
   wszBuf[MAX PATH - 1] = L'\0';

   WCHAR* wszFullPath = wszBuf ;

   //从绝对路径中提取文件名
   LPWSTR pch=wc srchr (wszFullPath, ’\\’ );
   if (pch)
       lstrcpy (fileName, ++pch) ;
   else
       lstrcpy (fileName, wszFullPath) ;
 }

17.6.4 总结与升华

总结一下,从X 文件读取模型并进行绘制其实很简单,就三步工作,简明扼要12个字的三步曲:加载网格,加载材质纹理, 绘制。
我们依然可以利用如下的核心代码米加强理解和记忆:

       //三部曲之一:从X文件中加载网格数据
	LPD3DXBUFFER pAdjBuffer  = NULL;
	LPD3DXBUFFER pMtrlBuffer = NULL;

	D3DXLoadMeshFromX(L"miki.X", D3DXMESH_MANAGED, g_pd3dDevice, 
		&pAdjBuffer, &pMtrlBuffer, NULL, &g_dwNumMtrls, &g_pMesh);

	//三部曲之二: 读取材质和纹理数据
	D3DXMATERIAL *pMtrls = (D3DXMATERIAL*)pMtrlBuffer->GetBufferPointer(); //创建一个D3DXMATERIAL结构体用于读取材质和纹理信息
	g_pMaterials = new D3DMATERIAL9[g_dwNumMtrls];
	g_pTextures  = new LPDIRECT3DTEXTURE9[g_dwNumMtrls];

	for (DWORD i=0; i 
  

另外, 在载入模型三步曲之二中,这句g_pMaterials[i].Ambient = g_pMaterials[i].Diffuse 是用于将材质对漫反射光的反应程度赋值给材质的环境光反应程度。这句加上和不加上渲染出来的模型会有不同的环境光效果,大家可以自己把这句注释起来重新编译运行一下。

void Direct3D_Render(HWND hwnd)
{
	g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(150, 150, 100), 1.0f, 0);
	// 三部曲之三:绘制
	g_pd3dDevice->BeginScene();                     // 开始绘制
	
	// 用一个for循环,进行网格各个部分的绘制
	for (DWORD i = 0; i SetMaterial(&g_pMaterials[i]);
		g_pd3dDevice->SetTexture(0, g_pTextures[i]);
		g_pMesh->DrawSubset(i);
	}
	g_pd3dDevice->EndScene();                       // 结束绘制
	g_pd3dDevice->Present(NULL, NULL, NULL, NULL);  // 翻转与显示
	 
}

17.7 示例程序D3demo12

这个程序的核心代码,其实重点部分在前面的X 文件模型载入三步曲核心代码中已经贴出过,这里只需要了解它们是放在哪里的就可以了。
运行这个程序,我们便会得到如下的效果, 一个高质量的初音模型,非常精致:

17.8 章节小憩

学完精彩绝伦的这章,我们从对3D 建模一无所知到了解了3DS Max 和Maya 这两大三维建模软件的威力,并可以熟练地从3DS Max 中导出帅气的人物模型成X 文件,供我们写的程序所用,其可谓是一趟奇幻的旅程。









推荐阅读
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • Metasploit攻击渗透实践
    本文介绍了Metasploit攻击渗透实践的内容和要求,包括主动攻击、针对浏览器和客户端的攻击,以及成功应用辅助模块的实践过程。其中涉及使用Hydra在不知道密码的情况下攻击metsploit2靶机获取密码,以及攻击浏览器中的tomcat服务的具体步骤。同时还讲解了爆破密码的方法和设置攻击目标主机的相关参数。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • 本文介绍了在Win10上安装WinPythonHadoop的详细步骤,包括安装Python环境、安装JDK8、安装pyspark、安装Hadoop和Spark、设置环境变量、下载winutils.exe等。同时提醒注意Hadoop版本与pyspark版本的一致性,并建议重启电脑以确保安装成功。 ... [详细]
  • 【Windows】实现微信双开或多开的方法及步骤详解
    本文介绍了在Windows系统下实现微信双开或多开的方法,通过安装微信电脑版、复制微信程序启动路径、修改文本文件为bat文件等步骤,实现同时登录两个或多个微信的效果。相比于使用虚拟机的方法,本方法更简单易行,适用于任何电脑,并且不会消耗过多系统资源。详细步骤和原理解释请参考本文内容。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • t-io 2.0.0发布-法网天眼第一版的回顾和更新说明
    本文回顾了t-io 1.x版本的工程结构和性能数据,并介绍了t-io在码云上的成绩和用户反馈。同时,还提到了@openSeLi同学发布的t-io 30W长连接并发压力测试报告。最后,详细介绍了t-io 2.0.0版本的更新内容,包括更简洁的使用方式和内置的httpsession功能。 ... [详细]
  • 本文介绍了在SpringBoot中集成thymeleaf前端模版的配置步骤,包括在application.properties配置文件中添加thymeleaf的配置信息,引入thymeleaf的jar包,以及创建PageController并添加index方法。 ... [详细]
  • 本文详细介绍了MysqlDump和mysqldump进行全库备份的相关知识,包括备份命令的使用方法、my.cnf配置文件的设置、binlog日志的位置指定、增量恢复的方式以及适用于innodb引擎和myisam引擎的备份方法。对于需要进行数据库备份的用户来说,本文提供了一些有价值的参考内容。 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 开发笔记:计网局域网:NAT 是如何工作的?
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了计网-局域网:NAT是如何工作的?相关的知识,希望对你有一定的参考价值。 ... [详细]
author-avatar
高人arm
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有