前言

可以说,魔方跟我的人生也有一定的联系。

在高中的学校接触到了魔方社,那时候的我虽然也能够还原魔方,可看到大神们总是可以非常快地还原,为此我也走上了学习高级公式CFOP的坑。当初学习的网站是在魔方小站,不过由于公式太多了,那一年主要也就学会了顶层公式PLL和底二层公式F2L,最好的时候大概30s能够复原一个魔方,不过后来还是退坑了。

然后到了大学,参加考核的时候被要求用DirectX9来实现考题规定的游戏,我选择了魔方。然后在仅有12天的时间狂肝Direct3D 9,虽然那时候写的代码还比较生涩,不过至少实现的效果还是比较满意的,至少在可玩性上我感觉还不错,甚至可以用来竞速。

Github项目--魔方

这个是DX9魔方的游玩过程。碍于图片最大只能上传10M,将就一下。

嗯,现在距离这个Demo都已经过去快两年了,然后电脑应为一些不可抗因素把系统升到了Win10。然后现在,我居然运行不了所有的DirectX 9游戏,包括我之前写的demo也翻车了。不过目前我学DirectX 11断断续续也是差不过有两年了,然后重构的念头一直在我脑海中回响。写了大半年的教程,中间也积累了不少的代码,用现有的代码框架应该也可以很快搭建出来吧。

截止目前,完成这个项目用了2天半,写下这套博客用了2天半

注意:本教程会主要是讲述一个3D魔方游戏的实现原理,即便不是用DirectX来进行开发,你也可以根据这里面的原理在OpenGL,WebGL,Unity3D等地方实现出来。

章节
实现一个3D魔方(1)
实现一个3D魔方(2)
实现一个3D魔方(3)

顺便下面安利一波本人正在编写的DX11教程。

DirectX11 With Windows SDK完整目录

欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。

前期准备

该项目的Direct3D 11源自Windows SDK,注意不是DirectX SDK!这意味着只要你有Visual Studio 2015/2017,只要安装了C++相关的组件,打开本项目你就可以直接生成出来并运行了。

说实话,即便是个看起来比较简单的魔方,内部的实现也是比较硬核的。而且因为是使用DirectX 11写的,对于正在学习或者想要学习DirectX 11的人来说,你必须要把很多底层的原理给弄懂,所以大多数人可能会偏向于先造一个自己的软引擎。

以下是对学习DX11的人的基本要求:

  1. 熟悉C++11和少量14,熟悉HLSL语法
  2. 学习过线性代数和3D数学基础,了解渲染管线的流程
  3. 初步了解Windows编程
  4. Direct3D 11的初始化
  5. HLSL的编译,顶点、索引、常量缓冲区的创建、绑定和修改
  6. 纹理映射、纹理数组的加载
  7. 第三人称摄像机
  8. 碰撞检测、鼠标拾取
  9. 特效管理框架的实现(本项目不使用Effects11或FX11)

而以下则是对只是想要了解魔方实现的人的基本要求:

  1. 熟悉C++11和少量14
  2. 学习过线性代数和3D数学基础
  3. 第三人称摄像机
  4. 碰撞检测、鼠标拾取

对了,本项目不打算使用光照。

为了尽可能简化开发流程,我把之前写教程实现的大部分模块都搬过来这里用了,这样可以尽可能屏蔽底层实现而让我更专注于魔方本身的实现。如果要理解这些模块的功能你仍需要花费大量的时间来学习。

首先列出项目的超长文件结构图(先不要被吓跑)。。。

其中从微软那边直接搬运过来的模块如下:

微软提供的模块 功能
DirectXTex/DDSTextureLoader DDS纹理加载
DirectXTex/WICTextureLoader WIC相关位图加载(估计用不上)
DirectXTex/ScreenGrab 截屏保存(估计用不上)
DXTK/Mouse(源码上有所修改) 鼠标类
DXTK/Keyboard(源码上有所修改) 键盘类


然后是自己之前积累下来的一些模块,也包括龙书的:

个人或龙书曾经编写过的模块 功能
Camera 简易摄像机
d3dUtil 包含了一些d3d常用的头文件和个人之前实现过的一些函数
DXTrace 贡献了HR宏,用于错误追踪
GameTimer 龙书的计时器
Vertex 包含了一些常用的顶点类型
Collision 用于鼠标拾取、碰撞检测

由于上述代码都是已经实现好的,所以对我来说里面的实现现在可以忽略。

而下面这些模块则是我需要重点进行修改和编写的

模块 功能
BasicEffect 特效、常量缓冲区的管理
d3dApp Direct3D和Windows的初始化
GameApp 管理游戏的逻辑实现部分
Rubik 魔方类

然后基础游戏框架使用的本人项目13的d3dAppGameApp。对于一般人来说,你只需要看懂Rubik类,以及GameApp类里面的游戏逻辑即可。前面的内容也是重点围绕这里面的代码来展开描述。

预期功能实现

本项目的魔方预期实现的功能和当前进度如下:

  1. 实现魔方的单层旋转和整体旋转(已实现)
  2. 提供键盘和鼠标操作(已实现)
  3. 检验魔方是否完成(已实现)
  4. 计时功能(已实现)

魔方的构造

首先,魔方的6个面可以使用下面的枚举值来确定:

  1. enum RubikFace {
  2. RubikFace_PosX, // +X面
  3. RubikFace_NegX, // -X面
  4. RubikFace_PosY, // +Y面
  5. RubikFace_NegY, // -Y面
  6. RubikFace_PosZ, // +Z面
  7. RubikFace_NegZ, // -Z面
  8. };

这和天空盒指定面的枚举值是一致的。所谓的+X面你可以理解为从魔方中心发射一条+X轴的射线所指向的面,注意这是建立在左手坐标系的基础上确定的。

然后,本项目提供了7种魔方纹理的颜色,由先的枚举值来确定:

  1. enum RubikFaceColor {
  2. RubikFaceColor_Black, // 黑色
  3. RubikFaceColor_Orange, // 橙色
  4. RubikFaceColor_Red, // 红色
  5. RubikFaceColor_Green, // 绿色
  6. RubikFaceColor_Blue, // 蓝色
  7. RubikFaceColor_Yellow, // 黄色
  8. RubikFaceColor_White // 白色
  9. };

所谓的黑色是指藏在魔方内部平时看不到的面,但是在魔方旋转的时候可以看到露出的一部分。

这里我准备了七张魔方表面的纹理贴图:

目前立方体结构体Cube的定义如下:

  1. struct Cube
  2. {
  3. // 获取当前立方体的世界矩阵
  4. DirectX::XMMATRIX GetWorldMatrix() const;
  5. RubikFaceColor faceColors[6]; // 六个面的颜色,索引0-5分别对应+X, -X, +Y, -Y, +Z, -Z面
  6. DirectX::XMFLOAT3 pos; // 旋转结束后中心所处位置
  7. DirectX::XMFLOAT3 rotation; // 仅允许存在单轴旋转,记录当前分别绕x轴, y轴, z轴旋转的弧度
  8. };

现在我们不讨论Cube::GetWorldMatrix的实现,你可以先默认它返回一个根据pos进行平移的矩阵。

可以看到这个结构体甚至不存放什么顶点和索引数据,它只记录一下关键的信息。这么做是方便我判断魔方是否还原,以及尽可能最简化魔方的旋转操作。

然后是魔方类Rubik的初步定义:

  1. class Rubik
  2. {
  3. public:
  4. template<class T>
  5. using ComPtr = Microsoft::WRL::ComPtr<T>;
  6. // 初始化资源
  7. void InitResources(ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> deviceContext);
  8. // 立即复原魔方
  9. void Reset();
  10. // 更新魔方状态
  11. void Update();
  12. // 绘制魔方
  13. void Draw(ComPtr<ID3D11DeviceContext> deviceContext, BasicEffect& effect);
  14. private:
  15. // 魔方 [X][Y][Z]
  16. Cube mCubes[3][3][3];
  17. // 顶点缓冲区,包含6个面的24个顶点
  18. // 索引0-3对应+X面
  19. // 索引4-7对应-X面
  20. // 索引8-11对应+Y面
  21. // 索引12-15对应-Y面
  22. // 索引16-19对应+Z面
  23. // 索引20-23对应-Z面
  24. ComPtr<ID3D11Buffer> mVertexBuffer;
  25. // 索引缓冲区,仅6个索引
  26. ComPtr<ID3D11Buffer> mIndexBuffer;
  27. // 纹理数组,包含7张纹理
  28. ComPtr<ID3D11ShaderResourceView> mTexArray;
  29. };

魔方的索引对应的关系满足左手坐标系,一级、二级、三级索引分别对应X轴、Y轴、Z轴方向上的偏移:

注意我们的魔方中心是始终位于世界坐标系的中心的,这样有利于我们对魔方进行旋转操作。此外你也可以看到,我将立方体六个正方形表面的24个顶点都同时存放在一个索引缓冲区中,在绘制的时候只需要设置顶点偏移量就可以指定当前绘制哪个面。所有的27个立方体都是依赖于这两个缓冲区,加上世界矩阵和纹理数组绘制出来的。

当然上面的索引缓冲区实际上也是可以扔掉的,只需要将顶点缓冲区中的顶点次序稍微调整下,然后使用原始拓扑类型D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP即可。正方形面此时顶点按索引的排布如下:

这个类在后续我们还会进行修改。

魔方的初始化

根据上面所给的数据结构,现在我需要初始化的数据有:纹理数组、顶点缓冲区、索引缓冲区、每个立方体的数据。

其中顶点和索引直接在初始化中提供即可。下面是Rubik::InitResources的实现:

  1. void Rubik::InitResources(ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> deviceContext)
  2. {
  3. // 初始化纹理数组
  4. mTexArray = CreateDDSTexture2DArrayFromFile(
  5. device,
  6. deviceContext,
  7. std::vector<std::wstring>{
  8. L"Resource/Black.dds",
  9. L"Resource/Orange.dds",
  10. L"Resource/Red.dds",
  11. L"Resource/Green.dds",
  12. L"Resource/Blue.dds",
  13. L"Resource/Yellow.dds",
  14. L"Resource/White.dds",
  15. });
  16. //
  17. // 初始化立方体网格模型
  18. //
  19. VertexPosTex vertices[] = {
  20. // +X面
  21. { XMFLOAT3(1.0f, -1.0f, -1.0f), XMFLOAT2(1.0f, 0.0f) },
  22. { XMFLOAT3(1.0f, 1.0f, -1.0f), XMFLOAT2(0.0f, 0.0f) },
  23. { XMFLOAT3(1.0f, 1.0f, 1.0f), XMFLOAT2(0.0f, 1.0f) },
  24. { XMFLOAT3(1.0f, -1.0f, 1.0f), XMFLOAT2(1.0f, 1.0f) },
  25. // -X面
  26. { XMFLOAT3(-1.0f, -1.0f, 1.0f), XMFLOAT2(1.0f, 0.0f) },
  27. { XMFLOAT3(-1.0f, 1.0f, 1.0f), XMFLOAT2(0.0f, 0.0f) },
  28. { XMFLOAT3(-1.0f, 1.0f, -1.0f), XMFLOAT2(0.0f, 1.0f) },
  29. { XMFLOAT3(-1.0f, -1.0f, -1.0f), XMFLOAT2(1.0f, 1.0f) },
  30. // +Y面
  31. { XMFLOAT3(-1.0f, 1.0f, -1.0f), XMFLOAT2(1.0f, 0.0f) },
  32. { XMFLOAT3(-1.0f, 1.0f, 1.0f), XMFLOAT2(0.0f, 0.0f) },
  33. { XMFLOAT3(1.0f, 1.0f, 1.0f), XMFLOAT2(0.0f, 1.0f) },
  34. { XMFLOAT3(1.0f, 1.0f, -1.0f), XMFLOAT2(1.0f, 1.0f) },
  35. // -Y面
  36. { XMFLOAT3(-1.0f, -1.0f, 1.0f), XMFLOAT2(1.0f, 0.0f) },
  37. { XMFLOAT3(-1.0f, -1.0f, -1.0f), XMFLOAT2(0.0f, 0.0f) },
  38. { XMFLOAT3(1.0f, -1.0f, -1.0f), XMFLOAT2(0.0f, 1.0f) },
  39. { XMFLOAT3(1.0f, -1.0f, 1.0f), XMFLOAT2(1.0f, 1.0f) },
  40. // +Z面
  41. { XMFLOAT3(1.0f, -1.0f, 1.0f), XMFLOAT2(1.0f, 0.0f) },
  42. { XMFLOAT3(1.0f, 1.0f, 1.0f), XMFLOAT2(0.0f, 0.0f) },
  43. { XMFLOAT3(-1.0f, 1.0f, 1.0f), XMFLOAT2(0.0f, 1.0f) },
  44. { XMFLOAT3(-1.0f, -1.0f, 1.0f), XMFLOAT2(1.0f, 1.0f) },
  45. // -Z面
  46. { XMFLOAT3(-1.0f, -1.0f, -1.0f), XMFLOAT2(1.0f, 0.0f) },
  47. { XMFLOAT3(-1.0f, 1.0f, -1.0f), XMFLOAT2(0.0f, 0.0f) },
  48. { XMFLOAT3(1.0f, 1.0f, -1.0f), XMFLOAT2(0.0f, 1.0f) },
  49. { XMFLOAT3(1.0f, -1.0f, -1.0f), XMFLOAT2(1.0f, 1.0f) },
  50. };
  51. // 设置顶点缓冲区描述
  52. D3D11_BUFFER_DESC vbd;
  53. ZeroMemory(&vbd, sizeof(vbd));
  54. vbd.Usage = D3D11_USAGE_IMMUTABLE;
  55. vbd.ByteWidth = sizeof vertices;
  56. vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
  57. vbd.CPUAccessFlags = 0;
  58. // 新建顶点缓冲区
  59. D3D11_SUBRESOURCE_DATA initData;
  60. ZeroMemory(&initData, sizeof(initData));
  61. initData.pSysMem = vertices;
  62. HR(device->CreateBuffer(&vbd, &initData, mVertexBuffer.ReleaseAndGetAddressOf()));
  63. WORD indices[] = { 0, 1, 2, 2, 3, 0 };
  64. // 设置索引缓冲区描述
  65. D3D11_BUFFER_DESC ibd;
  66. ZeroMemory(&ibd, sizeof(ibd));
  67. ibd.Usage = D3D11_USAGE_IMMUTABLE;
  68. ibd.ByteWidth = sizeof indices;
  69. ibd.BindFlags = D3D11_BIND_INDEX_BUFFER;
  70. ibd.CPUAccessFlags = 0;
  71. // 新建索引缓冲区
  72. initData.pSysMem = indices;
  73. HR(device->CreateBuffer(&ibd, &initData, mIndexBuffer.ReleaseAndGetAddressOf()));
  74. // 初始化魔方所有面
  75. Reset();
  76. // 预先绑定顶点/索引缓冲区到渲染管线
  77. UINT strides[1] = { sizeof(VertexPosTex) };
  78. UINT offsets[1] = { 0 };
  79. deviceContext->IASetVertexBuffers(0, 1, mVertexBuffer.GetAddressOf(), strides, offsets);
  80. deviceContext->IASetIndexBuffer(mIndexBuffer.Get(), DXGI_FORMAT_R16_UINT, 0);
  81. }

Rubik::Reset用来方便一次性还原魔方,初始化各个立方体的位置:

  1. void Rubik::Reset()
  2. {
  3. // 初始化魔方中心位置,用六个面默认填充黑色
  4. for (int i = 0; i < 3; ++i)
  5. for (int j = 0; j < 3; ++j)
  6. for (int k = 0; k < 3; ++k)
  7. {
  8. mCubes[i][j][k].pos = XMFLOAT3(-2.0f + 2.0f * i,
  9. -2.0f + 2.0f * j, -2.0f + 2.0f * k);
  10. mCubes[i][j][k].rotation = XMFLOAT3();
  11. memset(mCubes[i][j][k].faceColors, 0,
  12. sizeof mCubes[i][j][k].faceColors);
  13. }
  14. // +X面为橙色,-X面为红色
  15. // +Y面为绿色,-Y面为蓝色
  16. // +Z面为黄色,-Z面为白色
  17. for (int i = 0; i < 3; ++i)
  18. for (int j = 0; j < 3; ++j)
  19. {
  20. mCubes[2][i][j].faceColors[RubikFace_PosX] = RubikFaceColor_Orange;
  21. mCubes[0][i][j].faceColors[RubikFace_NegX] = RubikFaceColor_Red;
  22. mCubes[j][2][i].faceColors[RubikFace_PosY] = RubikFaceColor_Green;
  23. mCubes[j][0][i].faceColors[RubikFace_NegY] = RubikFaceColor_Blue;
  24. mCubes[i][j][2].faceColors[RubikFace_PosZ] = RubikFaceColor_Yellow;
  25. mCubes[i][j][0].faceColors[RubikFace_NegZ] = RubikFaceColor_White;
  26. }
  27. }

Rubik::InitResources中用到了我自己之前编写的CreateDDSTexture2DArrayFromFile函数,里面要求传递的是dds纹理文件,但是我现在所拥有的魔方贴图全部都是从画图工具弄出来的png格式。为此,我还需要对纹理进行格式的转换。

使用dxtex(DirectX Texture Tool)制作DDS纹理

dxtex通常是在你安装了DirectX SDK后可以找到的,位于Microsoft DirectX SDK\Utilities\bin\x86Microsoft DirectX SDK\Utilities\bin\x64中。没有安装该SDK的,你也可以在我的Github中找到:

点此查看

打开dxtex,载入png位图

然后选择Format-Change Surface Format,将位图格式改为Unsigned 32-bit: A8R8G8B8

紧接着,我们需要给它生成mipmap,否则可能会导致在用大纹理绘制实际较小的部分时,某些倾斜的条纹会因为采样而产生类似锯齿状条纹:

而且就是开了4倍MSAA都拯救不了这么强烈的锯齿感!

点击Format-Generate Mip Maps,程序自动为其创建Mipmap。在View选项中你可以通过Smaller Mipmap Level来观察生成的mipmap。

最后选择File-Save As,直接另存为.dds文件即可。

GameApp框架

该框架的流程图如下:

其中需要我做修改的部分主要落在了GameApp::Init, GameApp::UpdateSceneGameApp::DrawScene上。

GameApp::InitResources方法

该方法随GameApp::Init调用,用于初始化游戏所需的资源:

  1. bool GameApp::InitResource()
  2. {
  3. // 初始化魔方
  4. mRubik.InitResources(md3dDevice, md3dImmediateContext);
  5. // 初始化特效、着色器资源
  6. mBasicEffect.SetRenderDefault(md3dImmediateContext);
  7. mBasicEffect.SetViewMatrix(XMMatrixLookAtLH(
  8. XMVectorSet(6.0f, 6.0f, -6.0f, 1.0f),
  9. XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f),
  10. XMVectorSet(0.0f, 1.0f, 0.0f, 1.0f)
  11. ));
  12. mBasicEffect.SetProjMatrix(XMMatrixPerspectiveFovLH(
  13. XM_PI / 3, AspectRatio(), 1.0f, 1000.0f
  14. ));
  15. mBasicEffect.SetTextureArray(mRubik.GetTexArray());
  16. return true;
  17. }

对于mBasicEffect,你现在暂时不需要知道它底层原理,可以先把它当做一个类似于ID3DX11Effect的对象。它可以用于设置默认的渲染模式,以及各项所需的资源给HLSL,包括世界矩阵、观察矩阵、投影矩阵和纹理数组。

着色器的具体实现这里我们也先不提,我们把更细节的内容留到后续的章节来讲。现在要做的,就是利用现有的框架先把这个魔方给绘制出来。

GameApp::DrawScene方法

目前GameApp::UpdateScene还没有做任何事情,可以不管。GameApp::DrawScene的实现如下:

  1. void GameApp::DrawScene()
  2. {
  3. assert(md3dImmediateContext);
  4. assert(mSwapChain);
  5. // 使用偏紫色的纯色背景
  6. float backgroundColor[4] = { 0.45882352f, 0.42745098f, 0.51372549f, 1.0f };
  7. md3dImmediateContext->ClearRenderTargetView(mRenderTargetView.Get(), backgroundColor);
  8. md3dImmediateContext->ClearDepthStencilView(mDepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
  9. // 绘制魔方
  10. mRubik.Draw(md3dImmediateContext, mBasicEffect);
  11. // 省略目前没有作为的部分...
  12. HR(mSwapChain->Present(0, 0));
  13. }

然后Rubik::Draw的实现目前如下:

  1. void Rubik::Draw(ComPtr<ID3D11DeviceContext> deviceContext, BasicEffect& effect)
  2. {
  3. for (int i = 0; i < 3; ++i)
  4. for (int j = 0; j < 3; ++j)
  5. for (int k = 0; k < 3; ++k)
  6. {
  7. effect.SetWorldMatrix(mCubes[i][j][k].GetWorldMatrix());
  8. for (int face = 0; face < 6; ++face)
  9. {
  10. effect.SetTexIndex(mCubes[i][j][k].faceColors[face]);
  11. effect.Apply(deviceContext);
  12. deviceContext->DrawIndexed(6, 0, 4 * face);
  13. }
  14. }
  15. }

通过BasicEffect::SetTexIndex我们可以指定当前绘制的立方体面使用的是纹理数组中的哪一个纹理。

每绘制一个立方体中的一个表面,就需要切换一次世界矩阵,并应用所有的变更。

由于我把所有的顶点都放在同一个缓冲区了,只需要在ID3D11DeviceContext::DrawIndexed指定起始顶点的偏移量即可。

最终的效果如下:

目前的开发进度用了我半天时间,然后还有大半天的时间用来写这篇博客,理论上我稍微爆肝一点可能两天时间就可以弄出来了吧。虽然表面开发了半天,但为了这个教程至少也准备了大半年的时间。现在趁这个机会可以好好理顺一下自己的开发思路,可能要多花3-4天的时间。目前的项目我已经放到Github中了:

Github项目--魔方

欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。

DirectX11--实现一个3D魔方(1)的更多相关文章

  1. DirectX11--实现一个3D魔方(3)

    前言 (2019/1/9 09:23)上一章我们主要讲述了魔方的旋转,这个旋转真是有毒啊,搞完这个部分搭键鼠操作不到半天应该就可以搭完了吧... (2019/1/9 21:25)啊,真香 有人发这张图 ...

  2. DirectX11--实现一个3D魔方(2)

    前言 上一章我们主要讲述了魔方的构造和初始化.纹理的准备工作.目前我还没有打算讲Direct3D 11关于底层绘图的实现,因此接下来这一章的重点是魔方的旋转.因为我们要的是能玩的魔方游戏,而不是一个观 ...

  3. css3之3D魔方动画(小白版)

      在这里分享一下3D魔方动画,html5+CSS3即可完成~无图无真相,先上效果图 第一步非常简单,就是先将魔方的结构画出来.大家都玩过魔方,知道魔方是一个有六个面的正方体.这里我们先写一个大的di ...

  4. canvas实现3D魔方

    摘要:使用canvas实现可交互的3D魔方 一.简单分析 魔方物理性质: 1.中心块(6个):中心块与中心轴连接在一起,但可以顺着轴的方向自由的转动. 2.棱块(12个):棱块的表面是两个正方形,结构 ...

  5. 如何用CSS3画出一个立体魔方?

    前言 最近在写<动画点点系列>文章,上一期分享了< 手把手教你如何绘制一辆会跑车 >,本期给大家带来是结合CSS3画出来的一个立体3d魔方,结合了js让你随心所欲想怎么转,就怎 ...

  6. 如何用CSS3画出懂你的3D魔方?

    作者:首席填坑官∙苏南公众号:honeyBadger8,群:912594095,本文原创,著作权归作者所有,转载请注明原链接及出处. 前言 最近在写<每周动画点点系列>文章,上一期分享了& ...

  7. 使用CSS3实现一个3D相册

    CSS3系列我已经写过两篇文章,感兴趣的同学可以先看一下CSS3初体验之奇技淫巧,CSS3 3D立方体效果-transform也不过如此 第一篇主要列出了一些常用或经典的CSS3技巧和方法:第二篇是一 ...

  8. 第一章 用three.js创建你的第一个3D场景

    第一章 用three.js创建你的第一个3D场景 到官网下载three.js的源码和示例. 创建HTML框架界面 第一个示例的代码如下: 01-basic-skeleton.html 位于 Learn ...

  9. 如何用webgl(three.js)搭建一个3D库房-第一课

    今天我们来讨论一下如何使用当前流行的WebGL技术搭建一个库房并且实现实时有效交互 第一步.搭建一个3D库房首先你得知道库房长啥样,我们先来瞅瞅库房长啥样(这是我在网上找的一个库房图片,百度了“库房” ...

随机推荐

  1. 猴子吃桃儿问题(C#)

    猴子第一天摘了许多个桃子,先吃了所有桃子的一半,后又吃了一个:第二天又吃了剩下桃子的一半,后又吃了一个……第十天,剩1个桃子.问:猴子第一天摘了多少个桃子? 本程序对其做了修改,天数和吃一半后又吃了一 ...

  2. 经典51道SQL查询练习题

    数据表介绍 --1.学生表 Student(SId,Sname,Sage,Ssex) --SId 学生编号,Sname 学生姓名,Sage 出生年月,Ssex 学生性别 --2.课程表 Course( ...

  3. modbus串口通讯C#

    简介 公司给的一个小任务,这篇文章进行详细讲解 题目: modbus串口通讯 主要内容如下: 1.实现使用modbus通讯规约的测试软件: 2.具有通信超时功能: 3.分主站从站,并能编辑报文.生成报 ...

  4. MongoDB语法与现有关系型数据库SQL语法比较

    MongoDB语法            MySql语法 db.test.find({'name':'foobar'})             <==>          select ...

  5. 一文读懂 JAVA 异常处理

    JAVA 异常类型结构 Error 和 Exeption 受查异常和非受查异常 异常的抛出与捕获 直接抛出异常 封装异常并抛出 捕获异常 自定义异常 try-catch-finally try-wit ...

  6. 算法01 C语言设计

    8.21 #include <stdio.h> void bubbleSort(int **p, int n); int main(void){ int a[100]; int *b[10 ...

  7. Jmeter二次开发代码(3)

    package org.apache.jmeter.functions; import java.io.FileInputStream;import java.io.FileNotFoundExcep ...

  8. 一入OI深似海 4 —— 纪念我最后一次PJ(中)

    不知道怎么回事,直到比赛前10分钟才放我们进考场. 考场在体育馆里面,很大很壮观. 我匆匆忙忙地找到位子,屁股还没坐热,被老师告知不能带水. what?! 于是我只好把水放在统一放私人物品的地方. 电 ...

  9. JS中的事件委托(事件代理)

    一步一步来说说事件委托(或者有的资料叫事件代理) js中事件冒泡我们知道,子元素身上的事件会冒泡到父元素身上. 事件代理就是,本来加在子元素身上的事件,加在了其父级身上. 那就产生了问题:父级那么多子 ...

  10. MySQL数据库8.0.15 安装教程

    第一步:安装MySQL服务 这里下载完成的是一个压缩文件,直接将里面的‘mysql-8.0.15-winx64'文件夹解压到你想要安装的路径即可,我是直接安装在C盘的. 解压完后的文件路径如下图: 在 ...