问题描述
我正在尝试使用 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 无法打印到控制台,因此我无法调试着色器本身,只能查看结果并更改输入。我究竟做错了什么?这是我见过每个人这样做的方式。
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);
}
}
}
解决方法
投影矩阵定义了一个视域。该体积之外的任何几何体都将被剪裁。您的几何体被裁剪,因为它不在 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 答案。