OpenGL进阶(八) - GLSL入门
来源:互联网 发布:战国之怒坐骑进阶数据 编辑:程序博客网 时间:2024/05/29 05:57
简介
随着图形硬件的发展,渲染管线由固定不可更改想着可编程和更平滑的方向不断发展。越来越多的基于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);
- }
#version 400in 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);
- }
#version 400void 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;
- }
/*****************************************************************************Copyright: 2013, ustc All rights reserved.contact:k283228391@126.comFile name: main.cDescription:Using opengl shading language in SDL.Author:Silang QuanVersion: 1.0Date: 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 notbool windowed;//Whether the window is finebool windowOK;//Handler for GLSL programGLuint 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 windowedif( windowed == true ){//Set the screen to fullscreenscreen = SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_OPENGL|SDL_RESIZABLE| SDL_FULLSCREEN );//If there's an errorif( screen == NULL ){windowOK = false;return;}//Set the window state flagwindowed = false;}//If the screen is fullscreenelse if( windowed == false ){//Window the screenscreen = SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_OPENGL|SDL_RESIZABLE );//If there's an errorif( screen == NULL ){windowOK = false;return;}//Set the window state flagwindowed = 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 resultGLint 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 SDLinitSDL(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP,flags);if(glewInit() != GLEW_OK) exit(EXIT_FAILURE);//Init vertext shaderinitShader();//Set the OpenGLinitGL(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
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 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 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.
void glUseProgram(GLuint program); Installs a program object as part of current rendering stateParameters:programSpecifies 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进阶(八) - GLSL入门
- OpenGL进阶(八) - GLSL入门
- OpenGL进阶(十三) - GLSL光照(Lighting)
- OpenGL进阶(十六) - GLSL纹理(Texture)
- OpenGL进阶(十三) - GLSL光照(Lighting)
- openGL之glsl入门1--基本概念
- openGL之glsl入门2--helloworld
- OpenGL入门学习[八]
- 现代OpenGL+Qt学习笔记之八:GLSL双面渲染
- OpenGL进阶(九) - GLSL中VAO和VBO的使用
- OpenGL进阶(九) - GLSL中VAO和VBO的使用
- OpenGL GLSL
- OpenGL入门笔记(八)
- OpenGL入门学习(八)
- opengl 入门学习 随笔八
- OpenGL入门学习(八)
- OpenGL ES入门09-GLSL实现常见特效
- openGL之glsl入门3--正弦函数叠加为方波
- Xunsearch安装出错解决办法
- shopt -s expand_aliases
- linux清空文件内容而不删除文件
- 遇到的问题-----java The type java.lang.String cannot be resolved. It is indirectly referenced fr
- shell sed中引用shell变量及空格表示方法
- OpenGL进阶(八) - GLSL入门
- 孤儿进程
- 哈夫曼编码的原理与实现
- Notification的功能与用法
- C++ DLL __declspec(dllexport) & __declspec(dllimport) 解析
- [MySQL] Linux下MySQL-5.6源码安装
- IOS之确认网络环境(3G/WIFI)
- openGL es 5:光照
- Oracle group by 用法实例详解