the Fiddler's picture

[Examples] Add forward-compatible OpenGL 3.0 example

Project:The Open Toolkit library
Version:0.9.9-1
Component:Documentation
Category:feature request
Priority:normal
Assigned:Unassigned
Status:closed
Description

We should add a forward-compatible OpenGL 3.0 example to Examples.exe. This code should be of use.


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
the Fiddler's picture

#1

Title:Add OpenGL 3.0 example» [Examples] Add forward-compatible OpenGL 3.0 example
nythrix's picture

#2

Here goes a spinning cube example. Powered by OpenGL 3.0 forward compatible context!
The shaders are not ok (normals going wild:), and I don't know how to fix them yet. It has been too many new things for me so please be patient. This is my first experience with shaders as well. Tested against 0.9.8.1

Hopefully I'll get a GL3.1 + GLSL1.4 version of this up and running as soon as the drivers settle. Stay tuned.

Edit: I'm sure the winding is correct because it's the same cube from the other OpenTK examples. If anyone knows how to fix the shaders please post!
Edit2: It now occurred to me that I have to interpolate the normals through the primitive. Duh!
Edit3: Shaders separated into files now carry #version info. Minor code edits.
Edit4: Fixed GL.UniformMatrix4 parameters inconsistency.
Edit5:
There's probably tons of google hits here. Until we have a proper GL3+ tutorial, I'll try to keep this page updated.
The code now follows its SVN counterpart more closely. Updated to OpenTK 0.9.9.0.
Using Matrix4.Frustum fixed "normals" problem.
Shaders back into code.

using System;
using System.Diagnostics;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Math;
using System.IO;
 
namespace OpenGL3
{
    public class HelloGL3: GameWindow
    {
        string vertexShaderSource = @"
            #version 130
 
            precision highp float;
 
            uniform mat4 projection_matrix;
            uniform mat4 modelview_matrix;
 
            in vec3 in_position;
            in vec3 in_normal;
 
            out vec3 normal;
 
            void main(void)
            {
              //not a proper transformation if modelview_matrix specifies scaling
              normal = (modelview_matrix * vec4(in_normal, 0)).xyz;
 
              gl_Position = projection_matrix * modelview_matrix * vec4(in_position, 1);
            }";
 
        string fragmentShaderSource = @"
            #version 130
 
            precision highp float;
 
            const vec3 ambient = vec3(0.1, 0.1, 0.1);
            const vec3 lightVecNormalized = normalize(vec3(0.5, 0.5, 2));
            const vec3 lightColor = vec3(0.9, 0.9, 0.7);
 
            in vec3 normal;
 
            out vec4 out_frag_color;
 
            void main(void)
            {
              float diffuse = clamp(dot(lightVecNormalized, normalize(normal)), 0.0, 1.0);
              out_frag_color = vec4(ambient + diffuse * lightColor, 1.0);
            }";
 
        int vertexShaderHandle,
            fragmentShaderHandle,
            shaderProgramHandle,
            modelviewMatrixLocation,
            projectionMatrixLocation,
            vaoHandle,
            positionVboHandle,
            normalVboHandle;
 
        Vector3[] positionVboData = new Vector3[]{
            new Vector3(-1.0f, -1.0f,  1.0f),
            new Vector3( 1.0f, -1.0f,  1.0f),
            new Vector3( 1.0f,  1.0f,  1.0f),
            new Vector3(-1.0f,  1.0f,  1.0f),
            new Vector3(-1.0f, -1.0f, -1.0f),
            new Vector3( 1.0f, -1.0f, -1.0f), 
            new Vector3( 1.0f,  1.0f, -1.0f),
            new Vector3(-1.0f,  1.0f, -1.0f) };
 
        int[] indicesVboData = new int[]{
             // front face
                0, 1, 2, 2, 3, 0,
                // top face
                3, 2, 6, 6, 7, 3,
                // back face
                7, 6, 5, 5, 4, 7,
                // left face
                4, 0, 3, 3, 7, 4,
                // bottom face
                0, 1, 5, 5, 4, 0,
                // right face
                1, 5, 6, 6, 2, 1, };
 
        Matrix4 projectionMatrix, modelviewMatrix;
 
        public HelloGL3()
            : base( 640, 480,
            new GraphicsMode( new ColorFormat( 8, 8, 8, 8 ), 16 ), "OpenGL 3 Example", 0,
            DisplayDevice.Default, 3, 0,
            GraphicsContextFlags.ForwardCompatible | GraphicsContextFlags.Debug )
        {}
 
        public override void OnLoad(EventArgs e)
        {
 	        base.OnLoad(e);
 
            // Create shaders
            vertexShaderHandle = GL.CreateShader( ShaderType.VertexShader );
            fragmentShaderHandle = GL.CreateShader( ShaderType.FragmentShader );
 
            GL.ShaderSource( vertexShaderHandle, vertexShaderSource );
            GL.ShaderSource( fragmentShaderHandle, fragmentShaderSource );
 
            GL.CompileShader( vertexShaderHandle );
            GL.CompileShader( fragmentShaderHandle );
 
            // Create program
            shaderProgramHandle = GL.CreateProgram();
 
            GL.AttachShader( shaderProgramHandle, vertexShaderHandle );
            GL.AttachShader( shaderProgramHandle, fragmentShaderHandle );
 
            GL.LinkProgram( shaderProgramHandle );
 
            string programInfoLog;
            GL.GetProgramInfoLog( shaderProgramHandle, out programInfoLog );
            Debug.WriteLine( programInfoLog );
 
            GL.UseProgram( shaderProgramHandle );
 
            // Set matrices
            float aspectRatio = ClientSize.Height / ( float )( ClientSize.Width );
            projectionMatrix = Matrix4.Frustum( -1, 1, -aspectRatio, aspectRatio, 1, 100 );
            modelviewMatrix = CreateModelview( Vector3.UnitX, 0.5f, new Vector3( 0, 0, -4 ) );
 
            projectionMatrixLocation = GL.GetUniformLocation( shaderProgramHandle, "projection_matrix" );
            modelviewMatrixLocation = GL.GetUniformLocation( shaderProgramHandle, "modelview_matrix" );
 
            GL.UniformMatrix4( projectionMatrixLocation, false, ref projectionMatrix );
            GL.UniformMatrix4( modelviewMatrixLocation, false, ref modelviewMatrix );
 
            // Create vertex arrays
            GL.GenVertexArrays( 1, out vaoHandle );
            GL.BindVertexArray( vaoHandle );
 
            GL.GenBuffers( 1, out positionVboHandle );
            GL.BindBuffer( BufferTarget.ArrayBuffer, positionVboHandle );
            GL.BufferData<Vector3>( BufferTarget.ArrayBuffer,
                new IntPtr( positionVboData.Length * Vector3.SizeInBytes ),
                positionVboData, BufferUsageHint.StaticDraw );
 
            GL.GenBuffers( 1, out normalVboHandle );
            GL.BindBuffer( BufferTarget.ArrayBuffer, normalVboHandle );
            GL.BufferData<Vector3>( BufferTarget.ArrayBuffer,
                new IntPtr( positionVboData.Length * Vector3.SizeInBytes ),
                positionVboData, BufferUsageHint.StaticDraw );
 
            GL.EnableVertexAttribArray( 0 );
            GL.EnableVertexAttribArray( 1 );
 
            GL.VertexAttribPointer( 0, 3, VertexAttribPointerType.Float, true, Vector3.SizeInBytes, 0 );
            GL.VertexAttribPointer( 1, 3, VertexAttribPointerType.Float, true, Vector3.SizeInBytes, 0 );
 
            GL.BindAttribLocation( shaderProgramHandle, 0, "in_position" );
            GL.BindAttribLocation( shaderProgramHandle, 1, "in_normal" );
 
            // Other state
            GL.Enable( EnableCap.DepthTest );
            GL.ClearColor( System.Drawing.Color.MidnightBlue );
        }
 
        public Matrix4 CreateModelview( Vector3 rotationAxis, float angle, Vector3 translate )
        {
            return Matrix4.Rotate( rotationAxis, angle ) * Matrix4.CreateTranslation( translate );
        }
 
        protected override void OnUpdateFrame( FrameEventArgs e )
        {
            modelviewMatrix = Matrix4.RotateY( 0.03f ) * modelviewMatrix;
            GL.UniformMatrix4( modelviewMatrixLocation, false, ref modelviewMatrix );
 
            if( Keyboard[ OpenTK.Input.Key.Escape ] )
                Exit();
        }
 
        protected override void OnRenderFrame( FrameEventArgs e )
        {
            GL.Viewport( 0, 0, Width, Height );
            GL.Clear( ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit );
 
            GL.DrawElements( BeginMode.Triangles, indicesVboData.Length,
                DrawElementsType.UnsignedInt, indicesVboData );
 
            GL.Flush();
            SwapBuffers();
        }
    }
 
    public class Program
    {
        [STAThread]
        public static void Main()
        {
            using( HelloGL3 win = new HelloGL3() )
            {
                string version = GL.GetString( StringName.Version );
                if( version.StartsWith( "3.0" ) ) win.Run();
                else Debug.WriteLine( "Requested OpenGL version not available." );
            }
        }
    }
}
the Fiddler's picture

#3

Status:open» in progress (commit)

Thanks!

GL3.0 has a steeper learning curve, since you have to use VBOs and shaders, but it is a lot cleaner in actual use. Reduced state management pays off: you no longer have to worry aboout the current matrix, or whether you forgot to enable/disable textures, lighting, etc. There's also something quite awesome in seeing your pixel shader produce the results you wanted (typically after a long fight with the equations).

nythrix's picture

#4

Setting up a GL3+ rendering environment wasn't *THAT* hard. Yes, it took me some crying and hair loss (empty black window symptoms:) but I can say I've seen worse. Also, pre-GL3 knowledge comes very handy, i.e. you still know what to do with matrices, vertices and all the rest. Which makes me curious about newcomers and their first steps into modern OpenGL.

Back to the matter, did you manage to get a 3.1 context, Fiddler?

the Fiddler's picture

#5

My Ati drivers don't seem to support 'pure' 3.1 at this point - specifying 3.1 gets me a 3.0 context plus extensions (exactly the same as specifying 3.0).

It seems that Nvidia's 190.xx drivers support 3.1, but I won't install leaked alpha-quality drivers just to test that rumour! :)

Inertia's picture

#6

Quote:

GL.UniformMatrix4( ... , true/false, ... );

I think you should be more consistent with handling the matrices, either keep them all row- or all column-major. Ofcourse this is a convenient way to get the transpose modelview matrix for transforming the normal, but there's no // comment in your shader to keep track of this. You will not remember this hack next year without documenting it right now, and - as a side-bonus - documentation will also make sure you keep your hair 5 years longer! (at least.)

Problem: You are transforming the vertex position by a column-major projection matrix (it was transposed at GL.UniformMatrix4) and a row-major modelview matrix (no transpose).

Don't get this into the wrong throat, your work is very much appreciated. Thank you :)

nythrix's picture

#7

I think you should be more consistent with handling the matrices
I'm aware of the glitch and you're absolutely right. It's a leftover from a previous version of the example which was dependent on my own math module. Switching to OpenTK's I had to rewrite the equations because the two are not compatible.
Now the projection is transposed "at birth".
Nice shot Inertia, appreciate it.

PS: Even though I managed to write a scenegraph of sorts, I still find the sentences containing "OpenGL" and "matrix" stressful.

the Fiddler's picture

#8

Status:in progress (commit)» fixed

Ok, I've committed the example with slight modifications. It is available here.

Modifications:

  • Moved shader/VBO construction to the Load() method (the OpenGL context may not be available during the constructor).
  • Added several new methods to Matrix4 and updated the example to use those.
  • Tweaked the bg color and the light color/position.

Frankly, I don't like the interface of the current math library. It's missing several important methods and it's not very intuitive. I'm slowly morphing it to follow the XNA API, which will provide several nice perks: extensive tutorials, easier ports, cleaner API.

I'll also try to see if it's possible to force the context to be constructed and current during the GameWindow constructor. This was not possible with the older architecture, but the GameWindow is more versatile in 0.9.9.

Once more, many thanks for the tutorial!

nythrix's picture

#9

Glad to hear it's been of use. Credits go also to manjian and his GLSL 1.30 example.
If I manage to fix the look of the cube I'll drop a line here. Won't happen for a week though. The exams are finally over and all I want to do is switch off somewhere in the wild.

nythrix's picture

#10

It seems that Nvidia's 190.xx drivers support 3.1, but I won't install leaked alpha-quality drivers just to test that rumour! :)
Nvidia finally released its OpenGL 3.1 capable drivers a couple of days ago. On this occasion I'll target GL 3.1 on the upcoming tutorial. The code is the same but I seriously dislike the name. "GL 3.0 forward context tutorial" is not cool(TM).
Objections?

PS: Will also mark the code here as deprecated.

Edit: I'll upgrade the shaders to version 1.4 so we can properly transform the normals with inverse(mat4). Not available on 1.3.