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 的相关文章

  • 渲染具有透明度的纹理时,OpenGL 不需要的像素

    我已经为这个问题苦苦挣扎了一段时间了 当我使用 OpenGL 渲染 2D 纹理 在无透明度和部分透明度之间的过渡上具有透明度值 时 我得到了一些烦人的灰色像素 我认为这是像素值插值的产物 关于如何改进这一点有什么想法吗 I m attach
  • OpenGL什么时候完成函数中指针的处理?

    OpenGL有多项功能 http www opengl org wiki GLAPI glTexSubImage2D直接获取指针 他们中有一些从这些指针读取数据 http www opengl org wiki GLAPI glBuffer
  • OpenGL - 自动生成 glDrawArrays 的索引/步幅参数

    我正在渲染一个包含大量数据点 gt 1M 的网格结构 我的数据结构如图所示 所以我的索引缓冲区的内容看起来像这样0 100 1 101 2 102 3 103 我对索引缓冲区的巨大尺寸有点恼火 我需要它来定义我的三角形带 是否有可能告诉 O
  • 在 OpenGL 中实例化数百万个对象:提高每秒帧数

    我的最终目标是以 60 fps 渲染 100 万个不同尺寸和颜色的球体 我也希望能够在屏幕上移动相机 我已经修改了代码我正在学习的教程的这一页 http learnopengl com Advanced OpenGL Instancing尝
  • matplotlib:渲染到缓冲区/访问像素数据

    我想使用 matplotlib 生成的图作为 OpenGL 中的纹理 到目前为止 我遇到的 matplotlib 的 OpenGL 后端要么不成熟 要么已经停止使用 所以我想避免使用它们 我当前的方法是将图形保存到临时 png 文件中 并从
  • 与整数纹理进行 Alpha 混合以进行对象拾取

    问题描述 你好 在我们的 WebGL 应用程序中 我们正在绘制许多 甚至数十万 形状 并且我们想要发现当前鼠标位于哪个形状 我正在寻找一种有效的方法 Details 形状定义为有符号距离函数 https en wikipedia org w
  • WGL:没有双缓冲 + 多重采样 = 失败?

    我通常使用创建像素格式wglChoosePixelFormatARB 与这些论点 除其他外 WGL DOUBLE BUFFER ARB GL TRUE WGL SAMPLE BUFFERS ARB GL TRUE WGL SAMPLES A
  • 带有 std::vector 的 VBO

    我用 C 和 OpenGL 编写了一个模型加载器 我用过std vectors 来存储我的顶点数据 但现在我想将其传递给glBufferData 但是数据类型却截然不同 我想知道是否有办法可以相互转换std vector至已记录的const
  • OpenGL纹理渲染与原始不匹配

    我正在尝试使用 OpenGL 渲染纹理 我用作测试的纹理是白色背景上的一堆黑色矩形 如下所示 然而 在渲染时 纹理似乎被复制并叠加在其自身之上多次 我使用以下方法设置场景 std string vertexSource ShaderLoad
  • iPhone glShader二进制

    有谁有如何编译着色器 保存着色器二进制文件以及使用 glShaderBinary 稍后使用 iPhone iOS OpenGL ES 2 0 加载着色器的示例 这是不可能的 至少对于 iOS 4 及更低版本 iOS 不支持任何预编译的二进制
  • 为贝塞尔曲线中的每个点绘制切线

    我设法绘制了一条贝塞尔曲线 如下所示 glColor3f 0 1 0 glBegin GL LINE STRIP for int i 3 i lt nPt i 3 glColor3f 0 0 0 for float k 0 k lt NLI
  • Retina 显示屏中具有 QOpenGLWIdget 的 Qt MainWindow 显示错误大小

    我有一个 Qt 应用程序MainWindow 我嵌入一个QOpenGLWidget在里面 一切正常 直到我开始使用 Apple Retina 显示屏并在高 DPI 模式下运行我的应用程序 我的QOpenGLWidget只是它应该具有的大小的
  • 使用 OpenGL 渲染 QImage

    与我相关的其他问题 https stackoverflow com questions 20126354 render qimage from sooffscreenrenderer in qglwidget 我认为更核心的问题是 如何渲染
  • 开启TK onRenderFrame和onUpdateFrame的区别?

    我目前正在使用 OpenTK 框架和 OpenGL 用 C 编写 Jump n Run 游戏 Open TK 提供预设功能 例如GameWindow Run or GameWindow onUpdateFrame onRenderFrame
  • 如何使用OpenGL数组纹理?

    我正在尝试在OpenGL中使用精灵表 通过数组纹理实现它这就是我加载纹理的方式 QImage image image load C QtProjects project images spritesheet png png const un
  • 新显卡上的 nvoglv32.dll 中的绘制调用崩溃

    几天前 由于一些硬件更改 我设置了计算机并安装了新的 Windows 8 副本 其中 我将显卡从 Radeon HD 7870 更改为 Nvidia GTX 660 再次设置 Visual Studio 11 后 我从 Github 下载了
  • OpenGL 计算着色器调用

    我有一个与新计算着色器相关的问题 我目前正在研究粒子系统 我将所有粒子存储在着色器存储缓冲区中 以便在计算着色器中访问它们 然后我派遣一个一维工作组 define WORK GROUP SIZE 128 shaderManager gt u
  • glsl 着色器 - 颜色混合,正常模式(就像在 Photoshop 中一样)

    我试图创建混合 2 种颜色的效果 实际上是图像和颜色作为图像叠加 就像在 Photoshop 颜色叠加 和 正常混合 模式中一样 我正在使用 libgdx 这就是我到目前为止所拥有的 attribute vec4 a position at
  • 线性/非线性纹理映射扭曲的四边形

    In my 上一个问题 https stackoverflow com questions 10832909 quad strip texturing distortion 已经确定 当对四边形进行纹理化时 面被分解为三角形 并且纹理坐标以
  • 并排显示图像的一半 - OpenGL

    我为两个图像创建了两个纹理 现在我想在opengl中按图像2的左侧部分 完整的图像1 图像2的右侧部分的顺序显示该纹理 我已经做了如下 Image1 显示在 opengl 屏幕的中央 但屏幕的左右部分不正确 应分别显示 image2 的左侧

随机推荐