文章目录
- OpenGL中的坐标简介
- 标准化设备坐标
- 标准化设备坐标绘制 x y z 三个轴线
- 完整代码
- 顶点着色器
- 片段着色器
- Widget.h
- Widget.cpp
- 总结
OpenGL中的坐标简介
OpenGL 基于绘制流水线模型,而且绘制流水线的第一个步骤是对顶点进行一 系列的操作, 其中大部分属于几何操作。这些操作可以用一系列坐标变换来表示。
对于基于固定功能绘制流水线和立即绘制模式的 OpenGL 版本,绘制流水线中存在 6 个标架。 其中有些会用于我们的 OpenGL 应用程序代码中,有些则会用于我们编写的着色器中。并不是所有的这 6 个标架对应用程序都是可见的。同一个顶点在不同的标架下会有不同的表示。
这 6 个坐标在绘制流水线中通常按照下面的先后顺序出现:
- 对象坐标系或者建模坐标系
- 世界坐标系
- 眼坐标系或者照相机坐标系
- 裁减坐标系
- 标准化的设备坐标系
- 窗口坐标系或者屏幕坐标系
标准化设备坐标
先了解一下 标准化设备坐标 :( Normalized Device Coordinates, NDC )
标准化设备坐标是一个 x、y 和 z 值在 -1.0 到 1.0 的一小段空间。任何落在范围外的坐标都会被丢弃/裁剪,不会显示在你的屏幕上。下面你会看到我们定义的在标准化设备坐标中的三角形(忽略z轴):
最后通过使用由 glViewport 函数提供的数据,进行视口变换(Viewport Transform),标准化设备坐标会变换为屏幕空间坐标(Screen-space Coordinates)。
引用:[你好,三角形 - LearnOpenGL CN (learnopengl-cn.github.io)](https://learnopengl-cn.github.io/01 Getting started/04 Hello Triangle/)
标准化设备坐标绘制 x y z 三个轴线
上面的讲述忽略了 z 轴,下面绘制出 z 轴出来,对 NDC 坐标有个几何上的直观印象。
-
准备三个轴线的端点的坐标
float lines[] = { // x -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // y 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // z 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, };
利用
glDrawArrays (GL_LINES,0,2)
绘制 -
三个轴的正方形的端点的索引
因为数据已经存在线段的坐标中,所以可以使用 索引对象 EBO ,保存 x, y, z 轴的正方向端点的索引
unsigned int end[] = { 1,3,5 };
利用
glDrawArrays (GL_LINES,0,2)
绘制 -
绑定显卡缓存数据VBO
glGenBuffers (1,&VBO); glBindBuffer (GL_ARRAY_BUFFER,VBO); glBufferData (GL_ARRAY_BUFFER, sizeof(lines) + sizeof(end) , NULL, GL_STATIC_DRAW); glBufferSubData (GL_ARRAY_BUFFER, 0, sizeof(lines), lines );
-
指定顶点数组对象:Vertex Array Object,VAO
这个 A A A 我更愿意理解成 a t t r i b u t e attribute attribute 或者 a s s i s t a n t assistant assistant,为 GPU 缓存中的数据分类并记录哪些是位置数据,哪些是颜色数据等。
glGenVertexArrays (1,&VAO); glBindVertexArray(VAO); glVertexAttribPointer(0, // vao 索引,对应顶点着色器中的变量的位置 : “layout (location = 0) in vec3 aPos;” 3, // 变量中元素的个数, 比如 aPos变量 有 3 个数据【x,y,z】组成 GL_FLOAT, // 类型 GL_FALSE, // 标准化,是否在 [-1,1] 之间 3 * sizeof(float), // 步长,表示下个元组的首元素 和 该元组首元素之间的大小,因为顶点数据中 可能还夹杂颜色、法向量等数据 (void*)0 ); // 变量的偏移量,在多个变量混合时指定变量的偏移 glEnableVertexAttribArray(0); // 使能 location = 0 位置
-
指定元素缓冲对象( EBO 或者叫 IBO )
EBO 理解成 VAO 的成员,让VAO中已经记录成功的变量从新组合或者复用
glGenBuffers(1, &EBO); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(end), end, GL_STATIC_DRAW);
-
设置着色器程序
QString filename = ":/shader"; shaderProgram.addShaderFromSourceFile (QOpenGLShader::Vertex, filename+".vert"); shaderProgram.addShaderFromSourceFile (QOpenGLShader::Fragment, filename+".frag"); shaderProgram.link ();
-
渲染过程
// 绘制 x y z glLineWidth (20.0f); // 线宽 shaderProgram.setUniformValue ("u_color",1.0f, 0.0f, 0.0f, 1.0f); // 红色 glDrawArrays (GL_LINES,0,2); // 画 x 轴 shaderProgram.setUniformValue ("u_color",0.0f, 1.0f, 0.0f, 1.0f); // 绿色 glDrawArrays (GL_LINES,2,2); // 画 y 轴 shaderProgram.setUniformValue ("u_color",0.0f, 0.0f, 1.0f, 1.0f); // 蓝色 glDrawArrays (GL_LINES,4,2); // 画 z 轴 // 绘制 end glPointSize (30.0f); // 点大小 shaderProgram.setUniformValue ("u_color",1.0f, 0.0f, 0.0f, 1.0f); // 红色 glDrawElements (GL_POINTS,1,GL_UNSIGNED_INT,0); // x 端点 shaderProgram.setUniformValue ("u_color",0.0f, 1.0f, 0.0f, 1.0f); // 绿色 glDrawElements (GL_POINTS,1,GL_UNSIGNED_INT, (void *)(sizeof(unsigned int)*1)); // y 端点 shaderProgram.setUniformValue ("u_color",0.0f, 0.0f, 1.0f, 1.0f); // 蓝色 glDrawElements (GL_POINTS,1,GL_UNSIGNED_INT, (void *)(sizeof(unsigned int)*2)); // z 端点
完整代码
顶点着色器
因为 z 轴正对屏幕,如果不做旋转,显示不出 z 轴,所以需要一个模型矩阵 model 变量
#version 330 core
layout (location = 0) in vec3 aPos;
uniform mat4 model;
void main()
{
gl_Position = model * vec4(aPos.x, aPos.y, aPos.z, 1.0);
};
片段着色器
通过 u_color 设置不同的颜色
#version 330 core
uniform vec4 u_color;
void main()
{
gl_FragColor = vec4( u_color );
}
Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>
#include <QOpenGLShaderProgram>
class Widget : public QOpenGLWidget,QOpenGLFunctions_3_3_Core
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
// QOpenGLWidget interface
protected:
virtual void initializeGL() override;
virtual void paintGL() override;
virtual void resizeGL(int w, int h) override;
private:
unsigned int VAO, VBO, EBO;
QOpenGLShaderProgram shaderProgram;
int min,max;
double ratio;
};
#endif // WIDGET_H
Widget.cpp
#include "Widget.h"
#include <QApplication>
#include <QEvent>
#include <QThread>
#define qRandom QRandomGenerator::global ()
#define qout if( 1 ) qDebug() << __FILE__ << __LINE__ << ": "
float lines[] = {
// x
-1.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f,
// y
0.0f, -1.0f, 0.0f,
0.0f, 1.0f, 0.0f,
// z
0.0f, 0.0f, -1.0f,
0.0f, 0.0f, 1.0f,
};
unsigned int end[] = {
1,3,5
};
//float end[] = {
// // x
// 1.0f, 0.0f, 0.0f,
// // y
// 0.0f, 1.0f, 0.0f,
// // z
// 0.0f, 0.0f, 1.0f,
//};
Widget::Widget(QWidget *parent)
: QOpenGLWidget(parent)
{
setWindowTitle ("07_xyz");
resize (200,200);
ratio = qApp->devicePixelRatio ();
}
Widget::~Widget()
{
makeCurrent ();
glDeleteBuffers (1,&VBO);
glDeleteVertexArrays (1,&VAO);
doneCurrent ();
}
void Widget::initializeGL()
{
initializeOpenGLFunctions ();
const char *version =(const char *) glGetString (GL_VERSION);
qout << QString(version);
// ---------------------------------
glGenBuffers (1,&VBO);
glBindBuffer (GL_ARRAY_BUFFER,VBO);
glBufferData (GL_ARRAY_BUFFER,
sizeof(lines) ,
NULL,
GL_STATIC_DRAW);
glBufferSubData (GL_ARRAY_BUFFER,
0,
sizeof(lines),
lines );
QString filename = ":/shader";
shaderProgram.addShaderFromSourceFile (QOpenGLShader::Vertex, filename+".vert");
shaderProgram.addShaderFromSourceFile (QOpenGLShader::Fragment, filename+".frag");
shaderProgram.link ();
glGenVertexArrays (1,&VAO);
glBindVertexArray(VAO);
glVertexAttribPointer(0, // vao 索引,对应顶点着色器中的变量的位置 : “layout (location = 0) in vec3 aPos;”
3, // 变量中元素的个数, 比如 aPos变量 有 3 个数据【x,y,z】组成
GL_FLOAT, // 类型
GL_FALSE, // 标准化,是否在 [-1,1] 之间
3 * sizeof(float), // 步长,表示下个元组的首元素 和 该元组首元素之间的大小,因为顶点数据中 可能还夹杂颜色、法向量等数据
(void*)0 ); // 变量的偏移量,在多个变量混合时指定变量的偏移
glEnableVertexAttribArray(0); // 使用 location = 0 的索引
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(end), end, GL_STATIC_DRAW);
glBindBuffer (GL_ARRAY_BUFFER,0);
// glPolygonMode (GL_FRONT_AND_BACK,GL_LINE);
// 深度测试
glEnable(GL_DEPTH_TEST);
}
int rotateAngle = 0;
void Widget::paintGL()
{
glClearColor(0.2f, 0.3f, 0.3f, 1.0f); // 设置背景色
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glViewport ((max-min)/2 ,0,min,min);
shaderProgram.bind ();
glBindVertexArray(VAO);
shaderProgram.setUniformValue ("u_color",1.0f, 0.5f, 0.2f, 1.0f);
QMatrix4x4 model;
model.rotate ( -15, 1.0f, 0.0f, 0.0f);
model.rotate ( rotateAngle+15, 0.0f, 1.0f, 0.0f);
shaderProgram.setUniformValue ("model",model);
// 绘制 x y z
glLineWidth (20.0f); // 线宽
glPointSize (30.0f); // 点大小
shaderProgram.setUniformValue ("u_color",1.0f, 0.0f, 0.0f, 1.0f); // 红色
glDrawArrays (GL_LINES,0,2); // 画 x 轴
glDrawElements (GL_POINTS,1,GL_UNSIGNED_INT,0); // x 端点
shaderProgram.setUniformValue ("u_color",0.0f, 1.0f, 0.0f, 1.0f); // 绿色
glDrawArrays (GL_LINES,2,2); // 画 y 轴
glDrawElements (GL_POINTS,1,GL_UNSIGNED_INT, (void *)(sizeof(unsigned int)*1)); // y 端点
shaderProgram.setUniformValue ("u_color",0.0f, 0.0f, 1.0f, 1.0f); // 蓝色
glDrawArrays (GL_LINES,4,2); // 画 z 轴
glDrawElements (GL_POINTS,1,GL_UNSIGNED_INT, (void *)(sizeof(unsigned int)*2)); // z 端点
// qout << this->windowState ();
// if( this->windowState () != Qt::WindowMinimized && 1 ){
if( 1 ){
QThread::currentThread ()->msleep (50);
rotateAngle += 1 ;
update ();
}
}
void Widget::resizeGL(int w, int h)
{
qout << "resizeGL";
min = std::min (w,h) * ratio;
max = std::max (w,h) * ratio;
}
总结
如图所示,在 NDC 坐标中 Z Z Z 的正向是朝屏幕内部的,这结果还真是让人意外。