GLSL教程 - OpenGL骨架 :
在我们开始介绍OpenGL着色器之前,我们将通过我们将使用的OpenGL应用程序,至少在第一个着色器中。为了弥补OpenGL核心版本中一些丢失的功能,并防止代码过于夸张,我们将使用两个非常简单的库:数学和着色器。
那么让我们来看看OpenGL应用程序中的一些相关位。
主功能
让我们从主要功能开始。要创建OpenGL上下文并提供窗口界面,我们将使用freeGLUT。要访问扩展和OpenGL功能,我们将采用GLEW。初始化过程涉及要求GLUT创建上下文,如GLUT教程的初始化部分所述。
int main(int argc, char **argv) {
// GLUT initialization
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DEPTH|GLUT_DOUBLE|GLUT_RGBA|GLUT_MULTISAMPLE);
glutInitContextVersion (3, 3);
glutInitContextProfile (GLUT_CORE_PROFILE );
glutInitContextFlags(GLUT_DEBUG);
glutInitWindowPosition(100,100);
glutInitWindowSize(512,512);
glutCreateWindow("Lighthouse3D - Simple Shader Demo");
然后我们继续进行回调注册,也在GLUT教程中详细说明。
...
// Callback Registration
glutDisplayFunc(renderScene);
glutReshapeFunc(changeSize);
glutIdleFunc(renderScene);
// Mouse and Keyboard Callbacks
glutKeyboardFunc(processKeys);
glutMouseFunc(processMouseButtons);
glutMotionFunc(processMouseMotion);
glutMouseWheelFunc ( mouseWheel ) ;
...
继续,我们初始化GLEW并打印一些有关我们创建的OpenGL上下文的信息。
...
// Init GLEW
glewExperimental = GL_TRUE;
glewInit();
// print context information
printf ("Vendor: %s\n", glGetString (GL_VENDOR));
printf ("Renderer: %s\n", glGetString (GL_RENDERER));
printf ("Version: %s\n", glGetString (GL_VERSION));
printf ("GLSL: %s\n", glGetString (GL_SHADING_LANGUAGE_VERSION));
...
到目前为止,它们或多或少是标准的东西,我们会一遍又一遍地看到。现在我们已经准备好开始做我们的事了。我们打算调用设置功能。一个用于着色器,一个用于初始化缓冲区和一些OpenGL设置,最后一个函数用于初始化我们正在使用的VSL库。然后我们打电话glutMainLoop
。
...
if (!setupShaders())
return(1);
initOpenGL()
initVSL();
// GLUT main loop
glutMainLoop();
return(0);
}
这就是我们的主要功能。
设置着色器
在我们的示例中设置着色器将使用VSShaderLib来完成,以简化代码。代码首先加载和编译顶点和片段着色器的每个源代码文件。然后,它设置着色器中出现的变量的语义,请参阅Hello World示例。
调用prepareProgram
链接程序,如果成功,它将检索有关着色器中存在的统一变量使用的所有信息。稍后这将有用于设置这些变量。
然后,我们显示各个着色器和程序的infoLog,如果我们有一个有效的程序,则返回一个值指示。
该函数的代码如下:
GLuint setupShaders() {
// Shader for drawing the cube
shader.init();
shader.loadShader(VSShaderLib::VERTEX_SHADER, "shaders/helloWorld.vert");
shader.loadShader(VSShaderLib::FRAGMENT_SHADER, "shaders/helloWorld.frag");
// set semantics for the shader variables
shader.setProgramOutput(0,"outputF");
shader.setVertexAttribName(VSShaderLib::VERTEX_COORD_ATTRIB, "position");
shader.prepareProgram();
printf("InfoLog for Hello World Shader\n%s\n\n", shader.getAllInfoLogs().c_str());
return(shader.isProgramValid());
}
OpenGL和缓冲区初始化
在这个函数中,我们正在执行我们的应用程序所需的更多初始化。这包括基于球面坐标,一些OpenGL设置和顶点阵列对象(VAO)创建来计算初始相机位置。
摄像机位置放置在以原点为中心的球体中,半径为r。它由球面坐标(alpha,beta,r),所有全局变量定义,鼠标控制这些变量。在我们在渲染功能中设置相机之前,我们需要将它们转换为笛卡尔坐标。每次相机移动时都必须这样做,这里,在初始化时获取初始笛卡尔值。
然后,我们继续进行一些常见的OpenGL初始化,例如启用剔除,多重采样和深度测试。
最后一步是设置包含几何体的顶点数组对象,在这种情况下是一个立方体,其数据在头文件中定义。我们开始生成VAO并绑定它。然后我们创建四个缓冲区来保存数据,三个用于顶点属性,一个用于索引。
void initOpenGL()
{
// set the camera position based on its spherical coordinates
camX = r * sin(alpha * 3.14f / 180.0f) * cos(beta * 3.14f / 180.0f);
camZ = r * cos(alpha * 3.14f / 180.0f) * cos(beta * 3.14f / 180.0f);
camY = r * sin(beta * 3.14f / 180.0f);
// some GL settings
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
glEnable(GL_MULTISAMPLE);
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
// create the VAO
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
// create buffers for our vertex data
GLuint buffers[4];
glGenBuffers(4, buffers);
//vertex coordinates buffer
glBindBuffer(GL_ARRAY_BUFFER, buffers[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(VSShaderLib::VERTEX_COORD_ATTRIB);
glVertexAttribPointer(VSShaderLib::VERTEX_COORD_ATTRIB, 4, GL_FLOAT, 0, 0, 0);
//texture coordinates buffer
glBindBuffer(GL_ARRAY_BUFFER, buffers[1]);
glBufferData(GL_ARRAY_BUFFER, sizeof(texCoords), texCoords, GL_STATIC_DRAW);
glEnableVertexAttribArray(VSShaderLib::TEXTURE_COORD_ATTRIB);
glVertexAttribPointer(VSShaderLib::TEXTURE_COORD_ATTRIB, 2, GL_FLOAT, 0, 0, 0);
//normals buffer
glBindBuffer(GL_ARRAY_BUFFER, buffers[2]);
glBufferData(GL_ARRAY_BUFFER, sizeof(normals), normals, GL_STATIC_DRAW);
glEnableVertexAttribArray(VSShaderLib::NORMAL_ATTRIB);
glVertexAttribPointer(VSShaderLib::NORMAL_ATTRIB, 3, GL_FLOAT, 0, 0, 0);
//index buffer
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[3]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(faceIndex), faceIndex, GL_STATIC_DRAW);
// unbind the VAO
glBindVertexArray(0);
}
VSL初始化
VSL,特别是数学库,需要进行少量初始化以便于与着色器进行通信。设置我们将要使用的矩阵的制服是通过单个函数调用完成的,我们将在渲染函数中看到,但为了做到这一点,我们需要为着色器中定义的统一变量定义一些语义。在这里,我们假设我们的着色器将在名为Matrices 的块内定义所有矩阵制服。
void initVSL() {
vsml = VSMathLib::getInstance();
// tell VSL the uniform block name
vsml->setUniformBlockName("Matrices");
// set semantics for the matrix variables
vsml->setUniformName(VSMathLib::PROJ_VIEW_MODEL, "pvm");
vsml->setUniformName(VSMathLib::NORMAL, "normal");
vsml->setUniformName(VSMathLib::VIEW_MODEL, "viewModel");
vsml->setUniformName(VSMathLib::VIEW, "view");
}
changeSize函数
视口设置为整个窗口。然后该函数使用math lib来设置投影矩阵。这个lib的功能与不推荐使用的OpenGL和GLU中的功能非常相似。有关详细信息,请参阅文档。
void changeSize(int w, int h) {
float ratio;
// prevent a divide by zero, when window is zero height
if(h == 0)
h = 1;
// set the viewport to be the entire window
glViewport(0, 0, w, h);
// set the projection matrix
ratio = (1.0f * w) / h;
vsml->loadIdentity(VSMathLib::PROJECTION);
vsml->perspective(53.13f, ratio, 0.1f, 1000.0f);
}
渲染功能
此功能首先清除颜色和深度缓冲区,在模型上加载单位矩阵并查看矩阵,设置摄像机,要求数学库使着色器可以访问矩阵,然后绑定并渲染我们的VAO。
void renderScene(void) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// load identity matrices
vsml->loadIdentity(VSMathLib::VIEW);
vsml->loadIdentity(VSMathLib::MODEL);
// set the camera
vsml->lookAt(camX, camY, camZ, 0,0,0, 0,1,0);
// use our shader
glUseProgram(shader.getProgramIndex());
// send the matrices to the uniform buffer
vsml->matricesToGL();
// draw VAO
glBindVertexArray(vao);
glDrawElements(GL_TRIANGLES, faceCount*3, GL_UNSIGNED_INT, 0);
//swap buffers
glutSwapBuffers();
}
就是这样!转到下一部分以查看第一个着色器示例。