openGL系列文章目录
uniform
要想渲染一个场景以使它看起来是3D 的,需要构建适当的变换矩阵,并将它们应用于模型的每个顶点。在顶点着色器中应用所需的矩阵运算是最有效的,并且习惯上会将这些矩阵从C++/OpenGL 应用程序发送给着色器中的统一变量。使用“uniform”关键字在着色器中声明统一变量。以下示例声明了用于存储模型-视图和投影矩阵的变量,足够我们的立方体程序使用:
uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
关键字“mat4”表示这些是4×4 矩阵。这里我们将用来保存模型-视图矩阵的变量命名为mv_matrix,并将用来保存投影矩阵的变量命名为proj_matrix。因为3D 变换是4×4 的,因此mat4 是GLSL 着色器统一中常用的数据类型。将数据从C++/OpenGL 应用程序发送到统一变量需要执行以下步骤:(a)获取统一变量的引用;(b)将指向所需数值的指针与获取的统一引用相关联。
假设链接的渲染程序保存在名为“renderingProgram”的变量中,以下代码行表示,我们要把模型-视图和投影矩阵发送到两个统一变量mv_matrix 和proj_matrix 中去:
mvLoc = glGetUniformLocation(renderingProgram,"mv_matrix"); // 获取着色器程序中统一变量的位置
projLoc = glGetUniformLocation(renderingProgram,"proj_matrix");
glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvMat)); // 将矩阵数据发送到统一变量中
glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(pMat));
attribute
相较于如何处理统一变量,了解如何在OpenGL 管线中处理顶点属性非常重要。回想一下,在片段着色器光栅化之前,由顶点定义的图元(例如,三角形)被转换为片段。光栅化过程会线性插值顶点属性值,以便显示的像素能无缝连接建模的曲面。相比之下,统一变量的行为类似于初始化过的常量,并且在每次顶点着色器调用(即从缓冲区发送的每个顶点)中保持不变。统一变量本身不是插值的;无论有多少顶点,它始终包含相同的值。光栅着色器对顶点属性进行的插值在很多方面都很有用。稍后,我们将使用光栅化来插值颜色、纹理坐标和曲面法向量。重要的是要理解通过缓冲区发送到顶点属性的所有值都将在管线中被进一步插值。我们在顶点着色器中看到顶点属性被声明为“in”,表示它们从缓冲区接收值。顶点属性还可以改为声明为“out”,这意味着它们会将值发送到管线中的下一个阶段。例如,
顶点着色器中的以下声明将指定一个名为“color”的顶点属性,该属性输出vec4 类型的值:
out vec4 color;
没有必要为顶点位置声明一个“out”变量,因为OpenGL 有一个内置的vec4 变量用于此目的,它的名字叫作gl_Position。在顶点着色器中,我们将矩阵变换应用于传入的顶点(之前声明为位置的顶点),并将结果赋值给gl_Position:
gl_Position = proj_matrix * mv_matrix * position;
然后,变换后的顶点将自动输出到光栅着色器,最终将相应的像素位置发送到片段着色器。
光栅化过程如图1 所示。在glDrawArrays()函数中指定GL_TRIANGLES 时,光栅化是逐个三角形完成的。首先沿着连接顶点的线开始插值,其精度级别和像素显示密度相关。然后通过沿连接边缘像素的水平线插值来填充三角形的内部空间中的像素。
图1
varying
varying变量主要用于在Shader Stage间进行传递,注意的是在光栅化(Rasterization)的时候,这些变量也会跟着一起被光栅插值。那如果我们不想某个顶点属性被光栅化,该怎么办呢?在[OpenGL常用命令备忘录(Part A)]这篇文章提到的一个古老API,glShadeModel,它在固定管道渲染流水线上能起到控制图元属性是否被插值的功效(需要光栅化时传入参数GL_SMOOTH,不需要时传入GL_FLAT),那么当选择不插值时(GL_FLAT),流水线上发生了什么呢?
假设现在流水线上,经过裁剪、归一化等,生成了一个屏幕上的三角图元(三个顶点上的颜色属性分别是c1、c2、c3),进入光栅化阶段。假如进行插值,三角图元里各像(假设共n个)素会根据其各自位置对三个顶点的颜色值进行线性插值,生成对应的n个颜色值(cList[n]);假如不插值,则该三角形里所有像素都会是同一个值(cConst),这个值可能等于c1、c2或c3其中一个。到底是哪一个呢?这取决于哪个顶点是provoking-vertex(在[乱弹纪录I:Geometry Shader] 中也提及过它)。你可以在OpenGL端通过glProvokingVertex函数改变这个设置(参数GL_FIRST_VERTEX_CONVENTION/GL_LAST_VERTEX_CONVENTION决定取图元绘制顺序的第一个顶点还是最后一个顶点作为provoking-vertex)。
其实要让GLSL中某个作为顶点属性的varying变量不被光栅化,只要在它前面加一个flat关键字就可以了。这样它就像上述的那样,到达Fragmen Shader的图元上所有像素的该varying值都是相同的值(provoking vertex上的值):
varying是GLSL中限定符,varying限定的变量只能在shader之间传递,是Vertex Shader(顶点着色器)的输出,Fragment Shader(片段着色器)的输入,Shader中的声明和类型要保持一致。 varying定义格式如下:
attribute vec4 vPosition;
varying vec4 color;
void main() {
color= vec4(1,0,0,1);
gl_Position = vPosition;
}
#version 430
layout (location = 0) in vec3 vertPos;
layout (location = 1) in vec3 vertNormal;
out vec3 varyingNormal;
out vec3 varyingLightDir;
out vec3 varyingVertPos;
struct PositionalLight
{ vec4 ambient;
vec4 diffuse;
vec4 specular;
vec3 position;
};
struct Material
{ vec4 ambient;
vec4 diffuse;
vec4 specular;
float shininess;
};
uniform vec4 globalAmbient;
uniform PositionalLight light;
uniform Material material;
uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
uniform mat4 norm_matrix;
void main(void)
{ varyingVertPos = (mv_matrix * vec4(vertPos,1.0)).xyz;
varyingLightDir = light.position - varyingVertPos;
varyingNormal = (norm_matrix * vec4(vertNormal,1.0)).xyz;
gl_Position = proj_matrix * mv_matrix * vec4(vertPos,1.0);
}
参考
计算机图形学编程 使用OpenGL和C++