简介

        随着图形硬件的发展,渲染管线由固定不可更改想着可编程和更平滑的方向不断发展。越来越多的基于GPU的编程语言开始出现,cg,cuda,各种着色语言等等。

        今天要介绍的就是和OpenGL结合非常紧密的GLSL(OpenGL Shading Language). 通过OpenGL的API我们可以绘制图元,变换图形等等,当并不能改变基础的渲染管线。在OpenGL中使用GLSL,就能将渲染管线中固定的功能阶段转变成可编程的。

        编程环境:Ubuntu12.04 32bit  GTX480

GLSL简介

         OpenGL着色语言(GLSL――OpenGL Shading Language)是用来在OpenGL中着色编程的语言,也即开发人员写的短小的自定义程序,他们是在图形卡的GPU (Graphic Processor Unit图形处理单元)上执行的,代替了固定的渲染管线的一部分。比如:视图转换、投影转换等。GLSL(GL Shading Language)的着色器代码分成2个部分:Vertex Shader(顶点着色器)和Fragment(片断着色器),有时还会有Geometry Shader(几何着色器)。负责运行顶点着色的是顶点着色器。它可以得到当前OpenGL 中的状态,GLSL内置变量进行传递。它拥有一下的一些特点:

1.是一种高级的过程式语言;

2.作为OpenGL标准的一个部分,也就意味着开源,跨平台;

3.基于C和C++的语法和流程控制;

4.天然支持向量和矩阵的运算;

5.比C和C++更加严格的变量控制;

6.使用变量来处理输入和输出而不是读写文档;

7.Shader的长度并没有限制,也没有必要去查询。

     为什要使用OpenGL shader?

1.增加材料的真材实感 - 石头,草地,木头等等;

2.增加光照效果的真材实感 - 面光源,软阴影等等;

3.高级的渲染效果 - 全局照明,光线追踪等等;

4.非真实的材质 - 模拟画笔效果,钢笔绘制效果等等;

5.阶段贴图 - 动态生成2D和3D的纹理,而不是静态的图像;

6.图像处理 - 卷积,遮罩,复杂混合等;

7.动态效果 - 关键帧插值,粒子系统,动画;

8.可编程反走样方法;

9.通用计算 - 排序,数学建模,流体计算;

这些特性在使用opengl的时候可能可以去实现,但是都会有些局限,而现在,通过shader,我们可以通过显卡的硬件加速来显著增加渲染的速度,同时可以解放CPU。


写一个简单的Shader

首先来看一下电脑的OpenGL环境,终端运行:

glxinfo | grep OpenGL



基于SDL的OpenGL已经安装好(参考这里:SDL入门学习),接下来需要安装一下OpenGL的扩展库。

sudo apt-get install glew-utils libglew1.6


这次先绘制一个简单的矩形。

在工程文件夹下创建一个basic.vert,作为vertex shader.

#version 400 in vec3 VertexPosition; in vec3 VertexColor; out vec3 Color; void main() { 	Color = VertexColor; 	gl_Position = vec4( VertexPosition, 1.0); }

·in – for input parameters
·out – for outputs of the function. The returnstatement is also an option for sending the result of a function.
·inout – for parameters that are both input andoutput of a function (新版本的GLSL似乎已经废除)

再创建一个basic.frag,作为fragment shader.

#version 400 void main(void) { 	gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); }

创建main.c,代码如下:

/***************************************************************************** Copyright: 2013, ustc All rights reserved. contact:k283228391@126.com File name: main.c Description:Using opengl shading language in SDL. Author:Silang Quan Version: 1.0 Date: 2013.7.30 *****************************************************************************/ #include <SDL/SDL.h> #include <GL/glew.h> #include <GL/gl.h> #include <GL/glu.h> #include <stdio.h> #include <stdlib.h>   const int SCREEN_WIDTH = 800; const int SCREEN_HEIGHT =800; const int SCREEN_BPP = 32; SDL_Surface *screen; //Whether the window is windowed or not bool windowed; //Whether the window is fine bool windowOK; //Handler for GLSL program GLuint programHandle; GLuint vShader; GLuint fShader;  void quit( int code ) {     SDL_Quit( );     /* Exit program. */     exit( code ); }  char *textFileRead(char *fn) {       FILE *fp;     char *content = NULL;       int count=0;       if (fn != NULL) {         fp = fopen(fn,"rt");           if (fp != NULL) {         fseek(fp, 0, SEEK_END);       count = ftell(fp);       rewind(fp);               if (count > 0) {                 content = (char *)malloc(sizeof(char) * (count+1));                 count = fread(content,sizeof(char),count,fp);                 content[count] = '\0';             }             fclose(fp);         }     }     return content; }  void toggle_fullscreen() { 	//If the screen is windowed 	if( windowed == true ) 	{ 		//Set the screen to fullscreen 		screen = SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_OPENGL|SDL_RESIZABLE| SDL_FULLSCREEN );  		//If there's an error 		if( screen == NULL ) 		{ 			windowOK = false; 			return; 		}  		//Set the window state flag 		windowed = false; 	} 	//If the screen is fullscreen 	else if( windowed == false ) 	{ 		//Window the screen 		screen = SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_OPENGL|SDL_RESIZABLE );  		//If there's an error 		if( screen == NULL ) 		{ 			windowOK = false; 			return; 		}  		//Set the window state flag 		windowed = true; 	} }  void handleKeyEvent( SDL_keysym* keysym ) {     switch( keysym->sym ) 	{     case SDLK_ESCAPE:         quit( 0 );         break;     case SDLK_SPACE:         break;     case SDLK_F1: 		toggle_fullscreen(); 		break;     default:         break;     } }  void resizeGL(int width,int height) {     if ( height == 0 )     {         height = 1;     }     //Reset View     glViewport( 0, 0, (GLint)width, (GLint)height );     //Choose the Matrix mode     glMatrixMode( GL_PROJECTION );     //reset projection     glLoadIdentity();     //set perspection     gluPerspective( 45.0, (GLfloat)width/(GLfloat)height, 0.1, 100.0 );     //choose Matrix mode     glMatrixMode( GL_MODELVIEW );     glLoadIdentity(); }  void handleEvents() {     // Our SDL event placeholder.     SDL_Event event;     //Grab all the events off the queue.     while( SDL_PollEvent( &event ) ) {         switch( event.type ) {         case SDL_KEYDOWN:             // Handle key Event             handleKeyEvent( &event.key.keysym );             break;         case SDL_QUIT:             // Handle quit requests (like Ctrl-c).             quit( 0 );             break;         case SDL_VIDEORESIZE: 			//Handle resize event             screen = SDL_SetVideoMode(event.resize.w, event.resize.h, 16,                                       SDL_OPENGL|SDL_RESIZABLE);             if ( screen )             {                 resizeGL(screen->w, screen->h);             }             break;         }     } }  void initSDL(int width,int height,int bpp,int flags) {     // First, initialize SDL's video subsystem.     if( SDL_Init( SDL_INIT_VIDEO ) < 0 )     {         fprintf( stderr, "Video initialization failed: %s\n",                  SDL_GetError( ) );         quit( 1 );     }     atexit(SDL_Quit); 	//Set some Attribute of OpenGL in SDL     SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 5 );     SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 5 );     SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 5 );     SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 16 );     SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );      //Set the video mode     screen= SDL_SetVideoMode( width, height, bpp,flags);     if(!screen )     {         fprintf( stderr, "Video mode set failed: %s\n",SDL_GetError( ) );         quit( 1 );     windowed=false; 	} 	else windowed=true;     resizeGL(screen->w, screen->h);     //Set caption     SDL_WM_SetCaption( "OpenGL Shading Language Test", NULL );       }  void initShader() { 	 	vShader = glCreateShader( GL_VERTEX_SHADER ); 	fShader = glCreateShader( GL_FRAGMENT_SHADER ); 	printf("Here\n"); 	if(0 == vShader || 0 == fShader) 	{ 		fprintf(stderr, "Error creating vertex shader.\n"); 		quit(1); 	} 	 	GLchar* vShaderCode = textFileRead("basic.vert"); 	GLchar* fShaderCode = textFileRead("basic.frag"); 	const GLchar* vCodeArray[1] = {vShaderCode}; 	const GLchar* fCodeArray[1] = {fShaderCode}; 	glShaderSource(vShader, 1, vCodeArray, NULL); 	glShaderSource(fShader, 1, fCodeArray, NULL); 	 	glCompileShader(vShader); 	glCompileShader(fShader); 	free(vShaderCode); 	free(fShaderCode); 	//const GLchar* codeArray[] = {shaderCode}; 	//Check the compile result 	GLint logLen; 	glGetShaderiv(vShader, GL_INFO_LOG_LENGTH, &logLen); 	if(logLen > 0) 	{ 		char *log = (char *)malloc(logLen); 		GLsizei written; 		glGetShaderInfoLog(vShader, logLen, &written, log); 		printf("Shader compile error log: %s\n",log); 		free(log); 	} 	 	programHandle = glCreateProgram(); 	if(0 == programHandle) 	{ 		fprintf(stderr, "Error creating programHandle.\n"); 		quit(1); 	} 	 	glAttachShader(programHandle, vShader); 	glAttachShader(programHandle, fShader); 	glLinkProgram(programHandle); 	//glUseProgram(programHandle); }  void freeShader() { 	glDetachShader(programHandle, fShader); 	glDetachShader(programHandle, vShader); 	glDeleteShader(fShader); 	glDeleteShader(vShader); 	//glDetachShader(fShader); 	//glDetachShader(vShader); 	//glDetachShader(programHandle); } void renderGL() { 	/* These are to calculate our fps */     static GLint T0     = 0; 	static GLint Frames = 0;     // Clear the color and depth buffers.     glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );     // We don't want to modify the projection matrix. */     glMatrixMode( GL_MODELVIEW );     glLoadIdentity( );     // Move down the z-axis.     glTranslatef( 0.0, 0.0, -5.0 ); 	//Draw a square     glUseProgram(programHandle);     glBegin(GL_QUADS);             glVertex2f(-0.5f, -0.5f);             glVertex2f( 0.5f, -0.5f);             glVertex2f( 0.5f,  0.5f);             glVertex2f(-0.5f,  0.5f);     glEnd();         // Unbind shader     glUseProgram(0);     SDL_GL_SwapBuffers( );          /* Gather our frames per second */     Frames++;     { 	GLint t = SDL_GetTicks(); 	if (t - T0 >= 5000) { 	    GLfloat seconds = (t - T0) / 1000.0; 	    GLfloat fps = Frames / seconds; 	    printf("%d frames in %g seconds = %g FPS\n", Frames, seconds, fps); 	    T0 = t; 	    Frames = 0; 	}     } }  void initGL( int width, int height ) {     float ratio = (float) width / (float) height;     // Our shading model--Gouraud (smooth).     glShadeModel( GL_SMOOTH );     // Set the clear color.     glClearColor( 0, 0, 0, 0 );     // Setup our viewport.     glViewport( 0, 0, width, height );     //Change to the projection matrix and set our viewing volume.     glMatrixMode( GL_PROJECTION );     glLoadIdentity();     gluPerspective( 60.0, ratio, 1.0, 100.0 ); }  int main( int argc, char* argv[] ) {  	// Color depth in bits of our window. 	int flags= SDL_OPENGL|SDL_RESIZABLE; 	//Set the SDL 	initSDL(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP,flags); 	if(glewInit() != GLEW_OK) exit(EXIT_FAILURE); 	//Init vertext shader 	initShader(); 	//Set the OpenGL 	initGL(SCREEN_WIDTH, SCREEN_HEIGHT );      //main loop     while(true) 	{         /* Process incoming events. */         handleEvents( );         /* Draw the screen. */         renderGL( );     }     // Free Shader     freeShader();     return 0; } 

主要是增加了几个关于Shader的函数,initShader用于shader的初始化,freeShader用于删除shader,释放内存。使用shader之前还需要调用glewInit来初始化glew。


终端编译命令:

g++ main.c -o main -lSDL -lGL -lGLU -lGLEW



解释一下几个相关的API。

GLuint glCreateShader(GLenum shaderType); Parameter:  shaderType – GL_VERTEX_SHADER, GL_GEOMETRY_SHADER, GL_TESS_CONTROL_SHADER, GL_TESS_EVALUATION_SHADER, or GL_FRAGMENT_SHADER. Return Value:  the shader handler

void glShaderSource(GLuint shader, int numOfStrings, const char **strings, int *lengthOfStrings); Parameters:  shader – the handler to the shader. numOfStrings – the number of strings in the array. strings – the array of strings. lengthOfStrings – an array with the length of each string, or NULL, meaning that the strings are NULL terminated.

void glCompileShader(GLuint shader); Parameters:  shader – the handler to the shader.


void glUseProgram(GLuint program);  Installs a program object as part of current rendering state Parameters: program Specifies the handle of the program object whose executables are to be used as part of current rendering state.


...

整个opengl程序执行的流程如下:


更多函数参考OpenGL reference - http://www.opengl.org/sdk/docs/man/


参考

OpenGL 4.0 Shading Language Cookbook

GLSL Core Tutorial – Creating a Shader - http://www.lighthouse3d.com/tutorials/glsl-core-tutorial/creating-a-shader/

Hello GLSL - http://sindney.com/blog/posts/hello-glsl/

OpenGL reference - http://www.opengl.org/sdk/docs/man/