【C# OpenGL】基于DEM数据的3D地形绘制(基于CSGL)
发表于2018-07-16
本篇文章和大家介绍下基于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