高级计算机图形学 中国科学技术大学计算机学院 黄章进 zhuang@ustc.edu.cn
第十章之第四节 GLSL(III) 2
内容 OpenGL 与着色器的通信 attribute 变量 uniform 变量 着色器示例 顶点着色器 片段着色器 3
连接着色器与 OpenGL 调用 glcreateshader 创建着色器对象 调用 glshadersource 为着色器加载源代码 调用 glcompileshader 编译每个着色器 调用 glcreateprogram 创建程序对象 调用 glattachshader 把着色器对象连接到程序对象 调用 gllinkprogram 链接程序对象, 生成可执行程序 调用 gluseprogram 安装可执行程序替换 OpengGL 固定功能流水线处理模块 用 uniform 和 attribute 变量在应用程序和着色器间通信 4
应用程序 -> 着色器通信 OpenGL 应用程序有两种方式向着色器发送值 使用内置和用户定义的全局变量 uniform 变量 attribute 变量 使用纹理 纹理可解释为图像或数据数组 5
内置顶点属性 内置顶点属性 gl_color /gl_normal /gl_vertex 等 glbegin/glend 间用 glcolor /glnormal /glvertex 等函数指定 顶点数组方式 6
活动属性变量 活动 (active) 属性变量 : 由编译器和链接器决定的顶点着色器执行时会访问的属性 仅只声明从不使用的是非活动属性 程序对象链接失败, 如果活动的内置和自定义属性数目多于 GL_MAX_VERTEX_ATTRIBS (16) 7
自定义顶点属性 当程序对象成功链接后, 链接器生成一张活动属性变量名表 用 glgetattriblocation 查询活动属性变量的内存位置 在 glbegin/glend 间调用 glvertexattrib 给属性变量置值, 或通过 glvertexattribpointer 和 glenablevertexattribarray 来使用顶点数组 8
获取属性变量的索引 GLint glgetattriblocation(gluint program, const GLchar *name); 返回上一次链接时绑定到程序对象 program 中活动属性变量 name 的索引 如果 name 不是 program 的一个活动属性变量, 或是内置属性变量 ( 以 gl_ 开头 ), 返回 -1 如果 name 是活动属性矩阵, 返回矩阵第一列的索引 后一列的索引为前一列的索引加 1 9
显式绑定索引 void glbindattriblocation(gluint program, GLuint index, const GLchar *name); 指定下一次链接时绑定到程序对象 program 中属性变量 name 的索引为 index 如果 name 是内置属性变量 ( 以 gl_ 开头 ), 产生一个 GL_ INVALID_OPERATION 错误 如果 name 之前绑定过, 则索引被 index 替换 如果 name 是属性矩阵,index 绑定到第一列 index 取值 [0, GL_MAX_VERTEX_ATTRIBS-1] 程序成功链接后, 绑定才生效 10
设置顶点属性的值 void glvertexattrib{1234}{sfd}(gluint index, TYPE values); void glvertexattrib{1234}{sfd}v(gluint index, const TYPE *values); void glvertexattrib4{bsifd ubusui}v(gluint index, const TYPE *values); 设置索引为 index 的属性变量 (vec4) 的值 如果没有显式设置所有 4 个值,y 和 z 默认为 0.0,w 为 1.0 设置 n(=2,3,4) 列矩阵的属性值, 需要调用 n 次 glvertexattrib*() 分别为每一列加载值 属性 0 对应于内置属性 gl_vertex, 从而调用 glvertex 和以索引为 0 调用 glvertexattrib 等价 11
例子 顶点着色器中 : attribute float height; OpenGL 中 : GLint loc = glgetattriblocation(p,"height"); glbegin(gl_triangle_strip); glvertexattrib1f(loc,2.0); glvertex2f(-1,1); glvertexattrib1f(loc,2.0); glvertex2f(1,1); glvertexattrib1f(loc,-2.0); glvertex2f(-1,-1); glvertexattrib1f(loc,-2.0); glvertex2f(1,-1); glend(); 12
顶点属性数组 void glvertexattribpointer(gluint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer); 为索引值为 index 的属性变量的数组指定位置 pointer 和数据格式 void glenablevertexattribarray(gluint index) void gldisablevertexattribarray(gluint index) 启用 / 禁用属性 index 的顶点属性数组 启用后, 就可用 gldrawarrays() 等来为顶点属性变量加载值 13
例子 顶点着色器中 : attribute float height; OpenGL 中 : float vertices[8] = {-1,1, 1,1, -1,-1, 1,-1}; float heights[4] = {2,2,-2,-2}; GLint loc = glgetattriblocation(p,"height"); glenableclientstate(gl_vertex_array); glenablevertexattribarray(loc); glvertexpointer(2,gl_float,0,vertices); glvertexattribpointer(loc,1,gl_float,0,0,heights); 14
一致 (uniform) 变量 一致变量的值在图元 / 帧 / 场景内保持不变 内置 : 变换矩阵 裁剪平面 光源 材料 雾 用户定义 活动一致变量 : 由编译器和链接器决定的着色器执行时会访问的一致变量 程序对象链接失败, 如果活动的内置和自定义一致变量数目多于 GL_MAX_VERTEX_UNIFORM_COMPONENTS (512) 15
指定一致变量 当程序对象成功链接后, 链接器生成一张活动一致变量名表, 变量初值置为 0(GL_FALSE) 用 glgetuniformlocation 查询活动一致变量的内存位置 调用 gluniform 给一致变量加载值 不同于属性变量, 一致变量的位置 ( 索引 ) 不能显式指定 16
获取一致变量的索引 GLint glgetuniformlocation(gluint program, const GLchar *name); 返回上一次链接时绑定到程序对象 program 中活动一致变量 name 的索引 如果 name 不是 program 的一个活动一致变量, 或是保留一致变量 ( 以 gl_ 开头 ), 返回 -1 name 不能是结构 结构数组 向量 / 矩阵的部分 对结构和数组, 在 name 里用. 和 [] 来指定结构字段和数组元素 数组第一个元素的位置可在 name 中用数组名或数组名加 [0] 获取 17
设置一致变量的值 void gluniform{1234}{if}( GLint location, TYPE values ); void gluniform{1234}{if}v( GLint location, GLsizei count, const TYPE *values ); void gluniformmatrix{234}fv( GLint location, GLsizei count, GLboolean transpose, const GLfloat *values ); void gluniformmatrix{2x3,3x2,2x4,4x2,3x4,4x3}fv( GLint location, GLsizei count, GLboolean transpose, const GLfloat *values ); 设置当前使用程序对象在位置 location 的一致变量的值 18
设置一致变量的值 向量形式把 count 组值加载到 uniform 数组的 location 位置开始的 count 个元素 这里,location 不是数组元素下标 矩阵形式中的 transpose 为 GL_TURE 表示 values 按行主序指定 ; 否则, 按列主序指定 上述函数可用于加载布尔型变量, 自动转换类型 当函数名指定的大小和类型 ( 除去布尔型 ) 和着色器中声明的 uniform 变量不匹配时, 报错 当 count>1, 而着色器中声明的 uniform 变量不是数组时, 报错 19
例子 着色器中 : uniform float specintensity; uniform vec4 speccolor; uniform float t[2]; uniform vec4 colors[3]; 20
例子 ( 续 ) OpenGL 中 : GLint loc1,loc2,loc3,loc4; float specintensity = 0.98; float sc[4] = {0.8,0.8,0.8,1.0}; float threshold[2] = {0.5,0.25}; float colors[12] = {0.4,0.4,0.8,1.0, 0.2,0.2,0.4,1.0, 0.1,0.1,0.1,1.0}; loc1 = glgetuniformlocation(p,"specintensity"); gluniform1f(loc1, specintensity); loc2 = glgetuniformlocation(p,"speccolor"); gluniform4fv(loc2, 1, sc); loc3 = glgetuniformlocation(p,"t"); gluniform1fv(loc3, 2, threshold); loc4 = glgetuniformlocation(p,"colors"); gluniform4fv(loc4, 3, colors); 21
uniform 向量和数组 着色器中 : uniform vec4 speccolor; uniform float t[2]; OpenGL 中 : GLint loc1,loc2,loc3; float sc[4] = {0.8,0.8,0.8,1.0}; float threshold[2] = {0.5,0.25}; loc2 = glgetuniformlocation(p,"speccolor"); gluniform4f(loc2, sc[0], sc[1], sc[2], sc[3]); // gluniform4fv(loc2, 1, sc); /* 等价形式 */ loct0 = glgetuniformlocation(p, "t[0]"); gluniform1f(loct0, threshold[0]); loct1 = glgetuniformlocation(p, "t[1]"); gluniform1f(loct1, threshold[1]); 22
着色器示例 顶点着色器 波动 渐变 粒子系统 非真实感着色 Phong 光照 片段着色器 Phong 光照 环境映射 凹凸映射 23
波动顶点着色器 uniform float time; uniform float xs, zs, // frequencies uniform float h; // height scale void main() { vec4 t = gl_vertex; t.y = gl_vertex.y + h*sin(time + xs*gl_vertex.x) + h*sin(time + zs*gl_vertex.z); gl_position = gl_modelviewprojectionmatrix*t; } attribute 变量和 uniform 变量在顶点着色器内只读 24
Glint timeparam; timeparam = glgetuniformlocation(program, "time"); void idle() { gluniform1f(timeparam, (GLfloat) glutget(glut_elapsed_time)); glutpostredisplay(); } 25
渐变 (morphing) 效果 渐变 : 一个物体平滑地变换到另一个物体 假设两个物体的顶点有一一对应关系 顶点着色器需要输出一个由对应顶点对插值得到的顶点 26
一个顶点通过 gl_vertex 传入, 对应顶点用顶点属性变量传入 attribute vec4 vertices2; uniform float time; void main() { float s = 0.5*(1.0+sin(0.001*time)); vec4 t = mix(gl_vertex, vertices2, s); gl_position = gl_modelviewprojectionmatrix*t; gl_frontcolor = gl_color; } mix(x, y, a) 返回 x * (1.0-a) + y * a 27
GLint vertices2param; vertices2param = glgetattriblocation(program, "vertices2"); #define N 50 GLfloat vertices_one[n][3], vertices_two[n][3]; glbegin(gl_triangles); for (int i = 0; i < N; i++) { glvertexattrib3fv(vertices2param, vertices_two[i]); glvertex3fv(vertices_one[i]); } glend(); 28
粒子 (particle) 系统 粒子系统的基本思想 : 用真实或自定义物理规律来控制粒子的运动 每一时间步, 要为每个粒子确定一个新位置 考虑符合牛顿定律的质点, 质量为 m, 初始位置为 (x 0,y 0,z 0 ), 初始速度为 (v x,v y,v z ), 重力加速度为 g, 则在 t 时刻的位置为 x(t) = x 0 + v x t, y(t) = y 0 + v y t + g t 2 / (2m), z(t) = z 0 + v z t. 29
attribute vec3 vel; // 初始速度 uniform float g, m, t; void main() { vec3 object_pos; object_pos.x = gl_vertex.x + vel.x * t; object_pos.y = gl_vertex.y + vel.y * t + g/(2.0*m)*t*t; object_pos.z = gl_vertex.z + vel.z * t; gl_position = gl_modelviewprojectionmatrix * vec4(object_pos,1); } 30
非真实感着色 根据光线和法向的夹角给对象赋两种颜色 根据视线和方向的夹角把对象轮廓赋为黑色 const vec4 yellow = vec4(1.0, 1.0, 0.0, 1.0); const vec4 red = vec4(1.0, 0.0, 0.0, 1.0); const vec4 black = vec4(0.0, 0.0, 0.0, 1.0); if(dot(l, N) > 0.5) gl_frontcolor = yellow; else gl_frontcolor = red; if(abs(dot(e, N)) < 0.1) glfrontcolor = black; 31
Phong 光照模型 I =k d I d I =k d I d l n + k s I s (r v ) α + k a I a l n + k s I s (n h ) β + k a I a 32
内置 uniform 变量 struct gl_lightsourceparameters { vec4 ambient; // Acli vec4 diffuse; // Dcli vec4 specular; // Scli vec4 position; // Ppli vec4 halfvector; // Derived: Hi vec3 spotdirection; // Sdli float spotexponent; // Srli float spotcutoff; // Crli // (range: [0.0,90.0], 180.0) float spotcoscutoff; // Derived: cos(crli) // (range: [1.0,0.0],-1.0) float constantattenuation; // K0 float linearattenuation; // K1 float quadraticattenuation; // K2 }; uniform gl_lightsourceparameters gl_lightsource[gl_maxlights]; 33
struct gl_lightmodelparameters { vec4 ambient; // Acs }; uniform gl_lightmodelparameters gl_lightmodel; struct gl_materialparameters { vec4 emission; // Ecm vec4 ambient; // Acm vec4 diffuse; // Dcm vec4 specular; // Scm float shininess; // Srm }; uniform gl_materialparameters gl_frontmaterial; uniform gl_materialparameters gl_backmaterial; 34
Phong 光照 光源位置 gl_lightsource[i].position 在视点坐标系中给出 视点在视点坐标系的原点 N: 视点坐标系中的法向量 L: 视点坐标系中的顶点到光源向量 ( 光线 ) E: 视点坐标系中的顶点到视点向量 ( 视线 ) R: 视点坐标系中的理想反射向量 H: 视点坐标系中 L 和 E 的中值向量 35
改进的 Phong 顶点着色器 I void main(void) /* modified Phong vertex shader (without distance term) */ { gl_position = gl_modelviewprojectionmatrix * gl_vertex; vec4 ambient, diffuse, specular; vec4 eyeposition = gl_modelviewmatrix * gl_vertex; vec4 eyelightpos = gl_lightsource[0].position; vec3 N = normalize(gl_normalmatrix * gl_normal); vec3 L = normalize(eyelightpos.xyz - eyeposition.xyz); vec3 E = -normalize(eyeposition.xyz); vec3 H = normalize(l + E); 36
改进的 Phong 顶点着色器 II /* compute diffuse, ambient, and specular contributions */ float f = 1.0; float Kd = max(dot(l, N), 0.0); float Ks = pow(max(dot(n, H), 0.0), gl_frontmaterial.shininess); if (dot(l,n) < 0.0) f = 0.0; ambient = gl_frontlightproduct[0].ambient; diffuse = Kd*gl_FrontLightProduct[0].diffuse; specular = f*ks*gl_frontlightproduct[0].specular; } gl_frontcolor = ambient+diffuse+specular; 37
基于片段的 Phong 光照 利用 varying 变量把属性从顶点着色器传递到片断着色器 法向量 N 光线向量 L 视线向量 E 38
基于片段光照的顶点着色器 varying vec3 N, L, E; void main() { gl_position = gl_modelviewprojectionmatrix * gl_vertex; vec4 eyeposition = gl_modelviewmatrix * gl_vertex; vec4 eyelightpos = gl_lightsource[0].position; } N = normalize(gl_normalmatrix * gl_normal); L = normalize(eyelightpos.xyz - eyeposition.xyz); E = -normalize(eyeposition.xyz); 39
改进 Phong 光照片段着色器 I varying vec3 N; varying vec3 L; varying vec3 E; void main() { vec3 Normal = normalize(n); vec3 Light = normalize(l); vec3 Eye = normalize(e); vec3 Half = normalize(eye + Light); 40
改进 Phong 光照片段着色器 II float f = 1.0; float Kd = max(dot(normal, Light), 0.0); float Ks = pow(max(dot(half, Normal), 0.0), gl_frontmaterial.shininess); vec4 diffuse = Kd * gl_frontlightproduct[0].diffuse; if (dot(normal, Light) < 0.0) f = 0.0; vec4 specular = f * Ks * gl_frontlightproduct[0].specular; vec4 ambient = gl_frontlightproduct[0].ambient; } gl_fragcolor = ambient + diffuse + specular; 41
效果对比 逐顶点光照 逐片段光照 42
采样器 (Samplers) 提供对纹理对象的访问 定义了 1, 2, 和 3 维纹理以及立方体贴图的采样器 在着色器中 : uniform sampler2d mytexture; vec2 texcoord; vec4 texcolor = texture2d(mytexture, texcoord); 在应用程序中 : texmaplocation = glgetuniformlocation(myprog, mytexture ); gluniform1i(texmaplocation, 0); /* assigns to texture unit 0 */ 43
片段着色器的应用 纹理映射 平滑明暗环境映射凹凸映射 44
立方体贴图 用六张 2D 纹理图组成立方图纹理 OpenGL 支持立方体贴图 GLSL 通过立方图采采样器来支持 vec4 texcolor = texturecube(mycube, texcoord); 纹理坐标必须是 3D 的 45
立方图纹理示例 46
环境映射 用反射向量在立方图中定位纹理 47
用着色器实现环境映射 通常在世界坐标系中计算环境映射, 由于模型矩阵的作用, 世界坐标系可能会不同于对象坐标系 对象的位置和法向在对象坐标系中指定 把模型矩阵作为 uniform 变量传递给着色器 也可用于反射贴图或折射贴图 ( 例如模拟水 ) 48
反射贴图顶点着色器 varying vec3 R; void main(void) { gl_position = gl_modelviewprojectionmatrix*gl_vertex; vec3 N = normalize(gl_normalmatrix*gl_normal); vec4 eyepos = gl_modelviewmatrix*gl_vertex; R = reflect(-eyepos.xyz, N); } 49
反射贴图片段着色器 varying vec3 R; uniform samplercube texmap; void main(void) { gl_fragcolor = texturecube(texmap, R); } 50
凹凸映射 对每个片段扰动法向 把扰动存储为纹理 51
第 4 次作业 :GLSL 着色器 网格显示 : 可对网格进行交互式观察 顶点变换动画 : 用顶点着色器实现例如波动效果的网格 基于片段的 Phong 明暗处理 : 有距离衰减项, 可考虑实现不同类型光源 ( 方向光 点光源 聚光灯 ) 技术 + 创意 2010-1-5 前提交 52