WPF+OpenTK使用shader绘制图形
介绍
上篇文章写了WPF中简单的使用OpenTK,画了一个 三角形,今天我们写一个带摄像机的立方体。
第一步:XAML
首先在window中添加
KeyDown=“Window_KeyDown” MouseMove=“Window_MouseMove”
鼠标和键盘的两个响应事件
创建一个grid,其中添加一个WindowsFormsHost控件
<Window x:Class="OpenTkTest.openTK1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:OpenTkTest"
mc:Ignorable="d"
Title="openTK1" Height="800" Width="1200" KeyDown="Window_KeyDown" MouseMove="Window_MouseMove">
<Grid Margin="0,0,-8,0">
<WindowsFormsHost Initialized="WindowsFormsHost_Initialized" Name="Host" Height="600" Width="800"/>
</Grid>
</Window>
第二步:定时器
在初始化方法中定义一个定时器,用来显示fps值,注册了一个窗口大小变化的事件。
public openTK1()
{
InitializeComponent();
this.SizeChanged += new System.Windows.SizeChangedEventHandler(MainWindow_Resize);//注册窗口变化大小事件
//定时器
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromMilliseconds(1);
timer.Tick += this.TimerOnTick;
timer.Start();
}
第三步:WindowsFormsHost初始化
WindowsFormsHost初始化方法,创建了VBO、VAO、Shader初始化相机
private void WindowsFormsHost_Initialized(object sender, EventArgs e)
{
var flags = GraphicsContextFlags.Default;
#if USE_ANGLE
flags = GraphicsContextFlags.Embedded;
#endif
glControl = new GLControl(new GraphicsMode(32, 24), 2, 0, flags);
glControl.MakeCurrent();
glControl.Paint += GLControl_Paint;
glControl.Dock = DockStyle.Fill;
(sender as WindowsFormsHost).Child = glControl;
//这里使用了Shader.cs类 用来加载着色器文件
_shader = new Shader("../../shader/cube.vert", "../../shader/cube.frag");
_shader.Use();
GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);
//生成缓冲区对象VBO
_vertexBufferObject = GL.GenBuffer();
//我们可以使用glBindBuffer函数把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上:
GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject);
//专门用来把用户定义的数据复制到当前绑定缓冲的函数
//第一个参数为目标缓冲的类型,顶点缓冲对象当前绑定到GL_ARRAY_BUFFER目标上
//第二个参数为定点数量
//第三个参数为顶点数据
//第四个参数为参数指定了我们希望显卡如何管理给定的数据
//1.GL_STATIC_DRAW :数据不会或几乎不会改变。
//2.GL_DYNAMIC_DRAW:数据会被改变很多。
//3.GL_STREAM_DRAW :数据每次绘制时都会改变。
GL.BufferData(BufferTarget.ArrayBuffer, new IntPtr(_vertices.Length * sizeof(float)), _vertices, BufferUsageHint.StaticDraw);
//连接顶点着色器 创建VAO
_vertexArrayObject = GL.GenVertexArray();
GL.BindVertexArray(_vertexArrayObject);
//获取着色器中顶点
var vertexLocation = _shader.GetAttribLocation("aPos");
GL.EnableVertexAttribArray(vertexLocation);
//告诉OpenGL该如何解析顶点数据
//第一个参数为配置的顶点属性(我们在顶点着色器中使用layout(location = 0)定义了position顶点属性的位置值)
//第二个参数为指定顶点属性的大小
//第三个参数指定数据的类型
//第四个参数为定义我们是否希望数据被标准化(Normalize)。我们把它设置为GL_FALSE
//第五个参数为步长(连续的顶点属性组之间的间隔)
//第六个参数为位置数据在缓冲中起始位置的偏移量(Offset);
GL.VertexAttribPointer(vertexLocation, 3, VertexAttribPointerType.Float, false, 3 * sizeof(float), 0);
//初始化相机
_camera = new Camera(Vector3.UnitZ * 3, (float)this.Host.Width / (float)this.Host.Height);
}
顶点着色器文件:cube.vert
#version 330 core
layout (location = 0) in vec3 aPos;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_Position = vec4(aPos, 1.0) * model * view * projection;
}
片段着色器文件:cube.frag
第四步:窗口大小变化调整和FPS的显示
当窗口调整大小时,我们需要重新调整opengl的视口大小,否则中心点不正确。
protected void MainWindow_Resize(object sender, System.EventArgs e)
{
GL.Viewport(0, 0, (int)this.Host.Width , (int)this.Host.Height);
_camera.AspectRatio = (float)this.Host.Width / (float)this.Host.Height;
}
在标题上显示 fps。
private void TimerOnTick(object sender, EventArgs e)
{
if (DateTime.Now.Subtract(this.lastMeasureTime) > TimeSpan.FromSeconds(1))
{
this.Title = this.frames + "fps";
this.frames = 0;
this.lastMeasureTime = DateTime.Now;
}
this.glControl.Invalidate();
}
第四步:鼠标键盘的响应事件
//键盘按下
private void Window_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
const float cameraSpeed = 0.1f;
if (e.Key == Key.W)
{
_camera.Position += _camera.Front * cameraSpeed;
}
if (e.Key == Key.S)
{
_camera.Position -= _camera.Front * cameraSpeed;
}
if (e.Key == Key.A)
{
_camera.Position -= _camera.Right * cameraSpeed;
}
if (e.Key == Key.D)
{
_camera.Position += _camera.Right * cameraSpeed ;
}
if (e.Key == Key.Space)
{
_camera.Position += _camera.Up * cameraSpeed;
}
if (e.Key == Key.LeftShift)
{
_camera.Position -= _camera.Up * cameraSpeed ;
}
this.glControl.Invalidate();//重绘
}
//鼠标移动
private void Window_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
Point mouse = e.GetPosition(this);
if (_firstMove)
{
_lastPos = new Vector2((float)mouse.X, (float)mouse.Y);
_firstMove = false;
}
else
{
var deltaX = mouse.X - _lastPos.X;
var deltaY = mouse.Y - _lastPos.Y;
_lastPos = new Vector2((float)mouse.X, (float)mouse.Y);
_camera.Yaw += (float)deltaX * 0.2f;
_camera.Pitch -= (float)deltaY * 0.2f;
}
this.glControl.Invalidate();//重绘
}
最后的结果截图:
**相机和着色器类在程序中可以找到,后续我会上传资源,大家可以下载查看。
资源地址:https://download.csdn.net/download/qq_33930691/16781294?spm=1001.2014.3001.5501
*这样就实现了绘制了一个立方体,并且可以控制相机移动。但是还是有问题的 ,在视图中移动鼠标无效,但是在wpf界面上可以,问题应该是无法获取到WindowsFormsHost 控件上的事件导致的。
明天可以尝试使用帧缓冲的方式去创建视图,摆脱掉WindowsFormsHost 控件,应该就可以响应鼠标和键盘了