D3D12渲染技术之地形和水

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jxw167/article/details/83025688

在这个案例中,我们为山和水场景添加纹理, 第一个关键问题是我们在地上铺设草纹理, 因为地网格是一个大的表面,如果我们简单地在其上拉伸纹理,那么使用较少的纹理像素将覆盖每个三角形。 换句话说,表面没有足够的纹理分辨率; 我们会得到放大的纹理,因此,我们在地网格上重复草纹理以获得更高的分辨率。 第二个关键问题是我们使用时间函数模拟水波纹的滚动, 这种增加的运动使水效果更佳。 下图显示了演示的屏幕截图。
在这里插入图片描述

网格纹理坐标生成

下图显示了xz平面中的m×n网格和归一化纹理空间域[0,1] 中的对应网格, 从图中可以清楚地看出,xz平面中第i个网格顶点的纹理坐标是纹理空间中第i个网格顶点的坐标, 第i个顶点的纹理空间坐标是:
在这里插入图片描述

在这里插入图片描述
xz空间中网格顶点vij的纹理坐标由uv空间中的第i个网格顶点Tij给出。
因此,我们使用以下代码在GeometryGenerator :: CreateGrid方法中为网格生成纹理坐标:

GeometryGenerator::MeshData GeometryGenerator::CreateGrid(float width, float depth, uint32 m, uint32 n)
{
  MeshData meshData;
 
  uint32 vertexCount = m*n;
  uint32 faceCount  = (m-1)*(n-1)*2;
 
  float halfWidth = 0.5f*width;
  float halfDepth = 0.5f*depth;
 
  float dx = width / (n-1);
  float dz = depth / (m-1);
  float du = 1.0f / (n-1);
  float dv = 1.0f / (m-1);
 
  meshData.Vertices.resize(vertexCount);
  for(uint32 i = 0; i < m; ++i)
  {
    float z = halfDepth - i*dz;
    for(uint32 j = 0; j < n; ++j)
    {
      float x = -halfWidth + j*dx;
 
      meshData.Vertices[i*n+j].Position = XMFLOAT3(x, 0.0f, z);
      meshData.Vertices[i*n+j].Normal  = XMFLOAT3(0.0f, 1.0f, 0.0f);
      meshData.Vertices[i*n+j].TangentU = XMFLOAT3(1.0f, 0.0f, 0.0f);
 
      // Stretch texture over grid.
      meshData.Vertices[i*n+j].TexC.x = j*du;
      meshData.Vertices[i*n+j].TexC.y = i*dv;
       }
  }

纹理平铺

在地形网格上铺设草纹理, 但到目前为止,我们计算的纹理坐标位于单位域[0,1] ; 所以不会发生平铺。 要平铺纹理,我们指定wrap地址模式,并使用纹理变换矩阵将纹理坐标缩放5倍。 因此,纹理坐标被映射到域[0,5] ,以便纹理在平面网格表面上平铺5×5次:

void TexWavesApp::BuildRenderItems()
{
  auto gridRitem = std::make_unique<RenderItem>();
  gridRitem->World = MathHelper::Identity4x4();
  XMStoreFloat4x4(&gridRitem->TexTransform, 
    XMMatrixScaling(5.0f, 5.0f, 1.0f));
  …
}

纹理动画

要在水面上实现纹理滚动,我们将纹理平面中的纹理坐标转换为AnimateMaterials方法中的时间函数,该方法在每个更新周期调用。 如果每帧的位移很小,则会产生平滑动画的错觉。 我们使用wrap地址模式和无缝纹理,以便我们可以无缝地转换纹理空间平面周围的纹理坐标。 以下代码显示了我们如何计算水纹理的偏移矢量,以及我们如何构建和设置水的纹理矩阵:

void TexWavesApp::AnimateMaterials(const GameTimer& gt)
{
   // Scroll the water material texture coordinates.
   auto waterMat = mMaterials["water"].get();
 
   float& tu = waterMat->MatTransform(3, 0);
   float& tv = waterMat->MatTransform(3, 1);
 
   tu += 0.1f * gt.DeltaTime();
   tv += 0.02f * gt.DeltaTime();
 
   if(tu >= 1.0f)
     tu -= 1.0f;
 
   if(tv >= 1.0f)
     tv -= 1.0f;
     waterMat->MatTransform(3, 0) = tu;
   waterMat->MatTransform(3, 1) = tv;
 
   // Material has changed, so need to update cbuffer.
   waterMat->NumFramesDirty = gNumFrameResources;
}

总结

1、纹理坐标用于定义纹理上的三角形,该三角形映射到3D三角形。
2、为游戏创建纹理的最流行的方法是让美术在Photoshop或其他图像编辑器中制作它们,然后将它们保存为图像文件,如BMP,DDS,TGA或PNG, 然后游戏应用程序将加载时的图像数据加载到ID3D12Resource对象中, 对于实时图形应用程序,DDS(DirectDraw表面格式)图像文件格式是首选,因为它支持GPU本身理解的各种图像格式; 特别是,它支持可由GPU本机解压缩的压缩图像格式。
3、将传统图像格式转换为DDS格式有两种常用方法:使用导出到DDS的图像编辑器或使用名为texconv的Microsoft命令行工具。
4、我们可以使用CreateDDSTextureFromFile12函数从存储在磁盘上的图像文件创建纹理,该函数位于DVD上的Common / DDSTextureLoader.h / .cpp。
5、当我们放大表面并试图用几个纹理像素覆盖太多屏幕像素时,会发生放大, 当我们缩小表面并且太多纹理像素正试图覆盖太少的屏幕像素时,会发生缩小, Mipmap和纹理过滤器是处理放大和缩小的技术。 GPU本身支持三种纹理过滤(按质量最低,质量最低,质量最高,最昂贵):点,线性和各向异性过滤器。
6、寻址模式定义了Direct3D应该对[0,1]范围之外的纹理坐标做什么。 例如,纹理应该是平铺,镜像等。
7、纹理坐标可以像其他点一样缩放,旋转和平移。 通过每帧少量地逐渐变换纹理坐标,我们为纹理设置动画。

Demo下载地址:链接:https://pan.baidu.com/s/1X0Vikf6qGYGPKU-Nwf-wYA 密码:h79q

阅读更多

扫码向博主提问

海洋_

博客专家

非学,无以致疑;非问,无以广识
  • 擅长领域:
  • 3D引擎架构
  • 服务器架构
  • GPU渲染
  • 客户端架构
  • 引擎优化
去开通我的Chat快问
换一批

没有更多推荐了,返回首页