GLSL是OpenGL的着色语言, 我们之前的例子,都是用的着色器管理类GLShaderManager中的函数UseStockShader来使用着色器的,该函数完成的东西就是对我们的顶点数据进一步的加工,比如设置顶点颜色,设置顶点光照的强度,设置顶点纹理等。
假如说我们对UseStockShader函数一些默认的效果不满意,比如我们要让模型的一部分比另一部分更亮,也就是光线照到的地方,比没有照到的地方要亮,这个时候就要写自己的着色器。
如下图:
这也是我们这一节要实现的例子,模型的左上角比较亮。
着色器分为顶点着色器 ,几何着色器,片段着色器,本节只介绍顶点着色器和片段着色器
顶点着色器记录的是顶点的属性和一些对顶点的操作,顶点着色器的文件后缀为,vp
片段着色器记录的是片段的属性和一些对片段的操作,片段着色器的文件后缀是.fp
片段的意思就是组成模型的最小单元,比如一个三角形就是一个片段
顶点着色器在处理顶点后会将处理的结果传给片段着色器
着色器的入口函数同样是main函数
下面先写一个我们的顶点着色器,写完后我们在说怎么用它
创建一个记事本名字叫做DiffuseLight.txt,然后把后缀名改成.vp,最后是DiffuseLight.vp
在GLSL语言中,我们可以用vec3声明一个三维向量,vec4声明一个4维向量,用mat4声明一个4*4的矩阵,用mat3声明一个3*3的矩阵
我们在DiffuseLight.vp先来声明一下我们的全局变量
//指定OpenGL的最低版本为1.3
#version 130
//关键字in表示是要从外界输入的变量
in vec4 vVertex;
in vec3 vNormal;
//关键字uniform 表示统一值的意思
uniform vec4 diffuseColor; //漫反射光的颜色值
uniform vec3 vLightPosition; //光源的位置
uniform mat4 mvpMatrix; //模型视图投影矩阵
uniform mat4 mvMatrix; //视图矩阵
uniform mat3 normalMatrix; //模型视图的法线矩阵
//关键字out表示的是输出的值,也就是传给片段着色器的值,smooth表示颜色值vVaryingColor采用平滑插值的方式
smooth out vec4 vVaryingColor;
关键字in表示是要从外界输入的变量,我们要从外界输入模型的顶点vVertex,模型顶点的法线vNormal
关键字uniform 表示统一值的意思,也就是该图元都使用的一些相同的属性值,如果说图元是三角形,那么就表示三角形的三个顶点使用的都是统一值定义的属性值
统一值可以从外界赋值
我们可以用函数 glGetUniformLocation GLEW_GET_FUN(__glewGetUniformLocation) 来从外界获取这个统一值的字段
比如:
GLint locColor = glGetUniformLocation(diffuseLightShader, "diffuseColor");
比如:
GLfloat vDiffuseColor[] = { 0.0f, 0.0f, 1.0f, 1.0f };
glUniform4fv(locColor, 1, vDiffuseColor);
设置三维矩阵的方法是glUniformMatrix3fv
最后的smooth out vec4 vVaryingColor,表示我们会把颜色值vVaryingColor传到片段着色器里
下面来看我们的main函数
void main(void)
{
// 把顶点的法线转换到模型视图变换矩阵中
vec3 vEyeNormal = normalMatrix * vNormal;
// 把顶点转换到照相机坐标系
vec4 vPosition4 = mvMatrix * vVertex;
//去除顶点的缩放,还原为原状态
vec3 vPosition3 = vPosition4.xyz / vPosition4.w;
// 得到顶点到光源的方向
vec3 vLightDir = normalize(vLightPosition - vPosition3);
//函数dot表示 用顶点到光源的方向乘以顶点的法线,得到夹角的余弦值
//函数max表示将得到的余弦值和0作比较,取最大的
float diff = max(0.0, dot(vEyeNormal, vLightDir));
// 设置顶点的颜色,用上一步得到的diff乘以原来顶点的颜色,并保持alphp不变
vVaryingColor.rgb = diff * diffuseColor.rgb;
vVaryingColor.a = diffuseColor.a;
// 把顶点的投影到模型视图矩阵中
gl_Position = mvpMatrix * vVertex;
}
下面来写我们的片段着色器程序,建一个名为DiffuseLight.fp的文件,注意后缀为.fp
下面是DiffuseLight.fp中的内容
#version 130
//从顶点着色器接受的值
smooth in vec4 vVaryingColor;
out vec4 vFragColor;
void main(void)
{
vFragColor = vVaryingColor;
}
下面来看一下我们是怎么来用这两个文件的
我们创建一个新的OpenGL工程,添加一个.cpp源文件
首先来看一下我们需要包含的头文件和全局变量部分
#include
#include
#include
#include
#include
#include
#include
#define FREEGLUT_STATIC
#include
GLFrame viewFrame;
GLFrustum viewFrustum;
GLTriangleBatch sphereBatch;
GLMatrixStack modelViewMatrix;
GLMatrixStack projectionMatrix;
GLGeometryTransform transformPipeline;
GLShaderManager shaderManager;
GLuint diffuseLightShader;
GLint diffuseColor;
GLint vLightPosition;
GLint mvpMatrix;
GLint mvMatrix;
GLint normalMatrix;
下面是主函数main,和之前的代码一样
int main(int argc, char* argv[])
{
//设置工作路径
gltSetWorkingDirectory(argv[0]);
//初始化glut
glutInit(&argc, argv);
//申请一个带有双缓冲区,颜色缓冲区的窗口
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
//窗口大小
glutInitWindowSize(800, 600);
//窗口名字
glutCreateWindow("GLSL");
//窗口大小改变时的回调函数
glutReshapeFunc(ChangeSize);
//键盘按键响应函数
glutSpecialFunc(SpecialKeys);
//渲染的回调函数
glutDisplayFunc(RenderScene);
GLenum err = glewInit();
if (GLEW_OK != err) {
fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
return 1;
}
//初始化函数
SetupRC();
//主函数循环
glutMainLoop();
//循环结束后释放内存
ShutdownRC();
return 0;
}
然后是窗口改变大小时的回调函数ChangeSize
void ChangeSize(int nWidth, int nHeight)
{
//设置视口大小
glViewport(0, 0, nWidth, nHeight);
//将视图变换矩阵 和 模型 变换矩阵统一管理起来
transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
//设置视口变换矩阵
viewFrustum.SetPerspective(35.0f, float(nWidth) / float(nHeight), 1.0f, 100.0f);
//设置模型变换矩阵
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
}
下面是初始化函数SetupRC
我们可以用方法GLuint LoadShaderPairWithAttributes(const char *szVertexProgFileName, const char *szFragmentProgFileName, ...);
来加载我们自己写的着色器文件
void SetupRC(void)
{
glClearColor(0.3f, 0.3f, 0.3f, 1.0f );
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
shaderManager.InitializeStockShaders();
viewFrame.MoveForward(4.0f);
gltMakeSphere(sphereBatch, 1.0f, 26, 13);
//加载着色器,并设置需要传入着色器中的属性字段名
diffuseLightShader = shaderManager.LoadShaderPairWithAttributes("DiffuseLight.vp", "DiffuseLight.fp", 2, GLT_ATTRIBUTE_VERTEX, "vVertex",
GLT_ATTRIBUTE_NORMAL, "vNormal");
//得到顶点着色器中漫反射光颜色diffuseColor的位置引用
diffuseColor = glGetUniformLocation(diffuseLightShader, "diffuseColor");
//得到顶点着色器中光源vLightPosition位置的位置引用
vLightPosition = glGetUniformLocation(diffuseLightShader, "vLightPosition");
//的到顶点着色器中模型视图矩阵mvpMatrix的位置引用
mvpMatrix = glGetUniformLocation(diffuseLightShader, "mvpMatrix");
//得到顶点着色器中视图变换矩阵mvMatrix的位置引用
mvMatrix = glGetUniformLocation(diffuseLightShader, "mvMatrix");
//得到顶点着色器中模型视图矩阵normalMatrix的位置引用
normalMatrix = glGetUniformLocation(diffuseLightShader, "normalMatrix");
}
我们可以使用函数glUseProgram 来使用着色器,其中参数是函数LoadShaderPairWithAttributes的返回值
void RenderScene(void)
{
static CStopWatch rotTimer;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
modelViewMatrix.PushMatrix(viewFrame);
modelViewMatrix.Rotate(rotTimer.GetElapsedSeconds() * 10.0f, 0.0f, 1.0f, 0.0f);
GLfloat vEyeLight[] = { -100.0f, 100.0f, 100.0f };
GLfloat vDiffuseColor[] = { 0.0f, 0.0f, 1.0f, 1.0f };
//使用着色器
glUseProgram(diffuseLightShader);
//设置顶点着色器中的漫反射光diffuseColor的颜色值
glUniform4fv(diffuseColor, 1, vDiffuseColor);
//设置顶点着色器中的光源位置vLightPosition的值
glUniform3fv(vLightPosition, 1, vEyeLight);
//设置顶点着色器中的模型视图矩阵mvpMatrix值
glUniformMatrix4fv(mvpMatrix, 1, GL_FALSE, transformPipeline.GetModelViewProjectionMatrix());
//设置顶点着色器中的视图面环矩阵值mvMatrix
glUniformMatrix4fv(mvMatrix, 1, GL_FALSE, transformPipeline.GetModelViewMatrix());
//设置顶点着色器中的模型视图矩阵法线矩阵normalMatrix值
glUniformMatrix3fv(normalMatrix, 1, GL_FALSE, transformPipeline.GetNormalMatrix());
//绘制模型
sphereBatch.Draw();
modelViewMatrix.PopMatrix();
glutSwapBuffers();
glutPostRedisplay();
}