【C# OpenGL】基于DEM数据的3D地形绘制(基于CSGL)

发表于2018-07-16
评论0 3.8k浏览
本篇文章和大家介绍下基于DEM数据的3D地形绘制,如果有不了解的可以学习下,希望下面的内容能够帮到大家。

鼠标左键可以任意切换角度

可以选择网格和色块(未做纹理贴图)方式绘制

根据高程按色相填色

其他复杂地形测试

数据文件格式

说明
1.基于C#下的OpenGL库,CSGL,函数名和参数基本都和OpenGL保持一致,代码做少量修改即可在其他平台复用
2.数据文件格式为*.Ter,前几行为摘要(包含起始坐标,比例等),之后为每行x个,共y行的大数组,每个值为对应(x,y)位置的高程
3.贴出的代码为核心的OpenGL用户控件类代码和地形数据类代码,将该控件直接添加到创体中即可使用
4.控件实现了鼠标左键任意角度旋转,滚轮缩放,右键平移的功能,和一些其他绘制相关可选参数
TerrainData.CS
using System;
using System.Collections.Generic;
using System.Text;
namespace MapSupport.Model
{
    /// <summary>
    /// 地形数据
    /// </summary>
    [Serializable]
    public class TerrainData
    {
        /// <summary>
        /// 构造方法
        /// </summary>
        /// <param name="ncols">列数</param>
        /// <param name="nrows">行数</param>
        public TerrainData(int ncols, int nrows)
        {
            this.ncols = ncols;
            this.nrows = nrows;
            this.terrainMap = new float[this.ncols, this.nrows];
        }
        /// <summary>
        /// 列数
        /// </summary>
        public int ncols;
        /// <summary>
        /// 行数
        /// </summary>
        public int nrows;
        /// <summary>
        /// 起点经纬坐标
        /// </summary>
        public double xllcorner;
        public double yllcorner;
        /// <summary>
        /// 单元尺寸
        /// </summary>
        public double cellsize;
        /// <summary>
        /// 未定义数据
        /// </summary>
        public float nodataValue;
        /// <summary>
        /// 地形数据
        /// </summary>
        public float[,] terrainMap;
        /// <summary>
        /// 最大值
        /// </summary>
        public float maxValue;
        /// <summary>
        /// 最小值
        /// </summary>
        public float minValue;
    }
}

OpenGLPanel.CS
using System;
using System.Collections.Generic;
using System.Text;
namespace MapSupport.Model
{
    /// <summary>
    /// 地形数据
    /// </summary>
    [Serializable]
    public class TerrainData
    {
        /// <summary>
        /// 构造方法
        /// </summary>
        /// <param name="ncols">列数</param>
        /// <param name="nrows">行数</param>
        public TerrainData(int ncols, int nrows)
        {
            this.ncols = ncols;
            this.nrows = nrows;
            this.terrainMap = new float[this.ncols, this.nrows];
        }
        /// <summary>
        /// 列数
        /// </summary>
        public int ncols;
        /// <summary>
        /// 行数
        /// </summary>
        public int nrows;
        /// <summary>
        /// 起点经纬坐标
        /// </summary>
        public double xllcorner;
        public double yllcorner;
        /// <summary>
        /// 单元尺寸
        /// </summary>
        public double cellsize;
        /// <summary>
        /// 未定义数据
        /// </summary>
        public float nodataValue;
        /// <summary>
        /// 地形数据
        /// </summary>
        public float[,] terrainMap;
        /// <summary>
        /// 最大值
        /// </summary>
        public float maxValue;
        /// <summary>
        /// 最小值
        /// </summary>
        public float minValue;
    }
}
OpenGLPanel.CS
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Imaging;
using MapSupport.Model;
using CsGL.OpenGL;
namespace MapSupport.MapControl
{
    public class OpenGLPanel : OpenGLControl
    {
        /// <summary>
        /// 构造方法
        /// </summary>
        public OpenGLPanel()
            : base()
        {
            terrainData = null;
            // 绑定鼠标事件
            this.MouseWheel += OpenGLPanel_MouseWheel;
            this.MouseMove += OpenGLPanel_MouseMove;
            this.MouseDown += OpenGLPanel_MouseDown;
            this.MouseUp += OpenGLPanel_MouseUp;
            // 循环刷新时钟
            System.Windows.Forms.Timer timer;
            timer = new System.Windows.Forms.Timer();
            timer.Interval = 33;
            timer.Tick += timer_Tick;
            timer.Start();
        }
        /// <summary>
        /// 刷新时钟事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void timer_Tick(object sender, EventArgs e)
        {
            this.Refresh();
        }
        /// <summary>
        /// 执行OpenGL初始化
        /// </summary>
        protected override void InitGLContext()
        {
            base.InitGLContext();
            GL.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
            GL.glShadeModel(GL.GL_SMOOTH); // 阴暗处理采用平滑方式
            GL.glHint(GL.GL_PERSPECTIVE_CORRECTION_HINT, GL.GL_NICEST);// 最精细的透视计算
            GL.glClearDepth(1.0f);                                  // 清除深度缓冲
            GL.glEnable(GL.GL_DEPTH_TEST);                          // 开启深度
            GL.glDepthFunc(GL.GL_LEQUAL);                           // 设置深度测试方式
            GL.glEnable(GL.GL_TEXTURE_2D);                          // 允许使用纹理
            GL.glMatrixMode(GL.GL_PROJECTION);
            GL.glLoadIdentity();
            GL.gluOrtho2D(0.0, Size.Width, 0.0, Size.Height);
            // TODO: 在此添加其他初始化动作,比如建立显示
            // 鼠标操作参数初始化
            tAnglnc = pi / 90;
            tFovy = 45.0;
            prePt = new Point(1, 1);
            nowPtMove = new Point(-1, -1);
            tVerticalAng = 0;
            tHorizonAng = pi / 2;
            tRadius = 400.0;
            tEyeX = tRadius * Math.Cos(tVerticalAng) * Math.Cos(tHorizonAng);
            tEyeY = tRadius * Math.Cos(tVerticalAng);
            tEyeZ = tRadius * Math.Cos(tVerticalAng) * Math.Sin(tHorizonAng);
            translationX = 0;
            translationY = 0;
            tCenterX = 0;
            tCenterY = 0;
            tCenterZ = 0;
            tUpX = 0;
            tUpY = 1.0;
            tUpZ = 0;
        }
        /// <summary>
        /// 重写绘制函数
        /// </summary>
        public override void glDraw()
        {
            // GL.glClear(GL.GL_COLOR_BUFFER_BIT);
            GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);        // 清除深度缓冲
            if (terrainData != null)
            {
                float[] vecMove = { 0.5f, 0.5f, 0.5f };
                RenderTerrainMap(vecMove);
            }
        }
        /// <summary>
        /// 绘制地形
        /// </summary>
        /// <param name="terrainData"></param>
        public void DrawTerrain(TerrainData terrainData)
        {
            this.terrainData = terrainData;
            this.noDataValue = terrainData.nodataValue;
            // 触发一次鼠标事件调整画面比例
            this.OnMouseMove(new MouseEventArgs(MouseButtons.Left, 1, 100, 100, 0));
        }
        /// <summary>
        /// 视角变换渲染
        /// </summary>
        public void RenderSence()
        {
            // 设置新的投影矩阵
            GL.glMatrixMode(GL.GL_PROJECTION);
            GL.glLoadIdentity();
            GLU.gluPerspective(tFovy, aspect_ratio, 0.1, 2000.0);
            // 平移
            GL.glTranslated(translationX, translationY, 0.0f);
            // 更新视点
            GL.glMatrixMode(GL.GL_MODELVIEW);
            GL.glLoadIdentity();
            GLU.gluLookAt(tEyeX, tEyeY, tEyeZ, tCenterX, tCenterY, tCenterZ, tUpX, tUpY, tUpZ);
            // 光照绘制
            if (RenderLight) SetLight();
            else CloseLight();
            //GL.glColor3f(1.0f, 1.0f, 1.0f);
            // 参考用中心立方体
            //GL.glutWireCube(30.0);
        }
        /// <summary>
        /// 光照测试
        /// </summary>
        void SetLight()
        {
            float[] light_position = { 100f, -100f, 100f, 1f };
            float[] light_ambient = { 1.0f, 1.0f, 1.0f, 0.8f };
            float[] light_diffuse = { 1.0f, 1.0f, 1.0f, 0.8f };
            float[] light_specular = { 1.0f, 1.0f, 1.0f, 0.8f };
            GL.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, light_position);
            GL.glLightfv(GL.GL_LIGHT0, GL.GL_AMBIENT, light_ambient);
            GL.glLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, light_diffuse);
            GL.glLightfv(GL.GL_LIGHT0, GL.GL_SPECULAR, light_specular);
            // 开启光源
            GL.glEnable(GL.GL_LIGHT0);
            GL.glEnable(GL.GL_LIGHTING);
        }
        /// <summary>
        /// 关闭光照
        /// </summary>
        void CloseLight()
        {
            // 关闭光源
            GL.glDisable(GL.GL_LIGHT0);
            GL.glDisable(GL.GL_LIGHTING);
        }
        /// <summary>
        /// 地形数据
        /// </summary>
        private TerrainData terrainData;
        // 以下是视角变换相关参数
        double tEyeX, tEyeY, tEyeZ;
        double tCenterX, tCenterY, tCenterZ;
        double tUpX, tUpY, tUpZ;
        double tVerticalAng, tHorizonAng, tRadius, tAnglnc;
        float translationX, translationY;
        double pi = 3.1415926535897;
        double tFovy;
        Point prePt, nowPt;         // 旋转用
        Point nowPtMove;            // 移动用
        bool isMouseDonw = false;   // 鼠标按下,移动标识
        float zoomSpeed = 2.0f;     // 缩放速度
        // 以下是绘制相关参数
        float max_Height = 256;
        float draw_Height = 256;
        public int STEP_SIZE = 4;
        public int CELL_SIZE = 5;
        public bool RenderMode = false;             // 渲染模式 false 网格  true 贴图
        public bool RenderZeroHeightLayer = true;   // 水平面绘制
        public bool RenderLight = false;            // 光照绘制
        public bool ColorMode = false;              // 颜色模式
        private float scaleValueZ = 0.5f;
        private float scaleValueXY = 0.15f;
        private float noDataValue = -9999;
        // 窗口横纵比
        double aspect_ratio = 1;
        /// <summary>
        /// 鼠标滚轮事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OpenGLPanel_MouseWheel(object sender, MouseEventArgs e)
        {
            int tWheelCount = e.Delta / 120;
            if (tWheelCount > 0 && tFovy - zoomSpeed > 0)
            {
                // 放大
                tFovy -= zoomSpeed;
            }
            if (tWheelCount < 0 && tFovy + zoomSpeed < 90)
            {
                // 缩小
                tFovy += zoomSpeed;
            }
            GL.glMatrixMode(GL.GL_PROJECTION);
            GL.glLoadIdentity();
            GL.glLoadIdentity();
            GLU.gluPerspective(tFovy, 1, 0.1, 2000.0);// 注意zNear,zFar的取值
            GL.glMatrixMode(GL.GL_MODELVIEW);
            GL.glLoadIdentity();
            RenderSence();
        }
        /// <summary>
        /// 鼠标按下事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OpenGLPanel_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Right)
            {
                isMouseDonw = true;
                nowPtMove.X = e.X;
                nowPtMove.Y = e.Y;
            }
        }
        /// <summary>
        /// 鼠标抬起事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OpenGLPanel_MouseUp(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Right)
            {
                isMouseDonw = false;
            }
        }
        /// <summary>
        /// 鼠标移动事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OpenGLPanel_MouseMove(object sender, MouseEventArgs e)
        {
            // 设置控件焦点,防止滚轮事件无响应
            this.Focus();
            // 当左键按下时
            if (e.Button == MouseButtons.Left)
            {
                nowPt.X = e.X;
                nowPt.Y = e.Y;
                if (prePt.X != -1 && prePt.Y != -1 && nowPt.X != -1 && nowPt.Y != -1)
                {
                    // 计算移动量
                    double tDx = nowPt.X - prePt.X;
                    double tDy = nowPt.Y - prePt.Y;
                    double tDis = Math.Sqrt(tDx * tDx + tDy * tDy);
                    if (tDx > 0)
                    {
                        tHorizonAng += tAnglnc * tDx / tDis;
                        if (tHorizonAng < 0)
                        {
                            tHorizonAng += 2 * pi;
                        }
                        if (tHorizonAng > 2 * pi)
                        {
                            tHorizonAng -= 2 * pi;
                        }
                    }
                    else if (tDx < 0)
                    {
                        tHorizonAng += tAnglnc * tDx / tDis;
                        if (tHorizonAng < 0)
                        {
                            tHorizonAng += 2 * pi;
                        }
                        if (tHorizonAng > 2 * pi)
                        {
                            tHorizonAng -= 2 * pi;
                        }
                    }
                    if (tDy > 0)
                    {
                        tVerticalAng = tVerticalAng + tAnglnc * tDy / tDis;
                        if (tVerticalAng > pi / 2)
                        {
                            tVerticalAng = pi / 2;
                        }
                    }
                    else if (tDy < 0)
                    {
                        tVerticalAng = tVerticalAng + tAnglnc * tDy / tDis;
                        if (tVerticalAng < -pi / 2)
                        {
                            tVerticalAng = -pi / 2;
                        }
                    }
                    tEyeX = tRadius * Math.Cos(tVerticalAng) * Math.Cos(tHorizonAng);
                    tEyeY = tRadius * Math.Sin(tVerticalAng);
                    tEyeZ = tRadius * Math.Cos(tVerticalAng) * Math.Sin(tHorizonAng);
                }
                prePt.X = nowPt.X;
                prePt.Y = nowPt.Y;
                RenderSence();
            }
            if (e.Button == MouseButtons.Right)
            {
                if (nowPtMove.X != -1 && nowPtMove.Y != -1 && isMouseDonw)
                {
                    float moveSpeed = 1.0f;
                    // 根据缩放比例来计算移动速度,使移动速度尽可能与鼠标移动一致
                    // 缩小时提速
                    if (tFovy > 45)
                    {
                        moveSpeed += (float)tFovy / 90f * 1.0f;
                    }
                    // 放大时减速
                    if(tFovy < 45)
                    {
                        moveSpeed -= (45f - (float)tFovy) / 45f * 0.95f;
                    }
                    // 计算移动量 ★开始移动时有抖动,原因不明
                    float moveX = e.X - nowPtMove.X;
                    float moveY = e.Y - nowPtMove.Y;
                    translationX = moveX * moveSpeed;
                    translationY = -moveY * moveSpeed;
                }
                RenderSence();
            }
        }
        /// <summary>
        /// 控件大小改变时
        /// </summary>
        protected override void OnSizeChanged(EventArgs e)
        {
            base.OnSizeChanged(e);
            Size s = Size;
            // 计算窗口的纵横比
            aspect_ratio = (double)s.Width / (double)s.Height;
            RenderSence();
        }
        /// <summary>
        /// 计算绘制高度
        /// </summary>
        /// <param name="nX"></param>
        /// <param name="nY"></param>
        /// <returns></returns>
        private int DrawHeight(int nX, int nY)
        {
            if (terrainData == null)
            {
                return 0;
            }
            int x = nX;
            int y = nY;
            if (x > terrainData.ncols - 1) x = terrainData.ncols - 1;
            if (y > terrainData.nrows - 1) y = terrainData.nrows - 1;
            int result = 0;
            if (terrainData.terrainMap[x, y] == terrainData.nodataValue)
            {
                return (int)noDataValue;
            }
            else
            {
                max_Height = terrainData.maxValue - terrainData.minValue;
                // 计算出实际高度
                result = (int)(terrainData.terrainMap[x, y] / max_Height * draw_Height);
            }
            // 将高度坐标移动到中心位置
            result -= (int)(draw_Height / 2);
            return result;
        }
        /// <summary>
        /// 设置绘制颜色
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        private void SetVertexColor(int x, int y)
        {
            if (terrainData == null)
            {
                return;
            }
            int H_End = 0;
            int H_Start = 200;
            int S = 139;
            int V = 247;
            if (ColorMode)
            {
                Color color = GraphicsHelper.HsvToRgb(H_Start + (int)(DrawHeight(x, y) * 1.0 / draw_Height * (H_End - H_Start)), S, V);
                GL.glColor3f(color.R * 1.0f / 256, color.G * 1.0f /256, color.B * 1.0f / 256);
            }
            else
            {
                if (RenderMode)
                {
                    float fcolor = DrawHeight(x, y) * 1.0f / draw_Height + 0.6f;
                    GL.glColor3f(fcolor, fcolor, fcolor);
                }
                else
                {
                    GL.glColor3f(1.0f, 1.0f, 1.0f);
                }
            }
        }
        /// <summary>
        /// 绘制地形图
        /// </summary>
        /// <param name="vecMove"></param>
        private void RenderTerrainMap(float[] vecMove)
        {
            int nX = 0, nY = 0;
            int x, y, z;
            if (terrainData == null)
            {
                return;
            }
            // 绘制水平面
            if (RenderZeroHeightLayer)
            {
                for (nX = 0; nX < terrainData.ncols; nX += STEP_SIZE)
                {
                    for (nY = 0; nY < terrainData.nrows; nY += STEP_SIZE)
                    {
                        int tnX, tnY;
                        // 将x y坐标移动到中心位置
                        tnX = nX - terrainData.ncols / 2;
                        tnY = nY - terrainData.nrows / 2;
                        float[] fRealPt = new float[3];
                        GL.glBegin(GL.GL_POINTS);
                        {
                            x = tnX * CELL_SIZE;
                            y = (int)(-draw_Height / 2);
                            z = tnY * CELL_SIZE;
                            fRealPt[0] = x * scaleValueXY + vecMove[0];
                            fRealPt[1] = y * scaleValueZ + vecMove[1];
                            fRealPt[2] = z * scaleValueXY + vecMove[2];
                            GL.glColor3f(1f, 1f, 1f);
                            GL.glVertex3f(fRealPt[0], fRealPt[1], fRealPt[2]);
                            //GL.glVertex3f(x, z, y);
                        }
                        GL.glEnd();
                    }
                }
            }
            // 绘制地形
            for (nX = 0; nX < terrainData.ncols; nX += STEP_SIZE)
            {
                for (nY = 0; nY < terrainData.nrows; nY += STEP_SIZE)
                {
                    int tnX, tnY;
                    // 将x y坐标移动到中心位置
                    tnX = nX - terrainData.ncols / 2;
                    tnY = nY - terrainData.nrows / 2;
                    float[] fRealPt = new float[3];
                    // 选择渲染方式,绘制地形
                    if (RenderMode)
                    {
                        GL.glBegin(GL.GL_QUADS);
                    }
                    else
                    {
                        GL.glBegin(GL.GL_LINE_LOOP);
                    }
                    // 绘制(x,y)处的顶点
                    x = tnX * CELL_SIZE;
                    y = DrawHeight(nX, nY);
                    z = tnY * CELL_SIZE;
                    SetVertexColor(nX, nY);
                    fRealPt[0] = x * scaleValueXY + vecMove[0];
                    fRealPt[1] = y * scaleValueZ + vecMove[1];
                    fRealPt[2] = z * scaleValueXY + vecMove[2];
                    if (y != (int)noDataValue) GL.glVertex3f(fRealPt[0], fRealPt[1], fRealPt[2]);
                    // 绘制(x+1,y)处的顶点
                    x = (tnX + STEP_SIZE) * CELL_SIZE;
                    y = DrawHeight(nX + STEP_SIZE, nY);
                    z = tnY * CELL_SIZE;
                    SetVertexColor(nX, nY);
                    fRealPt[0] = x * scaleValueXY + vecMove[0];
                    fRealPt[1] = y * scaleValueZ + vecMove[1];
                    fRealPt[2] = z * scaleValueXY + vecMove[2];
                    if (y != (int)noDataValue) GL.glVertex3f(fRealPt[0], fRealPt[1], fRealPt[2]);
                    // 绘制(x+1,y+1)处的顶点
                    x = (tnX + STEP_SIZE) * CELL_SIZE;
                    y = DrawHeight(nX + STEP_SIZE, nY + STEP_SIZE);
                    z = (tnY + STEP_SIZE) * CELL_SIZE;
                    SetVertexColor(nX, nY);
                    fRealPt[0] = x * scaleValueXY + vecMove[0];
                    fRealPt[1] = y * scaleValueZ + vecMove[1];
                    fRealPt[2] = z * scaleValueXY + vecMove[2];
                    if (y != (int)noDataValue) GL.glVertex3f(fRealPt[0], fRealPt[1], fRealPt[2]);
                    // 绘制(x,y+1)处的顶点
                    x = tnX * CELL_SIZE;
                    y = DrawHeight(nX, nY + STEP_SIZE);
                    z = (tnY + STEP_SIZE) * CELL_SIZE;
                    SetVertexColor(nX, nY);
                    fRealPt[0] = x * scaleValueXY + vecMove[0];
                    fRealPt[1] = y * scaleValueZ + vecMove[1];
                    fRealPt[2] = z * scaleValueXY + vecMove[2];
                    if (y != (int)noDataValue) GL.glVertex3f(fRealPt[0], fRealPt[1], fRealPt[2]);
                    GL.glEnd();
                }
            }
        }
    }
}
来自:https://blog.csdn.net/ls9512/article/details/50332197

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引