OpenGL--使用Shader_如何加日志确定shader执行是否正常 opengles-程序员宅基地

技术标签: OpenGL  Shader  

创建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都会被自动卸载。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u010223072/article/details/80641050

智能推荐

Sample Probability Space_probability space例题-程序员宅基地

文章浏览阅读66次。Sample Probability SpaceA simple probability space consist of a tuple (Ω\OmegaΩ,ε\varepsilonε,p)Ω\OmegaΩ is a finite set (with cardinality k= ∣\mid∣Ω\OmegaΩ∣\mid∣)ε\varepsilonε = { A : A ⊆\subseteq⊆ Ω\OmegaΩ } consist of all finite subsetsEach events _probability space例题

python分布式服务系统框架_Cola:一个分布式爬虫框架 - 系统架构 - Python4cn(news, jobs)...-程序员宅基地

文章浏览阅读348次。由于早先写的WeiboCrawler问题很多,而且当时我有提到,其实可以实现一个通用的爬虫框架。最近由于要抓取新的数据,于是我就写了这个cola。下面的文字来自wiki。Cola是一个分布式的爬虫框架,用户只需编写几个特定的函数,而无需关注分布式运行的细节。任务会自动分配到多台机器上,整个过程对用户是透明的。依赖由于Cola配置文件使用的yaml,所以Cola只依赖于pyyaml,安装easy_i..._cola爬虫

名悦集团国庆出行自驾游攻略-程序员宅基地

文章浏览阅读75次。在这个买车已经不是什么难事的年代,大多数人出行都会选择自驾方式,但自驾出游必然要面临一系列的问题以及做足准备工作。名悦集团小编给大家总结出了这次国庆假期出行自驾游攻略,为了保证自驾过程的安全顺利,玩得更加痛快,临行前的车辆检查时必不可少的,这些项目可以自己检查,如果实在是懒或者不懂,可以在临行前去4S店做个基础保养,以让爱车在最佳车况陪伴自己和家人朋友开始这段愉快的旅程。1.轮胎轮胎的检查是自驾前需要关键的检查之一,在自驾的旅程上不管是轮胎没气还是爆胎,都是非常令人揪心的,如果是在高速或者...

Android 输入法框架源码分析总结(1)_android 输入法源码-程序员宅基地

文章浏览阅读3.6k次,点赞3次,收藏7次。参考文档 https://blog.csdn.net/huangyabin001/article/details/28434989https://blog.csdn.net/huangyabin001/article/details/28435093#commentshttps://blog.csdn.net/jieqiong1/article/details/712629871..._android 输入法源码

Ubuntu下使用nnUNet 训练自己的数据集(只管实现,不讲原理,通俗易懂)_nnunet训练自己的数据集-程序员宅基地

本文介绍了如何在Ubuntu下使用nnUNet训练自己的数据集,不涉及原理解析,只提供实现步骤。包括nnUNet简介、修改训练参数和文件位置等操作。详细内容可参考Tina的博文。

SSM实现秒杀系统案例-程序员宅基地

文章浏览阅读9.9k次,点赞10次,收藏88次。对于抢购系统来说,首先要有可抢购的活动,而且这些活动具有促销性质,这种大型活动的负载可能是平时的几十倍,所以通过增加硬件、优化瓶颈代码等手段是很难达到目标的,所以抢购系统得专门设计。在这里我们说的库存不是真正意义上的库存,其实是该促销可以抢购的数量,真正的库存在基础库存服务。用户点击『提交订单』按钮后,在抢购系统中获取了资格后才去基础库存服务中扣减真正的库存;而抢购系统控制的就是资格/剩余数。传统方案利用数据库行锁,但是在促销高峰数据库压力过大导致服务不可用,目前采用redis集群(16分片)缓存促销信息,

随便推点

leetcode 989. 数组形式的整数加法-程序员宅基地

文章浏览阅读137次。989. 数组形式的整数加法难度简单61对于非负整数X而言,X的数组形式是每位数字按从左到右的顺序形成的数组。例如,如果X = 1231,那么其数组形式为[1,2,3,1]。给定非负整数X的数组形式A,返回整数X+K的数组形式。示例 1:输入:A = [1,2,0,0], K = 34输出:[1,2,3,4]解释:1200 + 34 = 1234示例 2:输入:A = [2,7,4], K = 181输出:[4,5,5]解释:274 + 1..._989

正则表达式匹配各种括号内内容_正则表达式 匹配多个括号-程序员宅基地

文章浏览阅读2.8w次,点赞10次,收藏9次。用正则表达式匹配两个字符中间的文本String skh ="(?<=\\《)[^\\》]+";//用于匹配《》里面的文本String str="但实际上《kajdwdej》孙大伟多";//测试字符串Pattern pattern=Pattern.compile(skh); Matcher matcher=pattern.matcher(str); boolean is=matche_正则表达式 匹配多个括号

Android 中的Shape_linear radial sweep分别代表的什么-程序员宅基地

文章浏览阅读553次。之前一直看项目用过这个东西,但是自己都不怎么熟悉,大概就知道可以画一些圆角之类的~ 今天就来好好了解一下吧~Shape里面有很多属性,依次学习一下第一步~首先来写一个Button这个布局文件就不贴了...太简单了~ (PS:说贴出来的站出来,我保证不打死你!)接下来开始学习第一个属性:Solid:(填充)在Drawable里面创建一个butt_linear radial sweep分别代表的什么

graphpad细胞增殖曲线_如何用GraphPad Prism绘制剂量反应曲线?-程序员宅基地

文章浏览阅读6.1k次。上图是Sci文献中的dose–response curves (剂量反应曲线),横坐标是药物GS-Se-SG浓度的对数值,纵坐标是Rrelative cell viability(相对细胞活性,% of control),从图注中可以知道IC50为5.1 μM。那么什么是IC50呢?IC50 (half maximal inhibitory concentration)是指被测量的拮抗剂的半抑制浓..._graphpad细胞增殖曲线

二叉查找树(非递归、递归遍历)_二叉排序树非递归查找-程序员宅基地

文章浏览阅读658次。二叉查找树(含递归、非递归遍历)_二叉排序树非递归查找

spark-sql 查询报错:Invalid method name: ‘get_table_req‘_invalid method name: 'get_table_req-程序员宅基地

文章浏览阅读3.1k次。spark-sql> select * from zps_d001 limit 1;Error in query: org.apache.hadoop.hive.ql.metadata.HiveException: Unable to fetch table zps_xxx. Invalid method name: 'get_table_req'org.apache.spark.sql.AnalysisException: org.apache.hadoop.hive.ql.metadata.H_invalid method name: 'get_table_req

推荐文章

热门文章

相关标签