作为一个图形应用程序的一部分,shader定义了这个程序的图形渲染指令。但要让系统按照我们的设计去渲染图形,我们还需要把必要的数据传递给shader。shader是由gpu来负责执行的,那么数据如何能按照我们的要求传入shader,由gpu来获取呢?首先我们来粗略地了解一下shader中的数据类型吧。因为处理器不一样,shader也有一些自己特有的数据类型。标量数据类型有float, int, bool;矢量数据类型有vec2, vec3, vec4,即2、3、4维向量,其中的分量可以是三种标量数据类型中的任何一种),然后是矩阵,同样也有2、3、4维;采样器数据类型有6种:sampler1D,sampler2D,sampler3D,samplerCube,sampler1DShadow和sampler2DShadow。它们的功能是用于纹理采样;此外,还有跟c/c++一样的“结构体”,“数组”等。 shader中的数据可以分为如何三类: 1. Attribute类型。这一类数据是shader中变化频率最快的,它由应用程序传递到vertex shader,为每一顶点指定,一般是描述顶点的属性,如坐标、颜色、法线等。在opengl中,glVertex, glNormal等指令都会把这类数据传递给vertex shader。 2. Uniform类型。这一数据的变化频率相对比较低,一般来说全局的渲染属性就属于这一类数据,如光源的参数、视点的位置等,它们对于所有顶点来说都是一样的。 3. Varying类型。这种类型用于从vertex shader往pixel shader传递数据,vertex shader的计算结果通过它们流入到pixel shader环节。此外,GLSL还有一些内建的uniform和常量类型数据,它们以gl_打头,如uniform mat4 gl_ModelViewMatrix,const int gl_MaxLights = 8 等。在定义自己的变量时需要注意命名冲突的问题。以上是GLSL中数据类型的粗略介绍,详细的内容大家可以参考一下《OpenGL Shading Language, Second Edition》一书,感兴趣的朋友可以向我索取。下面我们转入今天的核心话题:数据传递。对于第1种数据类型——Attribute,OpenGL使用常规的顶点设置指令来传递相关数据,如前面我们提到的glVertex, glNormal等指令。比如glVertex,在指定了顶点的坐标之后,内建数据变量gl_Vertex(attribute vec4 gl_Vertex;)的值会被修改为glVertex指令所指定的值,然后这个值可以由vertex shader中的代码所使用。不过这仅限于系统内置的数据,对于可编程的图形流水线来说,要制造出精美复杂的渲染效果仅仅靠这一些内置的数据是不够的,通常情况下我们需要传入一些具有其他含义的数据给shader,这些数据将存入由OpenGL为其预留的空间。我们来看看下面的指令。指令void glVertexAttrib{1|2|3|4}{s|f|d}(GLuint index, TYPE v) 可以用来指定自定义的attribute数据。这跟OpenGL的其他顶点属性指令在使用上是一样的。其中的index指定了存入的位置(我们可以这么认为),vertex shader可以从这些位置把数据读取出来。另外,还有一些类似的指令,提供了对数组类型、单位化(即绝对值不超过1.0)数据的传递,如下: void glVertexAttrib{1|2|3}{s|f|d}v(GLuint index, const TYPE *v) [数组类型] void glVertexAttrib4{b|s|i|f|d|ub|us|ui}v(GLuint index, const TYPE *v)[数组类型] void glVertexAttrib4Nub(GLuint index, TYPE v) [单位化] void glVertexAttrib4N{b|s|i|f|d|ub|us|ui}v(GLuint index, const TYPE *v)[单位化]更为通用的一种指令是 void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer) 这条指令需要如下指令来配合使用 void glEnableVertexAttribArray(GLuint index)[打开该vertex attribute array] void glDisableVertexAttribArray(GLuint index)[关闭该vertex attribute array]实际上它们跟打开和关闭OpenGL客户端状态的使用方法是一样的。 OK,到此我们已经完成了一半的工作,在启动绘图指令之后(如glDawXxxx系列),应用程序便会把数据传递给shader。剩下的一半工作就是shader如何来访问传入的数据。第一种方案是所有的工作都由GLSL的链接器自动来完成,然后向OpenGL查询需要访问的数据。[用得较少,书中未详细说明,在此略过.....]。第二种方案是绑定,即由应用程序明确的表示把哪些索引(存储位置)绑定到哪些变量(名称),这样,在shader中直接引用变量的名称就可以了,如下: void glBindAttribLocation(GLuint program, GLuint index, const GLchar *name) 该指令把某个shader程序(program)中的index(存储位置)与变量name(变量名称)绑定。这样,程序通过把数据存储到index指定的位置,绑定之后,shader就可以通过name来访问,跟普通变量的使用方式一样。与之相对应的还有一条查询指令,用于查询shader中某个变量所对应的存储区索引位置: GLint glGetAttribLocation(GLuint program, const GLchar *name) 绑定指令可以将一个位置绑定到多个变量,即别名机制,就如同c++里可以创建引用一样;但是一个变量不能对应到多个索引位置,这一点比较容易理解;同时,并非所有的绑定都会生效。如果shader中没有使用某个变量,而这个变量被使用了绑定指令,则它是处于非激活状态的,对于这样的变量GLSL在处理过程中会把它优化掉。通过指令 void glGetActiveAttrib(GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name) 可以查询变量的激活状态。这是Attribute类型的数据传递方式。对于Uniform数据,没有“绑定”,而是通过查询对应变量对应的索引位置来设置。使用方式与Attribute类似,对应于下面一组指令: void glUniform{1|2|3|4}{f|i}(GLint location, TYPE v)[指定普通变量] void glUniform{1|2|3|4}{f|i}v(GLint location, GLuint count, const TYPE v)[指定数组变量] void glUniformMatrix{2|3|4}fv(GLint location, GLuint count, GLboolean transpose, const GLfloat *v)[指定矩阵变量] GLint glGetUniformLocation(GLuint program, const GLchar *name)[查询] void glGetUniformfv(GLuint program, GLint location, GLfloat *params)[查询数组] void glGetUniformiv(GLuint program, GLint location, GLint *params)[查询数组] void glGetActiveUniform(GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name)[查询激活的变量]需要注意一点的是,与Attribute类型不同,Uniform可以定义结构体变量,而结构体变量是无法直接查询的,只能查询到它的成员。比如,A是一个结构体,那么glGetUniformLocation( program, "A" )是无效的,而glGetUniformLocation( program, "A.b" )是有效的(b是A的一个成员变量)。其他的与Attribute类型基本一致。对于采样器类型,则有其专用的指令:glUniform1i 和 glUniform1iv。实际就是把对应的纹理单元编号传递给shader。以上就是关于数据传递的主要内容。还有一些特殊的情况,用得相对较少,在后续涉及到的时候再跟大家一起讨论。好了,我该继续学习去了,之后再跟大家一起分享我的学习成果^_^
阅读(3068) | 评论(1) | 转发(0) |