简介:D3D太阳系项目是一份基于Direct3D 9.0与Visual C++ 10开发的完整3D图形学实践资源,面向初学D3D的开发者、游戏编程爱好者和天文模拟学习者。项目完整实现了摄像机动态控制、行星自转与公转、椭圆轨道绘制、纹理贴图与天空盒渲染,并依据开普勒定律模拟太阳系运行,支持鼠标操作自由切换视角,代码注释明晰,工程结构便于逐模块研读。压缩包共109个文件,约14.2MB,主要包含C++源代码、Visual Studio工程配置、编译好的可执行程序以及行星表面纹理图片等,可直接运行观察效果,也能对照源码理解D3D9.0的场景构建、动画控制与光照控制思路。资源已有547人浏览学习,尤其对于希望掌握3D图形编程、游戏开发或科学可视化的读者而言,是一份不可多得的入门与进阶练手项目。
1. d3d太阳系初识:一个把公转自转做成可交互场景的DX11程序
第一次拿到这套《D3D太阳系(轨道自转公转)》的源码,我以为是又一个"几何体绕原点转圈圈"的教学Demo。真在窗口里跑起来才发现,它把太阳系可视化的两个核心诉求——行星沿轨道公转、行星绕自身轴自转——用Direct3D 11的完整渲染管线组织成了一个可自由拉近拉远、拖拽旋转的三维场景。你不但能看到八大行星按不同速度在轨道上运动,还能观察到自转轴倾角对行星纹理方向的影响,土星环在不同视角下的翻转也有模拟。对正在学DX11渲染管线的新手来说,它是一份从顶点缓冲到像素着色器全程伸手可摸的参考实现;对要做天文科普可视化、数字展厅大屏的开发者来说,这套项目的场景组织方式、参数化思路和实例化绘制方案,都可以直接拿过来当底座改造。最让我意外的是它没有依赖任何第三方引擎,纯靠D3D11、数学库和HLSL着色器就把整个太阳系的运动骨架搭干净了。
2. 架构设计与渲染链路:场景图、坐标变换与实例化绘制的取舍
2.1 场景图组织方式:行星节点与轨道节点分离
这套项目没有引入现成的场景图框架,而是在CPU侧自己维护了一套轻量节点结构。每个行星是一个独立节点,节点里保存了轨道半径、公转速度、自转速度、自转轴倾角、模型缩放这几个核心参数;轨道本身不单独建对象,而是靠每帧更新行星世界矩阵时,同步生成轨道环的顶点数据。这样设计的好处是,行星的"运动属性"和"渲染属性"聚合在一处,改任何一个参数都不需要去翻别的模块。
struct PlanetNode { float orbitRadius; // 轨道半径(模拟单位,非真实天文单位) float orbitSpeed; // 公转速度,单位:弧度/秒 float rotationSpeed; // 自转速度,单位:弧度/秒,负值代表逆向自转 float axisTilt; // 自转轴倾角,单位:度 float scale; // 模型缩放比例 XMFLOAT3 color; // 基础颜色,用于区分行星 XMMATRIX worldMatrix; // 每帧更新后的世界矩阵,传给GPU }; std::vector<PlanetNode> g_planets;这段结构把每个行星的轨道参数和渲染参数聚合在一起。orbitRadius和orbitSpeed决定它在场景中的空间位置和运动节奏;axisTilt和rotationSpeed决定它自身怎么转;而worldMatrix是每帧CPU更新的结果,最终会作为实例化数据传入GPU。实际渲染时有两个细节值得注意:第一,轨道环和行星本体是分开绘制的——轨道环用D3D11_PRIMITIVE_TOPOLOGY_LINELIST画出椭圆轮廓,行星本体用三角形网格;两组顶点共用同一套观察矩阵和投影矩阵,所以不会出现轨道环和行星错位的情况。第二,行星节点没有在CPU侧做父子层级递归,而是直接在世界空间里算出绝对矩阵。这样虽然放弃了层级变换的灵活性,但让每帧的矩阵运算非常直白,跟踪问题也容易定位。
2.2 坐标变换与矩阵更新顺序:世界、观察、投影三层变换怎么衔接
在这套项目里,矩阵更新的顺序是固定写死的:先根据相机参数重建观察矩阵,再遍历所有行星更新世界矩阵,最后在顶点着色器里把世界矩阵乘观察矩阵再乘投影矩阵,得到最终的组合矩阵。这个顺序如果颠倒,最常见的翻车结果就是行星不在轨道上,或者视角一移动整个场景乱飞。观察矩阵、投影矩阵是逐帧更新的常量缓冲,世界矩阵则放进实例化缓冲,两种缓冲在渲染管线里的角色完全不同。
void UpdateFrame(float deltaTime) { // 观察矩阵每帧重建:支持相机拖拽旋转后视角实时生效 XMMATRIX view = XMMatrixLookAtLH(g_camPos, g_camTarget, XMVectorSet(0, 1, 0, 0)); g_cbPerFrame.mView = XMMatrixTranspose(view); for (size_t i = 0; i < g_planets.size(); ++i) { PlanetNode& p = g_planets[i]; float angle = p.orbitSpeed * deltaTime; // 公转位置:绕Y轴旋转,轨道平面是XZ水平面 float x = cosf(angle) * p.orbitRadius; float z = sinf(angle) * p.orbitRadius; XMMATRIX matScale = XMMatrixScaling(p.scale, p.scale, p.scale); // 先倾斜轴,再自转,最后平移到轨道位置 XMMATRIX matTilt = XMMatrixRotationZ(XMConvertToRadians(p.axisTilt)); XMMATRIX matSpin = XMMatrixRotationY(p.rotationSpeed * deltaTime); XMMATRIX matTranslate = XMMatrixTranslation(x, 0.0f, z); p.worldMatrix = matScale * matTilt * matSpin * matTranslate; } }这段UpdateFrame是每帧运动的发动机。公转位置用cos和sin做圆周运动的参数化,轨道平面在水平面Y=0上;自转则先在局部坐标里做轴倾角旋转,再绕局部Y轴自转,最后平移。矩阵乘法的顺序决定了变换的嵌套关系:先缩放,再倾斜,再自转,最后平移到轨道点。如果先把平移乘进去,行星就会绕自身局部原点转而不是绕太阳转,这是新手最容易踩的坑。反过来说,这个顺序也决定了axisTilt只影响自转,不影响公转轨道平面——这正是我们想要的物理效果。
2.3 实例化绘制:为什么不用循环里逐个DrawCall
如果只是对每颗行星单独调用DrawIndexed,代码会更短,但每颗行星都要单独绑定一次实例缓冲,DrawCall数量跟行星数量线性挂钩。这套项目选择了DrawIndexedInstanced,把所有行星的世界矩阵和颜色塞进一个实例缓冲,一次绘制调用画出全部行星。在只有8颗行星时这个优化看起来不明显,但当你把卫星、小行星带的可视化节点加进来后,DrawCall数量能差出两三倍,这也是后来叠加节点时不碰性能红线的基础。
struct InstanceData { XMMATRIX worldMatrix; // 每颗行星独立的世界变换 XMFLOAT4 color; // 每颗行星的着色参数 }; std::vector<InstanceData> g_instanceData; // 每帧填充实例数据 void UpdateInstanceBuffer() { for (size_t i = 0; i < g_planets.size(); ++i) { g_instanceData[i].worldMatrix = g_planets[i].worldMatrix; g_instanceData[i].color = XMFLOAT4( g_planets[i].color.x, g_planets[i].color.y, g_planets[i].color.z, 1.0f ); } }HLSL顶点着色器里通过SV_InstanceID从实例缓冲中取出对应的世界矩阵,把顶点从模型空间变换到世界空间。这里有个关键点:顶点缓冲和实例缓冲要分别设置输入槽,顶点缓冲绑定在slot 0,实例缓冲绑定在slot 1,并在输入布局里用D3D11_INPUT_PER_INSTANCE_DATA声明实例数据。如果输入槽绑定反了,最常见的问题就是所有行星画在同一个位置,且模型扭曲成一团。
UINT strides[2] = { sizeof(Vertex), sizeof(InstanceData) }; UINT offsets[2] = { 0, 0 }; ID3D11Buffer* buffers[2] = { g_vertexBuffer, g_instanceBuffer }; g_immediateContext->IASetVertexBuffers(0, 2, buffers, strides, offsets);2.4 设备创建与资源组织:D3D11初始化时最容易忽略的细节
D3D11的设备创建是这套项目里最"模板化"的一段代码,但也是最容易在换机器后翻车的地方。创建设备时常见做法是用D3D11CreateDevice一把创建设备、立即上下文,然后单独创建交换链。注意D3D11_CREATE_DEVICE_BGRA_SUPPORT这个标志,如果你后续想用Direct2D在D3D表面叠加UI文字,少了这个标志就等着反复报错。示例代码里最常见的错误是交换链的BufferCount设为1,导致全屏切换时画面闪烁;主流做法是用双缓冲加DXGI_SWAP_EFFECT_FLIP_DISCARD。
DXGI_SWAP_CHAIN_DESC sd = {}; sd.BufferCount = 2; sd.BufferDesc.Width = width; sd.BufferDesc.Height = height; sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; sd.OutputWindow = g_hwnd; sd.SampleDesc.Count = 1; // 不开启MSAA,保持简单 sd.Windowed = TRUE; sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;着色器编译也是初始化阶段的重头戏。这套项目在初始化时调用D3DCompileFromFile加载HLSL源码,但有一个隐藏的坑:HLSL文件的编码必须是带BOM的UTF-8,否则编译器遇到中文注释会直接报错。如果你改过着色器文件里的中文注释后编译不过,先检查文件编码,而不是去怀疑代码逻辑。资源组织方面,项目把常量缓冲分成两套:每帧变化的mView和mProjection放一套,每颗行星的worldMatrix放进实例缓冲。这样分开的好处是避免每帧把整份数据Upload到GPU,减少带宽占用。
3. 核心代码拆解:轨道计算、几何生成与HLSL着色器的配合
3.1 行星参数表与轨道计算:逆向自转和轴倾角是怎么表达出来的
行星参数表是项目里最有"数据文档"气质的一段代码。它没有把参数散落在各个逻辑分支里,而是集中在CreateDefaultPlanetTable函数中,每个行星一行配置。轨道半径、公转速度、自转速度、轴倾角、缩放、基础颜色一目了然。尤其值得一提的是金星——它的自转速度是负值,轴倾角177.4度,这正是金星逆向自转的模拟结果。当你把自转速度设为负数时,行星纹理看起来会"倒着转",而轴倾角接近180度会导致行星像躺着绕太阳转,观感上非常接近真实金星的状态。
std::vector<PlanetNode> CreateDefaultPlanetTable() { std::vector<PlanetNode> planets; // 水星:轨道半径15,公转0.08弧度/秒,自转0.01弧度/秒,轴倾角0 planets.push_back({ 15.0f, 0.08f, 0.01f, 0.0f, 0.4f, XMFLOAT3(0.7f, 0.7f, 0.7f) }); // 金星:公转0.06,自转-0.005(逆向),轴倾角177.4度 planets.push_back({ 22.0f, 0.06f, -0.005f, 177.4f, 0.6f, XMFLOAT3(0.9f, 0.8f, 0.5f) }); // 地球:公转0.045,自转0.3,轴倾角23.4度 planets.push_back({ 30.0f, 0.045f, 0.3f, 23.4f, 0.65f, XMFLOAT3(0.3f, 0.5f, 0.9f) }); // 火星:公转0.036,自转0.28,轴倾角25.2度 planets.push_back({ 38.0f, 0.036f, 0.28f, 25.2f, 0.5f, XMFLOAT3(0.8f, 0.4f, 0.2f) }); // 木星:公转0.02,自转0.5,轴倾角3.1度 planets.push_back({ 52.0f, 0.02f, 0.5f, 3.1f, 1.2f, XMFLOAT3(0.7f, 0.5f, 0.3f) }); // 土星:公转0.015,自转0.45,轴倾角26.7度 planets.push_back({ 66.0f, 0.015f, 0.45f, 26.7f, 1.0f, XMFLOAT3(0.8f, 0.7f, 0.5f) }); // 天王星:公转0.008,自转0.35,轴倾角97.8度 planets.push_back({ 82.0f, 0.008f, 0.35f, 97.8f, 0.8f, XMFLOAT3(0.4f, 0.8f, 0.8f) }); // 海王星:公转0.006,自转0.33,轴倾角28.3度 planets.push_back({ 96.0f, 0.006f, 0.33f, 28.3f, 0.8f, XMFLOAT3(0.3f, 0.3f, 0.8f) }); return planets; }轨道环的生成在初始化时完成:对每颗行星,按轨道半径采样256个点,用sin和cos生成圆环顶点,连接成LINELIST。轨道环不跟自转互动,只跟随公转位置,始终在XZ水平面上。LINELIST的每两个顶点构成一条线段,渲染时不会出现相邻线段之间的过度绘制,适合画轨道这种细线条。
3.2 几何生成:用代码造出场里的球体,而不是加载外部模型
项目里的行星和太阳不是从外部文件加载的模型,而是运行时用参数化网格生成的球体。这样做的好处是资源目录干净,不需要额外美工资源;坏处是如果你想换一个精细的行星纹理,需要自己在代码里处理UV坐标。生成球体采用经纬网格法:经度分成32段,纬度分成16段,每个顶点由两个角度计算得到。生成的顶点包含位置、法线和UV坐标,这样同一个网格既能用于光照计算,也能用于纹理采样。
void CreateSphereMesh(std::vector<Vertex>& vertices, std::vector<UINT>& indices) { const int stackCount = 16; const int sliceCount = 32; // 经度方向循环 for (int i = 0; i <= stackCount; ++i) { float phi = i * XM_PI / stackCount; // 纬度角:0到PI float y = cosf(phi); float r = sinf(phi); for (int j = 0; j <= sliceCount; ++j) { float theta = j * 2 * XM_PI / sliceCount; // 经度角:0到2PI Vertex v; v.pos = { r * cosf(theta), y, r * sinf(theta) }; v.normal = v.pos; // 单位球面上法线等于位置 v.uv = { (float)j / sliceCount, (float)i / stackCount }; vertices.push_back(v); } } // 生成三角形索引,跳过极点处的退化三角形 for (int i = 0; i < stackCount; ++i) { for (int j = 0; j < sliceCount; ++j) { int a = i * (sliceCount + 1) + j; int b = a + sliceCount + 1; indices.push_back(a); indices.push_back(b); indices.push_back(a + 1); indices.push_back(b); indices.push_back(b + 1); indices.push_back(a + 1); } } }球体生成的代码虽然看起来长,但逻辑非常固定:先算顶点,再连索引。这里有个值得记住的经验——极点处顶点会重叠,法线全是(0,1,0)或(0,-1,0),如果光照计算时法线出问题,优先怀疑极点的索引是否生成了退化三角形。因为这里每两个经度段接缝处共享顶点,所以纹理贴图的左右边缘能自然接起来。
3.3 HLSL着色器逻辑:光照、太阳发光和星空背景的三种常见写法
行星的像素着色器使用了简化的光照模型:光照方向在每个行星的局部坐标里计算,保证行星在公转过程中始终有面向太阳的亮面和背向太阳的暗面。实现上是把世界空间中的光源方向乘上世界矩阵的逆矩阵,变换到模型局部坐标系,然后做一次点乘。投影到屏幕后,行星在公转经过轨道远端时会自然出现"新月"状的暗部,观感上很接近真实。
// 行星像素着色器:漫反射光照 + 简单纹理采样 float4 PSMain(PlanetPixelInput input) : SV_TARGET { float4 texColor = g_texture.Sample(g_sampler, input.uv); // 光照方向从世界空间变换到模型局部空间 float3 lightDir = normalize(g_lightDirModelSpace); float ndotl = saturate(dot(input.normal, lightDir)); float3 lit = texColor.rgb * (0.15f + 0.85f * ndotl); // 15%环境光 return float4(lit, 1.0f); }太阳的着色器完全不是同一个套路。太阳没有纹理,而是用径向渐变模拟自发光:像素离模型中心越近越亮,越远越暗,边界处用saturate做平滑过渡,让发光球体的边缘看起来是虚化的而不是一个硬边界。星空背景则是一个包围整个场景的大球体,球体内部贴了一张随机亮点纹理,顶点着色器里把每个顶点当做方向向量传递,让背景看起来在无限远处。这三段着色器加起来不到100行,但已经把"有光照的行星、自发光恒星、远景星空"三种完全不同的视觉需求都覆盖了,是值得反复对照学习的短而全的范例。
// 太阳像素着色器:距离场发光,球体模型半径为1 float4 SunPS(SunPixelInput input) : SV_TARGET { float dist = length(input.localPos.xyz); float brightness = saturate(1.0f - dist); float3 baseColor = float3(1.0f, 0.8f, 0.4f); // 暖黄色 float3 color = baseColor * (0.3f + brightness * 1.2f); return float4(color, 1.0f); }3.4 相机控制与交互模块:球坐标相机为什么是三维可视化首选
交互模块独立于渲染循环,处理鼠标消息和键盘消息,维护相机的三个核心参数:水平旋转角g_camTheta、俯仰角g_camPhi、观察半径g_camRadius。滚轮缩放不直接改相机位置,而是改半径,然后用球坐标换算成笛卡尔坐标,这样缩放和拖拽天然是统一的。俯仰角被限制在0.1到3.0弧度之间,防止视角翻转到头顶后发生天旋地转的翻转问题。
void UpdateCamera() { g_camPos.x = g_camTarget.x + g_camRadius * cosf(g_camPhi) * cosf(g_camTheta); g_camPos.y = g_camTarget.y + g_camRadius * sinf(g_camPhi); g_camPos.z = g_camTarget.z + g_camRadius * cosf(g_camPhi) * sinf(g_camTheta); }球坐标相机的代码只有三行,却是整套交互体验的核心。theta控制左右拖拽,phi控制上下俯仰,radius控制缩放。限制g_camPhi范围的原因前面说过——不加限制时,当视角接近正上方,cosf(phi)趋近0,相机位置会突然跳到很远,场景看起来像被弹飞。这种"相机约束"是三维可视化里非常容易被忽略的细节,却直接决定了用户操作时的体感舒适度。
4. 编译运行与避坑指南:从拷贝代码到看到第一帧的完整过程
4.1 环境准备与编译步骤:Visual Studio里怎么把工程跑起来
这套项目基于Direct3D 11,目标平台是Windows桌面应用,不依赖第三方库。拿到源码后,最省事的路径是装好Visual Studio 2022并勾选"使用C++的桌面开发"工作负载,Windows SDK单独确认一下。打开解决方案后,第一件事不是编译,而是核对两处项目属性:字符集是否为Unicode,Windows SDK版本是否与本地环境匹配。如果本机没有项目指定的SDK版本,VS会报错退出,解决方案是右键项目选择"重定解决方案目标",把SDK版本降到本地已安装的版本。
# 命令行编译方式,适合脚本化构建流程 MSBuild.exe D3DSolarSystem.sln /p:Configuration=Release /p:Platform=x64编译成功的标志是输出目录里出现D3DSolarSystem.exe,运行后应看到一个黑色背景、太阳居中的窗口。窗口启动后先做三件事:按住鼠标左键拖拽旋转视角,确认轨道环、行星、星空背景三层都在;滚轮拉远再推进,确认没有穿模;最后观察一会儿,确认公转和自转都在持续运动。如果启动时弹窗报缺DLL,优先检查是否装了最新的Visual C++运行库。
4.2 避坑指南:这五个问题我几乎每次重跑都会遇到
第一个坑是空窗口句柄导致设备创建失败。现象是程序一启动就崩溃,调用栈停在D3D11CreateDeviceAndSwapChain。原因多半是窗口创建和D3D设备初始化的顺序颠倒了,交换链拿到一个无效的HWND。解决方法是先CreateWindow,确保窗口句柄有效后再创建设备和交换链。
第二个坑是HLSL着色器编译报错,错误日志指向fxc内部错误,代码看起来没问题。大多数情况是HLSL文件用了带中文注释的UTF-8编码,而项目默认按系统代码页(GBK)解析,中文注释的字节被当成了非法字符。解决方法是把HLSL文件另存为带BOM的UTF-8,或者把着色器文件里的中文注释全部改成英文。这个方法对VS2019和VS2022都适用,是血泪经验。
第三个坑是行星全部挤在原点不动。现象是窗口能跑,轨道环也画出来了,但所有行星重叠在太阳位置。原因多半是实例缓冲的Stride设错了——如果你在IASetVertexBuffers里把实例缓冲的步长设成了sizeof(XMMATRIX)而没算上颜色分量,GPU读出来的矩阵会错位。解决方法是确认Stride与HLSLInstanceData结构体的大小完全一致。
第四个坑是运动速度看起来不稳定,行星星公转忽快忽慢。现象是场景运行中拖拽窗口或者帧率波动时,行星速度明显跳动。原因是代码里用累计时间计算角度而不是用帧间隔,当帧率从60掉到30时,运动看起来像"卡顿式加速"。解决方法是把时间参数改成每帧真实间隔的deltaTime,不再累计角度。
第五个坑是滚轮持续缩放后相机进入了太阳球体内部。现象是视角被一片亮白色填满,无法继续观察。原因是我在相机模块里设的g_camRadius下限10.0仍然小于太阳模型的视觉尺寸。解决方式是把相机最小半径与太阳缩放联动,在OnMouseWheel里判断g_camRadius小于太阳视觉尺寸的2倍时强制回弹,保证相机永远不会穿进太阳。
4.3 验证运行效果:怎么确认轨道与自转逻辑没写歪
跑起来之后第一件事不是看"像不像太阳系",而是验证三层逻辑是否各自独立运动。先用鼠标拖拽到正上方俯视,看轨道环应该是标准圆环,行星贴着轨道运动,没有穿出轨道线的现象。然后把视角拉平,选定一颗轴倾角大的行星(比如天王星),看它从正面转到背面时纹理方向应该保持不变,这说明自转轴倾角起效了。最后暂停自转(如果项目里有暂停键),单看公转,确保行星绕太阳转但自身不再旋转。这套验证方法我称之为"分层验证法"——公转、自转、轴倾角分开验证,每一层只确认一个运动维度,出问题能立刻定位是轨道计算、矩阵更新还是着色器的问题。
5. 二次开发:加一颗新行星、加一条卫星轨道要改哪几处
5.1 新行星的接入流程:参数表、实例数据和颜色各改一处
加一颗自定义的"演示行星X",改动集中在三处。第一处是建表函数CreateDefaultPlanetTable末尾追加一行参数;第二处是确认实例化缓冲的个数跟着参数表走,而不是写死的8;第三处如果新行星要不同的纹理贴图,还得在初始化时给模型绑定新的着色器资源视图。最小改动只需要第一处和初始化代码里把g_planets.size()作为缓冲区的大小。
planets.push_back({ 110.0f, 0.004f, 0.2f, 15.0f, 0.6f, XMFLOAT3(0.2f, 0.8f, 0.4f) });这一行把"演示行星X"放到轨道半径110处,公转速度0.004弧度/秒,自转速度0.2弧度/秒,轴倾角15度,模型缩放0.6,颜色偏绿。编译运行后,新轨道环会在海王星外侧出现,行星也会沿轨道运动。想加卫星轨道时,最简做法不是新建整套逻辑,而是复用PlanetNode结构:卫星的orbitRadius设为主星轨道半径的1.1倍,公转速度调大,在UpdateFrame里增加一个parentPos偏移量,让卫星世界矩阵以主星为中心额外平移一次。
5.2 把演示参数改成真实比例:轨道半径和自转周期的换算方法
项目默认参数为了视觉好看,把轨道间距拉得很开,行星大小做了夸张缩放。想更接近真实比例,需要查每颗行星的轨道半径(天文单位AU)和公转周期(年),然后做线性映射。最常见的映射公式是:视觉半径 = 基础半径 + AU值 × 缩放系数,公转角速度 = 2π / (公转周期秒数) × 时间系数。需要特别提醒的是,真实比例下行星会小到几乎肉眼不可见,所以没有项目会完全按真实比例展示行星大小——业界通行做法是轨道半径按真实AU映射,行星模型保持视觉可辨的放大,并在注释里写明"模型大小不按比例,按可读性缩放"。
5.3 一个能立刻见效的改造:给行星加暂停和速度调节热键
速度可调是交互演示里最常用也最好做的改造。加两个全局变量,在UpdateFrame开头做一次时间缩放,再把下面用到时间的地方全部替换成缩放后的值,最后在键盘消息处理里绑定上下方向键做加速减速。这个改造不到20行,但演示效果提升非常明显——做科普讲解时,可以先慢速解释地球的公转周期,再一键加速看木星怎么被甩在后面。
bool g_paused = false; float g_timeScale = 1.0f; void HandleKeyInput(UINT message) { if (message == WM_KEYDOWN) { if (GetAsyncKeyState(VK_SPACE) & 0x8000) g_paused = !g_paused; if (GetAsyncKeyState(VK_UP) & 0x8000) g_timeScale *= 1.2f; if (GetAsyncKeyState(VK_DOWN) & 0x8000) g_timeScale *= 0.8f; } } // UpdateFrame 中替换: float realDelta = deltaTime * (g_paused ? 0.0f : g_timeScale);从那以后,我每次做三维可视化演示,都会先把"暂停+速度调节"做进去,这个习惯省下了数不清的现场演示尴尬。希望这套D3D太阳系能帮你少走点弯路,祝跑通顺利。
本文还有配套的精品资源,点击获取