着色器
opengl是区别于过去的固定管线渲染,是可编程管线。那么我们需要自己去实现代码,告诉CPU和GPU如何渲染。也有一部分内容,我们没有写的时候,opengl也有保留默认的设置。
可编程管线流程:
顶点处理器:
每个顶点都需要经过一次,它从缓存区读出点的数据,进行矩阵变换位置,计算光照公式生成逐顶点颜⾊,⽣成/变换纹理坐标,同时将一些顶点的信息传递给下一个着色器。
几何处理器:
通过顶点着色器生成的点的位置之外,它也可以获取到点的属性之外的一些信息,点之间的相邻关系。于是,生成几何图元,例如三角形或者四边形。
裁剪:
变换到屏幕坐标系。
片段处理器:
每个裁剪后的点,在经历光栅化阶段的时候,通过片段处理器为图元中的每一个像素上色。
创建着色器:
创建着色器程序:
GLuint ShaderProgram = glCreateProgram();
我们还需要创建着色器对象,链接到着色器程序上。就是说着色器程序可以看成上面着色的流程。那么顶点处理器和几何处理器等等都是被包含在其中的步骤。
创建着色器对象:
GLuint ShaderObj = glCreateShader(ShaderType);
那么到底什么是着色器呢,着色器就是一段代码,传送到GPU中告诉它如何计算。所以着色器是一段代码,opengl中认为这段代码可以用字符串传入,并且好几段代码,也可以传入多个字符串,放在数组中。所以着色器的内容,要有一个字符串的数组,不是一个字符串。同时还需要每个字符串的长度的数组,显然,这两个数组是等长的。
const GLchar* p[1];//字符串数组,这里是一个,可以是多个
p[0] = pShaderText;//第一个字符串传入
GLint Lengths[1];//定义一个字符串长度的数组
Lengths[0]= strlen(pShaderText);//因为只有一个字符串,所以只记录了一个长度
glShaderSource(ShaderObj, 1, p, Lengths);//将着色器对象和代码链接起来
编译着色器对象
glCompileShader(ShaderObj);
检查链接状态
GLint success;
glGetShaderiv(ShaderObj, GL_COMPILE_STATUS, &success);
if (!success) {
GLchar InfoLog[1024];
glGetShaderInfoLog(ShaderObj, sizeof(InfoLog), NULL, InfoLog);
fprintf(stderr, "Error compiling shader type %d: '%s'\n", ShaderType, InfoLog);
}
GL_COMPILE_STATUS,对应的是前面的glCompileShader。如果不成功,输出着色器对象,以及日志的一些信息。
接下来就是我们之前提过的,着色器程序要包含着色器对象,现在着色器对象创建好了,代码也填充好了,需要装入到程序当中了。
glAttachShader(ShaderProgram, ShaderObj);
当装入好了所有着色器对象就可以连接起来
glLinkProgram(ShaderProgram);
检查着色器程序的连接情况
glGetProgramiv(ShaderProgram, GL_LINK_STATUS, &Success);
if (Success == 0) {
glGetProgramInfoLog(ShaderProgram, sizeof(ErrorLog), NULL, ErrorLog);
fprintf(stderr, "Error linking shader program: '%s'\n", ErrorLog);
}
验证管线是否可以被执行
glValidateProgram(ShaderProgram);
我们定义好了管线,也就是着色器程序,我们还要声明,我们使用的就是这个程序。
glUseProgram(ShaderProgram);
当我们定义好了管线以后,还没有讲着色器shader传入的代码。
#version 330 //告诉编译器我们的目标GLSL编译器版本是3.3
layout (location = 0) in vec3 Position; // 绑定定点属性名和属性,方式二缓冲属性和shader属性对应映射
void main()
{
gl_Position = vec4(0.5 * Position.x, 0.5 * Position.y, Position.z, 1.0); // 为glVertexAttributePointer提供返回值
}
如上,是定义的顶点着色器
layout (location = 0) in vec3 Position;
这个代码的意思是,我们之前定义缓冲区的时候,应该有这样的操作。
glVertexAttribPointer(0,3, GL_FLOAT, GL_FALSE, 0, 0);
我们顶点的属性,可能包含一些位置,纹理,和发现等。那么我们通过这个语句声明,我们每个顶点的属性索引,如例子是0,也就是说,我们定义了第一个索引位置,它的维度是3个,类型是gl_float等等。
当我们再使用layout,去找索引,并且类型要一至,三维,并定义一个形参,再main函数中使用。
每一个着色器阶段都只有一个main作为入口。
gl_position是一个内置变量,之后的裁剪空间会默认为gl_position就是在顶点处理阶段获得的位置。
除此之外,还有in类型以及out类型,定义什么是输入,什么是输出,在下一个阶段,会使用相同名称的变量。
找到一篇知乎上的Demo代码,一定要看懂:
#include <stdio.h>
#include <string.h>
#include <GL/glew.h>
#include <GL/freeglut.h>
#include "ogldev_util.h" //这里要添加作者的工具类用于读取文本文件
#include "ogldev_math_3d.h"
GLuint VBO;
// 定义要读取的顶点着色器脚本和片断着色器脚本的文件名,作为文件读取路径(这样的话shader.vs和shader.fs文件要放到工程的根目录下,保证下面定义的是这两个文件的文件路径)
const char* pVSFileName = "shader.vs";
const char* pFSFileName = "shader.fs";
static void RenderSceneCB()
{
glClear(GL_COLOR_BUFFER_BIT);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
// 依然还是绘制一个三角形
glDrawArrays(GL_TRIANGLES, 0, 3);
glDisableVertexAttribArray(0);
glutSwapBuffers();
}
static void InitializeGlutCallbacks()
{
glutDisplayFunc(RenderSceneCB);
}
static void CreateVertexBuffer()
{
Vector3f Vertices[3];
Vertices[0] = Vector3f(-1.0f, -1.0f, 0.0f);
Vertices[1] = Vector3f(1.0f, -1.0f, 0.0f);
Vertices[2] = Vector3f(0.0f, 1.0f, 0.0f);
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), Vertices, GL_STATIC_DRAW);
}
// 使用shader文本编译shader对象,并绑定shader都想到着色器程序中
static void AddShader(GLuint ShaderProgram, const char* pShaderText, GLenum ShaderType)
{
// 根据shader类型参数定义两个shader对象
GLuint ShaderObj = glCreateShader(ShaderType);
// 检查是否定义成功
if (ShaderObj == 0) {
fprintf(stderr, "Error creating shader type %d\n", ShaderType);
exit(0);
}
// 定义shader的代码源
const GLchar* p[1];
p[0] = pShaderText;
GLint Lengths[1];
Lengths[0]= strlen(pShaderText);
glShaderSource(ShaderObj, 1, p, Lengths);
glCompileShader(ShaderObj);// 编译shader对象
// 检查和shader相关的错误
GLint success;
glGetShaderiv(ShaderObj, GL_COMPILE_STATUS, &success);
if (!success) {
GLchar InfoLog[1024];
glGetShaderInfoLog(ShaderObj, 1024, NULL, InfoLog);
fprintf(stderr, "Error compiling shader type %d: '%s'\n", ShaderType, InfoLog);
exit(1);
}
// 将编译好的shader对象绑定到program object程序对象上
glAttachShader(ShaderProgram, ShaderObj);
}
// 编译着色器函数
static void CompileShaders()
{
// 创建着色器程序
GLuint ShaderProgram = glCreateProgram();
// 检查是否创建成功
if (ShaderProgram == 0) {
fprintf(stderr, "Error creating shader program\n");
exit(1);
}
// 存储着色器文本的字符串缓冲
string vs, fs;
// 分别读取着色器文件中的文本到字符串缓冲区
if (!ReadFile(pVSFileName, vs)) {
exit(1);
};
if (!ReadFile(pFSFileName, fs)) {
exit(1);
};
// 添加顶点着色器和片段着色器
AddShader(ShaderProgram, vs.c_str(), GL_VERTEX_SHADER);
AddShader(ShaderProgram, fs.c_str(), GL_FRAGMENT_SHADER);
// 链接shader着色器程序,并检查程序相关错误
GLint Success = 0;
GLchar ErrorLog[1024] = { 0 };
glLinkProgram(ShaderProgram);
glGetProgramiv(ShaderProgram, GL_LINK_STATUS, &Success);
if (Success == 0) {
glGetProgramInfoLog(ShaderProgram, sizeof(ErrorLog), NULL, ErrorLog);
fprintf(stderr, "Error linking shader program: '%s'\n", ErrorLog);
exit(1);
}
// 检查验证在当前的管线状态程序是否可以被执行
glValidateProgram(ShaderProgram);
glGetProgramiv(ShaderProgram, GL_VALIDATE_STATUS, &Success);
if (!Success) {
glGetProgramInfoLog(ShaderProgram, sizeof(ErrorLog), NULL, ErrorLog);
fprintf(stderr, "Invalid shader program: '%s'\n", ErrorLog);
exit(1);
}
// 设置到管线声明中来使用上面成功建立的shader程序
glUseProgram(ShaderProgram);
}
// 主函数
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGB);
glutInitWindowSize(1024, 768);
glutInitWindowPosition(100, 100);
glutCreateWindow("Tutorial 04");
InitializeGlutCallbacks();
// 必须在glut初始化后!
GLenum res = glewInit();
if (res != GLEW_OK) {
fprintf(stderr, "Error: '%s'\n", glewGetErrorString(res));
return 1;
}
printf("GL version: %s\n", glGetString(GL_VERSION));
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
CreateVertexBuffer();
// 编译着色器
CompileShaders();
glutMainLoop();
return 0;
}
shader的代码需要额外编写。
本文详细介绍了OpenGL中的可编程管线,强调了着色器在渲染过程中的作用。从顶点处理器到片段处理器,解释了每个阶段的功能。通过代码示例展示了如何创建、编译和链接着色器对象,并将其应用于渲染管线。着重阐述了顶点着色器和片段着色器的代码结构及其在图形渲染中的重要性。
6611

被折叠的 条评论
为什么被折叠?



