Criperum's picture

Issue with normal buffer data

Hello everyone.

I'm totally noob in OpenTK and 3D as well. I was trying to modificate and launch one of the examples (code below).
I was trying to slightly randomize lighting through randomizing normals buffer. But I've encountered with strange issue: when I randomize Normal buffer data (as in code sample) the cube wraps itself. When I randomize positions - the lighting wraps. As I understand there must be the opposite behavior (normals for lighting and positions for shape).

What am I doing wrong? Please help.

using System;
using System.Diagnostics;
using System.Drawing;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using OpenTK.Platform;
 
namespace OpenTKTestProject
{
    public class TestWindow: 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)
{
  //works only for orthogonal modelview
  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.0));
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,
            eboHandle;
 
        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) };
 
        Vector3[] normalVector3 = new Vector3[8];
 
 
        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 TestWindow()
            : base(640, 480,
            new GraphicsMode(), "OpenGL 3 Example", 0,
            DisplayDevice.Default, 3, 0,
            GraphicsContextFlags.ForwardCompatible | GraphicsContextFlags.Debug)
        { }
 
        protected override void OnLoad (System.EventArgs e)
        {
            VSync = VSyncMode.On;
 
            CreateShaders();
            CreateVBOs();
            CreateVAOs();
 
            // Other state
            GL.Enable(EnableCap.DepthTest);
            GL.ClearColor(Color.MidnightBlue);
        }
 
        void CreateShaders()
        {
            vertexShaderHandle = GL.CreateShader(ShaderType.VertexShader);
            fragmentShaderHandle = GL.CreateShader(ShaderType.FragmentShader);
 
            GL.ShaderSource(vertexShaderHandle, vertexShaderSource);
            GL.ShaderSource(fragmentShaderHandle, fragmentShaderSource);
 
            GL.CompileShader(vertexShaderHandle);
            GL.CompileShader(fragmentShaderHandle);
 
            Debug.WriteLine(GL.GetShaderInfoLog(vertexShaderHandle));
            Debug.WriteLine(GL.GetShaderInfoLog(fragmentShaderHandle));
 
            // Create program
            shaderProgramHandle = GL.CreateProgram();
 
            GL.AttachShader(shaderProgramHandle, vertexShaderHandle);
            GL.AttachShader(shaderProgramHandle, fragmentShaderHandle);
 
            GL.LinkProgram(shaderProgramHandle);
 
            Debug.WriteLine(GL.GetProgramInfoLog(shaderProgramHandle));
 
            GL.UseProgram(shaderProgramHandle);
 
            // Set uniforms
            projectionMatrixLocation = GL.GetUniformLocation(shaderProgramHandle, "projection_matrix");
            modelviewMatrixLocation = GL.GetUniformLocation(shaderProgramHandle, "modelview_matrix");
 
            float aspectRatio = ClientSize.Width / (float)(ClientSize.Height);
            Matrix4.CreatePerspectiveFieldOfView((float)Math.PI / 4, aspectRatio, 1, 100, out projectionMatrix);
            modelviewMatrix = Matrix4.LookAt(new Vector3(0, 3, 5), new Vector3(0, 0, 0), new Vector3(0, 1, 0));
 
            GL.UniformMatrix4(projectionMatrixLocation, false, ref projectionMatrix);
            GL.UniformMatrix4(modelviewMatrixLocation, false, ref modelviewMatrix);
        }
 
        void CreateVBOs()
        {
            Random rand = new Random();
            for (var i = 0; i < positionVboData.Length; i++)
            {
                Vector3 vec = positionVboData[i];
                normalVector3[i] = vec;
                vec.X = vec.X * (float)rand.NextDouble();
                vec.Y = vec.Y * (float)rand.NextDouble();
                vec.Z = vec.Z * (float)rand.NextDouble();
                positionVboData[i] = vec;
            }
 
 
            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(normalVector3.Length * Vector3.SizeInBytes),
                normalVector3, BufferUsageHint.StaticDraw);
 
            GL.GenBuffers(1, out eboHandle);
            GL.BindBuffer(BufferTarget.ElementArrayBuffer, eboHandle);
            GL.BufferData(BufferTarget.ElementArrayBuffer,
                new IntPtr(sizeof(uint) * indicesVboData.Length),
                indicesVboData, BufferUsageHint.StaticDraw);
 
            GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
            GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0);
        }
 
        void CreateVAOs()
        {
            // GL3 allows us to store the vertex layout in a "vertex array object" (VAO).
            // This means we do not have to re-issue VertexAttribPointer calls
            // every time we try to use a different vertex layout - these calls are
            // stored in the VAO so we simply need to bind the correct VAO.
            GL.GenVertexArrays(1, out vaoHandle);
            GL.BindVertexArray(vaoHandle);
 
            GL.EnableVertexAttribArray(0);
            GL.BindBuffer(BufferTarget.ArrayBuffer, positionVboHandle);
            GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, true, Vector3.SizeInBytes, 0);
            GL.BindAttribLocation(shaderProgramHandle, 0, "in_position");
 
            GL.EnableVertexAttribArray(1);
            GL.BindBuffer(BufferTarget.ArrayBuffer, normalVboHandle);
            GL.VertexAttribPointer(1, 3, VertexAttribPointerType.Float, true, Vector3.SizeInBytes, 0);
            GL.BindAttribLocation(shaderProgramHandle, 1, "in_normal");
 
            GL.BindBuffer(BufferTarget.ElementArrayBuffer, eboHandle);
 
            GL.BindVertexArray(0);
 
            GL.Viewport(0, 0, Width, Height);
            GL.BindVertexArray(vaoHandle);
 
        }
 
        protected override void OnUpdateFrame(FrameEventArgs e)
        {
            Matrix4 rotation = Matrix4.CreateRotationY((float)e.Time);
            Matrix4.Mult(ref rotation, ref modelviewMatrix, out modelviewMatrix);
            GL.UniformMatrix4(modelviewMatrixLocation, false, ref modelviewMatrix);
 
            if (Keyboard[OpenTK.Input.Key.Escape])
                Exit();
        }
 
        protected override void OnRenderFrame(FrameEventArgs e)
        {
 
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
 
 
 
            GL.DrawElements(BeginMode.Triangles, indicesVboData.Length,
                DrawElementsType.UnsignedInt, IntPtr.Zero);
 
            SwapBuffers();
        }
 
 
        [STAThread]
        static void Main(string[] args)
        {
            using (TestWindow example = new TestWindow())
            {
                //Utilities.SetWindowTitle(example);
                example.Run(30);
            }
        }
    }
}

Comments

Comment viewing options

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

You say "when I randomize Normal buffer data (as in code sample) the cube wraps itself". However, in the code sample you are randomizing positions, not normals:

                Vector3 vec = positionVboData[i];
                normalVector3[i] = vec; // assigns non-randomized data to normal
                vec.X = vec.X * (float)rand.NextDouble();
                vec.Y = vec.Y * (float)rand.NextDouble();
                vec.Z = vec.Z * (float)rand.NextDouble();
                positionVboData[i] = vec; // assigns randomized data to position
Criperum's picture

Yeah. That sample is after I've tried to fix an issue(sorry for that). However THAT code leads to lighting wraping instead of shape.

the Fiddler's picture

"GL.BindAttribLocation must be performed before GL.LinkProgram"

From https://stackoverflow.com/questions/11572218/vertex-attribute-buffers-ge...

Edit: see also http://www.khronos.org/opengles/sdk/docs/man/xhtml/glBindAttribLocation....

"Attribute bindings do not go into effect until glLinkProgram is called. After a program object has been linked successfully, the index values for generic attributes remain fixed (and their values can be queried) until the next link command occurs."

Criperum's picture

So why do buffers actually work? They work like swithched (normal <=> position) but they work. If I switch randomisation it switches.
Do you mean that code

CreateShaders();
CreateVBOs();
CreateVAOs();

is wrong and it should be like

CreateVBOs();
CreateVAOs();
CreateShaders();

??

the Fiddler's picture

Pretty much, yes.

Once you call GL.LinkProgram, you can no longer modify attribute locations unless you re-link the program. In other words, these lines:

            GL.BindAttribLocation(shaderProgramHandle, 0, "in_position");
            GL.BindAttribLocation(shaderProgramHandle, 1, "in_normal");

have no effect in your original code. If you use GL.GetAttribLocation, you'll see that "in_position" and "in_normal" are swapped compared to what you expect.

The right sequence is the following:

CreateShaders(); // GL.CreateShader(); GL.ShaderSource(); GL.CompileShader();
CreateProgram(); // GL.CreateProgram(); GL.AttachShader();
CreateVAOs();
LinkProgram(); // GL.LinkProgram(); GL.UseProgram();

Since attribute locations are set to stone by GL.LinkProgram, so you need to call this function *after* binding your desired locations in CreateVAOs().

Criperum's picture

If these lines:

GL.BindAttribLocation(shaderProgramHandle, 0, "in_position");
GL.BindAttribLocation(shaderProgramHandle, 1, "in_normal");

have no effect, so how vertex data actually binds to the pipeline? It renders that cube, so shaders get their data somehow.

Criperum's picture

Well, I've tried your way and it stops rendering the cube at all.

Criperum's picture

"glBindAttribLocation is used to associate a user-defined attribute variable in the program object specified by program with a generic vertex attribute index."

"When program is made part of current state, values provided via the generic vertex attribute index will modify the value of the user-defined attribute variable specified by name."

"glBindAttribLocation can be called before any vertex shader objects are bound to the specified program object. It is also permissible to bind a generic attribute index to an attribute variable name that is never used in a vertex shader."

This is from OpenGL Docs. As i under stand it does'nt matter when I use BindAttribLocation. Only thing I should know is that actual values will be written into shader variables in UseProgram.

the Fiddler's picture

Note this part of glBindAttribLocation docs:

Quote:

Attribute bindings do not go into effect until glLinkProgram is called. After a program object has been linked successfully, the index values for generic attributes remain fixed (and their values can be queried) until the next link command occurs.

GL.BindAttribLocation must be called before GL.LinkProgram, otherwise it will have no effect.

Criperum wrote:

If these lines:

GL.BindAttribLocation(shaderProgramHandle, 0, "in_position");
GL.BindAttribLocation(shaderProgramHandle, 1, "in_normal");

have no effect, so how vertex data actually binds to the pipeline? It renders that cube, so shaders get their data somehow.

GL.LinkProgram assigns attribute locations to all active attributes. You can retrieve the assigned locations using GL.GetAttribLocation.

If you call GL.BindAttribLocation, then GL.LinkProgram will use the locations you specify. If you don't call GL.BindAttribLocation then GL.LinkProgram will assign locations in a driver-specific manner.

My theory is that, in this case, the driver has decided to assign "in_position":1 and "in_normal":0, which is the opposite of what your code is expecting. You can test this theory using GL.GetAttribLocation.

Criperum's picture

I've tried this one:

protected override void OnLoad (System.EventArgs e)
        {
            VSync = VSyncMode.On;
 
            CreateShaders();
            CreateVBOs();
            CreateVAOs();
 
            GL.LinkProgram(shaderProgramHandle);
 
            //Debug.WriteLine(GL.GetProgramInfoLog(shaderProgramHandle));
 
            GL.UseProgram(shaderProgramHandle);
 
            // Other state
            GL.Enable(EnableCap.DepthTest);
            GL.ClearColor(Color.MidnightBlue);
        }

And the result is just background without cube at all.