OpenGL着色语言(OpenGL Shading Language,GLSL)是用来在OpenGL中着色编程的语言,是一种具有C/C++风格的高级过程语言,同样也以main函数开始,只不过执行过程是在GPU上。GLSL使用类型限定符而不是通过读取和写入操作来管理输入和输出。着色器主要分为顶点着色器(Vertex Shader)和片段着色器(Fragment Shader)两部分。
顶点着色器的主要功能是:
- 顶点法线变换及单位化
- 纹理坐标变换
- 光照参数生成
顶点着色器的输入内容包括:
- 着色器源代码
- attribute变量
- uniform变量
顶点着色器的输出内容包括:
- varying变量
- 内置的特殊变量,如gl_Position、gl_FrontFacing、gl_PointSize
片元着色器的主要功能:
- 在差值得到的值上进行操作
- 访问纹理
- 应用纹理
- 雾化
- 颜色融合
- 着色器源代码
- 用户自定义的varying变量
- uniform变量
- 采样器(Sampler)
- 一些内置的特殊变量(gl_PointCoord、gl_FragCoord、gl_FrontFacing等)
- 内置的特殊变量gl_FragColor
在OpenGL程序中使用着色器的初始化一般需要依次执行以下步骤:
- 1、顶点着色程序的源代码和片段着色程序的源代码分别写入到一个文件里(或字符数组)里面,一般顶点着色器源码文件后缀为.vert,片段着色器源码文件后缀为.frag;
- 2、使用glCreateshader()分别创建一个顶点着色器对象和一个片段着色器对象;
- 3、使用glShaderSource()分别将顶点/片段着色程序的源代码字符数组绑定到顶点/片段着色器对象上;
- 4、使用glCompileShader()分别编译顶点着色器和片段着色器对象(最好检查一下编译的成功与否);
- 5、使用glCreaterProgram()创建一个着色程序对象;
- 6、使用glAttachShader()将顶点和片段着色器对象附件到需要着色的程序对象上;
- 7、使用glLinkProgram()分别将顶点和片段着色器和着色程序执行链接生成一个可执行程序(最好检查一下链接的成功与否);
- 8、使用glUseProgram()将OpenGL渲染管道切换到着色器模式,并使用当前的着色器进行渲染;
以下是一个功能简单但流程完整的使用顶点着色器和片段着色器渲染的矩形图形。项目一共包含5个文件。2个资源文件(VertexShader.vert和FragmentShader.frag,分别是顶点着色器源码文件和片段着色器源码文件),2个cpp文件(Hello GLSL.cpp和Textfile.cpp),1个头文件Textfile.h。
VertexShader.vert文件内容:
//定义GLSL版本
#version 440
in vec4 VertexPosition;
in vec4 VertexColor;
out vec4 Color;
void main()
{
Color =VertexColor;
gl_Position = VertexPosition;
}
FragmentShader.frag文件内容:
#version 440
in vec4 Color; //汉字用于测试汉字是否可用,有报着色器源码注释含汉字运行报错的
out vec4 FragColor;
void main()
{
FragColor = Color;
}
Hello GLSL.cpp文件内容:
#include <GL/glew.h>
#include "Textfile.h"
#include <GL/freeglut.h>
#include <iostream>
#pragma comment(lib,"glew32.lib")
using namespace std;
GLuint vShader, fShader;//顶点/片段着色器对象
GLuint vaoHandle;// VAO对象
//顶点位置数组
float positionData[] = {
-0.5f,-0.5f,0.0f,1.0f,
0.5f,-0.5f,0.0f,1.0f,
0.5f,0.5f,0.0f,1.0f,
-0.5f,0.5f,0.0f,1.0f
};
//顶点颜色数组
float colorData[] = {
1.0f, 0.0f, 0.0f,1.0f,
0.0f, 1.0f, 0.0f,1.0f,
0.0f, 0.0f, 1.0f,1.0f,
1.0f,1.0f,0.0f,1.0f
};
void initShader(const char *VShaderFile, const char *FShaderFile)
{
//1、查看显卡、GLSL和OpenGL的信息
const GLubyte *vendor = glGetString(GL_VENDOR);
const GLubyte *renderer = glGetString(GL_RENDERER);
const GLubyte *version = glGetString(GL_VERSION);
const GLubyte *glslVersion = glGetString(GL_SHADING_LANGUAGE_VERSION);
cout << "显卡供应商 : " << vendor << endl;
cout << "显卡型号 : " << renderer << endl;
cout << "OpenGL版本 : " << version << endl;
cout << "GLSL版本 : " << glslVersion << endl;
//2、编译着色器
//创建着色器对象:顶点着色器
vShader = glCreateShader(GL_VERTEX_SHADER);
//错误检测
if (0 == vShader)
{
cerr << "ERROR : Create vertex shader failed" << endl;
exit(1);
}
//把着色器源代码和着色器对象相关联
const GLchar *vShaderCode = textFileRead(VShaderFile);
const GLchar *vCodeArray[1] = { vShaderCode };
//将字符数组绑定到对应的着色器对象上
glShaderSource(vShader, 1, vCodeArray, NULL);
//编译着色器对象
glCompileShader(vShader);
//检查编译是否成功
GLint compileResult;
glGetShaderiv(vShader, GL_COMPILE_STATUS, &compileResult);
if (GL_FALSE == compileResult)
{
GLint logLen;
//得到编译日志长度
glGetShaderiv(vShader, GL_INFO_LOG_LENGTH, &logLen);
if (logLen > 0)
{
char *log = (char *)malloc(logLen);
GLsizei written;
//得到日志信息并输出
glGetShaderInfoLog(vShader, logLen, &written, log);
cerr << "vertex shader compile log : " << endl;
cerr << log << endl;
free(log);//释放空间
}
}
//创建着色器对象:片断着色器
fShader = glCreateShader(GL_FRAGMENT_SHADER);
//错误检测
if (0 == fShader)
{
cerr << "ERROR : Create fragment shader failed" << endl;
exit(1);
}
//把着色器源代码和着色器对象相关联
const GLchar *fShaderCode = textFileRead(FShaderFile);
const GLchar *fCodeArray[1] = { fShaderCode };
glShaderSource(fShader, 1, fCodeArray, NULL);
//编译着色器对象
glCompileShader(fShader);
//检查编译是否成功
glGetShaderiv(fShader, GL_COMPILE_STATUS, &compileResult);
if (GL_FALSE == compileResult)
{
GLint logLen;
//得到编译日志长度
glGetShaderiv(fShader, GL_INFO_LOG_LENGTH, &logLen);
if (logLen > 0)
{
char *log = (char *)malloc(logLen);
GLsizei written;
//得到日志信息并输出
glGetShaderInfoLog(fShader, logLen, &written, log);
cerr << "fragment shader compile log : " << endl;
cerr << log << endl;
free(log);//释放空间
}
}
//3、链接着色器对象
//创建着色器程序
GLuint programHandle = glCreateProgram();
if (!programHandle)
{
cerr << "ERROR : create program failed" << endl;
exit(1);
}
//将着色器程序链接到所创建的程序中
glAttachShader(programHandle, vShader);
glAttachShader(programHandle, fShader);
//将这些对象链接成一个可执行程序
glLinkProgram(programHandle);
//查询链接的结果
GLint linkStatus;
glGetProgramiv(programHandle, GL_LINK_STATUS, &linkStatus);
if (GL_FALSE == linkStatus)
{
cerr << "ERROR : link shader program failed" << endl;
GLint logLen;
glGetProgramiv(programHandle, GL_INFO_LOG_LENGTH,
&logLen);
if (logLen > 0)
{
char *log = (char *)malloc(logLen);
GLsizei written;
glGetProgramInfoLog(programHandle, logLen,
&written, log);
cerr << "Program log : " << endl;
cerr << log << endl;
}
}
else//链接成功,在OpenGL管线中使用渲染程序
{
glUseProgram(programHandle);
}
}
void initVBO()
{
//绑定VAO
glGenVertexArrays(1, &vaoHandle);
glBindVertexArray(vaoHandle);
// Create and populate the buffer objects
GLuint vboHandles[2];
glGenBuffers(2, vboHandles);
GLuint positionBufferHandle = vboHandles[0];
GLuint colorBufferHandle = vboHandles[1];
//绑定VBO以供使用
glBindBuffer(GL_ARRAY_BUFFER, positionBufferHandle);
//加载数据到VBO
glBufferData(GL_ARRAY_BUFFER, 16 * sizeof(float),
positionData, GL_STATIC_DRAW);
//绑定VBO以供使用
glBindBuffer(GL_ARRAY_BUFFER, colorBufferHandle);
//加载数据到VBO
glBufferData(GL_ARRAY_BUFFER, 16 * sizeof(float),
colorData, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);//顶点坐标
glEnableVertexAttribArray(1);//顶点颜色
//调用glVertexAttribPointer之前需要进行绑定操作
glBindBuffer(GL_ARRAY_BUFFER, positionBufferHandle);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, (GLubyte *)NULL);
glBindBuffer(GL_ARRAY_BUFFER, colorBufferHandle);
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (GLubyte *)NULL);
}
void init()
{
//初始化glew扩展库
GLenum err = glewInit();
if (GLEW_OK != err)
{
cout << "Error initializing GLEW: " << glewGetErrorString(err) << endl;
}
//加载顶点和片段着色器对象并链接到一个程序对象上
initShader("VertexShader.vert","FragmentShader.frag");
//绑定并加载VAO,VBO
initVBO();
glClearColor(0.0, 0.0, 0.0, 0.0);
}
void display()
{
glClear(GL_COLOR_BUFFER_BIT);
//使用VAO、VBO绘制
glBindVertexArray(vaoHandle);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
glBindVertexArray(0);
glutSwapBuffers();
}
//ESC键用于退出使用着色器
void keyboard(unsigned char key, int x, int y)
{
switch (key)
{
case 27:
glDeleteShader(vShader);
glUseProgram(0);
glutPostRedisplay(); //刷新显示
break;
}
}
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
glutInitWindowSize(600, 600);
glutInitWindowPosition(100, 100);
glutCreateWindow("Hello GLSL");
init();
glutDisplayFunc(display);
glutKeyboardFunc(keyboard);
glutMainLoop();
return 0;
}
Textfile.cpp文件内容:
// textfile.cpp
// simple reading and writing for text files
#include "Textfile.h"
unsigned char * readDataFromFile(char *fn)
{
FILE *fp;
unsigned char *content = NULL;
int count = 0;
if (fn != NULL) {
fp = fopen(fn, "rb");
if (fp != NULL) {
fseek(fp, 0, SEEK_END);
count = ftell(fp);
rewind(fp);
if (count > 0) {
content = (unsigned char *)malloc(sizeof(unsigned char) * (count + 1));
count = fread(content, sizeof(unsigned char), count, fp);
content[count] = '\0';
}
fclose(fp);
}
}
return content;
}
//读入字符流
char *textFileRead(const 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;
}
int textFileWrite(char *fn, char *s)
{
FILE *fp;
int status = 0;
if (fn != NULL) {
fp = fopen(fn, "w");
if (fp != NULL) {
if (fwrite(s, sizeof(char), strlen(s), fp) == strlen(s))
status = 1;
fclose(fp);
}
}
return(status);
}
Textfile.h文件内容:
// textfile.h: interface for reading and writing text files
#ifndef TEXTFILE_H
#define TEXTFILE_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *textFileRead(const char *fn);
int textFileWrite(char *fn, char *s);
unsigned char *readDataFromFile(char *fn);
#endif
运行结果:
总结一下以上程序的执行过程:
1. 传统的初始化,创建窗口
2. 调用glewInit初始化glew库
3. 使用glGetString查询显卡和OpenGL以及GLSL等信息
4. 使用glCreateShader创建顶点/片段着色器对象
5. fread读入顶点/片段着色器的源码字符流
6. 使用glShaderSource将字符数组绑定到对应的着色器对象上
7. glCompileShader编译着色器对象
8. glCreateprogram创建着色器程序
9. glAttachShader将着色器程序链接到所创建的程序中
10.glLinkProgram将顶点/片段着色器、程序对象链接成一个可执行程序。
11.glUseProgram启用着色器渲染程序
程序:
1. glGenVertexArrays生成VAO,glBindVertexArray绑定VAO
2. glGenBuffers分别生成顶点位置VBO和颜色VBO
3. glBindBuffer绑定VBO
4. glBufferData加载实际数据到VBO
5. glEnableVertexAttribArray启用顶点/颜色VBO
6. glVertexAttribPointer对顶点/颜色数值内容进行解释(定义)
显示部分:
1. glBindVertexArray绑定VAO
2. glDrawArrays绘制图像
3. glBindVertexArray(0)解除VAO绑定