GUI WPF + OpenGL 3.1

Published July 11, 2020
Advertisement

We will see how to place OpenTK.GLControl on WPF window to make GUI application with 2D/3D graphics using modern OpenGL 3.

EditedTriangle_WPFOpenGL31CSharp_Result.png.ee287ff2ace0598d55c51f32540d8876.png

How to create the project from scratch

Note 1: RMB - Right Mouse Button click
Note 2: Good Color calculator for normalized values: http://doc.instantreality.org/tools/color_calculator/

  • Create WPF application, with the name "EditedTriangle". See the screenshot:

Spoiler

EditedTriangle_WPFOpenGL31CSharp_NewProject.png.3f540ad7ae9d7549ca58724fd8ce393c.png
  • Download OpenTK.GLControl.zip and OpenTK.zip
  • Create the empty "Libs" folder in the solution folder (where the ".sln" is placed)
  • Unzip "OpenTK" and "OpenTK.GLControl" folder in the "Libs" folder
  • Add references to "OpenTK.dll" and "OpenTK.GLControl.dll". For this: RMB on "References" -> select "Add Reference..." -> select "Browse" -> click the "Browse..." button -> select DLL's. See the screenshot with the result:

Spoiler

EditedTriangle_WPFOpenGL31CSharp_ReferenceManager.png.8af7d941f5bd6b5697792287d09a69c5.png
  • Add "Assemblies". For this: select "Assemblies" -> "Framework" -> check:

System.Drawing System.Windows.Forms WindowsFormsIntegration

, see the screenshot with the result:

Spoiler

EditedTriangle_WPFOpenGL31CSharp_Assemblies.png.527a86dc69d4109c08ec2fb91d29f6e1.png
  • Click "OK" button
  • Open the "MainWindow.xaml" and "MainWindow.xaml.cs” files in VS and edit code like this:
<Window x:Class="EditedTriangle.MainWindow"
        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:EditedTriangle"
        xmlns:opentk="clr-namespace:OpenTK;assembly=OpenTK.GLControl"
        mc:Ignorable="d"
        Title="Triangle" Height="256" Width="290">
    <Grid>
        <DockPanel LastChildFill="True">
            <StackPanel DockPanel.Dock="Right">
                <Button x:Name="buttonSetBGColor" Content="Set BG Color" Margin="5" Click="buttonSetBGColor_Click"></Button>
                <Button x:Name="buttonSetTRColor" Content="Set TR Color" Margin="5" Click="buttonSetTRColor_Click"></Button>
            </StackPanel>
            <WindowsFormsHost Margin="5">
                <opentk:GLControl x:Name="glControl" Load="glControl_Load" Paint="glControl_Paint" />
            </WindowsFormsHost>
        </DockPanel>
    </Grid>
</Window>
using System;
using System.Windows;
using OpenTK.Graphics.OpenGL;
using OpenTK.Graphics;

namespace EditedTriangle
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private int _numOfVertices = 0;
        private int _uColorLocation;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void glControl_Load(object sender, EventArgs e)
        {
            glControl.MakeCurrent();

            // Get a shader program ID
            int shaderProgram = InitShadersAndGetProgram();

            _numOfVertices = InitVertexBuffers();

            _uColorLocation = GL.GetUniformLocation(shaderProgram, "uColor");
            if (_uColorLocation < 0)
            {
                MessageBox.Show("Failed to get uColorLocation variable");
                return;
            }

            // Set a triangle color
            GL.Uniform3(_uColorLocation, 0.945f, 0.745f, 0.356f);

            // Set a color for clearing the glCotrol
            GL.ClearColor(new Color4(0.286f, 0.576f, 0.243f, 1f));
        }

        private void glControl_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
        {
            GL.Viewport(0, 0, glControl.Width, glControl.Height);

            // Clear the glControl with set color
            GL.Clear(ClearBufferMask.ColorBufferBit);

            if (_numOfVertices != 0)
            {
                GL.DrawArrays(PrimitiveType.Triangles, 0, _numOfVertices);
            }

            // Swap the front and back buffers
            glControl.SwapBuffers();
        }

        private int InitVertexBuffers()
        {
            float[] vertices = new float[]
            {
                0.0f, 0.5f,
                -0.5f, -0.5f,
                0.5f, -0.5f
            };
            int n = 3;

            int vbo;
            GL.GenBuffers(1, out vbo);

            // Get an array size in bytes
            GL.BindBuffer(BufferTarget.ArrayBuffer, vbo);
            int sizeInBytes = vertices.Length * sizeof(float);
            // Send the vertex array to a video card memory
            GL.BufferData(BufferTarget.ArrayBuffer, sizeInBytes, vertices, BufferUsageHint.StaticDraw);
            // Config the aPosition variable
            GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, 0, 0);
            GL.EnableVertexAttribArray(0);

            return n;
        }

        private int InitShadersAndGetProgram()
        {
            string vertexShaderSource =
                "#version 140\n" +
                "in vec2 aPosition;" +
                "void main()" +
                "{" +
                "    gl_Position = vec4(aPosition, 1.0, 1.0);" +
                "}";

            string fragmentShaderSource =
                "#version 140\n" +
                "out vec4 fragColor;" +
                "uniform vec3 uColor;" +
                "void main()" +
                "{" +
                "    fragColor = vec4(uColor, 1.0);" +
                "}";

            // Vertex Shader
            int vShader = GL.CreateShader(ShaderType.VertexShader);
            GL.ShaderSource(vShader, vertexShaderSource);
            GL.CompileShader(vShader);
            // Check compilation
            int ok;
            GL.GetShader(vShader, ShaderParameter.CompileStatus, out ok);
            if (ok == 0)
            {
                string vShaderInfo = GL.GetShaderInfoLog(vShader);
                MessageBox.Show("Error in the vertex shader:\n" + vShaderInfo);
                return -1;
            }

            // Fragment Shader
            int fShader = GL.CreateShader(ShaderType.FragmentShader);
            GL.ShaderSource(fShader, fragmentShaderSource);
            GL.CompileShader(fShader);
            GL.GetShader(fShader, ShaderParameter.CompileStatus, out ok);
            if (ok == 0)
            {
                string fShaderInfo = GL.GetShaderInfoLog(fShader);
                MessageBox.Show("Error in the fragment shader:\n" + fShaderInfo);
                return -1;
            }

            int program = GL.CreateProgram();
            GL.AttachShader(program, vShader);
            GL.AttachShader(program, fShader);
            GL.LinkProgram(program);
            GL.UseProgram(program);

            return program;
        }

        private void buttonSetBGColor_Click(object sender, RoutedEventArgs e)
        {
            var dialog = new System.Windows.Forms.ColorDialog();

            if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                GL.ClearColor(dialog.Color);
                glControl.Invalidate();
            }

        }

        private void buttonSetTRColor_Click(object sender, RoutedEventArgs e)
        {
            var dialog = new System.Windows.Forms.ColorDialog();

            if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                float r = dialog.Color.R / 255f;
                float g = dialog.Color.G / 255f;
                float b = dialog.Color.B / 255f;
                GL.Uniform3(_uColorLocation, r, g, b);
                glControl.Invalidate();
            }
        }
    }
}
2 likes 5 comments

Comments

jkane12

Looks good except missing Utils class?

July 10, 2020 02:04 PM
8Observer8

@jkane12 I rewrote the example above without the Utils class. Thank you for your note about the mistake.

July 11, 2020 03:01 PM
Alberth

The files with the solution are about 20% of a tutorial, the other 80% is the text explaining why the solution is the solution, and how it works. Without that, you can find copy/paste code all over the Internet.

July 11, 2020 05:20 PM
Paraparapartycrow

Finally an example that uses WPF and swaps buffers

March 05, 2024 12:30 PM
8Observer8

@Paraparapartycrow you're welcome!

March 06, 2024 04:35 PM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement