Direct3D高程图立体显示

在微软的DirectX Sample Browser中,有个RaycastTerrain例子,做得很好,将一张平面的地形图,在底面添加了一个底部,四周的高度差再封闭起来,然后再添加一个浅蓝色的背景,平面图形的立体感很强,而且灯光和阴影的效果也做得很棒!

Direct3D高程图立体显示_第1张图片

这个例子是基于C++的,这里模仿这个例子,给出一个基于C#的实现。思路也大致类似,找一张bmp格式的深度图,经过像素值的计算处理,生成Y轴方向的高度值,再经过纹理贴图,形成一个底部为空的三维立体图形。此时为了增强上述的立体效果,可以在底面添加一个底部,这里为了加快渲染的速度,底部做成一四边形,用两个三角形渲染,四边形的顶点可以参考bmp深度图的四个角的顶点坐标。对于底部的高度的选取,即Y轴方向的高度值的选取,可以选在bmp深度图Y最小坐标值或者Y最小坐标值的下方,所以需要计算出这个Y最小坐标值。因此定义如下函数,求bmp深度图Y最小坐标值:

public float GetMinHeight(string bitmapPath)
        {
            float minHeight = 65535.0f;
            Bitmap bitmap = new Bitmap(bitmapPath);
            xCount = (bitmap.Width - 1) / 2;
            yCount = (bitmap.Height - 1) / 2;
            cellWidth = bitmap.Width / xCount;
            cellHeight = bitmap.Height / yCount;
            for(int i=0;i

为了增加底部的显示效果,可以通过贴图来完成。为了增加实时渲染的效果,可以在OnCreateDevice中响应底部三角形顶点定义函数。底部2个三角形的顶点定义函数实现如下:

public void OnCreateBottomVertexBuffer(object sender,EventArgs e)
        {
            CustomVertex.PositionNormalTextured[] bottomVerts = (CustomVertex.PositionNormalTextured[])bottomVertexBuffer.Lock(0, 0);//绘制底面正方形的6个顶点
            string bitmapPath = @"F:\\workdir\\VC# Based DirectX\\height.BMP";
            float minHeight = GetMinHeight(bitmapPath);
            float xWidth = GetBitMapWidth(bitmapPath);
            float yHeight = GetBitMapHeight(bitmapPath);
            bottomVerts[0].Position = new Vector3(0.0f, minHeight-5.0f, 0.0f);
            bottomVerts[0].Normal = new Vector3(0, 0, -1);
            bottomVerts[0].Tu = 0.0f;//顶点0纹理坐标Tu
            bottomVerts[0].Tv = 20.0f;//纹理图片沿Y轴方向重复贴图20次

            bottomVerts[1].Position = new Vector3(0.0f, minHeight - 5.0f, yHeight);
            bottomVerts[1].Normal = new Vector3(0, 0, -1);
            bottomVerts[1].Tu = 0.0f;
            bottomVerts[1].Tv = 0.0f;

            bottomVerts[2].Position = new Vector3(xWidth, minHeight - 5.0f, yHeight);
            bottomVerts[2].Normal = new Vector3(0, 0, -1);
            bottomVerts[2].Tu = 20.0f;
            bottomVerts[2].Tv = 0.0f;

            bottomVerts[3].Position = new Vector3(0.0f, minHeight - 5.0f, 0.0f);
            bottomVerts[3].Normal = new Vector3(0, 0, -1);
            bottomVerts[3].Tu = 0.0f;
            bottomVerts[3].Tv = 20.0f;

            bottomVerts[4].Position = new Vector3(xWidth, minHeight - 5.0f, yHeight);
            bottomVerts[4].Normal = new Vector3(0, 0, -1);
            bottomVerts[4].Tu = 20.0f;
            bottomVerts[4].Tv = 0.0f;

            bottomVerts[5].Position = new Vector3(xWidth, minHeight - 5.0f, 0.0f);
            bottomVerts[5].Normal = new Vector3(0, 0, -1);
            bottomVerts[5].Tu = 20.0f;
            bottomVerts[5].Tv = 20.0f;
            bottomVertexBuffer.Unlock();
        }

剩下的问题就是如何封闭底部的四面高度差,方法也很简单,可以一面一面地进行。以下图为例:

Direct3D高程图立体显示_第2张图片

对于一张BMP格式的位图,将其划分为xCount*yCount个单元,每个单元的宽度为cellWidth,高度为cellHeight,则cellWidth=Width/xCount,cellHeight=Height/yCount。要想封住其四个边的高度差,以沿X轴方向的边缘高度差面为例,需要声明2(xCount+1)个顶点来记录相应的坐标值。其中xCount+1个点落在与X轴平行的那个边上,这些点都是Vector3类型的,这些点的Y坐标为底部平面的Y值,是一个定值,X坐标依次为每一个像素点单元所在的X坐标,Z坐标就是0。剩下的xCount+1个顶点则为X轴边缘高度差面与三维立体图的交点,即高度方向纵切图与上顶面的交点,该交点的Y坐标就是相应的像素点的高度值,X坐标依次为每一个像素点单元所在的X坐标,Z坐标也是0.其定义实现如下:

float minY = GetMinHeight(bitmapPath) - 5.0f;
            borderVertexBuffer1 = new VertexBuffer(typeof(CustomVertex.PositionColored),
                2 * (xCount + 1),//四周的一个面封闭总共所需要的顶点的数目
                device,
                Usage.Dynamic | Usage.WriteOnly,
                CustomVertex.PositionColored.Format,
                Pool.Default);
            borderVertices1 = new CustomVertex.PositionColored[2 * (xCount + 1)];//定义顶点
            int k;
            for (k = 0; k < xCount + 1; k++)//X轴上的点的定义
            {
                borderVertices1[k].Position = new Vector3(k * cellWidth, minY, 0.0f);
                borderVertices1[k].Color = System.Drawing.Color.Aqua.ToArgb();
            }
            for (; k < 2 * (xCount + 1); k++)//高程图上的边界点的定义
            {
                Color color = bitmap.GetPixel((int)((k - xCount - 1) * cellWidth), 0);
                float height = float.Parse(color.R.ToString()) + float.Parse(color.G.ToString()) + float.Parse(color.B.ToString());
                height /= 10;
                borderVertices1[k].Position = new Vector3((k - xCount - 1) * cellWidth, height, 0);//i * cellHeight=0
                borderVertices1[k].Color = System.Drawing.Color.Aqua.ToArgb();
            }
            borderVertexBuffer1.SetData(borderVertices1, 0, LockFlags.None);

这2(xCount+1)个顶点可以形成xCount个小矩形,每个矩形可以分为2个小三角形,每个小三角形可以由3个顶点来绘制,为了加快绘制的速度,可以考虑用顶点索引来完成。声明一个顶点索引数组,其大小为2*3*xCount*1.这个顶点索引数组依次记录了要绘制的三角形的顶点的编号。如下图所示:

Direct3D高程图立体显示_第3张图片

所以对于沿X轴方向的边缘高度差面的绘制就是如何绘制这些顶点三角形。其绘制顺序如下:

(V[0],V[1],V[xCount+1]), (V[1],V[xCount+1],V[xCount+2]),…,

(V[xCount],V[2*(xCount+1)-2],V[2*(xCount+1)-1])。

很有规律性,用一个for循环就可以搞定,其定义实现如下:

borderIndexBuffer1 = new IndexBuffer(typeof(int),
                6 * xCount * 1,
                device,
                Usage.WriteOnly,
                Pool.Default);
            borderIndices1 = new int[6 * xCount * 1];//初始化索引顶点

            for (int j = 0; j < xCount; j++)
            {
                borderIndices1[6 * (j)] = j;
                borderIndices1[6 * (j) + 1] = j + (xCount + 1);
                borderIndices1[6 * (j) + 2] = j + 1;
                borderIndices1[6 * (j) + 3] = j + 1;
                borderIndices1[6 * (j) + 4] = j + (xCount + 1);
                borderIndices1[6 * (j) + 5] = j + (xCount + 1) + 1;
            }
            borderIndexBuffer1.SetData(borderIndices1, 0, LockFlags.None);

渲染的时候直接调用DrawIndexedPrimitives即可。

/*第1个边界三角形*/
            device.SetStreamSource(0, borderVertexBuffer1, 0);
            device.VertexFormat = CustomVertex.PositionColored.Format;
            device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 2 * (xCount + 1), 0, borderIndices1.Length / 3);

同理其他3个边缘高度差面的绘制类似。下面贴上几张效果图:

Direct3D高程图立体显示_第4张图片

Direct3D高程图立体显示_第5张图片

Direct3D高程图立体显示_第6张图片

Direct3D高程图立体显示_第7张图片

P.S.附上主界面工程的代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

namespace 高程图01
{
    public partial class MainForm : Form
    {
        private Device device = null;//定义绘图设备
        bool pause = false;

        public Vector3 CamPostion = new Vector3(0, 100, 100);//定义摄像机位置
        public Vector3 CamTarget = new Vector3(125, 30, 125);//定义摄像机目标位置

        private float angleY = 0.01f;//定义绕Y轴旋转变量

        private int mouseLastX, mouseLastY;//记录鼠标按下时的坐标位置
        private bool isRotateByMouse = false;//记录是否由鼠标控制旋转
        private bool isMoveByMouse = false;//记录是否由鼠标控制移动

        private CustomVertex.PositionTextured[] vertices;//定义顶点变量
        private Texture texture;//定义贴图变量
        private Material material;//定义材质变量

        private VertexBuffer vertexBuffer;//定义顶点缓冲变量
        private IndexBuffer indexBuffer;//定义索引缓冲变量
        private int[] indices;//定义索引号变量

        private int xCount = 5, yCount = 4;//定义横向和纵向网格数目
        private float cellHeight = 1f, cellWidth = 1f;//定义单元的宽度和长度

        Material bottomMaterial;//底部材质变量
        Texture bottomTexture;//底部纹理变量
        VertexBuffer bottomVertexBuffer = null;//保存建立底部正方形的顶点

        private VertexBuffer borderVertexBuffer1 = null;
        private CustomVertex.PositionColored[] borderVertices1;//定义顶点变量
        private IndexBuffer borderIndexBuffer1;//定义四周顶点索引缓冲变量
        private int[] borderIndices1;//定义四周顶点的索引号变量

        private VertexBuffer borderVertexBuffer2 = null;
        private CustomVertex.PositionColored[] borderVertices2;//定义顶点变量
        private IndexBuffer borderIndexBuffer2;//定义四周顶点索引缓冲变量
        private int[] borderIndices2;//定义四周顶点的索引号变量

        private VertexBuffer borderVertexBuffer3 = null;
        private CustomVertex.PositionColored[] borderVertices3;//定义顶点变量
        private IndexBuffer borderIndexBuffer3;//定义四周顶点索引缓冲变量
        private int[] borderIndices3;//定义四周顶点的索引号变量

        private VertexBuffer borderVertexBuffer4 = null;
        private CustomVertex.PositionColored[] borderVertices4;//定义顶点变量
        private IndexBuffer borderIndexBuffer4;//定义四周顶点索引缓冲变量
        private int[] borderIndices4;//定义四周顶点的索引号变量

        public MainForm()
        {
            InitializeComponent();
        }
        public bool InitializeDirect3D()
        {
            try
            {
                PresentParameters presentParams = new PresentParameters();
                presentParams.Windowed = true; //指定以Windows窗体形式显示
                presentParams.SwapEffect = SwapEffect.Discard; //当前屏幕绘制后它将自动从内存中删除
                presentParams.AutoDepthStencilFormat = DepthFormat.D16;
                presentParams.EnableAutoDepthStencil = true;
                presentParams.PresentationInterval = PresentInterval.Immediate;
                device = new Device(0, DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, presentParams); //实例化device对象
                device.DeviceReset += new System.EventHandler(this.OnResetDevice);
                this.OnCreateDevice(device, null);
                this.OnResetDevice(device, null);
            }
            catch (DirectXException e)
            {
                MessageBox.Show(e.ToString(), "Error"); //处理异常
                return false;
            }
            return true;
        }
        //导入贴图和材质
        public void OnCreateDevice(object sender, EventArgs e)
        {
            Device device = (Device)sender;
            material = new Material();
            material.Diffuse = Color.White;
            material.Specular = Color.LightGray;
            material.SpecularSharpness = 15.0F;
            device.Material = material;
            texture = TextureLoader.FromFile(device, @"F:\\workdir\\VC# Based DirectX\\texture.jpg");

            //底部材料和贴图
            bottomMaterial = new Material();
            bottomMaterial.Ambient = Color.FromArgb(200, 255, 255, 255);
            bottomMaterial.Diffuse = Color.FromArgb(200, 255, 255, 255);
            bottomTexture = TextureLoader.FromFile(device,@"..\..\..\p1.bmp");
            bottomVertexBuffer = new VertexBuffer(typeof(CustomVertex.PositionNormalTextured), 6, device, 0, CustomVertex.PositionNormalTextured.Format, Pool.Default);
            bottomVertexBuffer.Created += new System.EventHandler(this.OnCreateBottomVertexBuffer);
            this.OnCreateBottomVertexBuffer(device, null);
        }
        //底部顶点
        public void OnCreateBottomVertexBuffer(object sender,EventArgs e)
        {
            CustomVertex.PositionNormalTextured[] bottomVerts = (CustomVertex.PositionNormalTextured[])bottomVertexBuffer.Lock(0, 0);//绘制底面正方形的6个顶点
            string bitmapPath = @"F:\\workdir\\VC# Based DirectX\\height.BMP";
            float minHeight = GetMinHeight(bitmapPath);
            float xWidth = GetBitMapWidth(bitmapPath);
            float yHeight = GetBitMapHeight(bitmapPath);
            bottomVerts[0].Position = new Vector3(0.0f, minHeight-5.0f, 0.0f);
            bottomVerts[0].Normal = new Vector3(0, 0, -1);
            bottomVerts[0].Tu = 0.0f;//顶点0纹理坐标Tu
            bottomVerts[0].Tv = 20.0f;//纹理图片沿Y轴方向重复贴图20次

            bottomVerts[1].Position = new Vector3(0.0f, minHeight - 5.0f, yHeight);
            bottomVerts[1].Normal = new Vector3(0, 0, -1);
            bottomVerts[1].Tu = 0.0f;
            bottomVerts[1].Tv = 0.0f;

            bottomVerts[2].Position = new Vector3(xWidth, minHeight - 5.0f, yHeight);
            bottomVerts[2].Normal = new Vector3(0, 0, -1);
            bottomVerts[2].Tu = 20.0f;
            bottomVerts[2].Tv = 0.0f;

            bottomVerts[3].Position = new Vector3(0.0f, minHeight - 5.0f, 0.0f);
            bottomVerts[3].Normal = new Vector3(0, 0, -1);
            bottomVerts[3].Tu = 0.0f;
            bottomVerts[3].Tv = 20.0f;

            bottomVerts[4].Position = new Vector3(xWidth, minHeight - 5.0f, yHeight);
            bottomVerts[4].Normal = new Vector3(0, 0, -1);
            bottomVerts[4].Tu = 20.0f;
            bottomVerts[4].Tv = 0.0f;

            bottomVerts[5].Position = new Vector3(xWidth, minHeight - 5.0f, 0.0f);
            bottomVerts[5].Normal = new Vector3(0, 0, -1);
            bottomVerts[5].Tu = 20.0f;
            bottomVerts[5].Tv = 20.0f;

            bottomVertexBuffer.Unlock();
        }
        //避免精度损失
        public float GetBitMapHeight(string bitmapPath)
        {
            Bitmap bitmap = new Bitmap(bitmapPath);
            xCount = (bitmap.Width - 1) / 2;
            yCount = (bitmap.Height - 1) / 2;
            cellWidth = bitmap.Width / xCount;
            cellHeight = bitmap.Height / yCount;
            return (float)(yCount * cellHeight);
        }
        //避免精度损失
        public float GetBitMapWidth(string bitmapPath)
        {
            Bitmap bitmap = new Bitmap(bitmapPath);
            xCount = (bitmap.Width - 1) / 2;
            yCount = (bitmap.Height - 1) / 2;
            cellWidth = bitmap.Width / xCount;
            cellHeight = bitmap.Height / yCount;
            return (float)(xCount*cellWidth);
        }

        //获得高度图Y方向上的最小高度值
        public float GetMinHeight(string bitmapPath)
        {
            float minHeight = 65535.0f;
            Bitmap bitmap = new Bitmap(bitmapPath);
            xCount = (bitmap.Width - 1) / 2;
            yCount = (bitmap.Height - 1) / 2;
            cellWidth = bitmap.Width / xCount;
            cellHeight = bitmap.Height / yCount;
            for(int i=0;i


你可能感兴趣的:(瞎搞,DirectX,Direct,3D,DirectX,c#)