简介
随着图形硬件的发展,渲染管线由固定不可更改想着可编程和更平滑的方向不断发展。越来越多的基于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/