一、基础类型和限定符
1.GLSL基础变量类型:
(1)Float;// IEEE浮点值
(2)int ;
(3)uint;
(4)bool;
(5)sampler; 采样器,作为访问纹理图像的不透明句柄。
OGL实现可能并不严格地实现这些类型,例如整数也可以存储在浮点寄存器中,最大整数也不一定是2^15。
2.变量的作用域
变量可以在使用时候声明,类似C++,不像C(c语言要求在函数开头声明所有使用到的类型)。
在函数外部声明的变量具有全局作用域,他们在着色器程序的所有函数中均可见(program中,也就是shader对象源代码在链接后可以共享该全局变量)。
函数内部的变量作用域和C++相同。
3.变量的初始化和转换
变量声明时候就可以初始化。可以指定为八进制,十进制,十六进制常量。u或U无符号。3E-7等价于3*10^-7, -3E-7等价于-3*10^-7的科学计数法。
例如:
int i, numParticles = 100;
float force, g = -9.8;
bool falling = true;
变量的转换,没有隐式类型转换,要求比C++还要严格,不同类型的转换到要使用类似该类型的构造函数一样的语法。
例如:
float scale = 10.0;
int ten = int(scale);
4.聚合类型(向量和矩阵类型)
GLSL支持每种基本类型的二维,三维,四维向量和矩阵(默认是列主序矩阵)。
float 的 vec2 vec3 vec4; mat2, mat3, mat4, mat2x2, mat2x3, mat3x4, mat4x4,mat4x2等各维。
int类型对应增加前缀i, 如ivec3。
uint类型对应增加前缀u, 如uvec3.
bool类型对应增加前缀b, 如bvec3.
mat3x4含义是3列4行,也就是行主序的mat4x3的写法一样。其实转置矩阵就是本来行主序的矩阵,用列主序类写,当然向量也要用列序来写,这样:v' = v*M; v'^T = (v*M)^T= M^T * v^T;
列式矩阵用v'^T描述向量的结果,其实坐标系意义上都是x,y,z,w所以都是一样的。
在行式矩阵中4D空间引入主要是包含平移的变换也放到矩阵中和透视除法,为了平移时候矩阵最后一列总是( 0,0,0,1)向量; 透视除法时候,主要是因为透视时候存在对结果每个元素统一的除法,可以放到向量第四维去,然后约定向量结果中如果最后一位不是1那么需要除以w,所以一般传入向量w=1。
列式矩阵中4D空间存在也是一样的,只是矩阵最后一列改为最后一行,最后一行平移变为了最后一列。向量最后一列改为了最后一行。全部矩阵向量都用列式表示,右乘矩阵(所以D3D中基于父坐标系表示子坐标系位置的M缩放*M旋转*M平移代数变换顺序,到OGL中就变成了基于local coordinate的M平移^T*M旋转^T*M缩放^T, ^T刚好是列式矩阵;模型到视口的变换,
OGL中也变成了M视口^T * M模型^T),右乘向量实现变换,结果为列式向量即可。
在CG语法中v*M时候,v会转换为行向量,乘积结果等于列式的M^T*v。
例如,世界坐标系中计算法向量:
// the same as mul(transpose(_World2Object), float4(input.normal, 0.0)) but fast.
float3 normalDir = normalize(mul(normalObjectDir, _World2Object).xyz);
向量的使用
向量的构造
vec3 velocity = vec3(0.0, 2.0, 3.0);
向量类型转换:
ivec3 = ivec3(velocity);
向量的截短和拉长:
vec4 color;
vec3 rgb = vec3(color);
vec4 white = vec4(rgb, 1.0);
访问和赋值向量中的元素:
1)通过名称
float r = color.r;
color.r = 1.0f;
2)通过下标
float r = color[0];
color[0] = 1.0f;
3)搅拌式
vec3 luminance = color.rrr;
vec4 color2 = color.abgr;
矩阵的使用
矩阵的初始化:
可以用向量初始化,或单个值指定,但是OGL是列主序矩阵,所以先填充的是第一列。
mat3 m =mat3 (1.0, 0.0, 0.0,
0.0, 1.0, 2.0,
0.0, 0.0, 1.0);
vec3 colum1 = (1.0, 0.0, 0.0);
vec3 colum2 = (1.0, 0.0, 0.0);
vec3 colum3 = (1.0, 0.0, 0.0);
mat3 m = mat3(colum1, colum2, colum3);
mat4 m = mat4(colum1, 1.0,
colum2, 2.0,
colum3, 1.0);
矩阵的访问和赋值:
通过下标形式访问和赋值矩阵的一个向量或者一个元素:
mat4 m = mat4(1.0);
vec4 = m[0]; //矩阵的第一列
float yScale = m[1][1];// 矩阵的第二列,第二行
6.结构
结构定义一组类型不一样的数据作为一个组,方便数据的记录和传递;定义一个结构后会定义一个新的类型,并隐式定义一个构造函数,可以通过这个构造函数来给结构赋值,通过.成员名来访问结构中的数据。
struct Particle( float lifetime, vec3 position, vec3 velocity);
Particle p = Particle(3.0, pos, vel);
7.数组
GLSL中可以定义任何类型的数组,包括结构体,从0到n-1, 但是只能是一维的,不能是二维及以上的。数组的声明:
可以指明大小也可以不指明大小,使用的时候指明大小,数组可以作为函数参数和返回值使用。
float coeff[3];// 等价于float[3] coeff;
int coeff[];
定义时候的初始化:
float coeff[3] = float[3](2.3, 1.0, 3.0);
可以用数组的下标来访问数组元素。
数组长度可以用默认内建.length()得到n大小;
for(int i = 0; i < coeff.length(); i++)
{
coeff[i] *= 1.0f;
}
8.布局限定符layout
顶点着色器输出和片段着色器输入不能使用布局限定符(顶点输入和片段输出中才有,输出的index是用于MRT指定的,默认是0)。
layout(location = 0)表示该变量在顶点属性中的位置为0.
对应顶点属性数组中0:
// 设置(设置或解析)数据到顶点属性的指定位置内
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, vVertices);
// 激活GPU内部顶点属性的指定位置
glEnableVertexAttribArray(0);
"#version 300 es "
"layout(location = 0) in vec4 vPosition; "
"void main() "
"{ "
" gl_Position = vPosition; "
"} ";
9.类型限定符
(1)const 全局常量类型,编译期常量值,可以在函数内部也可以作为函数参数类型。
(2)in 全局变量,着色器阶段输入。(GSLS 1.3以前attribute限定顶点着色器输入,varying限定片段着色器的输入,GSLS 1.3后就消除了)。片段着色器额外的in关键字限定符(和顶点着色器的输出的限定符一样):
centroid 在打开多点采样的时候,强迫一个片段输入变量的采样位于图元像素覆盖的区域内。
smooth 以透视校正的方式插值片段输入变量。
flat 不对片段输入插值(对于所有的片段输入都是和归属的顶点一样的,就像在单调着色中一样)。
noperspective 线性插值片段变量。
例如:flat centroid in fragment;
例如:
// Vertex Shader
smooth centroid out vec3 v_color;
// Fragment shader
smooth centroid in vec3 v_color;
(3)out 全局变量,着色器阶段输出, 顶点着色器的输出变量可以和片段着色器输入变量,使用相同的in关键字限定符,含义和片段中的in关键字相同。
(4)invariant限定符,强制多道渲染算法中,每道着色器渲染时计算位置完全相同(否则因为指令顺序的累积误差,多pass渲染时候,可能前面的计算和后面的计算会存在误差,大多数情况下是可以接受的)
在顶点和片段着色器中都声明为invariant,它可能对着色器性能产生影响,会禁用GLSL编译器所执行的优化。
在调试时候可以用:
#pragma STDGL invariant(all)
强制要求所有的变量都使用不变性。
(5)uniform 指定这个值从应用程序输入给着色器(在着色器真正执行前),在着色器内部图元中保持为常量值。
glUseProgram(programId);
glUniform2f(offsetLocationId, fXOffset, fYOffset);
//绘制三角形,真正根据设置执行着色器程序
glDrawArrays(GL_TRIANGLES, 0, 3);
若uniform变量是由顶点和片段着色器共享的,那么他们必须声明为相同的全局变量,任何类型都可以声明为uniform类型。
用glGetUniformLocation获取变量句柄,例如:GLuint offsetLocationId = glGetUniformLocation(programId, "offset");获取uniform变量句柄。
用glUniform*或glUniformMatrix*设置变量的值,例如:glUniform2f(offsetLocationId, fXOffset, fYOffset);设置uniform变量的值,设置变量可以是一个基础类型,一个向量,一个数组或包含向量的数组。
Uniform修饰结构体,然后用结构体传参:
例如:
struct gl_DepthRangeParameters{
float near;
float far;
float diff;
};
uniform gl_DepthRangeParameters gl_DepthRange;//通过app访问 gl_DepthRange实现通信。
uniform块:
Unform变量可以在指定的uniform块中声明,uniform块可以支持着色器的共享及其他功能。
uniform块是Opengl 3.1引入的,因为uniform的数目越来越增加,经常出现多个着色器程序共享uniform的情况,因为uniform变量会在glLinkProgram的时候产生uniform索引位置,所以不能共享,引入了uniform块,通过统一缓冲区对象(uniform buffer object),实现优化uniform的访问,和使得多着色器程序共享uniform变量。
uniform块的声明:
uniform Matrices {
mat4 ModelView;
mat4 Projection;
mat4 Color;
}
所有类型,除了采样器,都允许放到一个uniform块中。此外,uniform块必须声明为全局作用域。
uniform块的布局限定符
shared 指定uniform块在多个程序对象之间共享(默认的共享设置)。
packed 布局uniform块使得使用的内存最小化,通常不允许跨程序共享。
std140 使用opengl规范中默认的布局。
row_major 使得uniform块中的矩阵按照行主序的方式存储。
column_major 指定矩阵应该按照列主序的方式存储(这是默认的排序)。
uniform块的布局应用:
layout(shared, row_major) uniform uniformBlockName{...}; // 只是声明该uniform块的布局
影响所有后续的uniform布局可以使用:
layout(shared, row_major) uniform;该行之后声明的所有uniform都会使用该布局,直到修改或覆盖uniform声明位置。
访问uniform块中的变量,uniform块的uniform符号并不作为变量作用域,所以两个相同的Uniform块中声明相同的uniform变量将会导致错误,所以访问uniform变量的时候没有必要使用uniform块的名字。
访问uniform块的步骤:
1)glGetUniformBlockIndex获取uniform块索引号
GLuint glGetUniformBlockIndex(GLuint
program, const GLchar *
uniformBlockName
);
program
Specifies the name of a program containing the uniform block.
uniformBlockName
Specifies the address an array of characters to containing the name of the uniform block whose index to retrieve.
2)用glGetActiveUniformBlockiv()使用GL_UNIFORM_BLOCK_DATA_SIZE返回编译生成的uniform block 大小, glGetActiveUniformBlockiv()还可以获取和一个指定的uniform块相关的其它参数。
例如:glGetActiveUniformBlockiv(program, uboIdex, GL_UNIFORM_BLOCK_DATA_SIZE, &uboSize);
3)用malloc申请内存块,获取uniform块中的各个成员类型和大小,程序中的值填写malloc内存块
获取该uniform block的所有成员的索引号,
glGetUniformIndices
retrieves the indices of a number of uniforms within
program
.使用:
void
glGetUniformIndices(GLuint program,
GLsizei uniformCount, // 输入的个数
const GLchar **uniformNames, // 输入的名字
GLuint *uniformIndices); // 输出的索引号
获取这个特定的索引的offset,size,type(或者每个成员的该值调用一次):
glGetActiveUniformsiv — Returns information about several active uniform variables for the specified program object
void
glGetActiveUniformsiv( GLuint program,
GLsizei uniformCount,
const GLuint *uniformIndices,
GLenum pname,
GLint *params);
4)使用glGenBuffers/glBindBuffer/glBufferData填充到缓冲区
申请缓冲区,把缓冲区对象绑定到GL_UNIFORM_BUFFER目标,且把上述应用程序填写的malloc buffer值拷贝到缓冲区中。
5)调用glBindBufferRange/glBindBufferBase将unform块和帧缓冲区对象联系起来
void glBindBufferRange( GLenum target,
GLuint index,
GLuint buffer,
GLintptr offset,
GLsizeiptr size);
void
glBindBufferBase
(GLenum
target
, GLuint
index
, GLuint
buffer
);
将缓冲区对象buffer和uniform块index联系起来。target可以是GL_UNIFORM_BUFFER或者GL_TRANSFORM_FEEDBACK_BUFFER。glBindBufferBase在offset等于0,size等于缓冲区对象的大小。
5)用初始化和修改缓冲区对象的命令来初始化和修改uniform block块的值。
6)如果有多个着色器程序共享一个uniform块,为了避免它为每个程序分配一个不同的块索引,在调用glLinkProgram之前要调用glUniformBlockBinding()来绑定块。
void
glUniformBlockBinding
(GLuint
program
, GLuint
uniformBlockIndex
, GLuint
uniformBlockBinding
);
program
The name of a program object containing the active uniform block whose binding to assign.
uniformBlockIndex
The index of the active uniform block within
program
whose binding to assign.
uniformBlockBinding
Specifies the binding point to which to bind the uniform block with index
uniformBlockIndex
within
program
.(应该是之前和缓冲区对象绑定好的uniform block index).
uniform块使用实例:
"uniform Uniforms = {
vec3 translation; // 平移
float scale; // 缩放
vec4 rotation; //四元数
bool enabled;
}"
uboIndex = glGetUniformBlockIndex(program, "Uniforms");
glGetActiveUniformBlockiv(program, uboIndex, GL_UNIFORM_BLOCK_DATA_SIZE, &uboSize);
GLvoid *buffer = malloc(uboSize);
const char *names[NumUniformMembers] = {
"translation",
"scale",
"rotation",
"enabled",
};
enum {
Translation,
Scale,
Rotation,
Enabled,
NumUniformMembers
}
// 得到每个成员的indices,size,offset, type;用于填充CPU中的memory buffer数据,然后用
glBufferData传输到GPU VBO中。
GLuint indices[NumUniformMembers];
GLuint size[NumUniformMembers];
GLuint offset[NumUniformMembers];
GLuint type[NumUniformMembers];
glGetUniformIndices(program, NumUniformMembers, names, indices);
glGetActiveUniformsiv(program, NumUniformMembers, indices, GL_UNIFORM_OFFSET, offset);
glGetActiveUniformsiv(program, NumUniformMembers, indices, GL_UNIFORM_SIZE, offset);
glGetActiveUniformsiv(program, NumUniformMembers, indices, GL_UNIFORM_TYPE, offset);
使用memcpy将CPU应用程序中的具体变量值通过size,type字节数赋值给buffer中。
void *memcpy(void *dest, const void *src, size_t n);
memcpy(buffer + offset[Scale], &scale, size[Scale] * TypeSize(type[Scale]));
memcpy(buffer + offset[Translation], &scale, size[Translation] * TypeSize(type[Translation]));
memcpy(buffer + offset[Rotation], &scale, size[Rotation] * TypeSize(type[Rotation]));
memcpy(buffer + offset[Enabled], &scale, size[Enabled] * TypeSize(type[Enabled]));
填充满buffer后。
glGenBuffers(1,&ubo);
glBindBuffer(GL_UNIFORM_BUFFER, ubo);
glBufferData(GL_UNIFORM_BUFFER, uboSize, buffer, GL_STATIC_RAW);
glBindBufferBase(GL_UNIFORM_BUFFER, uboIndex, ubo);
二、运算符和语句
1.算术操作符,和C++基本一样,包括优先级,就是没有求模运算符。
2.运算符重载
GLSL中绝大多数运算符都进行了重载,尤其是向量和矩阵的运算操作。
因为运算符重载的存在,OGL中的矩阵右乘向量,矩阵左乘矩阵;如果是用矩阵左乘向量,那么向量会变为行式向量,矩阵不变,所以进行运算需要谨慎,最好查询CG或HLSL的说明文档。
3.语句
if else语句。
switch case default语句。
for,while, do..while语句和C++中一样,OES2.0对循环的限制比较多,3.0基本没有限制,但是为了性能和发热尽量减少循环的使用。
流程控制关键字:
break;
continue;
return;
discard; // 丢弃当前的片断值,且终止片段着色器的执行。
三、函数
内建函数
GLSL CG内建函数见:
http://http.developer.nvidia.com/Cg/index_stdlib.html
HLSL内建函数:
https://msdn.microsoft.com/en-us/library/bb509638(v=vs.85).aspx
自定义函数
用户自定义的函数可以在单个着色器对象内部定义,并在多个着色器程序中使用。
函数的参数有限定符:
in(默认), const in, out, inout(输入输出).
函数参数可以是任何类型,如果是数组类型要指明长度。
函数返回值可以是内置的GLSL类型或自定义的结构体,但不能是数组, 如果函数没有返回值便是void。
形式:
returnType functionName([accesssModifier] type1 variable1, [accesssModifier] type2 variable2, ...)
{
return returnValue; // 除非没有返回值
}
函数不能递归,原因是一些实现真正将编译链接后的着色器程序内嵌到GPU中运行,以支持没有堆栈支持的GPU进行渲染。
四、在GLSL程序中使用OpenGL状态值
Opengl api可以设置的所有值几乎都可以在顶点和片段着色器中访问和设置。
五、在着色器中访问纹理图像
顶点着色器和片段着色器使用纹理图像,OGL实现不一定支持顶点着色器使用纹理图像,但是片段着色器一定能使用纹理图像。
OGL纹理图像在片段着色器中需要用uniform修饰的采样器标识纹理图像,app cpu程序中需要访问和关联纹理对象到着色器采样器中。
例如:
着色器中:
uniform sampler2D tex;
void main()
{
// 需要纹理坐标在图像指定位置提取纹理单元值
gl_FragColor = gl_Color * texture2D(tex, gl_Texcoord[0].st);
}
CPU中:
GLint texSampler = glGetUniformLocation(program, "tex");
// 给采样器分配一个纹理单位,用glUniform1i或glUniform1iv
glUniform1i(texSampler, 2); // set tex to use GL_TEXTURE2
对纹理采样之后进行的计算由着色器代码控制(可以控制mipmap选择的偏移值,并使用投影纹理技巧),但是纹理图像的采样方法仍然由应用程序控制。例如CPU控制一幅纹理图像是否包含mipmap层,这些mipmap层是如何进行采样的,以及用于解析返回纹理单元值(基本是glTexParameter*()函数所设置的参数)的过滤器。
采样器除了可以作为全局变量,还可以作为函数参数,但是必须是类型匹配的纹理采样器。
采样器类型
https://msdn.microsoft.com/en-us/library/windows/desktop/bb509644(v=vs.85).aspx
https://www.khronos.org/opengl/wiki/Sampler_(GLSL)
依赖性纹理读取
读取纹理贴图中的值需要纹理坐标来指定读取,而纹理坐标可以来自顶点着色器自然来自应用程序的指定,也可以来自一个纹理图像。纹理图像还可以存放其它的图形数据,例如高度图,uv纹理坐标,法线贴图,光照材质信息等。
一个纹理的读取依赖另一个纹理的读取,前面一个纹理读取就叫依赖性纹理读取。
例如:
uniform sampler1D coords;
uniform sampler3D volume;
void main()
{
vec3 texCoords = texture1D(coords, gl_TexCoord[0].s);
vec3 volumeColor = texture3D(volume, texCoords);
...
}
纹理缓冲区
GLSL着色器中数组可以用作静态初始化的值,也可以作为值的集合呈现在uniform变量中的一个数组。如果需要考虑超出可用大小限制的数组,那么需要纹理缓冲区(ogl 3.1以前是直接用纹理图像来存储,但不直接),纹理缓冲区类似一维纹理,可以在着色器中使用一个整型值来索引,它提供了较为昂贵的纹理内存,且也支持较大的数据集合。可以像创建任何缓冲区对象一样来创建缓冲区,然后将缓冲区对象关联到纹理缓冲区。
关联缓冲区对象和纹理缓冲区对象的函数是glTexBuffer()
void glTexBuffer( GLenum target, // target是GL_TEXTURE_BUFFER
GLenum internalFormat, // 纹理格式,解释buffer中的数据
GLuint buffer); // 缓冲区对象buffer句柄
类似其它纹理图像,通过调用glActiveTexture()来指定那个纹理单元和纹理缓冲区相关联。
六、着色器预处理器
GLSL预处理指令由GLSL编译器在编译和链接之前解析。用于控制编译,创建条件编译代码以及定义一些值。但是没有文件包含指令。
#define // 宏定义,定义值和访问语句,但是不能提供字符串替换和链接工具
预定义的宏
__LINE__
__FILE__
__VERSION__
#undef // 取消宏的定义
#if //条件编译指令,对#define的处理
#ifdef
#ifndef
#else
#elif
#endif
#error text Cause the compiler to insert text (up to the first
newline character) into the shader information log
#pragma options Control compiler specific options
例如:
#pragma optimize(on) 优化开启(默认是开启的)
#pragma optimize(off)
#pragma debug(on) 开启或禁止调试(默认是禁止调试)
#pragma debug(off)
#pragma STDGL invariant(all) 开启多通道渲染不变性
#extension options Specify compiler operation with respect to
specified GLSL extensions
#version number Mandate a specific version of GLSL version support
#line options Control diagnostic line numbering
七、顶点着色器的细节
1.顶点着色器的输入
(1).glVertex*, glNormal*, glColor*等输入为:
gl_Vertex, gl_Normal, gl_TexCoord[n], gl_Color, gl_SecondaryColor, gl_FogCoord,
gl_MultiTexCoord[n], gl_VertexID,
gl_InstanceID.
(2).用户定义的uniform变量,直接用app设置uniform变量的值。
glGetUniformLocation(programId, "offset");
glUniform2f(offsetLocationId, fXOffset, fYOffset);
(3).用户定义的顶点属性,顶点信息的输入。
1)在OGL 1.3之前使用
GLint
glGetAttribLocation
(GLuint
program, const GLchar *
name
);获取顶点属性的值,或者void glBindAttribLocation(GLuint program,GLuint index,const GLchar *name);来绑定那个index(来自于之前用
glGetAttribLocation得到的index)和name关联。
设置着色器顶点属性或者变量的值为:
glVertexAtrrib*来设置或返回着色器变量中的值。
如果是矩阵变量,需要多次调用glVertexAtrrib4f来设置矩阵每列的值。
2)顶点着色器还可以利用顶点数组这个工具,和其它顶点数据一样,顶点属性变量的值也 可以存储在顶点数组中。通过glDrawArrays(), glDrawElement()等函数进行更新,可以用
glVertexAttribPointer()函数指定更新变量的数组(对VBO数据块进行解析)。
指定解析顶点数组是其中一部分,客户端还需要
启用顶点属性数组是通过glEnableVertexAttribArray(index)函数实现, index必须是0到GL_MAX_VERTEX_ATTRIBS-1之间的值。
//顶点位置和颜色数据
const GLfloat vertexData[] = {
-0.5f, 0.0f, 0.0f, 1.0f,
0.5f, 0.0f, 0.0f, 1.0f,
0.0f, 0.5f, 0.0f, 1.0f,
1.0f, 0.0f, 0.0f, 1.0f,
0.0f, 1.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f, 1.0f
};
glGenVertexArrays(1, &vaoId);
glBindVertexArray(vaoId);
//创建vertex buffer object对象
glGenBuffers(1, &vboId);
glBindBuffer(GL_ARRAY_BUFFER, vboId);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW);
// 指定VAO如何去解析VBO数据块中的数据。
// 在Shader创建之前或创建之后指定都是可以的,因为glDrawXXX时候才真正去处理Opengl状态机中的设置。
//启用顶点位置属性索引,要在display中指定的(vao包装了切换vao即可),因为需要开启顶点属性索引才能绘制,特别是绘制物体多的时候,需要切换才能正确绘制。
// 也可以封装在VAO中,只负责启用glEnableVertexAttribArray不关闭即可。
glEnableVertexAttribArray(0); // 激活顶点属性数组
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0); //指定Position顶点属性数据格式,大小会根据glDrawArrays截断。
//启用顶点颜色属性索引
glEnableVertexAttribArray(1);
//48是4*3*4 = 48, 指定Color顶点属性数据格式,大小会根据glDrawArrays截断。
// 输入到Shader中的时候会并行的从顶点取得一个数据,从颜色取得一个数据作为整个顶点的输入.再进行下一个顶点的输入。
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (void*)48);
2.顶点着色器的输出
1)顶点着色器输出,片段着色器不显式使用的变量
// vec4,经过变换后的顶点坐标(在裁剪坐标系中)
gl_Position 在着色器中通常是= gl_ModelViewProjectionMatrix * gl_Vertex实现。
多道渲染算法中要求gl_Position一致,那么用gl_Position = ftransform();
// float 顶点的点大小
gl_PointSize
// 用于控制点的输出大小,与glPointSize()类似,只是这里的操作是基于顶点进行的。
// 着色器中设置gl_PointSize,那么应用程序需要glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
// 用户定义的裁剪平面坐标系最终的顶点位置(和裁剪平面一样的坐标系,视觉或物体坐标系)
gl_ClipVertex
用户定义的裁剪平面使用glClipPlane函数指定,并且写入到gl_ClipVertex的坐标值中(该坐标值位于裁剪平面中)普通的裁剪空间是在视觉坐标系中,可以把当前的顶点变换到视觉坐标中:
gl_ClipVertex = gl_ModelViewMatrix * gl_Vertex;
2)可以写入到片段着色器,并且它的值可以在片段着色器中读取的变量:
gl_FrontColor(图元正面颜色), gl_BackColor(图元背面颜色), gl_FrontSecondaryColor, gl_BackSecondaryColor,默认情况下选择的都是正面颜色,要选择背面的颜色,用glEnable(GL_VERTEX_PROGRAM_TWO_SIDE),OGL底层将根据底层图元的方向来选择正面或者背面的颜色。
gl_TexCoord[n], // 第n个纹理坐标值
gl_FogFragCoord,// 片段雾坐标值
顶点着色器中使用纹理贴图,和片段着色器中一样,但是不能自动选择mipmap需要用GLSL的texture*Lod函数手工选择mipmap层。确保OGL是否支持顶点着色器纹理,用
GLint nVertexShaderTex;
glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &nVertexShaderTex);返回非0则支持。
顶点着色器输出的信息,可以在反馈模式下输出,在链接shader program之前调用glTransformFeedbackVarings
glGenQueries后,绑定反馈输出到缓冲区对象,然后glBeginTransformFeedback(),开始glDrawCall后查看反馈信息即可。
八、片断着色器的细节
1.片断着色器的输入
OpenGL程序也可以向片断着色器发生数据,片断着色器的数据来源有:
1) gl_FragCoord// vec4类型,着色器中好像没有直接利用
gl_Color// vec4类型
gl_SecondaryColor// vec4类型
gl_TexCoord[n] // vec4类型
gl_FogFragColor ;//float 类型,要是是视觉坐标系中的z值,要么是插值雾坐标
gl_FrontFacing // bool 类型,指定片断是否是正面图元
gl_PointCoord // vec2类型,点块纹理的位置在[0,1]之间,没有启用点块纹理则不起作用
上述的这些值都来自于顶点着色器或固定管线变换后,经过光栅化插值,到达基于单个像素的片断中。
2) 用户定义的uniform变量值,来自应用程序或者顶点着色器。
3)用户定义的顶点属性
2.片断着色器的输出
这些变量经过片断着色器都会产生输出:
1)gl_FragColor 是片断输出的最终颜色值,如果有辅助颜色,那么混合的辅助颜色。
2)gl_FragDepth 片断的最终深度,用于后面的深度测试,片断中无法修改x,y值,但是可以修改z值。
3)gl_FragData[n];gl_FragData数组允许写入到额外的缓冲区中
片断着色器可以将值写入到gl_FragColor或gl_FragData中,但是不能同时写入到两者。
写入到gl_FragColor中是写入了颜色缓冲区中,写入gl_FragData[n]中是写入了glDrawBuffers函数的数组的第n个缓冲区中,可以同时写入到多个非颜色缓冲区的glDrawBuffers指定的缓冲区中。
GLSL 1.3后允许给片断着色器的输出变量重命名,且可以控制这些输出到各种绑定的绘制缓冲区中。可以在链接之前指定片断输出名到一个缓冲区的映射,也可以在链接之后询问程序来确定变量的布局。
链接之前指定片断输出名到一个缓冲区的映射,用:
void
glBindFragDataLocation
(GLuint
program
, GLuint
colorNumber
, const char *
name
);
program
The name of the program containing varying out variable whose binding to modify
colorNumber
是0到GL_MAX_DRAW_BUFFERS的绘制缓冲区索引。
The color number to bind the user-defined varying out variable to
name
是非gl_开头的用户自定义的变量,如果是gl_开头则会报错
The name of the user-defined varying out variable whose binding to modify
链接之后可以通过:
GLint
glGetFragDataLocation( GLuint program, const char * name);
query the bindings of color numbers to user-defined varying out variables.
program The name of the program containing varying out variable whose binding to query.
name The name of the user-defined varying out variable whose binding to query.
返回和来自GLSL program的name输出相关的颜色缓冲区索引,利用该索引应该可以访问到该值,或者操作写入到
colorNumber缓冲区中的着色器输出结果,实现自己需要的缓存值和实现图形结果。