zoukankan      html  css  js  c++  java
  • 【OpenGL】Shader概述

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

    为了举例,我们以下都使用下面两个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.   
    3. // Check Vertex Shader  
    4. glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result);  
    5. glGetShaderiv(VertexShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);  
    6. std::vector VertexShaderErrorMessage(InfoLogLength);  
    7. glGetShaderInfoLog(VertexShaderID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]);  
    8. 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的话)。
  • 相关阅读:
    UILabel的使用
    CGAffineTransform的使用
    UIView的常用方法
    UICollectionViewController的用法1
    网址连接
    android developers blog
    Java并发编程:volatile关键字解析
    Android触摸屏事件派发机制详解与源码分析
    setScale,preScale 和 postScale 的区别
    android 内存
  • 原文地址:https://www.cnblogs.com/zhaoxinshanwei/p/4483733.html
Copyright © 2011-2022 走看看