chap 13 地形绘制基础
其实是一个数组,每个元素都指定了地形方格中某一顶点的高度值,每个元素只分配了1个字节的存储空间,
当加载到程序时,重新分配 浮点型 或 整型 数据来存储这些高度值,高度图的图形表示有很多,
用photoshop完成是不错的选择,一旦完成高度图的创建,将其保存为 8位 的RAW文件
RAW文件:仅连续存储图像中以字节为单位的每个像素的灰度值,他的特点是读取方便,可以理解为一个连续的字节存储块
(3)访问和修改高度值
2.创建地形集合信息
每行顶点数 和 单元数,每列顶点数 和 单元数,高度比例因子,总宽度,总深度,总顶点数,三角元数
地形顶点(TerrainVertex) 是 一个嵌套类,他只在地形类(Terrain)中有用
(1)顶点计算
(2)索引计算
方案二:
还有另一种纹理映射的方式,他的主要思路是使用一个"空"的纹理,然后基于一些已定义好的参数计算出纹理元的颜色,
例如,可以根据顶点不同的高度给出不同的颜色
(1)D3DXCreateTexture 创建一个空纹理
(2)由于一个纹理对象可有多级渐近纹理,所以要先 锁定 顶层纹理
(3)遍历每个纹理元并对其上色
//第一种方法是 通过文件去创建纹理,可以理解为直接粘贴纹理图
//第二种方法是 创建空纹理,然后再依据某些特定属性去生成的"立即纹理"
关键:计算地形三角元的明暗因子,他是一个0到1之间的浮点数
使用指定 到达光源的方向 来描述平行光 的光向量(L) 是为了更适合 漫射光光照的 计算
每个方格的面法向量设为N
注意:光的方向向量应为单位向量,这是为了求后面的cosine值,因为面的法向量也将被单位化,在对LN进行 点乘 的时候,直接得到明暗因子
LN夹角越大,此方格接受到的光照就越少,当这个角度超过了90度,很明显,光就照不到方格了
主要是创建一个 地形类 (Terrain)
其实是一个数组,每个元素都指定了地形方格中某一顶点的高度值,每个元素只分配了1个字节的存储空间,
当加载到程序时,重新分配 浮点型 或 整型 数据来存储这些高度值,高度图的图形表示有很多,
比如:灰度图,地形中某点海拔越高,相应点在灰度图中的亮度越大
用photoshop完成是不错的选择,一旦完成高度图的创建,将其保存为 8位 的RAW文件
RAW文件:仅连续存储图像中以字节为单位的每个像素的灰度值,他的特点是读取方便,可以理解为一个连续的字节存储块
当photoshop提示是否为RAW文件增加一个文件头时,选择"否"
std::vector<int> _heightmap; //分配整型数据来存储高度值
bool Terrain::readRawFile(std::string fileName)
{
std::vector<BYTE> in(_numVertices);
std::ifstream inFile(fileName.c_str(), std::ios_base::binary);
if(inFile == 0)
return false;
inFile.read( (char *)&in[0], in.size() ); //把RAW文件中的内容读入到BYTE容器in里面
inFile.close();
_heightmap.resize( _numVertices );
for(int i = 0; i<in.size(); i++)
_heightmap[i] = in[i]; //再把in的内容赋给int容器_heightmap,这样就得到了每个顶点的整型 高度值
return true;
}
(3)访问和修改高度值
int Terrain::getHeightmapEntry(int row, int col)
{
return _heightmap[row * _numVertsPerRow + col];
}
void Terrain::setHeightmapEntry(int row, int col, int value)
{
_heightmap[row * _numVertsPerRow + col] = value;
}
2.创建地形集合信息
每行顶点数 和 单元数,每列顶点数 和 单元数,高度比例因子,总宽度,总深度,总顶点数,三角元数
地形顶点(TerrainVertex) 是 一个嵌套类,他只在地形类(Terrain)中有用
(1)顶点计算
bool Terrain::computeVertices()
{
HRESULT hr = 0;
hr = _device->CreateVertexBuffer(
_numVertices * sizeof(TerrainVertex);
D3DUSAGE_WRITEONLY,
TerrainVertex::FVF,
D3DPOOL_MANAGED,
&_vb,
0
);
if(FAILED(hr))
return false;
int startX = -_width/2;
int startZ = _depth/2;
int endX = _width/2;
int endZ = -_depth/2;
float uCoordIncrementSize = 1.0f / (float)_numCellsPerRow;
float vCoordIncrementSize = 1.0f / (float)_numCellsPerCol;
TerrainVertex *v = 0;
_vb->Lock(0,0,(void**)&v,0);
int i = 0;
for(int z = startZ; z >= endZ; z -= _cellSpacing)
{
int j = 0;
for(int x = startX; x <= endX; x += cellSpacing)
{
int index = i * _numVertsPerRow + j;
v[index] = Terrain(
(float)x,
(float)_heightmap[index],
(float)z,
(float)j*uCoordIncrementSize,
(float)i*vCoordIncrementSize
);
j++;
}
i++;
}
_vb->Unlock();
return true;
}
(2)索引计算
//为什么要用索引,因为创建的顶点是按行创建的,不是按三角元创建的
bool Terrain::computeIndices()
{
HRESULT hr = 0;
hr = _device->CreateIndexBuffer(
_numTriangles * 3 * sizeof(WORD),
D3DUSAGE_WRITEONLT,
D3DFMT_INDEX16,
D3DPOOL_MANAGED,
&_ib,
0
);
if(FAILED(hr))
return false;
WORD *indices = 0;
_ib->Lock(0,0,(void**)&indices,0);
int baseIndex = 0;
for(int i=0; i<_numCellsPerCol; i++ )
{
for(int j=0; j<_numCellsPerRow; j++)
{
indices[baseIndex] = i * _numVertsPerRow + j;
indices[baseIndex + 1] = i * _numVertsPerRow + j + 1;
indices[baseIndex + 2] = (i+1) * _numVertsPerRow + j;
indices[baseIndex + 3] = (i+1) * _numVertsPerRow + j;
indices[baseIndex + 4] = i * _numVertsPerRow +j + 1;
indices[baseIndex + 5] = (i+1) * _numVertsPerRow + j +1;
baseIndex += 6;
}
}
_ib->Unlock();
return true;
}
3.纹理映射
bool Terrain::LoadTexture(std::string fileName)
{
HRESULT hr = 0;
hr = D3DXCreateTextureFromFile(
_device,
fileName.c_str(),
&_tex
);
if(FAILED(hr))
return false;
return true;
}
方案二:
还有另一种纹理映射的方式,他的主要思路是使用一个"空"的纹理,然后基于一些已定义好的参数计算出纹理元的颜色,
例如,可以根据顶点不同的高度给出不同的颜色
(1)D3DXCreateTexture 创建一个空纹理
(2)由于一个纹理对象可有多级渐近纹理,所以要先 锁定 顶层纹理
(3)遍历每个纹理元并对其上色
//第一种方法是 通过文件去创建纹理,可以理解为直接粘贴纹理图
//第二种方法是 创建空纹理,然后再依据某些特定属性去生成的"立即纹理"
bool Terrain::genTexture(D3DXVECTOR3 * directionToLight)
{
HRESULT hr = 0;
int texWidth = _numCellsPerRow;
int texHeight = _numCellsPerCol;
hr = D3DXCreateTexture(
_device,
texWidth, texHeight,
0, //创建一个完整的多级渐近纹理链
0, //Usage
D3DFMT_X8R8G8B8,
D3DPOOL_MANAGED,
&_tex
);
if(FAILED(hr))
return false;
//确保顶点的格式
D3DSURFACE_DESC textureDesc;
_tex -> GetLevelDesc(0, &textureDesc);
if(textureDesc.Format != D3DFMT_X8R8G8B8)
return false;
D3DLOCKED_RECT lockedRect;
_tex -> LockRect(
0,
&lockedRect,
0,
0
);
DWORD *imageData = (DWORD*)lockedRect.pBits;
//pitch每行字节数,pBits第一个字节地址
for(int i = 0; i < texHeight; i++)
{
for(int j = 0; j < texWidth; j++)
{
D3DXCOLOR c;
float height = (float)getHeightmapEntry(i,j);
if((height)<42.5f) c = d3d::BEACH_SAND;
else if((height)<85.0f) c = d3d::LIGHT_YELLOW_GREEN;
else if((height)<127.5f) c = d3d::PUREGREEN;
else if((height)<170.0f) c = d3d::DARK_YELLOW_GREEN;
else if((height)<212.5f) c = d3d::DARKBROWN;
else c = d3d::WHITE;
imageData[i * lockedRect.Pitch / 4 + j] = (D3DCOLOR)c;
}
}
_tex->UnlockRect(0);
if(!lightTerrain(directionToLight)) //照亮地形,主要是明暗因子的计算
{
::MessageBox(0,"lightTerrain() - FAILED",0,0);
return false;
}
hr = D3DXFilterTexture(
_tex,
0,
0,
D3DX_DEFAULT
);
if(FAILED(hr))
return false;
return true;
}
4.光照
自己设置灯
关键:计算地形三角元的明暗因子,他是一个0到1之间的浮点数
使用指定 到达光源的方向 来描述平行光 的光向量(L) 是为了更适合 漫射光光照的 计算
每个方格的面法向量设为N
注意:光的方向向量应为单位向量,这是为了求后面的cosine值,因为面的法向量也将被单位化,在对LN进行 点乘 的时候,直接得到明暗因子
LN夹角越大,此方格接受到的光照就越少,当这个角度超过了90度,很明显,光就照不到方格了
//计算每个面的明暗因子
float Terrain::ComputeShade(int cellRow, int cellCol, D3DXVECTOR3 *directionToLight)
{
float heightA = getHeightEntry(cellRow, cellCol);
float heightB = getHeightEntry(cellRow, cellCol + 1);
float heightC = getHeightEntry(cellRow + 1, cellRow);
D3DXVECTOR3 u(_cellSpacing, heightB - heightA, 0.0f);
D3DXVECTOR3 v(0.0f, heightC - heightA, -_cellSpacing);
D3DXVECTOR3 n;
D3DXVec3Cross(&n, &u, &v);
D3DXVec3Normalize(&n, &n);
float cosine = D3DXVec3Dot(&n, directionToLight); //计算出法向量和光照方向的cos值,这是一个0~1之间的数
if(cosine<0.0f)
cosine = 0.0f;
return cosine;
}
//对地形进行着色,也称 明暗处理
DWORD *imageData = (DWORD*)lockedRect.pBits;
for(int i = 0; i<textureDesc.Height; i++)
{
for(int j = 0; j<textureDesc.Width; j++)
{
int index = i * lockedRect.Pitch / 4 + j;
D3DXCOLOR c(imageData[index]);
c *= ComputeShade(i, j, directionToLight );
iamgeData[index] = (D3DCOLOR)c;
}
}
5.在地形中“行走”
float Terrain::getHeight(float x, float z)
{
x = ((float)_width / 2.0f) + x;
z = ((float)_depth / 2.0f) - z;
x /= (float)_cellSpacing;
z /= (float)_cellSpacing;
float col = ::floorf(x);
float row = ::floorf(z); //以上可以求出坐标所在的方格
float A = getHeightmapEntry(row, col);
float B = getHeightmapEntry(row, col+1);
float C = getHeightmapEntry(row+1, col);
float D = getHeightmapEntry(row+1, col+1); //得此方格的4个顶点
float dx = x - col; //把此方格移到起始点
float dz = z - row;
float height = 0.0f;
if(dz < 1.0f - dx) //判断此点是位于方格的上三角面还是下三角面
{
float uy = B - A;
float vy = C - A;
height = A + d3d::Lerp(0.0f, uy, dx) + d3d::Lerp(0.0f, vy, dz);
}
else
{
float uy = C - D;
float vy = B - D;
height = D + d3d::Lerp(0.0f, uy, 1.0f - dx) + d3d::Lerp(0.0f, vy, 1.0f - dz);
}
return height; //求得坐标所在点的高度值
}