在 OpenTK 3.3

问题描述

我正在尝试使用 OpenTK 为 OpenGL 处理多边形类。目标是创建一个类的实例,将像素坐标中的顶点数组传递给它,然后将其正确绘制到屏幕上。

我打算实现这一点的方法是使用一个投影矩阵(定义为屏幕类中的公共变量),着色器应该使用它来缩放从 NDC 到像素坐标的所有内容。我还打算将偏移向量传递给着色器以将其添加到位置。

使用

计算投影
Matrix4.CreateOrthographicOffCenter(0.0f,width,0.0f,height,-100.0f,100.0f);

用于多边形的顶点是:

float[] vertices = new float[]
                {
                    0.0f,100f,0f,};

这是我的顶点着色器的外观:

#version 330 core
layout (location = 0) in vec3 position;

uniform mat4 projection;
uniform vec3 offset;

void main()
{
    gl_Position = projection * vec4(position + offset,1.0);
}

这是多边形类:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using Shaders;
using Engine;

namespace Engine.Engine.Shape
{
    class polygon
    {

        float[] vertices;
        List<uint> indexes;
        uint[] indices;
        int VBO,VAO,EBO;
        Shader shader;
        Matrix4 projection;
        Vector3 offset;

        public polygon(float[] vertices)
        {
            this.vertices = vertices;
            this.projection = Screen.projection;

            offset = new Vector3(10,10,0);

            // This creates an index array for the EBO to use
            indexes = new List<uint>();
            for (uint curr_vert = 1; curr_vert < vertices.Length / 3 - 1; curr_vert++)
            {
                // For each triangle of the polygon,I select a fixed vertex (index 0),the current vertex and the next vertex
                uint[] triangle = new uint[] { 0,curr_vert,curr_vert + 1 };
                foreach (uint x in triangle)
                {
                    //I use this list to make life easier
                    indexes.Add(x);
                }
            }
            // Now that the list is complete I convert it into an array
            indices = indexes.ToArray();
        }

        public void Load()
        {
            string path = "../../../Shaders/";
            VBO = GL.GenBuffer();
            GL.BindBuffer(BufferTarget.ArrayBuffer,VBO);
            GL.BufferData(BufferTarget.ArrayBuffer,vertices.Length * sizeof(float),vertices,BufferUsageHint.StaticDraw);

            VAO = GL.GenVertexArray();
            GL.BindVertexArray(VAO);

            GL.VertexAttribPointer(0,3,VertexAttribPointerType.Float,false,3 * sizeof(float),0);
            GL.EnabLevertexAttribArray(0);



            // We create/bind the Element Buffer Object EBO the same way as the VBO,except there is a major difference here which can be REALLY confusing.
            // The binding spot for ElementArrayBuffer is not actually a global binding spot like ArrayBuffer is. 
            // Instead it's actually a property of the currently bound VertexArrayObject,and binding an EBO with no VAO is undefined behavIoUr.
            // This also means that if you bind another VAO,the current ElementArrayBuffer is going to change with it.
            // Another sneaky part is that you don't need to unbind the buffer in ElementArrayBuffer as unbinding the VAO is going to do this,// and unbinding the EBO will remove it from the VAO instead of unbinding it like you would for VBOs or VAOs.
            EBO = GL.GenBuffer();
            GL.BindBuffer(BufferTarget.ElementArrayBuffer,EBO);
            // We also upload data to the EBO the same way as we did with VBOs.
            GL.BufferData(BufferTarget.ElementArrayBuffer,indices.Length * sizeof(uint),indices,BufferUsageHint.StaticDraw);
            // The EBO has Now been properly setup. Go to the Render function to see how we draw our rectangle Now!

            shader = new Shader(path + "shader.vert",path + "shader.frag");
            shader.Use();
            


        }

        public void Show()
        {

            shader.Use();
            shader.SetMatrix4("projection",projection);
            shader.SetVector3("offset",offset);
            GL.BindVertexArray(VAO);
            GL.DrawElements(PrimitiveType.Triangles,indices.Length,DrawElementsType.UnsignedInt,0);
        }

    }
}

预期结果是一个灰色背景的窗口,左上角一个白色多边形,在每个轴上从角落偏移 10 个像素。

但是,如果我尝试将偏移量或投影矩阵应用于着色器,则不会出现多边形。由于 GLSL 无法打印到控制台,因此我无法调试着色器本身,只能查看结果并更改输入。我究竟做错了什么?这是我见过每个人这样做的方式。

这段代码中用到的Shader类如下(可以找到here):

using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using OpenTK.Graphics.OpenGL;
using OpenTK;

namespace Shaders
{
    // A simple class meant to help create shaders.
    public class Shader
    {
        public readonly int Handle;

        private readonly Dictionary<string,int> _uniformlocations;

        // This is how you create a simple shader.
        // Shaders are written in GLSL,which is a language very similar to C in its semantics.
        // The GLSL source is compiled *at runtime*,so it can optimize itself for the graphics card it's currently being used on.
        // A commented example of GLSL can be found in shader.vert
        public Shader(string vertPath,string fragPath)
        {
            // There are several different types of shaders,but the only two you need for basic rendering are the vertex and fragment shaders.
            // The vertex shader is responsible for moving around vertices,and uploading that data to the fragment shader.
            //   The vertex shader won't be too important here,but they'll be more important later.
            // The fragment shader is responsible for then converting the vertices to "fragments",which represent all the data OpenGL needs to draw a pixel.
            //   The fragment shader is what we'll be using the most here.

            // Load vertex shader and compile
            var shaderSource = File.ReadAllText(vertPath);

            // GL.CreateShader will create an empty shader (obvIoUsly). The ShaderType enum denotes which type of shader will be created.
            var vertexShader = GL.CreateShader(ShaderType.VertexShader);

            // Now,bind the GLSL source code
            GL.ShaderSource(vertexShader,shaderSource);

            // And then compile
            CompileShader(vertexShader);

            // We do the same for the fragment shader
            shaderSource = File.ReadAllText(fragPath);
            var fragmentShader = GL.CreateShader(ShaderType.FragmentShader);
            GL.ShaderSource(fragmentShader,shaderSource);
            CompileShader(fragmentShader);

            // These two shaders must then be merged into a shader program,which can then be used by OpenGL.
            // To do this,create a program...
            Handle = GL.CreateProgram();

            // Attach both shaders...
            GL.AttachShader(Handle,vertexShader);
            GL.AttachShader(Handle,fragmentShader);

            // And then link them together.
            LinkProgram(Handle);

            // When the shader program is linked,it no longer needs the individual shaders attacked to it; the compiled code is copied into the shader program.
            // Detach them,and then delete them.
            GL.DetachShader(Handle,vertexShader);
            GL.DetachShader(Handle,fragmentShader);
            GL.DeleteShader(fragmentShader);
            GL.DeleteShader(vertexShader);

            // The shader is Now ready to go,but first,we're going to cache all the shader uniform locations.
            // Querying this from the shader is very slow,so we do it once on initialization and reuse those values
            // later.

            // First,we have to get the number of active uniforms in the shader.
            GL.GetProgram(Handle,GetProgramParameterName.ActiveUniforms,out var numberOfUniforms);

            // Next,allocate the dictionary to hold the locations.
            _uniformlocations = new Dictionary<string,int>();

            // Loop over all the uniforms,for (var i = 0; i < numberOfUniforms; i++)
            {
                // get the name of this uniform,var key = GL.GetActiveUniform(Handle,i,out _,out _);

                // get the location,var location = GL.GetUniformlocation(Handle,key);

                // and then add it to the dictionary.
                _uniformlocations.Add(key,location);
            }
        }

        private static void CompileShader(int shader)
        {
            // Try to compile the shader
            GL.CompileShader(shader);

            // Check for compilation errors
            GL.GetShader(shader,ShaderParameter.CompileStatus,out var code);
            if (code != (int)All.True)
            {
                // We can use `GL.GetShaderInfoLog(shader)` to get information about the error.
                var infoLog = GL.GetShaderInfoLog(shader);
                throw new Exception($"Error occurred whilst compiling Shader({shader}).\n\n{infoLog}");
            }
        }

        private static void LinkProgram(int program)
        {
            // We link the program
            GL.LinkProgram(program);

            // Check for linking errors
            GL.GetProgram(program,GetProgramParameterName.LinkStatus,out var code);
            if (code != (int)All.True)
            {
                // We can use `GL.GetProgramInfoLog(program)` to get information about the error.
                throw new Exception($"Error occurred whilst linking Program({program})");
            }
        }

        // A wrapper function that enables the shader program.
        public void Use()
        {
            GL.UseProgram(Handle);
        }

        // The shader sources provided with this project use hardcoded layout(location)-s. If you want to do it dynamically,// you can omit the layout(location=X) lines in the vertex shader,and use this in VertexAttribPointer instead of the hardcoded values.
        public int GetAttribLocation(string attribName)
        {
            return GL.GetAttribLocation(Handle,attribName);
        }

        // Uniform setters
        // Uniforms are variables that can be set by user code,instead of reading them from the VBO.
        // You use VBOs for vertex-related data,and uniforms for almost everything else.

        // Setting a uniform is almost always the exact same,so I'll explain it here once,instead of in every method:
        //     1. Bind the program you want to set the uniform on
        //     2. Get a handle to the location of the uniform with GL.GetUniformlocation.
        //     3. Use the appropriate GL.Uniform* function to set the uniform.

        /// <summary>
        /// Set a uniform int on this shader.
        /// </summary>
        /// <param name="name">The name of the uniform</param>
        /// <param name="data">The data to set</param>
        public void SetInt(string name,int data)
        {
            GL.UseProgram(Handle);
            GL.Uniform1(_uniformlocations[name],data);
        }

        /// <summary>
        /// Set a uniform float on this shader.
        /// </summary>
        /// <param name="name">The name of the uniform</param>
        /// <param name="data">The data to set</param>
        public void SetFloat(string name,float data)
        {
            GL.UseProgram(Handle);
            GL.Uniform1(_uniformlocations[name],data);
        }

        /// <summary>
        /// Set a uniform Matrix4 on this shader
        /// </summary>
        /// <param name="name">The name of the uniform</param>
        /// <param name="data">The data to set</param>
        /// <remarks>
        ///   <para>
        ///   The matrix is transposed before being sent to the shader.
        ///   </para>
        /// </remarks>
        public void SetMatrix4(string name,Matrix4 data)
        {
            GL.UseProgram(Handle);
            GL.UniformMatrix4(_uniformlocations[name],true,ref data);
        }

        /// <summary>
        /// Set a uniform Vector3 on this shader.
        /// </summary>
        /// <param name="name">The name of the uniform</param>
        /// <param name="data">The data to set</param>
        public void SetVector3(string name,Vector3 data)
        {
            GL.UseProgram(Handle);
            GL.Uniform3(_uniformlocations[name],data);
        }
    }
}

其余的代码可以在here

解决方法

投影矩阵定义了一个视域。该体积之外的任何几何体都将被剪裁。您的几何体被裁剪,因为它不在 Orthographic projection 定义的体积的近平面和远平面之间。几何 z 坐标为 0,但到近平面的距离为 0.1,到远平面的距离为 100。

要么改变几何体的 z 坐标并沿负 z 轴移动几何体:

offset = new Vector3(10,10,0);

offset = new Vector3(10,-10);

或者改变正射投影的近平面:

Matrix4.CreateOrthographicOffCenter(0.0f,width,0.0f,height,0.1f,100.0f);

Matrix4.CreateOrthographicOffCenter(0.0f,-100.0f,100.0f);

在着色器代码中,矩阵从左边乘以向量(这很常见)。因此,您不得转置矩阵:

gl_Position = projection * vec4(position + offset,1.0);

GL.UniformMatrix4(_uniformLocations[name],true,ref data)

GL.UniformMatrix4(_uniformLocations[name],false,ref data);
,

我发现了问题。事实证明,因素的顺序确实改变了产品。

通过改变

gl_Position = projection * vec4(position + offset,1.0);

gl_Position = vec4(position + offset,1.0) * projection;

一切正常。

编辑:矩阵被传递转置,禁用它并撤消此处解释的更改也有效。

否 不要更改着色器,但不要转置矩阵。 (GL.UniformMatrix4(_uniformLocations[name],ref data);) 参见 man 答案。