需要继承的两个类
#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>class OpenGLWidget : public QOpenGLWidget,public QOpenGLFunctions_3_3_Core
{
Q_OBJECT
public:
explicit OpenGLWidget(QWidget *parent = nullptr);
virtual void initializeGL() override;
virtual void resizeGL(int w, int h) override;
virtual void paintGL() override;
signals:};
需要Override的三个函数
virtual void initializeGL() override;
virtual void resizeGL(int w, int h) override;
virtual void paintGL() override;
CMakeLists.txt需要添加openGL库
find_package(OpenGL REQUIRED)
target_link_libraries(Model PRIVATE ${OPENGL_LIBRARIES})
CPU和GPU(显卡)
怎么把数据存储在显卡上
VBO和VAO
我们用VBO来存储数据,而用VAO来告诉计算机这些数据分别有什么属性、起什么作用。
VBO----传数据
VAO----数据属性
VBO:存放顶点数据的
vbo有多种类型
VAO:记录内存如何定义;
vao只有一种类型
vao中的每一条记录都是一个顶点属性指针,用来记录存储数据的VBO中的数据的属性;
这些属性由glVertexAttribPointer函数设置。
注意
1,使用不同的VBO缓存对象,必须要先绑定,不同位置绑定不同VBO就是使用不同GPU缓存中的数据;
VAO和VBO可以创建多个,如果关联使用
即使应用程序完全没有用到任何缓冲区,OpenGL仍然需要在使用着色器的时候拥有至少一个创建好的VAO(创建到绑定),所以这两行代码用来创建OpenGL要求的VAO。
以数组的形式创建,在glGenVertexArray()和glGenBuffer中第一个参数指定数组元素个数(VBO和VAO个数)即可;
VAO和VBO,EBO的初始化过程
每一组都要从generate(生成)开始,VAO使能(glEnableVertexAttribArray)结束;
生成可以一起,从绑定开始要分开。
#include "testopenglwidget.h"
#include <iostream>
//为什么不能作为成员对象
unsigned int VAOs[2],VBOs[2],EBO;
unsigned int shaderProgram;
float firast_vertices[]={
//第一个三角形
-0.9f,0.0f,0.0f,
0.0f,0.0f,0.0f,
-0.45f,0.5f,0.0f
};
float second_vertices[]={
//第二个三角形
0.9f,0.0f,0.0f,
0.0f,0.0f,0.0f,
0.45f,0.5f,0.0f
};
//着色器
const char *vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n" "void main()\n"
"{\n"
"gl_Position = vec4(aPos.x, aPos.y, aPos.z,1.0);\n"
"}\n";
const char *fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
"FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n" "}\n\0";
TestOPenGLWidget::TestOPenGLWidget(QWidget *parent)
: QOpenGLWidget(parent)
{
}
void TestOPenGLWidget::initializeGL()
{
//初始化opengl对象
initializeOpenGLFunctions();
//创建VBO和VAO对象
glGenVertexArrays(2,VAOs);
glGenBuffers(2,VBOs);
//绑定第一组VAO,VBO对象
glBindVertexArray(VAOs[0]);
glBindBuffer(GL_ARRAY_BUFFER,VBOs[0]);
//为缓冲对象(VBO,IBO 等)分配空间并存储数据
glBufferData(GL_ARRAY_BUFFER,sizeof(firast_vertices),firast_vertices,GL_STATIC_DRAW);
//设置定点属性
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,3*sizeof(float),(void*)0);
glEnableVertexAttribArray(0);
//glBindBuffer(GL_ARRAY_BUFFER,0);
//绑定第二组VAO,VBO对象
//绑定VAO,VBO对象
glBindVertexArray(VAOs[1]);
glBindBuffer(GL_ARRAY_BUFFER,VBOs[1]);
//为缓冲对象(VBO,IBO 等)分配空间并存储数据
glBufferData(GL_ARRAY_BUFFER,sizeof(second_vertices),second_vertices,GL_STATIC_DRAW);
//设置定点属性
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,3*sizeof(float),(void*)0);
glEnableVertexAttribArray(0);
// glBindBuffer(GL_ARRAY_BUFFER,0);
//glBindVertexArray(0 );
glBindVertexArray(0);
//初始化着色推进器
unsigned int vertexShader=glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader,1,&vertexShaderSource,NULL);
glCompileShader(vertexShader);
int success;
char infoLog[512];
glGetShaderiv(vertexShader,GL_COMPILE_STATUS,&success);
if(!success){
glGetShaderInfoLog(vertexShader,512,NULL,infoLog);
std::cout<<"VertexShader complie erroe"<<std::endl;
std::cout<<infoLog<<std::endl;
}
unsigned int fragmentShader=glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader,1,&fragmentShaderSource,NULL);
glCompileShader(fragmentShader);
glGetShaderiv(fragmentShader,GL_COMPILE_STATUS,&success);
if(!success){
glGetShaderInfoLog(fragmentShader,512,NULL,infoLog);
std::cout<<"fragmentShader complie erroe"<<std::endl;
std::cout<<infoLog<<std::endl;
}
shaderProgram=glCreateProgram();
glAttachShader(shaderProgram,vertexShader);
glAttachShader(shaderProgram,fragmentShader);
glLinkProgram(shaderProgram);
glGetProgramiv(shaderProgram,GL_COMPILE_STATUS,&success);
if(!success){
glGetProgramInfoLog(shaderProgram,512,NULL,infoLog);
std::cout<<"ShaderProgram link erroe"<<std::endl;
std::cout<<infoLog<<std::endl;
}
//临时生成的着色器初始化着色推进器之后需要删除
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
//glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
}
void TestOPenGLWidget::resizeGL(int w, int h)
{
}
void TestOPenGLWidget::paintGL()
{
glClearColor(0.2f,0.3f,0.3f,1.0f);
glClear(GL_COLOR_BUFFER_BIT);
//使用着色推进器
glUseProgram(shaderProgram);
//画第一个三角形
glBindVertexArray(VAOs[0]);
glDrawArrays(GL_TRIANGLES,0,3);
//第二个
glBindVertexArray(VAOs[1]);
glDrawArrays(GL_TRIANGLES,0,3);
}
函数
glBufferData
为缓冲对象(VBO,IBO 等)分配空间并存储数据;
GL_STATIC_DRAW:数据不会或几乎不会改变。
GL_DYNAMIC_DRAW:数据会被改变很多。
GL_STREAM_DRAW :数据每次绘制时都会改变。
VBO,EBO数据初始化的区别--glBufferData
VBO----顶点坐标初始化
EBO-----顶点索引下标初始化
glVertexAttribPointer
配置顶点属性指针
也就是设置VAO中的指针如何管理VBO中的数据。
void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer);
index:一个顶点属性中有多个属性,Index代表一个顶点属性中不同属性的索引;
size:每个属性在数组所占据的元素个数;
stride:一个顶点属性(包括顶点坐标,颜色等)所占据的字节数;
pointer:指向缓冲对象中第一个顶点属性的第一个分量的地址;
float vertices[]={
/*顶点坐标*/0.5f,0.5f,0.0f,/*颜色*/1.0,0.0,0.0,
0.5f,-0.5f,0.0f,0.0,1.0,0.0,
-0.5f,-0.5f,0.0f,0.0,0.0,1.0,
-0.5f,0.5f,0.0f,0.5,0.5,0.5
};
//设置定点第一个属性---定点坐标
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,6*sizeof(float),(void*)0);
glEnableVertexAttribArray(0);
//设置定点第二个熟悉---颜色值
glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,6*sizeof(float),(void*)(3*sizeof(float)));
glEnableVertexAttribArray(1);
glBindBuffer
用于绑定VBO或者EBO缓冲区对象
绑定VBO---glBindBuffer(GL_ARRAY_BUFFER, 1)
解绑VBO---glBindBuffer(GL_ARRAY_BUFFER, 0)
项目中通常在不使用VBO的情况下,绘制之前,执行glBindBuffer(GL_ARRAY_BUFFER, 0),否则会导致数组顶点无效,界面无法显示;
glEnableVertexAttribArray
启用顶点属性
着色器
本质上也是一段程序,完成着色功能,内部由着色代码;
着色推进器的生成过程
定义全局推进器
建立顶点着色器和片段着色器
链接两个着色器生成推进器着色器
删除两个着色器
使用着色推进器
创建顶点着色器
创建片段着色器
链接顶点着色器和片段着色器生产着色推进器
生成多个着色器推进器
只需要根据顶点着色器和片段着色器的异同创建使用相同或者不同的着色器link生成着色推进器即可。
使用不同着色器的区别是什么
getShaderSource
替换着色器对象中的源代码;
第二个参数是1,表示数组只有一个元素。
//初始化着色推进器
unsigned int vertexShader=glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader,1,&vertexShaderSource,NULL);
glCompileShader(vertexShader);
int success;
char infoLog[512];
glGetShaderiv(vertexShader,GL_COMPILE_STATUS,&success);
if(!success){
glGetShaderInfoLog(vertexShader,512,NULL,infoLog);
std::cout<<"VertexShader complie erroe"<<std::endl;
std::cout<<infoLog<<std::endl;
}
unsigned int fragmentShader=glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader,1,&fragmentShaderSource,NULL);
glCompileShader(fragmentShader);
glGetShaderiv(fragmentShader,GL_COMPILE_STATUS,&success);
if(!success){
glGetShaderInfoLog(fragmentShader,512,NULL,infoLog);
std::cout<<"fragmentShader complie erroe"<<std::endl;
std::cout<<infoLog<<std::endl;
}
shaderProgram=glCreateProgram();
glAttachShader(shaderProgram,vertexShader);
glAttachShader(shaderProgram,fragmentShader);
glLinkProgram(shaderProgram);
glGetProgramiv(shaderProgram,GL_COMPILE_STATUS,&success);
if(!success){
glGetProgramInfoLog(shaderProgram,512,NULL,infoLog);
std::cout<<"ShaderProgram link erroe"<<std::endl;
std::cout<<infoLog<<std::endl;
}
//临时生成的着色器初始化着色推进器之后需要删除
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
glUseProgram
使用着色推进器
EBO(IBO)
索引缓存对象
EBO的索引数组要求
必须要头尾相连
EBO便捷式建立
QOpenGLBuffer vbo(QOpenGLBuffer::VertexBuffer);
QOpenGLBuffer ebo(QOpenGLBuffer::IndexBuffer);
makeCurrent
doneCurrent
openGL绘制
openGL绘制本质上都是绘制三角形,任何图形都是由很小的三角形构成。
画矩形的方式
1,画两个三角形的六个坐标;----VBO
2,利用四个点的坐标和索引创建;----EBO
EBO绘制的过程
1,建立坐标和索引数组;
2,建立保存数据的EBO,并且分配内存和初始化;
3,glDrawElement()绘制。
EBO和VBO,VAO创建到XX的过程
VBO和VAO:
1,创建缓冲区对象名
unsigned int VAO,VBO;
2,绑定分配内存
//创建VBO和VAO对象
glGenVertexArrays(1,&VAO);
glGenBuffers(1,&VBO);
//绑定VAO,VBO对象
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER,VBO);
//为缓冲对象(VBO,IBO 等)分配空间并存储数据
glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);
//设置定点属性
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,3*sizeof(float),(void*)0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER_BINDING,0);
glBindVertexArray(0 );
3,
glBindBuffer(GL_ARRAY_BUFFER_BINDING,0);
glBindVertexArray(0 );
EBO:
关于VAO,VBO,EBO的绑定和解绑
EBO,VBO初始化完成之后就可以解绑;
VAO设置完成顶点之后属性也可以解绑,但是VAO绘图之前需要再次绑定,用于绘图;
缓存对象VBO和EBO一定要在VAO解绑之前完成一系列初始化工作,,否则VAO解绑之后的操作就没有用了。
EBO,VBO绑定和解绑的区别
VBO解绑不影响VAO对他已经初始化的数据的的使用;
而EBO解绑会被VAO识别到,并且记录EBO解绑;
如果EBO没有解绑,glDrawElement最后一个参数就不需要指定;如果EBO解绑了,最后一个参数就需要指定为索引数组的地址(数组名);
EBO,VBO,VAO的存在关系
可以没有EBO,但是也没有EBO都需要VAO和VBO
glDrawArray()----获取VBO缓存数据
这个函数是从array buffer,也就是VBO中获取绘制数据。
glDrawElement----获取EBO缓存数据
这个函数是从EBO中获取绘制的数据。
openGL中数据类型对程序的影响
因为VAO顶点属性的规则中记录了顶点的数据类型,系统按照数据类型大小读取字节,如果数据类型出错,数据将读取错误,甚至读不到数据;
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,3*sizeof(float),(void*)0);
绘制图形是需要指定数据的类型的,如果数据类型出错,也会导致程序运行出错;
glpolygonmode
绘制模式
默认是填充的,如果不想要填充,可以自己设置。
polygon---多边形
// 设置正面为填充模式
glPolygonMode(GL_FRONT, GL_FILL);
// 设置反面为线形模式
glPolygonMode(GL_BACK, GL_LINE);
OPenGlShaderProgram
头文件:
#include <QopenGLShaderProgram[>
通过建立对象建立着色推进器
1,使用字符串建立---addShaderFromSourceCode
三步链接:
绑定使用:
2,文件建立---addShaderFromSourceFile
资源文件添加顶点和片元着色器文件
着色推进器对象的使用过程
建立--链接--绑定使用
着色推进器的绑定位置
需要在paintGL中绑定,否则会导致一些绘制不成功。
GLSL
GL----OpenGL
SL-----shader language
顶点数量限制
GLSL语法
向量用来创建向量
输入输出
顶点着色器和片元着色器一个作为输入,一个作为输出
输出--out
输入--in
注意:需要使用相同变量来作为口对口的接受数据。
layout-----location
layout(location = 0)表示该变量(aPos)需要读入的数据在顶点属性中的位置为0.
attributeLocation
着色推进器的attributeLocation()成员函数可以用于返回location配置的顶点属性的编号,用于后面的代码。
bindAttributeLocation
顶点着色器文件中可以不用指定读入的顶点属性的编号;使用shaderProgram的顶点属性位置绑定函数;
GLint vertex_attri_number=2;
shaderProgram.bindAttributeLocation("aPos",vertex_attri_number);
//设置定点属性
glVertexAttribPointer(vertex_attri_number,3,GL_FLOAT,GL_FALSE,3*sizeof(float),(void*)0);
glEnableVertexAttribArray(vertex_attri_number);
顶点着色器中的in变量
获取VAO中顶点属性的输入
顶点着色器中的out变量
将变量的值输出给片元着色器;
片段着色器的in变量
获取顶点着色器的输入或者自己定义的输入数据;
片段着色器的out变量
将设置的变量值数据输出到像素,形成图形以及图形的属性,如颜色。
uniform
shaderProgram.setUniformValue
设置Uniform需要传送给显卡的变量和变量的值
frag文件
#version 330 core
out vec4 FragColor;
uniform vec4 ourColor;
void main()
{
FragColor =ourColor;
}
Opengl文件
TestOPenGLWidget::TestOPenGLWidget(QWidget *parent)
: QOpenGLWidget(parent)
{
timer=new QTimer(this);
timer->start(100);
connect(timer,&QTimer::timeout,this,&TestOPenGLWidget::OnTimerOut);
}
void TestOPenGLWidget::OnTimerOut()
{
makeCurrent();
int timeValue=QTime::currentTime().second();
float greenValue=(sin(timeValue)/2.0f)+0.5f;
shaderProgram.setUniformValue("ourColor",0.0f,greenValue,0.0f,1.0f);
doneCurrent();
update();
}
vert文件
openGL文件
uniform的使用过程
在顶点或者片段着色器中定义uniform变量(顶点坐标属性--vert,颜色属性--frag),在OpenGL文中setUniformValue()给变量赋值即可。
颜色数据加入顶点数据
顶点属性的使用过程
建立顶点属性数组
顶点属性数组初始化VBO或者EBO
VAO设置顶点属性
顶点着色器指定顶点属性读取数据索引
纹理--texture
作用:增添真实颜色
头文件
#include <QOpenGLTexture>
纹理坐标
通过纹理坐标获取像素颜色信息。
让纹理坐标和绘制的顶点关联起来,一 一对应,就可以让图片覆盖顶点的图形。
使用过程
创建纹理对象
m_texture=new QOpenGLTexture(QImage(":/image/tree.png"));//.mirrored());
增加纹理坐标
float vertices[]={
//顶点坐标 颜色 纹理坐标
0.5f,0.5f,0.0f, 1.0,0.0,0.0, 1.0,1.0,
0.5f,-0.5f,0.0f, 0.0,1.0,0.0, 1.0,0.0,
-0.5f,-0.5f,0.0f, 0.0,0.0,1.0, 0.0,0.0,
-0.5f,0.5f,0.0f, 1.0,1.0,0.0, 0.0,1.0
};
设置(增加)VAO纹理属性
//设置顶点第一个属性---定点坐标
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,8*sizeof(float),(void*)0);//从第一个开始,偏移量就是0
glEnableVertexAttribArray(0);
//设置顶点第二个熟悉---颜色值
glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,8*sizeof(float),(void*)(3*sizeof(float)));//从第4个开始,偏移量就是3
glEnableVertexAttribArray(1);
//设置顶点第三个熟悉----纹理坐标
glVertexAttribPointer(2,2,GL_FLOAT,GL_FALSE,8*sizeof(float),(void*)(6*sizeof(float)));//从第7个开始,偏移量就是6
顶点着色器设置纹理变量location
#version 330 core
layout (location = 0)in vec3 aPos;
layout (location = 1)in vec3 aColor;
layout (location = 2)in vec2 aTextureCoor;
out vec3 ourColor;
out vec2 textureCoor;
void main()
{
gl_Position = vec4(aPos.x, -aPos.y, aPos.z,1.0);
ourColor=aColor;
textureCoor=aTextureCoor;
}
片段着色器接收纹理坐标
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
in vec2 textureCoor;
uniform sampler2D texture0;
void main()
{
FragColor =texture(texture0,textureCoor);
}
使用时绑定texture对象
sampler---采样器
smapler2D---2维的采样器
通过纹理坐标获取像素颜色信息的过程称为采样
纹理单元
mix()
第三个参数代表返回混合色中,第二个纹理占多少。
GLSL内建的mix
函数需要接受两个值作为参数,并对它们根据第三个参数进行线性插值。如果第三个值是0.0,它会返回第一个输入;如果是1.0,会返回第二个输入值。0.2会返回80%的第一个输入颜色和20%的第二个输入颜色,即返回两个纹理的混合色。
多个纹理单元怎么使用
建立纹理对象;
设置纹理坐标;
shaderProgram.setUniformValue给着色器中定义的采样器赋值区分采样器;
着色器设置,使用纹理坐标
纹理对象绑定,然会绘制;
纹理对象的激活和绑定
1,bind()通过参数指定激活和绑定的对象;
2,bind不指定激活对象,只绑定,使用glActiveTexture()激活
纹理环绕
当纹理坐标超出默认范围时,可以设置填充多余空间的环绕方式来展示不同的视觉效果输出!默认为GL_REPEAT
,还有GL_MIRRORED_REPEAT
,GL_CLAMP_TO_EDGE
和GL_CLAMP_TO_BORDER
!
glTexParameteri()----设置纹理环绕方式
S---X
T---Y
镜像重复
边缘填充
纹理过滤
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); //缩小
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); //放大
GL_NEAREST(也叫邻近过滤,Nearest Neighbor Filtering)是OpenGL默认的纹理过滤方式。当设置为GL_NEAREST的时候,OpenGL会选择中心点最接近纹理坐标的那个像素。
会有很多像素点:
GL_LINEAR(也叫线性过滤,(Bi)linear Filtering)它会基于纹理坐标附近的纹理像素,计算出一个插值,近似出这些纹理像素之间的颜色。
多级渐远纹理
向量
加法和减法
A±B = (u±x, v±y, w±z)
GLSL: vec3±vec3
归一化
(将长度变为1)
GLSL: normalize(vec3) 或normalize(vec4)
点积
A·B = ux + vy + wz
GLSL: dot(vec3,vec3) 或dot(vec4,vec4)
叉积
A×B = (vz−wy, wx−uz, uy−vx)
构建
GLSL: cross(vec3,vec3)
矩阵
缩放矩阵
缩放矩阵用于改变物体的大小或者将点沿朝向或远离原点的方向移动。
缩放矩阵A在单位矩阵的基础上,将位于A00, A11, A22的值替换为x、y、z缩放因子。
原坐标与移动因子相乘。
构建缩放矩阵的函数
matrix.scale(x,y,x);
位移矩阵(平移矩阵)
平移矩阵A用于将物体从一个位置移至另一位置。、
后面那三个T就决定新的点移动到哪;
原坐标与移动因子相加;
构建平移矩阵的函数
matrix.translate(x,y,z)
旋转矩阵
在3D空间中旋转物体需要指定旋转轴和旋转角(以度或弧度为单位)。
旋转哪个轴是以哪个轴作为定轴旋转,其他两个轴的数据会变化;
旋转的点就是在两个不同的坐标中具有相同坐标值的点
旋转之后的点的坐标在原来的坐标中的坐标的计算公式:
x=cos*这个点的向量长度;
构建旋转矩阵的函数
matrix.rotate(angle,x,y,z)
物体不饶三个轴旋转怎么实现
实践中,当在3D空间中旋转轴不穿过原点时,物体使用欧拉角进行旋转需要几个额外的步骤。一般有:
(1)平移旋转轴以使它经过原点;
(2)绕x轴、y轴、z轴旋转适当的欧拉角;
(3)复原步骤(1)中的平移。
相机--视图矩阵
矩阵的使用
顶点着色器中设置矩阵;
矩阵和顶点坐标进行矩阵变换;
shaderProgram.setUniformValue设置矩阵的数据;
设置矩阵的数据来源;
旋转和移动
一定要先移动再旋转;
因为旋转时图像就记录了旋转中心坐标,如果旋转之后再移动图像;那么图像将绕着原来的旋转中心做像地球的一样的公转,而不是自转;
先操作的在矩阵中写在后面。
QMatrix中的scale()
透视投影
w坐标
顶点相对于摄像机的坐标
深度缓冲---让立方体一个面看不到一个面
如果不添加深度缓冲:就是这样的效果:
glEnable(GL_DEPTH_TEST);
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
OPENGL的数据类型
vec4
用来存储齐次3D坐标的GLSL数据类型是vec4(“vec”代表向量,同时也可以用来表示点)
mat4
GLSL中的mat4数据类型用来存储4×4矩阵
这些特殊的数据类型都是对象,因为其内部具有成员函数。
部分操作
反转图片
1,纹理坐标对应不同的顶点坐标就行。
2,着色器中设置
设置点的大小
glPointSize(30.0f);
片段着色器中怎么获取顶点着色器输入的坐标
预定义变量 gl_FragCoord
#version 430 out vec4 color; void main(void)
{
if (gl_FragCoord.x < 295) color = vec4(1.0, 0.0, 0.0, 1.0);
else color = vec4(0.0, 0.0, 1.0, 1.0);
}
问题
使用glClearColor之后没有窗口显示:
就是你的显卡太老 了,换电脑或者换电脑显卡。
Fragment shader contains a user varying, but is linked without a vertex shader.
数据类型问题
OPengl构造函数初始化问题
属于Opengl的东西,不要尝试像普通widget组件一样在构造函数中初始化,必须在initializeGL中完成OpenGL初始化;
bind()
bind的作用
绑定当前对象,开始使用的数据是当前对象的
着色器对象
opengl窗口大小怎么设置
顶点属性是什么东西
就是顶点着色器中的变量
VBO和顶点属性的绑定关系
绑定VBO之后,接着使用glVertexAttribPointer()指定一个顶点属性的数据,那么这个顶点属性的指针指向的就是之前绑定的这个VBO中发数据。
VAO和顶点属性的关系
当glEnableVertexAttribArray配置顶点属性之前绑定的是哪一个VAO,那么这个VAO就会记录glEnableVertexAttribArray配置的顶点属性的配置,也会是对应着哪个VBO。
glClear和glClearColor的位置
应该要放在paintGL中,否则,因为initializeOpenGLFunctions()只执行一次,如果放在initializeOpenGLFunctions()中,update时就会有多余的图形:
开启深度测试的重要性
不开启就会出现看不见的图片
两种绘图方式
glDrawArrays(GL_TRIANGLE_FAN,0,24);
for (int i = 0; i < 6; ++i)
{
glDrawArrays(GL_TRIANGLE_FAN, i * 4, 4);
}
逆时针坐标一定要用GL_TRIANGLE_FAN
rotate的旋转
绕着三个轴转动的方向为顺时针方向旋转。
void MainWindow::timerEvent(QTimerEvent *e)
{
makeCurrent();
m_camera.update();
if(x_angle>=360) x_angle-=360;
if(x_angle<=0) x_angle+=360;
if(y_angle>=360) y_angle-=360;
if(y_angle<=0) y_angle+=360;
if(z_angle>=360) z_angle-=360;
if(z_angle<=0) z_angle+=360;m_model.setToIdentity();
m_model.rotate(x_angle,1,0,0);
m_model.rotate(y_angle,0,1,0);
m_model.rotate(z_angle,0,0,1);
update();
doneCurrent();
}void MainWindow::keyPressEvent(QKeyEvent *e)
{
if(e->key()==Qt::Key_S) x_angle+=2;
else if(e->key()==Qt::Key_W) x_angle-=2;
else if(e->key()==Qt::Key_D) y_angle+=2;
else if(e->key()==Qt::Key_A) y_angle-=2;
else if(e->key()==Qt::Key_X) z_angle+=2;
else if(e->key()==Qt::Key_Z) z_angle-=2;
}
注意:小于0必须判定处理,否则会出错。
实现不同旋转速度的原理
void MainWindow::timerEvent(QTimerEvent *e)
{
makeCurrent();
m_camera.update();
float _speed = 1;
for (auto w : m_models)
{
float _y = w->Rotate().y() + _speed;
if (_y >= 360)
_y -= 360;
w->SetRotate({ 0, _y, 0 });
++_speed;
}
update();
doneCurrent();
}
第一个第一次是1,speed就++,那么第二个就是speed的值,也就是2,第三个就是3.
光照
法线
法线的方向总设置为顶点坐标值相同的方向:
法线的使用
1,增加顶点对应的法线坐标;
2,顶点着色器中创建法线坐标属性,经过必要的计算和变换输出给片段着色器;
OPengl多个物体成为一个整体的前提条件是什么
makeCurrent()----doneCurrent的重要性
update重新绘制的时候,一定要把所有需要改变和绘制的所有数据放在makeCurrent和doneCurrent之间:
这两个函数的作用是:
加载当前上下文,OPenGL绘制的所有操作必须在有效的当前上下文之间操作。
错误使用
void OpenGLWidget::SetMotorNumbers(const unsigned int n)
{
if(n!=4&&n!=6){
throw std::logic_error("Unable to set "+std::to_string(n)+" motors");
}
motor_number=n;
motors_.clear();
if(motor_number==6){
for (int i = 0; i < motor_number; ++i)
{
auto motor = new MotorSimulator();
motor->Init();
if(i==0) motor->SetPos({-2,0,1});
else if(i==1) motor->SetPos({-2,0,-1});
else if(i==2) motor->SetPos({0,0,1});
else if(i==3) motor->SetPos({0,0,-1});
else if(i==4) motor->SetPos({2,0,1});
else if(i==5) motor->SetPos({2,0,-1});
motors_ << motor;
}
}
else if(motor_number==4){
for (int i = 0; i < motor_number; ++i)
{
auto motor = new MotorSimulator();
motor->Init();
if(i==0) motor->SetPos({-1,0,1});
else if(i==1) motor->SetPos({-1,0,-1});
else if(i==2) motor->SetPos({1,0,1});
else if(i==3) motor->SetPos({1,0,-1});
motors_ << motor;
}
}
makeCurrent();
update();
doneCurrent();
}
正确使用
void OpenGLWidget::SetNumbers(const unsigned int n)
{
makeCurrent();//开始
if(motor_number==6){
for (int i = 0; i < motor_number; ++i)
{
auto motor = new MotorSimulator();
motor->Init();
if(i==0) motor->SetPos({-2,0,1});
else if(i==1) motor->SetPos({-2,0,-1});
else if(i==2) motor->SetPos({0,0,1});
else if(i==3) motor->SetPos({0,0,-1});
else if(i==4) motor->SetPos({2,0,1});
else if(i==5) motor->SetPos({2,0,-1});
motors_ << motor;
}
}
else if(motor_number==4){
for (int i = 0; i < motor_number; ++i)
{
auto motor = new MotorSimulator();
motor->Init();
if(i==0) motor->SetPos({-1,0,1});
else if(i==1) motor->SetPos({-1,0,-1});
else if(i==2) motor->SetPos({1,0,1});
else if(i==3) motor->SetPos({1,0,-1});
motors_ << motor;
}
}
update();
doneCurrent();//结束
}
影响物体转动速度的因素
不同物体:
同一次(相同时间)--转动角度越大的转的越快;
同一个物体:
需要一个频率值,频率值增加--速度增加;
频率值减小-----速度减小;
设计:
在定时器中,每次计算当前时间与开始时间的时间差delta_time,并根据时间差来调整物体的旋转速度。
初始状态下,旋转速度可以设为一个较小的值,例如0.1。
在每次更新旋转角度时,将旋转角度增加旋转速度乘以delta_time的结果,即
rotation += speed * delta_time。这样旋转速度就会随着时间的推移不断增加,使得旋转速度呈现出逐渐加快的效果。
旋转角度=旧旋转角度+速度*速度变化频率;
旋转角度=旧旋转角度+角度增量;
每一次的速度增量不同,速度才不同;
总结:
1,每一次增加的角度不同,转速就不同;
2,想要看到速度变化,角度增量和角度初始值应该尽量小;
物体为何不转的问题:
不转的计算方法:
int position=vel*131072;
int angle=position*360.0/131072;
(angle=vel*360)
这样计算的angle,物体不会转。
增加的转动角度不能是某个数的整数倍
增加的角度不能是某个角度的整数倍,更不能是360度的整数倍;虽然角度一直在增加,但是每一次到达的位置是相同的。
eg:
float y_turn=motor->motor_shaft->Rotate().y() + speed_*300;
motor->motor_shaft->SetRotate({ x_angle, y_turn, z_angle });
std::cout<<y_turn<<std::endl;
speed_++;
看到物体转动的原则
转一圈必须分为多次转动,每一次转动到不同的位置,如果每一次都转动到相同位置,即使转动的总角度在增加,物体转动的位置也不会改变;
角度改变不等于位置改变
物体转动的原理
一段时间转动一定的角度,时间连续起来,转动的角度连续起来,看起来就是连续转动;
两句金句
决定物体转动的是位置不是角度;
决定物体转动速度的是角度增量;
加速:
角度增量越大,转的越快;
角度增量越小,转的越慢;
减速:
角度减量越大,转的越快;
角度减量越小,转的越慢;
总结:角度增量(减量也是增量)越大,转的越快。