这篇文章讲述了Shader是如何编译和链接,最终在OpenGL程序中使用的。当然,不了解这些我们仍然可以正常工作,但是作为初学者,了解这些会让我更能明白自己在干嘛。。。

综述

哈,大名鼎鼎的Shader终于让我给见到了……之前在学习Unity3D的时候就被群里的大牛耳濡目染说Shader如何如何重要,现在终于轮到自己领教了。吐槽完毕,进入正题。

Shader的编译器被内嵌到OpenGL库的内部,而且必须在运行OpenGL程序时才能编译。目前还没有可以提前编译Shader的工具。在最新的OpenGL4.1中好像正在改善。

目前的学习中,我使用的是这个教程提 供的一个载入shader的代码。代码不长,功能不全,只能同时载入vertex shader和fragment shader(这里是保存在两个单独的文件里,后缀分别的vertexshader和fragmentshader,后缀不重要,即便是txt也可以,重 要的是内容使用的是GLSL语法),但是对于初学者够用了。(实际上,我们通常需要至少两个shader。)代码如下:

  1. GLuint LoadShaders(const char * vertex_file_path,const char * fragment_file_path){
  2. // Create the shaders
  3. GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
  4. GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);
  5. // Read the Vertex Shader code from the file
  6. std::string VertexShaderCode;
  7. std::ifstream VertexShaderStream(vertex_file_path, std::ios::in);
  8. if(VertexShaderStream.is_open())
  9. {
  10. std::string Line = "";
  11. while(getline(VertexShaderStream, Line))
  12. VertexShaderCode += "n" + Line;
  13. VertexShaderStream.close();
  14. }
  15. // Read the Fragment Shader code from the file
  16. std::string FragmentShaderCode;
  17. std::ifstream FragmentShaderStream(fragment_file_path, std::ios::in);
  18. if(FragmentShaderStream.is_open()){
  19. std::string Line = "";
  20. while(getline(FragmentShaderStream, Line))
  21. FragmentShaderCode += "n" + Line;
  22. FragmentShaderStream.close();
  23. }
  24. GLint Result = GL_FALSE;
  25. int InfoLogLength;
  26. // Compile Vertex Shader
  27. printf("Compiling shader : %sn", vertex_file_path);
  28. char const * VertexSourcePointer = VertexShaderCode.c_str();
  29. glShaderSource(VertexShaderID, 1, &VertexSourcePointer , NULL);
  30. glCompileShader(VertexShaderID);
  31. // Check Vertex Shader
  32. glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result);
  33. glGetShaderiv(VertexShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
  34. std::vector VertexShaderErrorMessage(InfoLogLength);
  35. glGetShaderInfoLog(VertexShaderID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]);
  36. fprintf(stdout, "%sn", &VertexShaderErrorMessage[0]);
  37. // Compile Fragment Shader
  38. printf("Compiling shader : %sn", fragment_file_path);
  39. char const * FragmentSourcePointer = FragmentShaderCode.c_str();
  40. glShaderSource(FragmentShaderID, 1, &FragmentSourcePointer , NULL);
  41. glCompileShader(FragmentShaderID);
  42. // Check Fragment Shader
  43. glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result);
  44. glGetShaderiv(FragmentShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
  45. std::vector FragmentShaderErrorMessage(InfoLogLength);
  46. glGetShaderInfoLog(FragmentShaderID, InfoLogLength, NULL, &FragmentShaderErrorMessage[0]);
  47. fprintf(stdout, "%sn", &FragmentShaderErrorMessage[0]);
  48. // Link the program
  49. fprintf(stdout, "Linking programn");
  50. GLuint ProgramID = glCreateProgram();
  51. glAttachShader(ProgramID, VertexShaderID);
  52. glAttachShader(ProgramID, FragmentShaderID);
  53. glLinkProgram(ProgramID);
  54. // Check the program
  55. glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
  56. glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength);
  57. std::vector ProgramErrorMessage( max(InfoLogLength, int(1)) );
  58. glGetProgramInfoLog(ProgramID, InfoLogLength, NULL, &ProgramErrorMessage[0]);
  59. fprintf(stdout, "%sn", &ProgramErrorMessage[0]);
  60. glDeleteShader(VertexShaderID);
  61. glDeleteShader(FragmentShaderID);
  62. return ProgramID;
  63. }

为了举例,我们以下都使用下面两个shader。

第一个shader是一个顶点着色器,vertex shader,文件名为ExampleShader.vertexshader。

  1. #version 400
  2. in vec3 VertexPosition;
  3. in vec3 VertexColor;
  4. out vec3 Color;
  5. void main()
  6. Color = VertexColor;
  7. gl_Position = vec4( VertexPosition, 1.0 );
  8. }

这里简单解释一下。它接受两个输入和一个输出,并使用输入VertexPosition给gl_position赋值,使用VertexColor给输出Color赋值,而Color将会传递给下面的片段着色器。

第二个是片段着色器,fragment shader,文件名为ExampleShader.fragmentshader。

  1. #version 400
  2. in vec3 Color;
  3. out vec4 FragColor;
  4. void main() {
  5. FragColor = vec4(Color, 1.0);
  6. }

顶点着色器会在每个顶点上调用一次,而片段着色器则会在每个像素上调用一次。

----------------------------------------------------------------分割线
--------------------------------------------------------------------

编译一个Shader

如上图所示,一个shader的编译过程主要分为3个部分:首先创建一个shader对象(Shader Object)。例如下面分别创建了两个shader对象:
  1. // Create the shaders
  2. GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
  3. GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);

然后将shader源代码(source
code)复制到shader对象中。由于这里是从文件里读入代码,因此先将源代码分别读入到一个string类型的变量里
(VertexShaderCode和FragmentShaderCode),再把指针传递给shader对象:

  1. // Read the Vertex Shader code from the file
  2. std::string VertexShaderCode;
  3. std::ifstream VertexShaderStream(vertex_file_path, std::ios::in);
  4. if(VertexShaderStream.is_open())
  5. {
  6. std::string Line = "";
  7. while(getline(VertexShaderStream, Line))
  8. VertexShaderCode += "n" + Line;
  9. VertexShaderStream.close();
  10. }
  1. char const * VertexSourcePointer = VertexShaderCode.c_str();
  2. glShaderSource(VertexShaderID, 1, &VertexSourcePointer , NULL);

最后,编译shader。通常我们需要检查一下shader是否编译成功,不成功的话再打出错误信息,这通过两个变量Result和InfoLogLength来实现:

  1. GLint Result = GL_FALSE;
  2. int InfoLogLength;
  1. glCompileShader(VertexShaderID);
  2. // Check Vertex Shader
  3. glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result);
  4. glGetShaderiv(VertexShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
  5. std::vector VertexShaderErrorMessage(InfoLogLength);
  6. glGetShaderInfoLog(VertexShaderID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]);
  7. fprintf(stdout, "%sn", &VertexShaderErrorMessage[0]);

上面的筛选的vertex shader的编译过程,当然fragment shader的编译是一样的。

链接一个Shader

在我们编译完成shader后,正式在OpenGL管道中使用它们之前,我们还需要链接它们。这主要是为了解决不同shader之间的输入和输
出匹配问题。例如这里,ExampleShader.vertexshader里的输出Color需要和
ExampleShader.fragmentshader里的输入Color相匹配。
和编译shader类似,我们首先需要创建一个shader program object。
  1. GLuint ProgramID = glCreateProgram();

然后将之前创建好的shader object附加给它。

  1. glAttachShader(ProgramID, VertexShaderID);
  2. glAttachShader(ProgramID, FragmentShaderID);

最后,进行链接。

  1. glLinkProgram(ProgramID);

和之前需要检查编译状态类似,我们也需要检查链接状态,如果链接不成功,就打出提示信息。

  1. // Check the program
  2. glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
  3. glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength);
  4. std::vector ProgramErrorMessage( max(InfoLogLength, int(1)) );
  5. glGetProgramInfoLog(ProgramID, InfoLogLength, NULL, &ProgramErrorMessage[0]);
  6. fprintf(stdout, "%sn", &ProgramErrorMessage[0]);

删除一个Shader

在我们把一个shader对象附加一个program对象后,我们就可以删除它了,以便释放它占用的资源和句柄。注意,如果一个shader已
经被附加到一个program对象(比如现在),那么实际上它并没有立刻被删除,而是出于一种挂起状态。当它从附加的program对象上被卸载时,才会
真正从内存中删除。
  1. glDeleteShader(VertexShaderID);
  2. glDeleteShader(FragmentShaderID);

指定使用一个Shader Program

为了在OpenGL管道里真正使用我们创建好的shader program对象,我们需要使用glUseProgram(
ProgramID);函数,ProgramID就是上面函数的返回值,它提供了一个Shadr
program对象的句柄。在一个OpenGL程序中,我们可以使用多个shader
programs,我们可以通过使用glUseProgram在OpenGL管道里进行切入和切除,以便选择不同的程序。

删除一个Shader Program

当我们不再需要一个shader
program时,可以通过调用glDeleteProgram(ProgramID)来删除一个shader
program,以释放OpenGL内存。注意,如果当前的shader
program正在被使用,那么它并不会立刻被删除,而是出于一种挂起状态,直到程序不再使用它(可能切换到另一个Shader
Program)。这时,之前附加的shader对象就会真正被删除了(如果之前调用过glDeleteShader的话)。

【OpenGL】Shader概述的更多相关文章

  1. OpenGL Shader in OpenCASCADE

    OpenGL Shader in OpenCASCADE eryar@163.com Abstract. As implementation of one of the strategic steps ...

  2. A Simple OpenGL Shader Example II

    A Simple OpenGL Shader Example II eryar@163.com Abstract. The OpenGL Shading Language syntax comes f ...

  3. A Simple OpenGL Shader Example

    A Simple OpenGL Shader Example eryar@163.com Abstract. OpenGL Shading Language, the high-level progr ...

  4. OpenGL Shader源码分享

    Opengl shader程序,旗帜混合纹理加载,通过N张图片,能够组合出数百个:http://www.eyesourcecode.com/thread-39015-1-1.html 用GLSL做了一 ...

  5. 【玩转cocos2d-x之四十】怎样在Cocos2d-x 3.0中使用opengl shader?

    有小伙伴提出了这个问题.事实上GLProgramCocos2d-x引擎自带了.全然能够直接拿来用. 先上图吧. 使用opengl前后的对照: watermark/2/text/aHR0cDovL2Js ...

  6. 「游戏引擎 浅入浅出」4.1 Unity Shader和OpenGL Shader

    「游戏引擎 浅入浅出」从零编写游戏引擎教程,是一本开源电子书,PDF/随书代码/资源下载: https://github.com/ThisisGame/cpp-game-engine-book 4.1 ...

  7. 【Unity Shaders】Shader学习资源和Surface Shader概述

    写在前面 写这篇文章的时候,我断断续续学习Unity Shader半年了,其实还是个门外汉.我也能体会很多童鞋那种想要学好Shader却无从下手的感觉.在这个期间,我找到一些学习Shader的教程以及 ...

  8. Unity Shader概述

    一.概述 在Unity中需要配合使用材质和Unity Shader才能达到需要的效果.常见的流程:(1)创建一个材质:(2)创建一个Unity Shader,并把它赋给创建的材质:(3)把材质赋给要渲 ...

  9. OpenGL shader 中关于顶点坐标值的思考

    今天工作中需要做一个事情: 在shader内部做一些空间距离上的计算,而且需要对所有的点进行计算,符合条件的显示,不符合条件的点不显示. 思路很简单,在vertex shader内知道顶点坐标,进行计 ...

随机推荐

  1. mysql中的内连接,外连接实例详解

    内连接: 只连接匹配的行左外连接: 包含左边表的全部行(不管右边的表中是否存在与它们匹配的行),以及右边表中全部匹配的行右外连接: 包含右边表的全部行(不管左边的表中是否存在与它们匹配的行),以及左边 ...

  2. codeforces 60B bfs

    题意:给出一个六面体分为k层,每层n行m列,每个小立方体有'.'(空)与'#'(障碍)的状态,第一层某个空的位置有一个水龙头,水流每次往六个方向流动(...).最少时间水流能把立方体空的部分填满. 思 ...

  3. 【JavaScript学习整理】js基础

    HTML,CSS属于标记语言, JavaScript是基于客户端的脚本语言. 变量: 语法  var 变量名 = value var是系统内部关键字,用来声明变量 变量名规则:  1.不能以数字开头  ...

  4. linux用户管理与用户组的重要文件

    用户管理的2个重要文件:/etc/passwd和/etc/shadow. /etc/passwd文件里存放的是用户的信息,其中不包含密码:passwd文件中每一行代表一个用户,且每一行分为7个字段使用 ...

  5. Android GreenDao 中文表名,中文字段DAO生成乱码的问题

    在gradle.properties 文件中加入编码类型 # Project-wide Gradle settings. # IDE (e.g. Android Studio) users: # Gr ...

  6. python3 字符串属性(三)

    maketrans 和 translate的用法(配合使用) 下面是python的英文用法解释 maketrans(x, y=None, z=None, /) Return a translation ...

  7. python3与Redis连接操作

    Python3之redis使用   简介 redis是一个key-value存储系统,和Memcache类似,它支持存储的value类型相对更多,包括string(字符串),list(链表),set( ...

  8. 十五 Django框架,缓存

    由于Django是动态网站,所有每次请求均会去数据进行相应的操作,当程序访问量大时,耗时必然会更加明显,最简单解决方式是使用:缓存,缓存将一个某个views的返回值保存至内存或者memcache中,5 ...

  9. Convolutional Neural Networks for Visual Recognition 4

    Modeling one neuron 下面我们开始介绍神经网络,我们先从最简单的一个神经元的情况开始,一个简单的神经元包括输入,激励函数以及输出.如下图所示: 一个神经元类似一个线性分类器,如果激励 ...

  10. [原]NYOJ-组合数-32

    大学生程序代写 http://acm.nyist.net/JudgeOnline/problem.php?pid=32 /*组合数 时间限制:3000 ms  |  内存限制:65535 KB 难度: ...