OpenGL图像渲染管线学习(一)

渲染
把三维场景绘制成二维图片的模型成像过程。
在OpenGL中,任何事物都是在3D空间中,而屏幕和窗口却是2D像素数组。OpenGL的图形渲染管线,通过将3D坐标转成适应屏幕的2D像素来统一进行管理。其主要分为两部分:第一部分把3D坐标转换成2D坐标,第二部分是把2D坐标转变成实际的有颜色的像素。
图形渲染管线可以理解为一条流水线,数据从流水线上各个工位流过进行加工,每个工位都只执行特定的工作,它不关心下一步和上一步做什么,只要接受到上一部传入的数据,处理完后传下去而已。显卡上有成千上万的小处理核心,他们在GPU上都有自己的工位,以便并行快速的处理数据。这些流水线上的工作,可以称为着色器,不同阶段的流水线运行不同的着色器。
可以把渲染管线(流水线)抽象为以下几个阶段。蓝色部分是暴露给我们可以自定义着色器的部分(自己安排)。
图形渲染管线 from LearnOpenGL
CPU将顶点数据传给GPU,进入图形渲染管线的第一部分——顶点着色器,它接收单独的顶点作为输入,同时允许对顶点属性记性一些基本处理。
图元装配会把顶点着色器的输出的所有顶点作为输入,根据渲染类型绘制出图元的形状。(图元是绘制模型的基本组成,包括:点、线、三角形、四边形、多边形)。
图元装配阶段的输出(一系列顶点的集合)会给到几何着色器。几何着色器可以通过产生新顶点构造出新的或其他的图元来生成其他形状。
几何着色器的输出会被传入光栅化阶段,在这里会把图元映射为最终屏幕上对应的像素,生成供片段做色漆使用的片段(Fragment)。并裁切(Clipping)超出视图以外的所有像素。(片段(Fragment)是OpenGL渲染一个像素所需的所有数据)
片段着色器的主要目的是计算一个像素的最终颜色,包含3D场景的数据,比如光照、阴影等,用来计算最终像素的颜色。
最后一个阶段Apha测试和混合(Blending)阶段,检测片段对应的深度,来判断在其他物体前面还是后面,是否需要丢弃。检查alpha值,对物体进行混合。
在大多数情况下,我们只要配置顶点和片段藕色器就行了,几何着色器是可选的,通常使用它默认的着色器就行了。

顶点输入

OpenGL只处理在三个坐标轴上-1.0到1.0范围内的值,叫做“标准化设备坐标”,范围内的值才会现实在屏幕上。所以第一步就是将顶点数据进行标准化到-1.0到1.0之间。(OpenGL使用的是左手坐标系,Z轴的正方向是垂直屏幕向内的)
标准化设备坐标数据会通过CPU发送到显卡内存中(一次发送大量数据,过程较慢),顶点着色器可以快速访问这部分内存得到顶点数据。OpenGL通过顶点缓冲对象(Vertex Buffer Objects,VBO)管理这个内存,这是一个OpenGL对象。

unsigned int VBO;
glGenBuffers(1,&VB0);	//使用此函数和一个缓冲ID生成一个VBO对象
//将新创建的缓冲绑定到GL_ARRAY_BUFFER目标上
glBindBuffer(GL_ARRAY_BUFFER,VBO); 
//把之前定义的顶点数据vertices复制到缓冲内存中,GL_STATIC_DRAW表示数据不会或几乎不会改变
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
顶点着色器(Vertex Shader)–处理顶点数据

顶点着色器是流水线上我们遇到的第一个可编程着色器,而且需要我们至少设置一个顶点着色器。

//一个简单的GLSL着色器代码
#version 330 core	//对应OpenGL3.3,GLSL420对应OpenGL4.2。且使用核心模式
layout (localization = 0) in vec3 aPos;	//in关键字声明aPos是输入的顶点属性。layout (location = 0)设定了变量的位置值从0开始

void main()
{
	gl_Posiion = vec4(aPos.x, aPos.y, aPos.z, 1.0); //gl_Position是预定义变量,vec4类型。是该顶点着色器的输出
}

如何创建编译一个着色器

const char *vertexShaderSource = "#version 330 core\n"
	"layout (localization = 0) in vec3 aPos;\n"
	"void main()\n"
	"{\n"
	"	gl_Posiion = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
	"}\0"; //硬编码,以便在运行时动态编译

unsigned int vertexShader;	//声明一个着色器对象,以ID来引用
vertexShader = glCreateShader(GL_VERTEX_SHADER); //创建一个着色器,着色器类型:GL_VERTEX_SHADER-顶点着色器。

//将(着色器对象,源码字符串数量,源码,NUll)作为参数,绑定源码和着色器对象
glShaderSource(vertexShader, 1, &vertexShaderSource, Null);

glCompileShader(vertexShader);//检测是否编译成功

//检测是否编译错误
int success;		//用来接受是否编译成功,0表示成功
char infoLog[512];	//以防万一用来存储错误消息
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &sucess);//传入着色器对象,检查是否编译成功

if(!success)
{
	glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);	//获取错误消息
	std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}
片段着色器(Fragment Shader)–输出颜色

片段着色器是最后一可编程着色器。其主要是计算像素最后的颜色(RGBA)输出。
一个简单的片段着色器

#version 330 core
out vec4 FragColor;	//使用out关键字声明片段着色器的输出变量,一个4分响亮vec4类型

void main()
{
	FlagColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}

创建并编译一个片段着色器

unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);	//类型变为片段着色器GL_FRAGMENT_SHADER
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);

过程与顶点着色器一致。

着色器程序–链接多个着色器,组成流水线

着色器程序才是流水线上真正操作数据的工具。前面创建的着色器对象需要链接到着色器程序中,才能发挥作用。
链接着色器对象时,程序对象(着色器程序对象)会把每个着色器的输出链接到下个着色器的输入,所以输入和输出必须匹配。

unsigned int shaderProgram;
shaderProgram = glCreateProgram();	//创建一个着色器程序,返回程序对象的ID引用
//把之前创建的着色器对象vertexShader,fragmentShader附加到程序对象上
glAttachShader(shaderProgram,vertexShader);
glAttachShader(shaderProgram,fragmentShader);
glLinkProgram(shaderProgram);//执行glLinkProgram可以将它们链接

//检测是否编译错误
glGetProgramiv(shaderProgram, GL_LINK_STATUS,&success);//换了检测函数,不是glGetShaderiv
if(!success)
{
	glGetProgramInfoLog(shaderProgram,512,NULL,infoLog);
}

glUseProgram(shaderProgram); //激活这个程序对象,以便使用
//激活后可以删除链接的着色器对象,已经不需要了
glDeleteShader(vertexShader)
glDeleteShader(fragmentShader)
链接顶点属性–将顶点数据链接到顶点着色器属性上

copy from LearnOpenGL
图中是顶点缓冲数据的图形解释。数据紧密排列,每个顶点数值由3个4字节的值组成。

//前两个参数是顶点属性的位置和大小,第三四个参数指明参数类型以及是否需要将数据标准化,也就是映射到0-1之间
//第五个参数是属性组之间的间隔,现在是3个float的长度,之后如果增加颜色,就需要额外3个位置
//最后一个参数表示数据在缓冲中起始位置的偏移量
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,3*sizeof(float),(void*)0);
//启用顶点属性
//ps:在顶点输入小章节中,我们创建了顶点缓冲对象,并且绑定了GL_ARRAY_BUFFER,顶点属性0就会链接到这个顶点数据
glEnableVertexAttribArry(0);

以上我们准备好了所有的数据,可以整理一下尝试创建一个三角形了

//复制顶点数组到缓冲中
glBindBuffer(GL_ARRAY_BUFFER,VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//链接顶点属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
//启用着色器程序
glUseProgram(shaderProgram);
//绘制物体
顶点数组对象(VA0)

看上一节的代码,发现其实大部分逻辑都在处理设置顶点属性,而且每绘制一个物体都要重复。
顶点数组对象可以看作对多个顶点缓冲对象的管理。
只要进行一次绑定设置,就可以在不同顶点数据和属性之间切换
并且OpenGL的核心模式也要求我们使用VAO,能更方便的处理顶点输入
VAO copy from LearnOpenGL
创建 使用 一个顶点数组对象(VAO)

//创建一个VAO对象
unsigned int VAO;
glGenVertexArrays(1, &VAO);
//绑定VAO
glBindVerqtexArray(VAO);
//把顶点数组复制到缓冲中
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//链接顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
//--------如果物体不改变,以上初始化代码只运行一次

//--------以下绘制代码可在渲染中循环调用
//绘制
glUseProgram(shaderProgram);
glBindVertexArray(VAO);	//绑定需要的vao
//第一个参数是图元类型,第二个参数是顶点数组的起始索引,第三个参数是绘制顶点的数量
glDrawArrays(GL_TRIANGLES, 0, 3);
元素缓冲对象(EBO)

一个三角形有三个顶点,每个顶点都是一个三行一列的矩阵。
一个四边形包含两个三角形,有六个顶点,其中有两个顶点是完全一致的,这就会造成大量的内存浪费。
我们可以只记录四个顶点,再通过制定绘制顺序,就可以完成绘制。元素缓冲对象就提供了这样的功能。

原始数组

float vertices[] = {
    // 第一个三角形
    0.5f, 0.5f, 0.0f,   // 右上角
    0.5f, -0.5f, 0.0f,  // 右下角
    -0.5f, 0.5f, 0.0f,  // 左上角
    // 第二个三角形
    0.5f, -0.5f, 0.0f,  // 右下角
    -0.5f, -0.5f, 0.0f, // 左下角
    -0.5f, 0.5f, 0.0f   // 左上角
};

元素缓冲对象(EBO)

float vertices[] = {
    0.5f, 0.5f, 0.0f,   // 右上角
    0.5f, -0.5f, 0.0f,  // 右下角
    -0.5f, -0.5f, 0.0f, // 左下角
    -0.5f, 0.5f, 0.0f   // 左上角
};

unsigned int indices[] = {
    // 注意索引从0开始! 
    // 此例的索引(0,1,2,3)就是顶点数组vertices的下标,
    // 这样可以由下标代表顶点组合成矩形

    0, 1, 3, // 第一个三角形
    1, 2, 3  // 第二个三角形
};

那么如何创建使用,我们直接看完整的绘制代码

//创建VAO对象
unsigned int VAO;
glGenVertexArrays(1, &VAO);
glBindVerqtexArray(VAO);
//创建EBO对象
unsigned int EBO;
glGenBuffers(1, &EBO);
//把顶点数组复制到VBO中
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//同样把绑定EBO,然后把绘制三角形的索引复制到缓冲里
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
//链接顶点数据
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
//---------以上代码初始化一次

//---------以下代码再渲染中循环调用
//绘制
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);//调用的DrawElements
glBindVertexArray(0);

基础的三角形已经绘制完了,下面会进入其他基础章节。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值