OpenGL--使用Shader

2023-05-16

创建Shader

关于在OpenGL中怎么创建Shader这个在很早我博客中就有过详细介绍了。这里全当复习,温故而知新~
在OpenGL中,存在Program和Shader两个概念,Program相当于当前渲染管线所使用的程序,是Shader的容器,可以挂载多个Shader。而每个Shader相当于一个C模块,首先需要对Shader脚本进行编译,然后讲编译好的Shader挂载到Program上,在OpenGL的渲染中使用Program来使Shader生效,整个流程如下图所示:
这里写图片描述
整个流程其实就是创建Shader和创建Program两个子流程。创建Shader的流程如下:

  1. 调用glCreateShader()创建一个Shader对象。
  2. 调用glShaderSource()创建加载Shader脚本源码。
  3. 调用glCompileShader()编译Shader脚本。

然后创建好的Shader需要被挂载到Program中,创建Program的流程如下:

  1. 调用glCreateProgram()创建一个Program对象。
  2. 调用glAttachShader()将创建好的Shader进行挂载。
  3. 调用glLinkProgram()执行链接操作。
  4. 最后在需要使用Shader时,调用glUseerProgram()应用当前Shader。

整个流程中的编译、链接其实和我们的c/c++的编译、链接一样,先编译成.o/.dll库,然后把这些库文件链接成一个可执行的程序。
我们可以创建多个Program,但同时只可以激活一个Program。创建完Program之后,只需要在调用OpenGL绘制方法前使用glUseProgram即可应用当前的Shader。如果激活的Program没有挂载片段Shader,那么片段Shader执行的结果就是为定义的。如果激活一个不存在的Shader,那么所有Shader的执行结果都是未定义的。

下面来简单介绍下流程相关的API:

//创建一个Shader对象,并返回引用该对象的句柄--一个非0整数
//参数shaderType 为Shader类型,一般是GL_VERTEX_SHADER、GL_FRAGMENT_SHADER、GL_GEOMETRY_SHADER
GLuint glCreateShader(GLenum shaderType);

//加载源码到Shader中,这个操作会将Shader脚本代码复制到Shader对象中,多次调用会覆盖上次脚本
//参数shader是创建时返回的shader对象句柄
//参数count是string数组的长度
//参数string是shader的源码,一个字符串数组
//参数length是一个int数组,对应string参数这个字符串数组中每个字符串的长度,当这些字符串都是以‘/0‘结尾时,可以将这个参数设为NULL
void glShaderSource(GLuint shader, int count, const char **string, int *length);

//编译存储于Shader中的代码,参数shader表示对象句柄
void glCompileShader(GLuint shader);


----------


//创建一个Program对象,同样返回引用它的对象句柄--一个非0整数
GLuint glCreateProgram();

//将一个已经编译好的Shader挂载到Program中
//参数program和shader分别表示创建时它两对象返回的的句柄
//一个Shader可以同时被挂载到多个Program中,但同一种类型的Shader,Program只能挂载一个
void glAttachShader(GLuint program, GLuint shader);

//对指定的Program对象执行链接操作,Program在链接成功后才可以执行
//链接操作会将Program中的所有Uniform变量初始化为0
void glLinkProgram(GLuint program);

//激活指定的Program,接下来的绘制会使用指定的Program进行渲染
void glUseProgram(GLuint program);

最后演示下上面讲的接口的使用事例,useShader()函数接收两个参数,分别是顶点着色器和片段着色器的源码。

void useShader(const char* vs, const char* fs)
{
    int v = glCreateShader(GL_VERTEX_SHADER);
    int f = glCreateShader(GL_FRAGMENT_SHADER);

    glShaderSource(v, 1, &vs, NULL);
    glShaderSource(f, 1, &fs, NULL);

    glCompileShader(v);
    glCompileShader(f);

    int p = glCreateProgram();

    glAttachShader(p, v);
    glAttachShader(p, f);

    glLinkProgram(p);
    glUseProgram(p);
}

属性变量

在Shader中,属性变量和统一变量由应用程序设置,Attribute属性变量用于传递顶点信息,而Uniform统一变量则用于传递用户自定义的变量。这两种变量在Shader中会被定义为全局变量,在OpenGL中要设置这两种变量,就需要先获取它们的地址,然后调用OpenGL相关的设置接口为它们赋值。
1.设置属性变量的接口
属性变量包含的是顶点数据,因此在片段着色器中不能直接使用。而且在顶点着色器中它是只读的。要使用它首先得获取地址,这个信息只有在Program链接之后才可以获取。(某些驱动程序,在获取地址之前还必须调用glUseProgram()方法激活Program)
下面是获取属性变量地址的接口:

//参数program为要操作的Program对象句柄
//参数name为要获取的属性变量名
GLint glGetAttribLocation(GLuint program, char *name);

获取到位置后,然后就可以使用glVertexAttribxx系列方法来为这个属性赋值了。

2.设置属性变量的时机
先假设有这样一个属性变量,获取它的位置如下:

GLint local = glGetAttribLocation(program, "myattribute");

设置属性变量是在渲染时为其赋值的,OpenGL渲染时赋值有两种形式,一种是在glBegin()和glEnd()中间,在使用glVertex系列函数生成顶点前。先调用glVertexAttrib系列函数进行赋值,接下来生成的顶点会绑定前面设置的属性变量。

glBegin(GL_TRIANGLE_STRIP);
    glVertexAttrib1f(local, 1.0f);
    glVertex2f(0.0f, 0.0f);
    glVertexAttrib1f(local, 2.0f);
    glVertex2f(0.0f, 1.0f);
    glVertexAttrib1f(local, 3.0f);
    glVertex2f(1.0f, 1.0f);
    glVertexAttrib1f(local, 4.0f);
    glVertex2f(1.0f, 0.0f);
glEnd();

第二种情况是使用顶点数组渲染时,这个得先激活属性变量数组的功能,

void glEnableVertexAttribArray(GLint local);

开启这个功能后,需要调用glVertexAttribPointer()方法,将属性变量的值批量传入,属性变量的数组和顶点数组是一一对应的。

//参数local,属性变量的位置
//参数size,属性变量的分量数量,必须为1~4,如1为float、2~3为vec2~3
//参数type,属性类型,如GL_FLOAT
//参数normalized,是否对传入的值执行一次归一化操作
//参数stride,顶点数组中,两个顶点之间的步幅,0表连续的顶点
//参数pointer,属性变量列表指针,与顶点数组中的顶点一一对应
void glVertexAttribPointer(GLint local, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer);

下面是使用顶点数组进行渲染时,为顶点数组中的每一个顶点绑定属性变量的事例:

//定义4个顶点
float vertices[8] = {
    0.0f, 0.0f,
    0.0f, 1.0f,
    1.0f, 1.0f,
    1.0f, 0.0f
};
float myattributes[4] = {1.0f, 2.0f, 3.0f, 4.0f};

//获取一个已经成功链接的Program中的myattribute属性变量
GLint local = glGetAttribLocation(p, "myattribute");

//使用顶点数组
glEnableClientState(GL_VERTEX_ARRAY);
//启用顶点属性变量数组
glEnableVertexAttribArray(local);
//设置顶点数组
glVertexPointer(2, GL_FLOAT, 0, vertices);
//设置顶点属性数组
glVertexAttribPointer(local, 1, GL_FLOAT, GL_FALSE, 0, myattributes);

统一变量

属性变量相当于每个顶点的私有只读变量,而Uniform统一变量则相当于整个Program的全局只读变量。统一变量和属性变量一样,都是先获取变量的位置,然后调用相关的接口进行设置的。不过统一变量在绘制时不能修改,所以必须在绘制前设置它的值。
至于设置接口和属性变量一致,只是将方法名中的Attrib或VertexAttrib替换成Uniform。统一变量的设置比属性变量要轻松得多,因为不需要想办法绑定到每个顶点上,只需要在渲染之前进行设置就可以了。当然它数据类型可以是纹理或者矩阵类型,这些使用时自行find~这里就不再累赘呢。

错误处理

Shader在编译或链接的时候一般比较容易出现错误,而编译和链接方法的返回值都是void,那么当它们出现错误时怎么知道呢?这就需要使用glGetShaderiv()和glGetProgramiv()这两个函数,它们接口原型一致,都是传入指定的对象,以及要获取的状态枚举,并传入一个GLint指针来接收状态的值。

//查询GL_COMPILE_STATUS可以得到编译的结果,GL_TRUE表示成功,GL_FALSE表示失败
void glGetShaderiv(GLuint shader, GLenum pname, GLint *params);

//查询GL_LINK_STATUS可以得到链接的结果,GL_TRUE表示成功,GL_FALSE表示失败
void glGetProgramiv(GLuint program, GLenum pname, GLint *params);

如果发生错误,错误日志会被保存到InfoLog中,可以调用glGetShaderInfoLog()和glGetProgramInfoLog()方法从中查询错误相关信息。这两个方法原型也一致,第一个参数为Shader或Program的句柄,maxLength参数表示infoLog缓冲区的长度,length 参数指针会输出实际复制到infoLog中的字节数,infoLog参数为用于接收日志信息字符串。

void glGetShaderInfoLog(GLuint shader, GLsizei maxLength, GLsizei *length, GLchar *infoLog);

void glGetProgramInfoLog(GLuint program, GLsizei maxLength, GLsizei *length, GLchar *infoLog);

那么InfoLog日志长度是多少呢?使用glGetShaderiv()和glGetProgramiv()这两个函数,传入GL_INFO_LOG_LENGTH类型,可以获取日志的长度。

下面我们来实际看个事例,它获取Shader日志并将日志用printf()打印出来。

void printShaderLog(GLuint shader)
{
    GLint shaderState;
    glGetShaderiv(shader, GL_COMPILE_STATES, &shaderState);
    if(shaderState == GL_TRUE)
    {
        return;
    }
    GLsizei bufferSize = 0;
    glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &bufferSize);
    if(bufferSize > 0)
    {
        GLchae* buffer = new char[bufferSize];
        glGetShaderInfoLog(shader, bufferSize, NULL, buffer);
        printf("%s", buffer);
        delete[] buffer;
    }
}

清理工作

在调用glCreateShader()和glCreateProgram()方法后,不需要使用的时候,需要使用glDeleteShader()和glDeleteProgram()进行释放。
当一个Shader被挂载到Program中时,glDeleteShader()是无法释放这个Shader的,只会将这个Shader标记为以删除,还需要调用glDetachShader()将Shader从Program中卸除。

void glDetachShader(GLuint program, GLuint shader);
void glDeleteShader(GLuint shader);
void glDeleteProgram(GLuint program);

同样当一个Program在被使用时,glDeleteProgram()方法是无法释放这个Program的,只是将这个Program标记为已删除,当Program不再被使用时,Program才会被释放。当Program真正被释放时,所有挂载在它上面的Shader都会被自动卸载。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

OpenGL--使用Shader 的相关文章

  • 尝试 glUseProgram 时出现 Opengl 错误 1281

    我有什么想法可以调试这个opengl进一步错误 1281 我正在从文件加载源代码 编译 链接 然后尝试检查错误glUseProgram 在我的对象的绘制方法中 log info gl2 glIsProgram shaderProgram t
  • 使用帧缓冲区将深度缓冲区渲染为纹理

    我正在使用 JOGL 但这个问题通常适用于 OpenGL 似乎也有类似的问题 但它们要么是针对 GLSL 代码 与复制帧缓冲区的内容有关 要么是一般建议 使用帧缓冲区对象而不是glCopyTexSubImage2D Question 我正在
  • 从活动顶点数组生成平滑法线

    我正在尝试通过挂钩 OpenGl 调用来破解和修改旧版 opengl 固定管道游戏的多个渲染功能 而我当前的任务是实现着色器照明 我已经创建了一个适当的着色器程序 可以正确照亮大部分对象 但该游戏的地形是在没有提供正常数据的情况下绘制的 游
  • 3D 图形矩阵 4x4 中最后一行的 magic 4 的用途是什么?

    当我阅读有关WebGL的书时 我看到了下一个矩阵描述 有关于书中最后一行的信息 WebGL 初学者指南 初学者指南 Diego Cantor Brandon Jones 神秘的第四排 第四排没有任何特殊之处 意义 元素 m4 m8 m12
  • 如何使着色器淡入某种颜色?

    这是我当前使用的着色器 它通过缓慢降低不透明度来淡化对象 我想褪成紫色 如何才能做到这一点 着色器 frag uniform sampler2D texture uniform float opacity void main vec4 pi
  • libgdx 中帧缓冲区的结果不明确

    我得到以下奇怪的结果帧缓冲区 http libgdx badlogicgames com nightlies docs api com badlogic gdx graphics glutils FrameBuffer htmllibgdx
  • OpenGL NURBS 曲面

    我正在学习 OpenGL 我想要一个中间有轻微驼峰的表面 我目前正在使用这段代码 但我不确定如何调整 ctrl 点以使其达到我想要的方式 它目前就像 我想要这样的 我不完全确定我应该使用哪些控制点 并且我对其工作原理感到困惑 include
  • OpenGL Z 偏置(多边形偏移)限制

    我有两个共面的多边形 我尝试做 glEnable GL POLYGON OFFSET FILL glPolygonOffset 0 1 并期望其中一个明显 位于 另一个之上 这种情况直到大约 70 75 个单位之外 近剪裁平面为 1 远剪裁
  • 没有着色器的 OpenGL

    我已经阅读了一些教程来编写以下代码 唯一的区别是原始教程使用 SDL 而不是 GLEW 我不明白这段代码有什么问题 它可以编译 但我没有看到三角形 教程也没有使用着色器 include
  • 使用 openGL、SOIL 加载图像

    我尝试了很多使用 SOIL 在 openGL 中加载和显示图像的示例 运行下面的源代码时 它仅显示一个没有图像的白色四边形 我尝试打开一个名为 foto 的图像 我将图像文件放在程序的文件夹中 bool keyStates new bool
  • DirectX 世界视图矩阵乘法 - GPU 或 CPU 的地方

    我是 directx 的新手 但令我惊讶的是 我看到的大多数示例中 世界矩阵和视图矩阵都是作为顶点着色器的一部分相乘 而不是与 CPU 相乘并将结果传递给着色器 对于刚性对象 这意味着您为对象的每个顶点将相同的两个矩阵相乘一次 我知道 GP
  • 纹理openGl。 C++、qt

    我试图用草纹理覆盖我的地形 由高度图制成 但它没有按预期工作 我什至无法在简单的 GL QUAD 上获取纹理 结果是多色网络 void GLWidget initializeGL glEnable GL TEXTURE 2D 在 QGLwi
  • (定义一个宏)方便OpenGL命令调试?

    有时插入条件打印和检查需要很长时间glGetError 使用二分搜索的形式来缩小范围 其中第一个函数调用是 OpenGL 首先报告错误 我认为如果有一种方法可以构建一个宏 我可以包装所有可能失败的 GL 调用 并有条件地调用 那就太酷了gl
  • QOpenGLFunctions 缺少重要的 OpenGL 函数

    QOpenGLFunctions 似乎缺少重要的函数 例如 glInvalidateFramebuffer 和 glMapBuffer 据我了解 QOpenGLFunctions 加载桌面 OpenGL 函数和 ES 函数的交集 如果是这样
  • OpenGL z轴指向哪里?

    我正在尝试了解 OpenGL 坐标系 我到处都看到它被描述为右撇子 但这与我的经验不符 我尝试绘制一些形状和 3 d 对象 我发现 z 轴显然指向 屏幕 而 x 指向右侧 y 指向上方 这是左手坐标系的描述 我缺少什么 编辑 例如 http
  • gluPerspective 与 gluOrtho2D

    我查看了 MSDN 上关于这两个函数的文档 但是 我不太明白这两个功能之间的区别 一个是用于设置 3D 相机视图 另一个是用于设置 2D 相机视图 如果能得到解答就太好了 预先感谢您的评论 正交投影基本上是没有透视的 3D 投影 本质上 这
  • glEnableVertexAttribArray 中“index”参数的含义以及(可能)OS X OpenGL 实现中的错误

    1 我是否正确理解 要使用顶点数组或VBO进行绘制 我需要所有属性在着色器程序链接之前调用glBindAttribLocation 或者在着色器程序成功链接后调用glGetAttribLocation 然后使用glVertexAttribP
  • lwjgl 3 , glUniformMatrix4 导致 jre 崩溃

    我正在使用 lwjgl 3 并学习现代 opengl 3 我想将统一矩阵发送到顶点着色器 以便我可以应用转换 我尝试过 但程序因此错误而崩溃 A fatal error has been detected by the Java Runti
  • LibGDX - 着色器适用于桌面但不适用于 Android

    我编写了一个简单的程序 可以在 3D 环境中渲染球体 并根据球体周围的四个光源为其着色 当我在桌面上运行该程序时 它工作得很好 但在 Android 设备上 球体只是纯色的 下面是一些图片来说明我正在谈论的内容 gt Desktop gt
  • OpenGL 与 Eclipse CDT + MinGW + GLEW + GLFW:未定义的参考

    Edit 与此同时 我已经弄清楚了这一点 并在下面写了详细的答案 我刚刚尝试在 Win7 上从 Express 版本的 MSVC 10 切换到 Eclipse CDT 在配置时遇到了以下简单 OpenGL 代码的问题 在 Visual St

随机推荐