最近一直在专心研究利用GLSL编写Shader,写点东西将自己学的总结一下,把自己学习shader的经历分享一下,希望能对有兴趣学习shader的同学有些帮助,但这些玩意还算不上教程,很多都是我自己在学习中的问题以及如何解决的,有什么不足还请各位指出,想要系统的学习GLSL的话还是推荐大家看《OpenGL Shading Language 3rd Edition》,但如果你对OpenGL也一无所知的话,那就请从《OpenGL Programming Guide 7th Edition》读起了。
本次主要是给大家一个GLSL的简单示例,对于一门新的语言或者技术,初学者入门总是要通过阅读一些示例程序,我在初学GLSL的时候也是这样,但是上网找了一圈发现,网上的大牛们所给的示例大部分都是仅仅有shader的代码,而没有相应的OpenGL应用程序,好不容易找到一个完整的也只是简简单单的渲染一个2D三角形,但是如果如果是想渲染一个带有光照的3D模型呢?如何在OpenGL程序中加入对shader的支持呢?如何向shader中传递数据呢?等等这些问题虽然书中会有答案,但是却缺少一个完整的示例将他们系统的整合起来,下面我就给出一个利用GLSL+OpenGL实现最基本的Binn-phong+lamber光照的的程序,至于具体算法就不细讲了,有兴趣的可以自己研究一下,很简单的。
在本程序中,顶点着色器完成了几乎所有任务,包括顶点由物体坐标系到投影坐标系的变换以及所有的光照计算,而片段着色器只是完成了颜色数据的传递,但在后面由于一些算法的要求(例如bumpmap),我们还会将光照计算移到片段着色器中,这里要注意,其实除了个别一些功能以外(例如顶点的空间变化或者纹理查询),很多的计算均可以放在顶点着色器或者片段着色器里,其区别只是计算速度与图像的质量。下面是着色器代码:
顶点着色器(Vertex Shader):
uniform vec3 lightposition;//光源位置
uniform vec3 eyeposition;//相机位置
uniform vec4 ambient;//环境光颜色
uniform vec4 lightcolor;//光源颜色
uniform float Ns;//高光系数
uniform float attenuation;//光线的衰减系数
varying vec4 color;//向片段着色其传递的参数
void main()
{ /*很多示例中都是利用uniform参数从应用程序中向shader里传递当前模型视图矩阵和模型视图投影矩阵,其实对于初学者来说,我们大可以先用GLSL的内建变量:gl_ModelViewMatrix和gl_ModelViewProjectionMatrix代替,而顶点坐标的变换则直接可以利用内建函数ftransform()实现。当然,如果你想自己传递这些参数也是可以的,后面会介绍一下。而gl_Vertex和gl_Normal则分别表示当前传入的顶点的物体坐标系坐标和表面法向量,gl_Position则是用来传输投影坐标系内顶点坐标的内建变量。注意内建变量是不用声明的,直接使用就行*/ vec3 ECPosition = vec3(gl_ModelViewMatrix * gl_Vertex);
vec3 N = normalize(gl_NormalMatrix * gl_Normal);
vec3 L = normalize(lightposition - ECPosition);
vec3 V = normalize(eyeposition - ECPosition);
vec3 H = normalize(V + L);
vec3 diffuse = lightcolor * max(dot(N , L) , 0);
vec3 specular = lightcolor * pow(max(dot(N , H) , 0) , Ns) * attenuation;
color = vec4(clamp((diffuse + specular) , 0.0 , 1.0) , 1.0);
color = color + ambient;
gl_Position = ftransform();
} 片段着色器(Fragment Shader): //这里的的varying变量名称要与顶点着色器里的一样,否则会编译错误 varying vec4 color;
void main()
{ //gl_FragColor是输出片段颜色的内建变量,凡是以gl_开头的变量都是内建变量
gl_FragColor = color;
}
其实shader程序不是很难写,我学习的过程中主要是卡在了OpenGL程序和shader程序的关联上,其实后来看看并不是太难,总结起来也就是读取shader代码;建立、编译shader;向shader中传递数据,其他的和一般的OpenGL程序差不多,下面给出OpenGL程序: //头文件是我自己封装的工具包,功能包括:读取shader源文件;初始化shader和program等 #include <GLSL_Toolkit.h>
GLfloat ambient[4] = {1.0 , 0.0 , 0.0 , 1.0};
GLfloat lightcolor[4] = {1.0 , 1.0 , 1.0 , 1.0};
GLfloat eyeposition[3] = {0.0 , 10.0 , 30.0};
GLfloat skycolor[3] = {0.7 , 0.7 , 1.0};
GLfloat groundcolor[3] = {0.2 , 0.2 , 0.2};
GLfloat Ns = 8;
GLfloat attenuation = 0.1;
GLfloat objectSize = 15.0;
GLuint program;
GLuint vShader , fShader;
////////////////////////////////////////////////////////
void myInit()
{
glewInit();
glClearColor(0.0 , 0.0 , 0.0 , 1.0);
glEnable(GL_DEPTH_TEST);
//读取源文件
const GLchar *vShaderSource = readShaderSource("GLSL_sample2.vert");
const GLchar *fShaderSource = readShaderSource("GLSL_sample2.frag");
//初始化shader和program
vShader = buildShader(&vShaderSource , GL_VERTEX_SHADER);
fShader = buildShader(&fShaderSource , GL_FRAGMENT_SHADER);
program = buildShaderProgram(vShader , fShader);
}
void myReshape(int w , int h)
{
glViewport(0 , 0 , (GLsizei)w , (GLsizei)h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(90 , 1 , 0.1 , 1000.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(eyeposition[0] , eyeposition[1] , eyeposition[2] , 0.0 , 0.0 , 0.0 , 0.0 , 1.0 , 0.0);
}
void myDisplay()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glColor3f(1.0 , 1.0 , 1.0);
/*这里是程序比较重要的地方:向给shader中的uniform变量赋值。注意赋值函数使用的位置,只有当一个program启用的时候,里面的uniform变量才会被分配索引,所以过早的对uniform变量赋值会长生错误。同时注意函数的格式*/
glUseProgram(program);
glUniform3f(glGetUniformLocation(program , "lightposition") , 30.0 , 30.0 , 30.0);
glUniform3f(glGetUniformLocation(program , "eyeposition") , eyeposition[0] , eyeposition[1] , eyeposition[2]);
glUniform4f(glGetUniformLocation(program , "ambient") , ambient[0] , ambient[1] , ambient[2] , ambient[3]);
glUniform4f(glGetUniformLocation(program , "lightcolor") , lightcolor[0] , lightcolor[1] , lightcolor[2] , lightcolor[3]);
glUniform1f(glGetUniformLocation(program , "Ns") , Ns);
glUniform1f(glGetUniformLocation(program , "attenuation") , attenuation);
/*刚开始我以为要利用shader进行渲染就要必须将模型变为一个一个顶点信息向shader中传入,但其实并不是这样,我们同样可以利用glut的内建模型或者自己动手载入模型,和平时一样。同时注意一点,glut的一些内建模型只包含平面法线,没有纹理坐标,所以在后面我们给模型添加纹理的时候有些就无法显示出纹理*/
glutSolidTeapot(objectSize);
glutSwapBuffers();
}
void main(int argc , char** argv)
{
glutInit(&argc , argv);
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
glutInitWindowPosition(100 , 100);
glutInitWindowSize(600 , 600);
glutCreateWindow("GLSL_example2");
myInit();
glutReshapeFunc(myReshape);
glutDisplayFunc(myDisplay);
glutIdleFunc(myDisplay);
glutMainLoop();
}
程序效果:
前面说到了我们自己向shader中传递矩阵数据,这里简单说一下:
在程序中向声明个变量,例如GLfloat modelViewMatrix[16];
然后在完成视图模型矩阵的设置后调用glGetFloatv(GL_MODELVIEW_MATRIX ,modelViewMatrix);
然后在program启用后调用glUniformMatrix4fv(glGetUniformLocation("modelViewMatrix"), 1 , GL_FALSE , modelViewMatrix);
这样就完成了向shader中传递我们自己的矩阵数据。 总的说来整个程序并不是很难,但我是折腾了有一阵才搞成功,估计还是我太水了的缘故吧。。。