理解GL管线(二)着色器(认识顶点着色器和片段着色器)

1.GLSL语言

GLSL是着色器语言的一种,其他的着色器语言如HLSL,微软的3D框架DirectX等。着色器语言程序主要运行在GPU上。

GLSL是与OpenGL兼容的专用着色器语言,因此我们需要用GLSL编写着色器使用的程序代码。并将编写完的代码载入各个着色器阶段。其过程如下:

(1)编写GLSL程序代码(可以写进文件中,也可以硬编码在C++程序的字符串中。)

(2)使用C++获取GLSL程序代码。

(3)创建OpenGL着色器对象,并将GLSL程序代码加载进着色器对象。

(4)用OpenGL命令编译并链接着色器对象,并将其安装进GPU。

实践中,至少要顶点着色器和片段着色器阶段的GLSL代码,而曲面细分着色器和几何着色器阶段是可选的。

2.图元

  OpenGL只允许绘制三类图形:点、线、三角形。它们叫做图元。即便是复杂的3D模型通常也是由许多三角形构成的。

  图元由顶点组成。例如三角形有三个顶点。

  顶点的来源可以是任意的,从文件中读取、硬编码在C++程序中或者直接在GLSL代码中。加载的顶点会被载入管线,经由着色器处理,最终变成屏幕上的各种形状。

  Gl通过【glDrawArrays(GLenum mode,Glint first,Glsizei count)】方法绘制图元,其中mode是图元类型,first是从那个顶点开始绘制(通常是顶点0)。当调用该方法时,管线中的着色器(顶点着色器开始)开始执行。

3.光栅化

  我们的显示器屏幕由光栅(矩形像素阵列)组成。当光栅化后,OpenGL将物体中的图元转化为片段,片段拥有关于像素的信息。光栅化过程确定了所有像素需要绘制的位置,这个过程会进行插值,以便确定两个顶点间的所有像素的位置。通过【glpolygonMode】方法可以设置呈现线框效果还是填充效果。

  光栅化不仅可以对像素插值,任何顶点着色器的输出变量和片段着色器的输入变量都可以基于对应的像素插值。利用该功能可以实现平滑的颜色渐变和真是光照等效果。

4.着色器

  顶点着色器:即处理顶点的着色器,所有顶点被载入管线后都会经由顶点着色器(即会对每个顶点执行一次(通常时并行的)),处理后载入管线的下一步。通过顶点着色器可以改变绘制内容的形状,包括形状大小、位置、角度、投影等等。

  片段着色器:片段着色器负责为光栅化的像素指定颜色,所有顶点光栅化后经由片段着色器都会被赋值颜色,并且也会被插值处理。

代码示例:画一个三角形,并且左边为红色,右边为蓝色。

 

//GLSL程序代码(顶点着色器),写在【vertShrfer.glsl】中
#version 430

uniform float offset;

void main(void)
{

if(gl_VertexID == 0)
gl_Position = vec4(0.25 + offset, -0.25, 0.0, 1.0);
if(gl_VertexID == 1)
gl_Position = vec4(-0.25 + offset, -0.25, 0.0, 1.0);
if(gl_VertexID == 2)
gl_Position = vec4(0.25 + offset, 0.25, 0.0, 1.0);

}

//GLSL程序代码(片段着色器),写在【fragShrfer.glsl】中
#version 430
out vec4 color;
void main(void)
{ if(gl_FragCoord.x < 200)
color = vec4(1.0, 0.0, .0, 1.0);
 else color = vec4(0.0,0.0,1.0,1.0);
}

#include <GL\glew.h>
#include <GLFW\glFW3.h>
#include <string>
#include <iostream>
#include <fstream>

#define numVAOs 1    

GLuint renderingProgram;      
GLuint vao[numVAOs]; //定义voa

float x = 0.0f;//三角形在X轴偏移量
float inc = 0.01f;//三角形的移动步长

//读取文件内容并转成String
string readShaderSource(const char *filePath) {
    string content; 
    ifstream fileStream(filePath, ios::in); 
    string line = ""; 
    while (!fileStream.eof()) { 
        getline(fileStream, line); 
        content.append(line + "\n"); 
    } 
    fileStream.close(); 
    return content;
}
//创建着色器程序
GLuint createShaderProgram() { 
    string vertShaderStr = readShaderSource("vertShader.glsl"); //从文件中读取着色器程序代码字符串
    string fragShaderStr = readShaderSource("fragShader.glsl"); //同上
    
    const char *vertShaderSrc = vertShaderStr.c_str(); 
    const char *fragShaderSrc = fragShaderStr.c_str(); 
    
    GLuint vShader = glCreateShader(GL_VERTEX_SHADER); //创建顶点着色器对象,返回对象ID
    GLuint fShader = glCreateShader(GL_FRAGMENT_SHADER); //创建片段着色器对象,返回对象ID

    glShaderSource(vShader, 1, &vertShaderSrc, NULL); //将GLSL代码载入空着色器对象
    glShaderSource(fShader, 1, &fragShaderSrc, NULL); //同上
    glCompileShader(vShader); //编译着色器
    glCompileShader(fShader); //同上

    GLuint vfProgram = glCreateProgram(); //创建OpenGL程序对象,它包含一系列编译过的着色器对象。
    glAttachShader(vfProgram, vShader); //将着色器对象加入程序对象
    glAttachShader(vfProgram, fShader); //同上
    glLinkProgram(vfProgram); //请求GLSL编译器确保新加入着色器对象的兼容性
    //返回程序对象ID
    return vfProgram;
}

int main(void) {
	//初始化glfw库
	if (!glfwInit())
	{
	 exit(EXIT_FAILURE);
	}
	//设置gl主版本号
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
	//设置gl次版本号,与主版本号一起指定了机器必须与opengl4.3版本兼容
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	//创建glfw窗口,大小1280x640像素,窗口标题(window1)
	GLFWwindow* window = glfwCreateWindow(1280,640,"window1", NULL, NULL);
	//通知GLFW将我们窗口的上下文设置为当前opengl线程的主上下文
	glfwMakeContextCurrent(window);
	//初始化glew库
	if (glewInit() != GLEW_OK)
	{
	  exit(EXIT_FAILURE);
	}
	//设置缓冲区交换间隔(帧)。 默认情况下,交换间隔为0,但因缓冲区
	//有可能在屏幕更新的中间交换,出现屏幕撕裂的情况。
	//所以,可以将该间隔设为1,即每帧更新一次。 它可以设置为更高的
	//值,但这可能导致输入延迟。
	glfwSwapInterval(1);

	init(window);
	//检查GLFW是否被要求退出
	while (!glfwWindowShouldClose(window)) {
	 display(window, glfwGetTime()); //glfwGetTime:获取glfw初始化后经过的时间
	 //交换缓冲区
	 glfwSwapBuffers(window);
	 //glfwPollEvents函数检查有没有触发什么事件(比如键盘输入、鼠标移动等)、更新窗口状态,并调用对应的回调函数(可以通过回调方法手动设置)。
	 glfwPollEvents();
	}
	//glfw销毁窗口
	glfwDestroyWindow(window);
	//glfw终止运行
	glfwTerminate();
	exit(EXIT_SUCCESS);
}

void init(GLFWwindow* window) {
	renderingProgram = createShaderProgram(); //创建OpenGL程序对象,它包含一系列编译过的着色器对象
	glGenVertexArrays(numVAOs, vao); //创建Vao
	glBindVertexArray(vao[0]);//激活VAO

}

void display(GLFWwindow* window, double currentTime) {
	glClear(GL_DEPTH_BUFFER_BIT);//清除深度缓冲区
	glClearColor(0.0,0.0,0.0,0.1);//清除颜色缓冲区
	glClean(GL_COLOR_BUFFER_BIT)//将背景清除为黑色
	glUseProgram(renderingProgram); //将OpenGL程序(包含我们编译进去的两个着色器对象)载入管线阶段(在GPU上);这一步并没有运行着色器,只是将着色器加载进硬件
	
	x += inc;
	if(x > 1.0f) inc = -0.001f;
	if(x < -1.0f) inc = 0.01f;
	GLuint offsetLoc = glGetUniformLocation(renderingProgram,"offset");//获取offset统一变量指针
	glProgramUniform1f(renderingProgram,offsetLoc,x);//给统一变量赋值
	glDrawArrays(GL_TRIANGLES, 0, 3);//启动管线,描绘内容,图元是点。即描画一个点。
}

5.着色器代码解析:

顶点着色器:

  #version 430 //指明GL版本:4.3

  void main(void) //着色器是一个程序,与C++类似,也是从main函数开始

  gl_Position//这是一个预定义的输出变量。用来设置顶点。并发送至下一个管线。

  gl_VertexID//这也是一个预定义变量,会在每次调用时自增。

  gl_FragCoord//这也是一个预定义变量,他可以访问输入片段的坐标

 

  vec4//四元组,这里用来存储x,y,z,和齐次坐标值。

  offset//统一变量,CPU传给GPU的数据

  createShaderProgram//这个方法应该支持重载,以便加载不同的着色器组合。

 

片段着色器:

  out vec4 color; //指定一个输出变量,类型为四元组,名称是color。输出变量会自动输出到管线的下一阶段。

VAO:

  vao是用来管理缓冲区的,所有的缓冲区都会被存入VAO。即便不使用任何缓冲区,OpenGl仍然需要创建至少一个VAO。

posted @ 2021-08-02 01:19  吉尔加斯  阅读(1273)  评论(0编辑  收藏  举报