鼠标左键可以任意切换角度
可以选择网格和色块(未做纹理贴图)方式绘制
根据高程按色相填色
其他复杂地形测试
数据文件格式
==========================================说明====================================
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;
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();
}
}
}
}
}