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 控件,应该就可以响应鼠标和键盘了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

飞起的代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值