【GLSL教程】(二)在OpenGL中使用GLSL

vampirem發表於2013-09-17

設定GLSL

這一節講述在OpenGL中配置GLSL,假設你已經寫好了頂點shader和畫素shader。如果你還沒有準備好,可以從如下網址獲得相關內容:

http://www.3dshaders.com/home/

http://www.opengl.org/sdk/tools/ShaderDesigner/

http://developer.amd.com/archive/gpu/rendermonkey/pages/default.aspx

在OpenGL中,GLSL的shader使用的流程與C語言相似,每個shader類似一個C模組,首先需要單獨編譯(compile),然後一組編譯好的shader連線(link)成一個完整程式。

這裡將忽略ARB擴充套件,只列舉OpenGL2.0的程式碼。建議使用GLEW庫:

http://glew.sourceforge.net/

下面的程式碼檢查OpenGL 2.0是否可用:

  1. #include <GL/glew.h>   
  2. #include <GL/glut.h>   
  3.   
  4. void main(int argc, char **argv)  
  5. {  
  6.     glutInit(&argc, argv);  
  7.     ...  
  8.     glewInit();  
  9.   
  10.     if (glewIsSupported("GL_VERSION_2_0"))  
  11.         printf("Ready for OpenGL 2.0\n");  
  12.     else  
  13.     {  
  14.         printf("OpenGL 2.0 not supported\n");  
  15.         exit(1);  
  16.     }  
  17.     setShaders();  
  18.   
  19.     glutMainLoop();  
  20. }  
#include <GL/glew.h>
#include <GL/glut.h>

void main(int argc, char **argv)
{
    glutInit(&argc, argv);
    ...
    glewInit();

    if (glewIsSupported("GL_VERSION_2_0"))
        printf("Ready for OpenGL 2.0\n");
    else
    {
        printf("OpenGL 2.0 not supported\n");
        exit(1);
    }
    setShaders();

    glutMainLoop();
}

下圖顯示了建立shader的必要步驟,函式的具體使用方法將在下面各小結描述:


建立shader

下圖顯示了建立shader的步驟:


首先建立一個物件作為shader的容器,這個建立函式將返回容器的控制程式碼。

  1. GLuint glCreateShader(GLenum shaderType);  
  2. 引數:  
  3. ·shaderType – GL_VERTEX_SHADER or GL_FRAGMENT_SHADER.  
GLuint glCreateShader(GLenum shaderType);
引數:
·shaderType – GL_VERTEX_SHADER or GL_FRAGMENT_SHADER.
你可以建立許多shader,但記住所有的頂點shader只能有一個main函式,所有畫素shader也一樣。

下一步將新增原始碼。shader的原始碼是一個字串陣列,新增的語法如下:

  1. void glShaderSource(GLuint shader, int numOfStrings, const char **strings, int *lenOfStrings);  
  2. 引數:  
  3. ·shader – the handler to the shader.  
  4. ·numOfStrings – the number of strings in the array.  
  5. ·strings – the array of strings.  
  6. ·lenOfStrings – an array with the length of each string, or NULL, meaning that the strings are NULL terminated.  
void glShaderSource(GLuint shader, int numOfStrings, const char **strings, int *lenOfStrings);
引數:
·shader – the handler to the shader.
·numOfStrings – the number of strings in the array.
·strings – the array of strings.
·lenOfStrings – an array with the length of each string, or NULL, meaning that the strings are NULL terminated.
最後編譯shader:

  1. void glCompileShader(GLuint shader);  
  2. 引數:  
  3. •shader – the handler to the shader.  
void glCompileShader(GLuint shader);
引數:
•shader – the handler to the shader.


建立程式

下圖顯示了獲得一個可以執行的shader程式的步驟:


首先建立一個物件,作為程式的容器。此函式返回容器的控制程式碼。

  1. GLuint glCreateProgram(void);  
GLuint glCreateProgram(void);

你可以建立任意多個程式,在渲染時,可以在不同程式中切換,甚至在某幀返回固定功能流水線。比如你想用折射和反射shader繪製一個茶杯,然後回到固定功能生成立方體環境貼圖(cube map)顯示背景。

下面將把上一節編譯的shader附加到剛剛建立的程式中。方法如下:

  1. void glAttachShader(GLuint program, GLuint shader);  
  2. 引數:  
  3. ·program – the handler to the program.  
  4. ·shader – the handler to the shader you want to attach.  
void glAttachShader(GLuint program, GLuint shader);
引數:
·program – the handler to the program.
·shader – the handler to the shader you want to attach.
如果同時有頂點shader和片斷shader,你需要把它們都附加到程式中。你可以把多個相同型別(頂點或畫素)的shader附加到一個程式中,如同一個C程式可以有多個模組一樣,但它們只能有一個main函式。

你也可以把一個shader附加到多個程式,比如你想在不同程式中使用某個相同的shader。

最後一步是連線程式。方法如下:

  1. void glLinkProgram(GLuint program);  
  2. 引數:  
  3. ·program – the handler to the program.  
void glLinkProgram(GLuint program);
引數:
·program – the handler to the program.
在連線操作之後,shader的原始碼可以被修改並重編譯,並不會影響到整個程式。

程式連線後,可以呼叫glUseProgram來使用程式。每個程式都分配了一個控制程式碼,你可以事先連線多個程式以備使用。

  1. void glUseProgram(GLuint prog);  
  2. 引數:  
  3. ·prog – the handler to the program you want to use, or zero to return to fixed functionality.  
void glUseProgram(GLuint prog);
引數:
·prog – the handler to the program you want to use, or zero to return to fixed functionality.
當一個程式被使用後,如果被再次連線,它將被自動替換並投入使用,所以沒有必要再次呼叫上面這個函式。如果使用的引數為0,表示將使用固定功能流水線。

 例子

下面的程式碼包含了上面描述的所有步驟,引數p,f,v是全域性的GLuint型變數。

  1. void setShaders()  
  2. {  
  3.     char *vs,*fs;  
  4.   
  5.     v = glCreateShader(GL_VERTEX_SHADER);  
  6.     f = glCreateShader(GL_FRAGMENT_SHADER);    
  7.   
  8.     vs = textFileRead("toon.vert");  
  9.     fs = textFileRead("toon.frag");  
  10.   
  11.     const char *vv = vs;  
  12.     const char *ff = fs;  
  13.   
  14.     glShaderSource(v, 1, &vv, NULL);  
  15.     glShaderSource(f, 1, &ff, NULL);  
  16.   
  17.     free(vs);free(fs);  
  18.   
  19.     glCompileShader(v);  
  20.     glCompileShader(f);  
  21.   
  22.     p = glCreateProgram();  
  23.   
  24.     glAttachShader(p, v);  
  25.     glAttachShader(p, f);  
  26.   
  27.     glLinkProgram(p);  
  28.     glUseProgram(p);  
  29. }  
void setShaders()
{
    char *vs,*fs;

    v = glCreateShader(GL_VERTEX_SHADER);
    f = glCreateShader(GL_FRAGMENT_SHADER);  

    vs = textFileRead("toon.vert");
    fs = textFileRead("toon.frag");

    const char *vv = vs;
    const char *ff = fs;

    glShaderSource(v, 1, &vv, NULL);
    glShaderSource(f, 1, &ff, NULL);

    free(vs);free(fs);

    glCompileShader(v);
    glCompileShader(f);

    p = glCreateProgram();

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

    glLinkProgram(p);
    glUseProgram(p);
}
GLUT版的完整例子如下:

http://lighthouse3d.com/wptest/wp-content/uploads/2011/03/glutglsl_2.0.zip

完整例子中包含了shader程式碼及文字檔案讀入程式。

 

錯誤處理

除錯shader是很困難的。目前還沒有像printf這樣的東西,雖然未來可能出現有除錯功能的開發工具。

編譯階段的狀態可以用如下函式獲得:

  1. void glGetShaderiv(GLuint object, GLenum type, int *param);  
  2. 引數:  
  3. ·object – the handler to the object. Either a shader or a program  
  4. ·type – GL_COMPILE_STATUS.  
  5. ·param – the return value, GL_TRUE if OK, GL_FALSE otherwise.  
void glGetShaderiv(GLuint object, GLenum type, int *param);
引數:
·object – the handler to the object. Either a shader or a program
·type – GL_COMPILE_STATUS.
·param – the return value, GL_TRUE if OK, GL_FALSE otherwise.
連線階段的狀態可以用如下函式獲得:

  1. void glGetProgramiv(GLuint object, GLenum type, int *param);  
  2. 引數:  
  3. ·object – the handler to the object. Either a shader or a program  
  4. ·type – GL_LINK_STATUS.  
  5. ·param – the return value, GL_TRUE if OK, GL_FALSE otherwise.  
void glGetProgramiv(GLuint object, GLenum type, int *param);
引數:
·object – the handler to the object. Either a shader or a program
·type – GL_LINK_STATUS.
·param – the return value, GL_TRUE if OK, GL_FALSE otherwise.
如果發生錯誤,就需要從InfoLog中找到更多的資訊。這個日誌儲存了最後一次操作的資訊,比如編譯時的警告、錯誤,連線時發生的各種問題。這個日誌甚至可以告訴你硬體是否支援你的shader。不幸的是InfoLog沒有一個規範,所以不同的驅動/硬體可能產生不同的日誌資訊。

為了獲得特定shader或程式的日誌,可以使用如下程式:

  1. void glGetShaderInfoLog(GLuint object, int maxLen, int *len, char *log);  
  2. void glGetProgramInfoLog(GLuint object, int maxLen, int *len, char *log);  
  3. 引數:  
  4. ·object – the handler to the object. Either a shader or a program  
  5. ·maxLen – The maximum number of chars to retrieve from the InfoLog.  
  6. ·len – returns the actual length of the retrieved InfoLog.  
  7. ·log – The log itself.  
void glGetShaderInfoLog(GLuint object, int maxLen, int *len, char *log);
void glGetProgramInfoLog(GLuint object, int maxLen, int *len, char *log);
引數:
·object – the handler to the object. Either a shader or a program
·maxLen – The maximum number of chars to retrieve from the InfoLog.
·len – returns the actual length of the retrieved InfoLog.
·log – The log itself.
GLSL規範有必要在這裡進行一些改進:你必須知道接收InfoLog的長度。為了找到這個準確的值,使用下面的函式:

  1. void glGetShaderiv(GLuint object, GLenum type, int *param);  
  2. void glGetProgramiv(GLuint object, GLenum type, int *param);  
  3. 引數:  
  4. ·object – the handler to the object. Either a shader or a program  
  5. ·type – GL_INFO_LOG_LENGTH.  
  6. ·param – the return value, the length of the InfoLog.  
void glGetShaderiv(GLuint object, GLenum type, int *param);
void glGetProgramiv(GLuint object, GLenum type, int *param);
引數:
·object – the handler to the object. Either a shader or a program
·type – GL_INFO_LOG_LENGTH.
·param – the return value, the length of the InfoLog.

下面的函式可以用來列印InfoLog的內容:

  1. void printShaderInfoLog(GLuint obj)  
  2. {  
  3.     int infologLength = 0;  
  4.     int charsWritten  = 0;  
  5.     char *infoLog;  
  6.    
  7.     glGetShaderiv(obj, GL_INFO_LOG_LENGTH,&infologLength);  
  8.    
  9.     if (infologLength > 0)  
  10.     {  
  11.         infoLog = (char *)malloc(infologLength);  
  12.         glGetShaderInfoLog(obj, infologLength, &charsWritten, infoLog);  
  13.         printf("%s\n",infoLog);  
  14.         free(infoLog);  
  15.     }  
  16. }   
  17.   
  18. void printProgramInfoLog(GLuint obj)  
  19. {  
  20.     int infologLength = 0;  
  21.     int charsWritten  = 0;  
  22.     char *infoLog;  
  23.    
  24.     glGetProgramiv(obj, GL_INFO_LOG_LENGTH,&infologLength);  
  25.    
  26.     if (infologLength > 0)  
  27.     {  
  28.         infoLog = (char *)malloc(infologLength);  
  29.         glGetProgramInfoLog(obj, infologLength, &charsWritten, infoLog);  
  30.         printf("%s\n",infoLog);  
  31.         free(infoLog);  
  32.     }  
  33. }  
void printShaderInfoLog(GLuint obj)
{
    int infologLength = 0;
    int charsWritten  = 0;
    char *infoLog;
 
    glGetShaderiv(obj, GL_INFO_LOG_LENGTH,&infologLength);
 
    if (infologLength > 0)
    {
        infoLog = (char *)malloc(infologLength);
        glGetShaderInfoLog(obj, infologLength, &charsWritten, infoLog);
        printf("%s\n",infoLog);
        free(infoLog);
    }
} 

void printProgramInfoLog(GLuint obj)
{
    int infologLength = 0;
    int charsWritten  = 0;
    char *infoLog;
 
    glGetProgramiv(obj, GL_INFO_LOG_LENGTH,&infologLength);
 
    if (infologLength > 0)
    {
        infoLog = (char *)malloc(infologLength);
        glGetProgramInfoLog(obj, infologLength, &charsWritten, infoLog);
        printf("%s\n",infoLog);
        free(infoLog);
    }
}

清理

前面的小節講到了附加一個shader到一個程式中,這裡的呼叫是將shader從程式中分離:

  1. void glDetachShader(GLuint program, GLuint shader);  
  2. 引數:  
  3. ·program – The program to detach from.  
  4. ·shader – The shader to detach.  
void glDetachShader(GLuint program, GLuint shader);
引數:
·program – The program to detach from.
·shader – The shader to detach.
注意,只有沒有附加到任何程式的shader可以被刪除,刪除shader和程式的呼叫如下:

  1. void glDeleteShader(GLuint id);  
  2. void glDeleteProgram(GLuint id);  
  3. 引數:  
  4. ·id – The hanuler of the shader or program to delete.  
void glDeleteShader(GLuint id);
void glDeleteProgram(GLuint id);
引數:
·id – The hanuler of the shader or program to delete.

如果一個shader還附加在某個程式中,這個shader並不能真正刪除,只能標記為刪除。當這個shader從所有程式中分離之後,才會被最終刪除。


相關文章