As usual when I try to get my fingers wet with modern OpenGL, using one of the clever demos I can find on some blogs, something goes wrong.
Expected behavior: Draw a triangle and the colors should be interpolated between the 3 vertices.
Found behavior: Triangle is red. And it does not matter which colors I write into the color array.
Code first (sorry 160 lines - modern OpenGL is spammy...).
open System
open System.Drawing
open System.Collections.Generic
open OpenTK
open OpenTK.Graphics
open OpenTK.Graphics.OpenGL
open OpenTK.Input
module Shaders =
let vertexShader =
"""#version 330
in vec3 vPosition;
in vec3 vColor;
out vec4 color;
uniform mat4 modelview;
void
main()
{
gl_Position = modelview * vec4(vPosition, 1.0);
color = vec4( vColor, 1.0);
}
"""
let fragmentShader =
"""#version 330
in vec4 color;
out vec4 outputColor;
void
main()
{
outputColor = color;
}
"""
let initShaders () : int =
let makeShader shaderType src =
let sh = GL.CreateShader(shaderType)
GL.ShaderSource(sh,src)
GL.CompileShader(sh)
sh
let pgmId = GL.CreateProgram()
let vsh = makeShader ShaderType.VertexShader vertexShader
let fsh = makeShader ShaderType.FragmentShader fragmentShader
GL.AttachShader(pgmId,vsh)
GL.AttachShader(pgmId,fsh)
GL.LinkProgram(pgmId)
pgmId
let failMinusOne = function
| -1 -> failwith "Something is -1 which should not be -1!"
| x -> x
type Game(width,height) =
inherit GameWindow(width, height, GraphicsMode.Default, "F# OpenTK Sample")
do base.VSync <- VSyncMode.On
let mutable shaderProgramId = -1
let mutable attribute_vcol = -1
let mutable attribute_vpos = -1
let mutable uniform_mview = -1
let mutable vbo_col = 0
let mutable vbo_pos = 0
let vertex_data =
[|
Vector3(-0.8f, -0.8f, 0.f)
Vector3( 0.8f, -0.8f, 0.f)
Vector3( 0.f, 0.8f, 0.f)
|]
let col_data =
[|
Vector3( 1.f, 1.f, 1.f)
Vector3( 0.f, 0.f, 1.f)
Vector3( 0.f, 1.f, 0.f)
|]
let mview_data = [| Matrix4.Identity |]
/// <summary>Load resources here.</summary>
/// <param name="e">Not used.</param>
override o.OnLoad e =
base.OnLoad(e)
o.Title <- "Hello OpenTK!"
shaderProgramId <- Shaders.initShaders ()
GL.ClearColor(Color.CornflowerBlue)
GL.Enable(EnableCap.DepthTest)
attribute_vpos <- GL.GetAttribLocation(shaderProgramId, "vPosition") |> failMinusOne
attribute_vcol <- GL.GetAttribLocation(shaderProgramId, "vColor") |> failMinusOne
uniform_mview <- GL.GetUniformLocation(shaderProgramId, "modelview") |> failMinusOne
vbo_col <- GL.GenBuffer()
vbo_pos <- GL.GenBuffer()
/// <summary>
/// Called when your window is resized. Set your viewport here. It is also
/// a good place to set up your projection matrix (which probably changes
/// along when the aspect ratio of your window).
/// </summary>
/// <param name="e">Not used.</param>
override o.OnResize e =
base.OnResize e
GL.Viewport(base.ClientRectangle.X, base.ClientRectangle.Y, base.ClientRectangle.Width, base.ClientRectangle.Height)
let projection = Matrix4.CreatePerspectiveFieldOfView(float32 (Math.PI / 4.), float32 base.Width / float32 base.Height, 1.f, 64.f)
GL.MatrixMode(MatrixMode.Projection)
GL.LoadMatrix(ref projection)
/// <summary>
/// Called when it is time to setup the next frame. Add you game logic here.
/// </summary>
/// <param name="e">Contains timing information for framerate independent logic.</param>
override o.OnUpdateFrame e =
base.OnUpdateFrame e
if base.Keyboard.[Key.Escape] then base.Close()
else
GL.BindBuffer(BufferTarget.ArrayBuffer,vbo_col)
GL.BufferData<Vector3>(BufferTarget.ArrayBuffer,Array.length col_data * Vector3.SizeInBytes,col_data,BufferUsageHint.StaticDraw)
GL.VertexAttribPointer(attribute_vcol, 3, VertexAttribPointerType.Float, false, 0, 0)
GL.BindBuffer(BufferTarget.ArrayBuffer,vbo_pos)
GL.BufferData<Vector3>(BufferTarget.ArrayBuffer,Array.length vertex_data * Vector3.SizeInBytes,vertex_data,BufferUsageHint.StaticDraw )
GL.VertexAttribPointer(attribute_vpos, 3, VertexAttribPointerType.Float, false, 0, 0)
GL.UniformMatrix4(uniform_mview,false,ref mview_data.[0])
GL.BindBuffer(BufferTarget.ArrayBuffer,0)
GL.UseProgram(shaderProgramId)
/// <summary>
/// Called when it is time to render the next frame. Add your rendering code here.
/// </summary>
/// <param name="e">Contains timing information.</param>
override o.OnRenderFrame(e) =
base.OnRenderFrame e
GL.Clear(ClearBufferMask.ColorBufferBit ||| ClearBufferMask.DepthBufferBit);
GL.EnableVertexArrayAttrib(attribute_vpos,0)
GL.EnableVertexArrayAttrib(attribute_vcol,0)
GL.DrawArrays(PrimitiveType.Triangles,0,Array.length vertex_data)
GL.DisableVertexArrayAttrib(attribute_vpos,0)
GL.DisableVertexArrayAttrib(attribute_vcol,0)
GL.Flush()
base.SwapBuffers()
[<EntryPoint>]
let main argv =
use game = new Game(800,600)
do game.Run(30.,30.)
0 // return an integer exit code
After a few hours trying to find what is going wrong I ran out of ideas. Adding more triangles also seems to flop. But then, the fact that the triangle shows as the vertices suggest, makes me think, the download of the data to the gpu is okay-ish.
But for you guys who do that day in day out, it will probably be easy to spot where things go wrong.
You are using an OpenGL 4.5 function, EnableVertexArrayAttrib
, to enable your vertex attrib array on vertex array object (VAO) with ID 0. This is peculiar, since your shaders are versioned as 330
, which is much, much older, but more importantly invalid, since you aren't using any VAO.
You can enable/disable the vertex attrib arrays the classical way, like this:
GL.EnableVertexAttribArray(attribute_vpos)
GL.EnableVertexAttribArray(attribute_vcol)
GL.DisableVertexAttribArray(attribute_vpos)
GL.DisableVertexAttribArray(attribute_vcol)
This results in the properly colored triangle on my NVidia GTX 760 desktop, because it acts on the currently active vertex array information.
I would advise to have a second look at the way you are using the state machine. Enabling vertex arrays and defining their structure via VertexAttribPointer
is program-specific and belongs together. Usually, you'd use a VAO to group this information and unbind it after drawing finishes. If the vertex attrib arrays are supposed to be global state, there's no point in disabling them, and this fact should be well-documented. As is, the code is in danger of developing spurious interactions between seemingly unrelated functions, because it's sharing complex state of the OpenGL state machine.
A possible approach would be:
Setup
Create shaders, keep shader program handle and attrib locations
Create buffers, initialize data, then save their handles and leave them unbound
Create vertex array structure as VAO, then save its handle and leave it unbound
Drawing
Approaches like this group the interaction with the state machine in a more structured way, and reduce reliance of your program on OpenGL state machine values remaining unchanged.
There's no need for the various mutable values and their initialization to -1. They can be bound in order, directly assigning the correct handle (assuming the OpenGL context is already present; see comments.)