3D地形绘制 - 第一篇

鉴于学习3D地形绘制已经有一段时间了,所以想把所学过的内容重新回顾一遍。参考资料:《3D游戏编程》。

 

第一篇 高度图

1 高度图(Height Map)

       在高度图中,高度值表现为0 ~ 255之间的明暗值。高度图技巧的使用方法就是:首先,将想要制作的三维地形制作为只包含二维高度信息的高度图,然后,利用高度图信息重新制作为三维地形(terrain)。

 

2 高度图生成原理

       高度图的主要原理就是将二维信息转变为三维顶点信息,一般情况下,二维xy平面空间在三维图形中对应的是xz平面。其核心原理是:将二维(x ,z)坐标所对应的像素亮度值换算为三维(x, y, z)坐标中的y值,因此,大部分情况下,高度图只包含256色亮度值的黑白图片文件。【二维图形中的亮度值可用来表示三维空间中的高度值】

 

3 高度图实际生成代码

       采用伪代码(pseudo code)实现大致的算法,具体如下:

             

	pBits= 读取高度图文件
        cx= 高度图的长度
        cz= 高度图的宽度
        vb= 创建顶点缓冲
        p= vb->lock()
        for(z= 0; z < cz; z++)
        {
             for(x = 0; x < cx; x++)
             {
                    v.p.x= x ? cx / 2.0
                    v.p.z= -(z ? cz / 2.0)
                    v.p.y= pBits + x + y * cx
                    v.n= Normalize(v.p)
                    v.tu= x / (cx – 1.0)
                    v.tv= z / (cz – 1.0)
                    *p++= v
             }
        }
        vb->unlock()
 

       在该篇实例中,这部分伪代码对应的是InitVB()函数。利用纹理对象读取高度图信息之后,调用LockRect()函数获取存储器的固定指针进行访问。该方法就是光影贴图(lightmapping)技术中所使用的必须技术。

       调用LockRect()函数后,将纹理的长度和存储器的指针存储在名为D3DLOCKED_RECT的结构体中,通过结构体的成员参数中的pBits参数可以访问纹理的像素单位。【结构体中的Pitch值表现为转入纹理的下一扫描线所需要的字节数。】

       下面是实现InitVB()函数的代码。

// 创建顶点缓冲,设置顶点值
HRESULT InitVB()
{
	D3DSURFACE_DESC ddsd;
	D3DLOCKED_REDT	d3drc;

	g_pTextHeight->GetLevelDesc(0, &ddsd);	// 纹理信息
	g_cxHeight = ddsd.Width;				// 纹理的长度
	g_czHeight = ddsd.Height;				// 纹理的宽度
	
	if (FAILED(g_pd3dDevice->CreateVertexBuffer(ddsd.Width*ddsd.Height*sizeof(CUSTOMVERTEX),
		0, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &g_pVB, NULL)))
	{
		return E_FAIL;
	}

	// 纹理存储器
	g_pTexHeight->LockRect(0, &d3drc, NULL,	D3DLOCK_READONLY);
	VOID* pVertices;

	// 顶点缓冲
	if (FAILED(g_pVB->Lock(0, g_cxHeight*g_czHeight*sizeof(CUSTOMVERTEX),(void**)&pVertices, 0)))
	{
		return E_FAIL;
	}

	CUSTOMVERTEX v;
	CUSTOMVERTEX* pV = (CUSTOMVERTEX*)pVertices;

	for (DWORD z = 0; z < g_czHeight; z++)
	{
		for (DWORD x = 0; x < g_cxHeight; x++)
		{
			v.p.x = (float)x - g_cxHeight / 2.0f;		// 顶点的x坐标(以原点为准对齐网格)
			v.p.z = -((float)z - g_czHeight / 2.0f);	// 顶点的z坐标,因为z轴指向显示器内,所以要乘以“-”
			v.p.y = ((float)(*((LPDWORD)d3drc.pBits + x + z*(d3drc.Pitch/4)) & 0x000000ff)) / 10.0f;	// 由于是DWORD,所以要Pitch/4
			v.n.x = v.p.x;
			v.n.y = v.p.y;
			v.n.z = v.p.z;
			D3DXVec3Normalize(&v.n, &v.n);
			v.t.x = (float)x / (g_cxHeight - 1);
			v.t.y = (float)z / (g_czHeight - 1);
			*pV++ = v;
		}
	}

	g_pVB->Unlock();
	g_pTexHeight->UnlockRect(0);

	return S_OK;
}

在InitIB()函数中,索引是按照“左侧上端三角形 + 右侧下端三角形”的顺序创建的。首先,调用Animate()函数创建旋转矩阵,旋转整个地形。然后,调用SetupLights()函数旋转光源。最后,在Render()函数中调用DrawMesh()函数绘制整个地形。

 

4 实例运行画面

3D地形绘制 - 第一篇_第1张图片

5 附录


本实例源代码下载地址:

【下载地址一】【下载地址二】


你可能感兴趣的:(3D游戏编程基础)