博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
DirectX11 动态缓存 & 水波演示Demo
阅读量:4087 次
发布时间:2019-05-25

本文共 3044 字,大约阅读时间需要 10 分钟。

DirectX11 动态缓存 & 水波演示Demo

1. 动态缓存是什么?

到目前为止,我们一直使用的是静态缓存(static buffer),它的内容是在初始化时固定下来的。相比之下,动态缓存(dynamic buffer)的内容可以在每一帧中进行修改。当实现一些动画效果时,我们通常使用动态缓存区。例如,我们要模拟一个水波效果,并通过函数f(x ,z ,t)来描述水波方程,计算当时间为t时,xz平面上的每个点的高度。在这一情景中,我们必须使用“山峰与河谷”中的那种三角形网格,将每个网格点代入f(x, z , t)函数得到相应的水波高度。由于该函数依赖于时间t(即,水面会随着时间而变化),我们必须在很短的时间内(比如1/30秒)重新计算这些网格点,以得到较为平滑的动画。所以,我们必须使用动态顶点缓存区来实时更新三角形网格顶点的高度。

2. 如何创建动态缓存?

前面提到,为了获得一个动态缓存区,我们必须在创建缓存区时将Usage标志值指定为D3D11_USAGE_DYNAMIC;同时,由于我们要向缓存区写入数据,所以必须将CPU访问标志值指定为D3D11_CPU_ACCESS_WRITE。

D3D11_BUFFER_DESC vbd;vbd.Usage = D3D11_USAGE_DYNAMIC;vbd.ByteWidth = sizeof(Vertex) * mWaves.VertexCount();vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;vbd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;vbd.MiscFlags = 0;HR(md3dDevice->CreateBuffer(&vbd, 0, &mWavesVB));

3. 如何使用动态缓存?

使用ID3D11Buffer::Map函数获取缓存区内存的起始地址指针,并向它写入数据:

HRESULT ID3D11DeviceContext::Map(    ID3D11Resource *pResource ,    UINT Subresource,    D3D11_MAP MapType,    UINT MapFlags,    D3D11_MAPPED_SUBRESOURCE *pMappedResource);

1.pResource:指向要访问的用于读/写的资源的指针。缓存是一种Direct3D 11资源,其他类型的资源,例如纹理资源,也可以使用这个方法进行访问。

2.Subresource:包含在资源中的子资源的索引。后面我们会看到如何使用这个索引,而缓存不包含子资源,所以设置为0。

3.MapType:常用的标志有以下几个:

  • D3D11_MAP_WRITE_DISCARD:让硬件抛弃旧缓存,返回一个指向新分配缓存的指针,通过指定这个标志,可以让我们写入新分配的缓存的同时,让硬件绘制已抛弃的缓存中的内容,可以防止绘制停顿。
  • D3D11_MAP_WRITE_NO_OVERWRITE:我们只会写入缓存中未初始化的部分;通过指定这个标志,可以让我们写入未初始化的缓存的同时,让硬件绘制前面已经写入的内容,可以防止绘制停顿。
  • D3D11_MAP_READ:表示应用程序(CPU)会读取GPU缓存的的一个副本到系统内存中。

4.MapFlags:可选标志,这里不使用,所以设置为0;具体细节可参见SDK文档。

5.pMappedResource:返回一个指向D3D11_MAPPED_SUBRESOURCE的指针,这样我们就可以访问用于读/写的资源数据。

D3D11_MAPPED_SUBRESOURCE结构定义如下:

typedef struct D3D11_MAPPED_SUBRESOURCE{    void  *pData;    UINT Row Pitch;    UINT DepthPitch;}D3D11_MAPPED_SUBRESOURCE

1.pData:指向用于读/写的资源内存的指针,你必须将它转换为资源中存储的数据的格式。

2.RowPitch:资源中一行数据的字节大小。例如,对于一个2D纹理来说,这个大小为一行的字节大小。

3.DepthPitch:资源中一页数据的大小。例如,对于一个3D纹理来说,这个大小为3D纹理中一个2D图像的字节大小。

RowPitch和DepthPitch的区别是针对2D和3D资源(类似于2D和3D数组)而言的。顶点/索引缓存本质上是1D数组,RowPitch和DepthPitch的值是相同的,都等于顶点/索引缓存的字节大小。

4. 水波演示程序

下面的代码展示如何在水波演示程序中更新顶点缓存:

D3D11_MAPPED_SUBRESOURCE mappedData;HR(md3dImmediateContext->Map(mWavesVB, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedData));Vertex* v = reinterpret_cast
(mappedData.pData);for(UINT i = 0; i < mWaves.VertexCount(); ++i){ v[i].Pos = mWaves[i]; v[i].Color = XMFLOAT4(0.0f, 0.0f, 0.0f, 1.0f);}md3dImmediateContext->Unmap(mWavesVB, 0);

当你完成缓存区的更新操作之后,必须调用ID3D11Buffer::Unmap函数。

当使用动态缓存区时,必然会有一些额外开销,因为这里存在一个从CPU内存向GPU内存回传数据的过程。 所以,在实际工作中应尽可能多使用静态缓存区,少使用动态缓存区。在Direct3D的最新版本中已经引入了一些新特性用于减少对动态缓存区的需求。例如:

1.可以在顶点着色器中实现简单动画。

2.通过渲染到纹理(render to texture)和顶点纹理拾取(vertex texture fetch)功能,可以实现完全运行在GPU上的水波模拟动画。

3.几何着色器为GPU提供了创建和销毁图元的能力,在以前没有几何着色器时,这些工作都是由CPU来完成的。

索引缓存区可以是动态的。不过,在水波演示程序中,三角形的拓扑结构始终不变,只有顶点高度会发生变化;所以,这里只需要让顶点缓存区变为动态缓存区。

本章的水波演示程序使用了一个动态缓存区来实现简单的水波效果。本书不会将重点放在水波模拟算法的实现细节上(有兴趣的读者可以参见[Lengyel02]),我们只是用它来说明动态缓存区的用法:在CPU上更新模拟数据,然后调用Map/Unmap方法更新顶点缓存区。

注意:在水波演示程序中,我们以线框模式渲染水波;是因为我们现在还没有讲到灯光的用法,在实体填充模式下,很难看出水波的运动效果。注意:我们再次强调,这个示例应该在GPU上使用更高级的方式实现,比如渲染到纹理和顶点纹理拾取。但是由于我们还没有讲到些技术,所以现在只能在CPU上实现,暂时使用动态顶点缓存区来更新顶点。

完整项目源代码请在DirectX11 龙书官网下载。建议使用VS阅读源代码。

5. 程序运行结果截图

这里写图片描述

这里写图片描述

你可能感兴趣的文章
聊聊编码那些事,顺带实现base64
查看>>
TypeScript for React (Native) 进阶
查看>>
React 和 ReactNative 的渲染机制/ ReactNative 与原生之间的通信 / 如何自定义封装原生组件/RN中的多线程
查看>>
JavaScript实现DOM树的深度优先遍历和广度优先遍历
查看>>
webpack4 中的 React 全家桶配置指南,实战!
查看>>
react 设置代理(proxy) 实现跨域请求
查看>>
通过试题理解JavaScript
查看>>
webpack的面试题总结
查看>>
实践这一次,彻底搞懂浏览器缓存机制
查看>>
Koa2教程(常用中间件篇)
查看>>
React Hooks 完全指南
查看>>
React16常用api解析以及原理剖析
查看>>
教你发布你npm包
查看>>
nvm 和 nrm 的安装与使用
查看>>
React Hooks 一步到位
查看>>
React Redux常见问题总结
查看>>
前端 DSL 实践指南
查看>>
ReactNative: 自定义ReactNative API组件
查看>>
cookie
查看>>
总结vue知识体系之实用技巧
查看>>