以文本方式查看主题 - 中文XML论坛 - 专业的XML技术讨论区 (http://bbs.xml.org.cn/index.asp) -- 『 C/C++编程思想 』 (http://bbs.xml.org.cn/list.asp?boardid=61) ---- 网格模型高级技术(4) (http://bbs.xml.org.cn/dispbbs.asp?boardid=61&rootid=&id=71128) |
-- 作者:卷积内核 -- 发布时间:1/5/2009 1:42:00 PM -- 网格模型高级技术(4) 索引顶点混合 Direct3D通过索引顶点混合,扩展了使用多个混合矩阵对顶点进行混合的支持。在索引顶点混合中,混合矩阵被保存在矩阵调色板(可以看成一个矩阵数组)中,使用矩阵索引来引用特定的混合矩阵。矩阵索引使用8位无符号整数表示,由每个顶点提供,因为Direct3D中限定每个顶点最多受到4个混合矩阵的影响,所以每个顶点最多具有4个矩阵索引,每个顶点的矩阵索引被组合成一个DWORD类型的整数存储和表示。因为每个顶点最多受4个混合矩阵的影响,所以在渲染一个三角形时最多可能需要使用12个混合矩阵,在这种情况下,矩阵调色板中最少需要包含12个混合矩阵。 下图演示了一个顶点使用矩阵索引引用4个混合矩阵,图中的顶点通过矩阵索引引用矩阵调色板中索引为0、2、5、6的混合矩阵: 下图演示了渲染一个三角形时,如何通过矩阵索引引用矩阵调色板中的12个混合矩阵: 在渲染图形时,要想启用索引顶点混合,需要将渲染状态D3DRS_INDEXEDVERTEXBLENDENABLE设置为TRUE,如果启用了索引顶点混合,则在顶点数据中就需要包含矩阵索引数据。如果将渲染状态D3DRS_INDEXEDVERTEXBLENDENABLE设置为FALSE则禁用索引顶点混合,但是又启用了图形混合功能,这时在顶点数据中就不需要包含矩阵索引数据,每个顶点的矩阵索引默认为0~3。 D3DRS_INDEXEDVERTEXBLENDENABLE MaxVertexBlendMatrixIndex When software vertex processing is used, 256 matrices could be used for indexed vertex blending, with or without normal blending. For a given physical device, this capability may vary across Direct3D devices depending on the parameters supplied to IDirect3D9::CreateDevice. 综合上面图形混合和索引顶点混合的内容,对顶点混合可以总结如下: (1)不管是否使用索引顶点混合,混合矩阵都是通过索引从矩阵调色板中引用。 (2)如果不使用索引顶点混合,则混合矩阵索引最大为3,而且在顶点数据中不需要包含矩阵索引,矩阵索引默认为0~3。 (3)如果使用索引顶点混合,矩阵索引最大为255,而且在顶点数据中需要指定影响该顶点混合矩阵的索引。 (4)使用图形混合之前,首先需要检查当前设备支持的调色板容量是否满足要求,因为使用软件顶点处理模式时,最大混合矩阵索引为255,肯定能满足要求,所以如果硬件顶点处理模式下矩阵调色板容量不能满足要求,只需简单转换为使用软件顶点处理模式即可。
蒙皮骨骼动画跟单纯的骨骼动画相比,只是使用了顶点混合技术,解决单纯的骨骼动画存在的问题。 首先我们需要定义包含顶点混合权重和法线向量的结构体,还需要定义两个混合矩阵: struct sBlendVertex{ D3DXVECTOR3 pos; float blend; D3DXVECTOR3 normal;}; 接着在回调函数ModifyDeviceSetttings()中用下列语句检查硬件是否支持索引顶点混合,如果当前硬件不支持索引顶点混合,则使用软件顶点处理模式: if(pDeviceSettings->BehaviorFlags & D3DCREATE_HARDWARE_VERTEXPROCESSING) pDeviceSettings->BehaviorFlags = D3DCREATE_MIXED_VERTEXPROCESSING; g_dxut_mesh = new CDXUTMesh();V_RETURN(g_dxut_mesh->Create(pd3dDevice, L"mslogo.x")); float scale = (float) fTime; 因为要进行顶点混合,所以函数SetTransform()将要设置的不再是普通的世界坐标变换矩阵,而是骨骼变换矩阵,所以它的第一个参数应该采用宏D3DTS_WORLDMATRIX(index)的格式,而不像以前一样使用标识符D3DTS_WORLD。宏D3DTS_WORLDMATRIX(index)的定义如下: #define D3DTS_WORLDMATRIX(index) (D3DTRANSFORMSTATETYPE)(index + 256) 该宏负责将索引号index映射到相应的变换状态下。 在设置好各个骨骼矩阵之后,在回调函数OnFrameRender()中渲染网格模型: g_dxut_mesh->Render(pd3dDevice);
运行截图: |
-- 作者:卷积内核 -- 发布时间:1/5/2009 1:43:00 PM -- 主程序: #pragma warning(disable : 4127 4995) struct sBlendVertex #define BLEND_VERTEX_FVF (D3DFVF_XYZB1 | D3DFVF_NORMAL) #define IDC_TOGGLE_FULLSCREEN 1 #define release_com(p) do { if(p) { (p)->Release(); (p) = NULL; } } while(0) ID3DXFont* g_font; CDXUTDialogResourceManager g_dlg_resource_manager; CDXUTMesh* g_dxut_mesh; //-------------------------------------------------------------------------------------- IDirect3D9* pD3D = DXUTGetD3DObject(); if( FAILED( pD3D->CheckDeviceFormat( pCaps->AdapterOrdinal, pCaps->DeviceType, AdapterFormat, return true; static bool is_first_time = true; if(is_first_time) // if using reference device, then pop a warning message box. if(pDeviceSettings->BehaviorFlags & D3DCREATE_HARDWARE_VERTEXPROCESSING) // if current device does not support vertex blending, use software device. return true; //-------------------------------------------------------------------------------------- LPWSTR w_last_back_slash = wcsrchr(wbuf, '\\'); if(w_last_back_slash) V_RETURN(g_dlg_resource_manager.OnCreateDevice(pd3dDevice)); D3DXCreateFont(pd3dDevice, 18, 0, FW_BOLD, 1, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, // add vertex blending weight and normal information sBlendVertex* blend_vertices; // count min and max x coordinate of all vertices for calculating vertex blending weight DWORD num_vertices = g_dxut_mesh->GetMesh()->GetNumVertices(); float min_x = 1e10f; for(DWORD i = 0; i < num_vertices; i++) if(blend_vertices[i].pos.x > max_x) // calculate for all vertices blending weight vertex_buffer->Unlock(); return S_OK; V_RETURN(g_dlg_resource_manager.OnResetDevice()); // set dialog position and size g_button_dlg.SetLocation(pBackBufferSurfaceDesc->Width - 170, 0); // setup view matrix D3DXMATRIX mat_view; D3DXMatrixLookAtLH(&mat_view, &eye, &at, &up); // set projection matrix // set material material.Ambient.r = material.Diffuse.r = 1.0f; pd3dDevice->SetMaterial(&material); // setup light light.Type = D3DLIGHT_DIRECTIONAL; D3DXVECTOR3 light_dir(0.0f, 0.0f, 10.0f); pd3dDevice->SetRenderState(D3DRS_AMBIENT, 0x00404040); return S_OK; //-------------------------------------------------------------------------------------- release_com(g_text_sprite); release_com(g_font); //-------------------------------------------------------------------------------------- // calculate blend matrix // set vertex blend method and blend matrix //-------------------------------------------------------------------------------------- // show frame and device states // show helper information if(g_show_help) text_helper.End(); //-------------------------------------------------------------------------------------- if(g_settings_dlg.IsActive()) // Clear the render target and the zbuffer // Render the scene V( pd3dDevice->EndScene() ); if(g_settings_dlg.IsActive()) *pbNoFurtherProcessing = g_button_dlg.MsgProc(hWnd, uMsg, wParam, lParam); return 0; //-------------------------------------------------------------------------------------- case IDC_TOGGLE_REF: case IDC_CHANGE_DEVICE: //-------------------------------------------------------------------------------------- g_button_dlg.SetCallback(OnGUIEvent); int x = 35, y = 10, width = 125, height = 22; g_button_dlg.AddButton(IDC_TOGGLE_FULLSCREEN, L"Toggle full screen", x, y, width, height); //-------------------------------------------------------------------------------------- // Set the callback functions // Initialize DXUT and create the desired Win32 window and Direct3D device for the application // Start the render loop // TODO: Perform any application-level cleanup here return DXUTGetExitCode(); [B][URL=http://www.cppblog.com/Files/changingnow/VertexBlend.rar]下载示例工程[/URL][/B] |
-- 作者:卷积内核 -- 发布时间:1/5/2009 1:44:00 PM -- 蒙皮骨骼动画网格模型接口是对上一节骨骼动画网格模型接口的扩展,添加了处理蒙皮信息的功能。
进一步扩展结构体D3DXMESHCONTAINER 为了在网格模型中包含蒙皮信息,需要进一步扩展D3DXMESHCONTAINER_DERIVEED,其定义如下: struct D3DXMESHCONTAINER_DERIVED : public D3DXMESHCONTAINER{ IDirect3DTexture9** ppTextures; ID3DXMesh* pOrgMesh; DWORD MaxBonesInflPerVertex; DWORD NumAttrGroups; ID3DXBuffer* pBoneCombBuffer; D3DXMATRIX** ppBoneMatrices; D3DXMATRIX** ppBoneOffsetMatrices; DWORD NumMatrixPalettes; bool UseSoftwareVP;}; Describes a subset of the mesh that has the same attribute and bone combination. typedef struct D3DXBONECOMBINATION { DWORD AttribId; DWORD FaceStart; DWORD FaceCount; DWORD VertexStart; DWORD VertexCount; DWORD * BoneId;} D3DXBONECOMBINATION, *LPD3DXBONECOMBINATION; The size of the array varies based on the type of mesh generated. A non-indexed mesh array size is equal to the number of weights per vertex (pMaxVertexInfl in ID3DXSkinInfo::ConvertToBlendedMesh). An indexed mesh array size is equal to the number of bone matrix palette entries (paletteSize in ID3DXSkinInfo::ConvertToIndexedBlendedMesh). 结构体D3DXBONECOMBINATION用来描述网格中具有同样属性的部分,也就是网格模型的一个子集,这个网格模型子集也称为属性组。属性组实际上是用来标识网格模型中被指定的骨骼矩阵所影响的子网格,不同属性组所标识的子网格需要用不同的纹理、材质进行渲染,该子网格可以通过调用函数DrawIndexedPrimitive()或者DrawSubset()进行绘制。 成员变量BoneId指向一个数组,该数组表示的是在单独的一次绘制中,即一次DrawSubset()函数调用中所用到的全部骨骼矩阵,该数组的大小与将要生成的蒙皮网格类型有关,在索引顶点混合蒙皮网格中,它的大小等于函数ConvertToIndexedBlendedMesh()中的输入参数paletteSize,也就是结构体D3DXMESHCONTAINER_DERIVEED的成员变量NumMatrixPalettes。变量NumMatrixPalettes表示进行索引顶点混合时所需要的矩阵调色板的容量,它的数值需要根据硬件设备能力进行相应的设置。
cAllocateHierarchy类的设计实现 蒙皮骨骼动画网格模型接口中cAllocateHierarchy类和骨骼动画网格模型接口中的cAllocateHierarchy类基本相同,区别较大的是CreateMeshContainer()函数中增加了对蒙皮信息的处理: // generate skin meshif(skin_info != NULL){ new_mesh_container->pSkinInfo = skin_info; skin_info->AddRef(); if(skin_info == NULL) release_com(mesh_container->MeshData.pMesh); HRESULT hr; DWORD max_faces_infl_per_triangle; index_buffer->Release(); if(FAILED(hr)) max_faces_infl_per_triangle = min(max_faces_infl_per_triangle, 12); IDirect3DDevice9* device = DXUTGetD3DDevice(); D3DCAPS9 caps; if((caps.MaxVertexBlendMatrixIndex+1)/2 < max_faces_infl_per_triangle) hr = skin_info->ConvertToIndexedBlendedMesh(mesh_container->pOrgMesh, 0, mesh_container->NumMatrixPalettes, return hr;
函数GenerateSkinnedMesh()判断当前网格容器是否包含蒙皮信息,如果当前网格模型中不包含蒙皮信息,则直接退出该函数。接下来确定所需要的矩阵调色板的容量,最后调用函数ConvertToIndexedBlendedMesh()根据初始网格模型提供的相应参数生成索引蒙皮网格模型。函数ConvertToIndexedBlendedMesh()的声明如下: HRESULT ConvertToIndexedBlendedMesh( LPD3DXMESH pMesh, DWORD Options, DWORD paletteSize, CONST DWORD * pAdjacencyIn, LPDWORD pAdjacencyOut, DWORD * pFaceRemap, LPD3DXBUFFER * ppVertexRemap, DWORD * pMaxVertexInfl, DWORD * pNumBoneCombinations, LPD3DXBUFFER * ppBoneCombinationTable, LPD3DXMESH * ppMesh); Remarks This method does not run on hardware that does not support fixed-function vertex blending.
|
-- 作者:卷积内核 -- 发布时间:1/5/2009 1:44:00 PM -- cSkinMesh类的设计与实现 cSkinMesh类的实现与cAnimMesh的实现基本相同,区别在以下几点。
首先是在load_from_xfile()中增加了对网格模型骨骼矩阵的保存: if(frame == NULL) if(frame->pMeshContainer != NULL) if(FAILED(hr)) if(frame->pFrameSibling != NULL) if(FAILED(hr)) if(frame->pFrameFirstChild != NULL) if(FAILED(hr)) D3DXMESHCONTAINER_DERIVED* mesh_container = (D3DXMESHCONTAINER_DERIVED*) base_mesh_container; if(mesh_container->pSkinInfo != NULL) if(mesh_container->ppBoneMatrices == NULL) for(UINT i = 0; i < num_bones; i++) if(frame == NULL) mesh_container->ppBoneMatrices[i] = &frame->CombinedTransformMatrix; return S_OK; WCHAR wpath[MAX_PATH]; V_RETURN(D3DXLoadMeshHierarchyFromXW(wpath, D3DXMESH_MANAGED, m_device, m_alloc_hierarchy, NULL, &m_root_frame, V_RETURN(setup_bone_matrix_pointers(m_root_frame)); V_RETURN(D3DXFrameCalculateBoundingSphere(m_root_frame, &m_object_center, &m_object_radius)); return S_OK;
接下来是DrawMeshContainer()的实现,该函数负责当前网格容器中具体网格模型的渲染,其实现大致可以分为以下几个步骤: 【1】如果当前网格模型是蒙皮网格模型 (1)根据当前硬件设备的性能决定是否采用软件顶点处理模式。 (2)激活索引顶点混合。 (3)设置顶点混合所需要的矩阵索引数量。 (4)渲染索引蒙皮网格模型,在具体渲染时,需要逐个属性组进行渲染,在渲染每个子网格时,首先需要设置骨骼混合矩阵、材质和数组。 (5)恢复相关渲染状态。 【2】如果当前网格模型不是蒙皮网格模型,则直接设置组合变换矩阵、材质和纹理,然后进行渲染。 D3DXMESHCONTAINER_DERIVED* mesh_container = (D3DXMESHCONTAINER_DERIVED*) base_mesh_container; if(mesh_container->pSkinInfo != NULL) if(mesh_container->MaxBonesInflPerVertex == 1) D3DXBONECOMBINATION* bone_comb = (D3DXBONECOMBINATION*) mesh_container->pBoneCombBuffer->GetBufferPointer(); for(UINT attr_index = 0; attr_index < mesh_container->NumAttrGroups; attr_index++) if(matrix_index != UINT_MAX) D3DXMatrixMultiply(&mat_palette, m_device->SetTransform(D3DTS_WORLDMATRIX(palette_index), &mat_palette); DWORD attr_id = bone_comb[attr_index].AttribId; m_device->SetMaterial(&mesh_container->pMaterials[attr_id].MatD3D); mesh_container->MeshData.pMesh->DrawSubset(attr_index); // restore render state m_device->SetRenderState(D3DRS_INDEXEDVERTEXBLENDENABLE, FALSE); if(mesh_container->UseSoftwareVP) for(UINT i = 0; i < mesh_container->NumMaterials; i++) mesh_container->MeshData.pMesh->DrawSubset(i); return S_OK;
蒙皮骨骼动画网格模型类的使用 首先需要修改顶点处理模式,如果当前Direct3D设备使用纯硬件顶点处理模式,则改为混合顶点处理模式: // If video card does not support hardware transform and light, then uses sofaware mode.if((pCaps->DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) == 0) pDeviceSettings->BehaviorFlags = D3DCREATE_SOFTWARE_VERTEXPROCESSING; 接下来在回调函数OnCreateDevice()中创建cSkinMesh类的实例: g_skin_mesh = new cSkinMesh(); 再接下来在回调函数OnFrameRender()中渲染网格模型: V(g_skin_mesh->render(&g_mat_world, fElapsedTime)); 最后在回调函数OnDestroyDevice()中释放网格模型: g_skin_mesh->destroy();
运行截图: |
-- 作者:卷积内核 -- 发布时间:1/5/2009 1:45:00 PM -- 主程序: #pragma warning(disable : 4127 4995) #define IDC_TOGGLE_FULLSCREEN 1 #define release_com(p) do { if(p) { (p)->Release(); (p) = NULL; } } while(0) ID3DXFont* g_font; CDXUTDialogResourceManager g_dlg_resource_manager; cSkinMesh* g_skin_mesh; //-------------------------------------------------------------------------------------- IDirect3D9* pD3D = DXUTGetD3DObject(); if( FAILED( pD3D->CheckDeviceFormat( pCaps->AdapterOrdinal, pCaps->DeviceType, AdapterFormat, return true; // !!important, change vertex processing to mixed mode. static bool is_first_time = true; if(is_first_time) // if using reference device, then pop a warning message box. return true; V_RETURN(g_dlg_resource_manager.OnCreateDevice(pd3dDevice)); D3DXCreateFont(pd3dDevice, 18, 0, FW_BOLD, 1, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, return S_OK; V_RETURN(g_dlg_resource_manager.OnResetDevice()); // set dialog position and size g_button_dlg.SetLocation(pBackBufferSurfaceDesc->Width - 170, 0); // setup view matrix D3DXMATRIX mat_view; D3DXMatrixLookAtLH(&mat_view, &eye, &at, &up); // set projection matrix // setup light light.Type = D3DLIGHT_DIRECTIONAL; D3DXVECTOR3 light_dir(0.0f, -1.0f, 1.0f); pd3dDevice->SetRenderState(D3DRS_AMBIENT, 0x00505050); return S_OK; //-------------------------------------------------------------------------------------- release_com(g_text_sprite); release_com(g_font); g_skin_mesh->destroy(); //-------------------------------------------------------------------------------------- //-------------------------------------------------------------------------------------- // show frame and device states // show helper information if(g_show_help) text_helper.End(); //-------------------------------------------------------------------------------------- if(g_settings_dlg.IsActive()) // Clear the render target and the zbuffer // Render the scene V( pd3dDevice->EndScene() ); if(g_settings_dlg.IsActive()) *pbNoFurtherProcessing = g_button_dlg.MsgProc(hWnd, uMsg, wParam, lParam); return 0; //-------------------------------------------------------------------------------------- case IDC_TOGGLE_REF: case IDC_CHANGE_DEVICE: //-------------------------------------------------------------------------------------- g_button_dlg.SetCallback(OnGUIEvent); int x = 35, y = 10, width = 125, height = 22; g_button_dlg.AddButton(IDC_TOGGLE_FULLSCREEN, L"Toggle full screen", x, y, width, height); //-------------------------------------------------------------------------------------- // Set the callback functions // Initialize DXUT and create the desired Win32 window and Direct3D device for the application // Start the render loop // TODO: Perform any application-level cleanup here return DXUTGetExitCode(); [B][URL=http://www.cppblog.com/Files/changingnow/UseSkinMesh.rar]下载示例工程[/URL][/B] |
-- 作者:卷积内核 -- 发布时间:1/5/2009 1:45:00 PM -- 一个网格模型中可以包含多个动画集,在渲染网格时通过动画控制器可以在各个动画集之间进行切换,从而可以在渲染网格模型时根据具体情况播放不同的动画。这里将具有多个骨骼动画的网格模型称为"多骨骼动画网格模型",当然骨骼动画网格模型也完全可以具有蒙皮信息,下面的示例程序演示了渲染多骨骼动画网格模型时骨骼动画之间的切换,骨骼动画间的切换是通过动画控制器来完成的。 在示例程序MultiAnimMesh中渲染的网格模型具有4个动画集,即具有4个动作,通过按下数字键"1" ~ "4"在4个动作之间进行切换: case 49: // press key "1" g_skin_mesh->m_anim_controller->GetAnimationSetByName("Walk", &g_anim_set); g_skin_mesh->m_anim_controller->SetTrackAnimationSet(0, g_anim_set); break; Gets an animation set, given its name. HRESULT GetAnimationSetByName( LPCSTR pName, LPD3DXANIMATIONSET * ppAnimSet); Remarks SetTrackAnimationSet()设置要播放的动画集: Applies the animation set to the specified track. HRESULT SetTrackAnimationSet( UINT Track, LPD3DXANIMATIONSET pAnimSet); Remarks
运行效果图: 步行
跑步
观望 挥手 |
-- 作者:卷积内核 -- 发布时间:1/5/2009 1:46:00 PM -- 主程序: #pragma warning(disable : 4127 4995) #define IDC_TOGGLE_FULLSCREEN 1 #define release_com(p) do { if(p) { (p)->Release(); (p) = NULL; } } while(0) ID3DXFont* g_font; CDXUTDialogResourceManager g_dlg_resource_manager; cSkinMesh* g_skin_mesh; //-------------------------------------------------------------------------------------- IDirect3D9* pD3D = DXUTGetD3DObject(); if( FAILED( pD3D->CheckDeviceFormat( pCaps->AdapterOrdinal, pCaps->DeviceType, AdapterFormat, return true; // !!important, change vertex processing to mixed mode. static bool is_first_time = true; if(is_first_time) // if using reference device, then pop a warning message box. return true; V_RETURN(g_dlg_resource_manager.OnCreateDevice(pd3dDevice)); D3DXCreateFont(pd3dDevice, 18, 0, FW_BOLD, 1, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, return S_OK; V_RETURN(g_dlg_resource_manager.OnResetDevice()); // set dialog position and size g_button_dlg.SetLocation(pBackBufferSurfaceDesc->Width - 170, 0); // setup view matrix D3DXMATRIX mat_view; D3DXMatrixLookAtLH(&mat_view, &eye, &at, &up); // set projection matrix // setup light light.Type = D3DLIGHT_DIRECTIONAL; D3DXVECTOR3 light_dir(-1.0f, -1.0f, 1.0f); pd3dDevice->SetRenderState(D3DRS_AMBIENT, 0x00505050); return S_OK; //-------------------------------------------------------------------------------------- release_com(g_text_sprite); release_com(g_font); g_skin_mesh->destroy(); //-------------------------------------------------------------------------------------- //-------------------------------------------------------------------------------------- // show frame and device states // show helper information if(g_show_help) text_helper.SetInsertionPos(10, surface_desc->Height - 15 * 6); text_helper.End(); //-------------------------------------------------------------------------------------- if(g_settings_dlg.IsActive()) // Clear the render target and the zbuffer // Render the scene V( pd3dDevice->EndScene() ); if(g_settings_dlg.IsActive()) *pbNoFurtherProcessing = g_button_dlg.MsgProc(hWnd, uMsg, wParam, lParam); return 0; case 49: // press key "1" case 50: // press key "2" case 51: // press key "3" case 52: // press key "4" //-------------------------------------------------------------------------------------- case IDC_TOGGLE_REF: case IDC_CHANGE_DEVICE: //-------------------------------------------------------------------------------------- g_button_dlg.SetCallback(OnGUIEvent); int x = 35, y = 10, width = 125, height = 22; g_button_dlg.AddButton(IDC_TOGGLE_FULLSCREEN, L"Toggle full screen", x, y, width, height); //-------------------------------------------------------------------------------------- // Set the callback functions // Initialize DXUT and create the desired Win32 window and Direct3D device for the application // Start the render loop // TODO: Perform any application-level cleanup here return DXUTGetExitCode();
[B][URL=http://www.cppblog.com/Files/changingnow/MultiAnimMesh.rar]下载示例工程[/URL][/B] |
W 3 C h i n a ( since 2003 ) 旗 下 站 点 苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》 |
230.469ms |