本篇记录一下关于OpenGL程序中绑定各种GLSL变量的一些注意问题(有些是近期编写代码感受强烈的)。以供参考。——ZwqXin.com

本文来源于 ZwqXin (http://www.zwqxin.com/), 转载请注明
      原文地址:http://www.zwqxin.com/archives/shaderglsl/communication-between-opengl-glsl.html

首先回顾一下shader程式的装载(包括vertexShader、geometryShader、fragmentShader,近期新提出的tessellation相关的Shaders没使用过显卡也没支持):

  1. shader代码可以用文本文件保存,可以在程序中用字符串保存,但最终还是必须在程序中以字符串的形式传入Shader对象中;
  2. 创建Shader对象(glCreateShader——成功时返回非0的无符号Handle值,指涉Shader对象);
  3. 把shader代码传入shader对象(glShaderSource——注意此函数的参数,字符流地址参量是GLchar*,不支持宽字符。执行后可删除内存上保存的shader代码字符串副本);
  4. 编译Shader对象——正如我们编译任何代码一样(glCompileShader——应该以GL_COMPILE_STATUS为参调用glGetShaderiv检查编译是否成功。如果代码出现问题会在这个阶段报错,debug时可用glGetError查看更具体的错误类型);
  5. 按上步骤把一个“过程”中需要的各类型shader编译好(通常每种类型最多建一个shader对象——因为对于一个渲染管道(流程)来说,顶点、单几何体、像素都只处理一遍,不可能返回。shaders只是插入这个流程中取代对应的固定管道处理的“外挂”[在抹除固定管道处理、全shader时代来临之前可以这么说],在一个渲染流程中起作用的shaders姑且统称为一个shader过程);
  6. OpenGL通过一个名为shaderProgram的对象来与shader交互。也可以说shaders通过这个对象连接到我们的OpenGL应用程序中,它具体地指涉shader过程。宏观概念上类似于我们平时写的“程序”,不过它是基于GPU的;
  7. 创建这么一个shaderProgram对象(glCreateProgram——成功时返回非0的无符号Handle值,指涉Shader程序对象);
  8. 把之前创建的shaders,一个一个地Attach到这个shaderProgram对象上(glAttachShader——当然理论上可以attach多于一个的同类型完整的shader,譬如vertexShader。但是在一个特定的渲染流程中只允许其中一个起作用,你明白的);
  9. 链接shaderProgram——正如我们链接任何程序一样(glLinkProgram——应该以GL_LINK_STATUS为参调用glGetProgramiv检查链接是否成功。)
  10. 至此一个shader程式装载完毕。在进入一批渲染流程前(即渲染一组物件前)启用这个shaderProgram对象(glUseProgram),它就会在这批渲染流程中起作用了。渲染完后可以(也应该)调用glUseProgram(NULL)来关闭这种介入,或者以其他shaderProgram渲染别的物件。

对以上有疑问或想知道更具体的同学可参考opengl的Document。希望发现bug或者不妥之处的同学在鄙人博客(www.zwqxin.com)指出。

开始正题了。

在openGL3.0+中,shader里的输入输出变量以in/out这种看上去不甚优雅的限定符标识。先不管,我们下面沿用2.0的限定符,说说在shader中传递我们的变量的时候的一些点:

1. attribute变量

一般是指vertex attribute(顶点属性)——每个顶点都有一份。在vertexShader中,我们处理的是每个顶点,而我们希望传入的变量时每个顶点各异的时候,就使用这种变量(在shader中以attribute为限定符),它不必是传统意义上的“顶点属性”(顶点位置、法线之类),但它确实又是一种顶点的“属性”。OpenGL3.0之前(或者说,固定管道被严重BS之前),我们可以很舒心地使用gl_Vertex, gl_MultiTexcoord[], gl_Normal这类内置的attribute变量来指涉传入shader里的传统的顶点属性,但如今其实我们最好习惯于“没有你们的日子”(因为被BS了)。

这种变量需要在GPU里的Shader的存储空间中有固定的位置(地址)。在链接shaderProgram(见上文)之前,这个位置是未确定的,因此我们可以在这个shaderProgram调用glLinkProgram之前,为这个attribute变量指定一个位置(用无符号值表示):glBindAttribLocation:

  1. //为shader中的attribute变量attribName绑定到一个位置
  2. GLuint nHandle = glCreateProgram()
  3. glAttachShader(nHandle, nShaderHandle1);
  4. ....
  5. glBindAttribLocation(nHandle, 2,  "attribName");
  6. glLinkProgram(nHandle);

在我的ZWGLSL类中,封装后提供给上层的shader装载API是setXXShader(建立各类型shader对象)和Load(类似上面的代码,建立shaderProgram,attach,bind和link)。上层怎么指定一个attribute的位置呢?当然可以把glLinkProgram独立起来——不过这样就拆开了上面的代码,上层需要多个调用,个人不喜欢。于是便在类内提供一个map容器:

  1. //Load函数大致:
  2. GLuint nHandle = glCreateProgram()
  3. glAttachShader(nHandle, nShaderHandle1);
  4. ....
  5. for(std::map<GLuint, const GLchar *>::iterator p = m_LocAttribMap.begin();p != m_LocAttribMap.end(); ++p)
  6. {
  7. glBindAttribLocation(m_nShaderProgram, p->first, p->second);
  8. }
  9. glLinkProgram(nHandle);
  10. //BindAttributeLocation函数:
  11. void ZWGLSL::BindAttributeLocation(const GLchar *AttributeName, GLuint nLocation)
  12. {
  13. m_LocAttribMap.insert(std::pair<GLuint, const GLchar *>(nLocation, AttributeName));
  14. }
  15. //外部调用一个shader的装载代码(需要绑定一个attribute变量的位置的情形):
  16. m_ZWShader.SetVertexShader("XXX.vert");
  17. m_ZWShader.SetFragmentShader("XXX.frag");
  18. m_ZWShader.BindAttributeLocation("attribName",  2);
  19. m_ZWShader.Load();

我们利用这个“位置”(上面为2)来指定需要传给shader里的attribue变量的数据(见下文)。

另一种获得这个“位置”的方法:我们不要去显式设定这个位置,而是去获取它。通常,如果shader里有attribute变量,且我们没有为它绑定一个位置(见1上文),那么shaderProgram在链接后,会自动为它分配一个位置。我们可以在任何需要的时候获取(查询)这个位置:glGetAttribLocation,就不必局限于在shaderProgram的创建和链接之间去绑定了:

  1. //GetAttributeLocation函数:
  2. GLint ZWGLSL::GetAttributeLocation(const GLchar *AttributeName)
  3. {
  4. return glGetAttribLocation(m_nShaderProgram, AttributeName);
  5. }
  6. //外部调用一个shader的装载代码:
  7. m_ZWShader.SetVertexShader("XXX.vert");
  8. m_ZWShader.SetFragmentShader("XXX.frag");
  9. m_ZWShader.Load();
  10. ....
  11. //获取一个attribute变量的位置
  12. GLint  nAttribLoc = m_ZWShader.GetAttributeLocation("attribName");
  13. if(-1 != nAttribLoc )
  14. {
  15. //使用
  16. }
 

这里返回一个有符号的int值,因为当要查询的这个变量在shader中不存在,或者它没有作用(非活动的:non-active),就会返回-1,否则才是它的位置。(当我们在shader里定义了一个变量,但是代码里却没见它有什么作为,就说它是非活动的。glGetActiveAttrib可返回那些活动的attributes。)

使用atribute变量的“位置”为它传递数据:

a)传统的glVertex3f类逐点绘制下(使用glVertexAttrib3f函数,以“位置”为首参):

  1. //nAttribLoc是获得的一个vec3的attribute变量的位置
  2. glBegin(GL_QUADS);
  3. glNormal3f(vNormal.x, vNormal.y, vNormal.z);
  4. glVertexAttrib3f(nAttribLoc, vAttribData.x, vAttribData.y, vAttribData.z);
  5. glTexCoord2d(0.0, 0.0);
  6. glVertex3d(pt1.x, pt1.y, pt1.z);
  7. glNormal3f(vNormal.x, vNormal.y, vNormal.z);
  8. glVertexAttrib3f(nAttribLoc, vAttribData.x, vAttribData.y, vAttribData.z);
  9. glTexCoord2d(0.0,  1.0);
  10. glVertex3d(pt2.x, pt2.y, pt2.z);
  11. .......
  12. glEnd();
 

b)VBO【学一学,VBO】下:

  1. //nAttribLoc是获得的一个vec3的attribute变量的位置
  2. glBindBuffer(GL_ARRAY_BUFFER,  nPosVBO);
  3. glEnableClientState(GL_VERTEX_ARRAY);
  4. glVertexPointer(3, GL_FLOAT, 0, NULL);
  5. glBindBuffer(GL_ARRAY_BUFFER, nNormVBO);
  6. glEnableClientState(GL_NORMAL_ARRAY);
  7. glNormalPointer(GL_FLOAT, 0, NULL);
  8. glBindBuffer(GL_ARRAY_BUFFER, nTexcoordVBO);
  9. glEnableClientState(GL_TEXTURE_COORD_ARRAY);
  10. glTexCoordPointer(2, GL_FLOAT, 0, NULL);
  11. glBindBuffer(GL_ARRAY_BUFFER, nAttrbDataVBO);
  12. glEnableVertexAttribArray(nAttribLoc);
  13. glVertexAttribPointer(nAttribLoc, 3, GL_FLOAT, GL_FALSE, 0, NULL);
  14. glDrawElements/glDrawArrays
  15. glDisableVertexAttribArray(nAttribLoc);
  16. glDisableClientState(GL_TEXTURE_COORD_ARRAY);
  17. glDisableClientState(GL_NORMAL_ARRAY);
  18. glDisableClientState(GL_VERTEX_ARRAY);
  19. glBindBuffer(GL_ARRAY_BUFFER, NULL);
 

有必要说明一下,在VBO中,glEnableClientState/glVertexPointer(glNormalPointer,glTexCoordPointer等)可以正确地为shader中内置的gl_Vertex, gl_Normal, gl_MultiTexcoord[]这些attribute变量指定VBO中的数据,但是这些传统顶点属性之外的就需要glEnableVertexAttribArray/glVertexAttribPointer了(事实上在日后不提倡用内置attribute变量后,这些传统的顶点属性也得被一视同仁地用这两个函数指定启用和数据格式)。参数除了特别的首参需要一个“位置”外,基本是一样的(中间多了个不常用的是否normalize数据的参)。

注意,每个顶点属性的数据都依托在一个VBO中了。要想给一个attrbute变量传递数据,请把数据交给一个VBO对象。

2.uniform变量

uniform变量的特点是,对于一个vertexShader内处理的每个顶点(或者fragmentShader内处理的每个像素,诸此类推),它都是不变的。事实上它相当于一个全局量,并非每个顶点/几何/像素所雍有的变量(只是uniform对它们每一个都public而已)。当然了,你想在一个渲染流程中改变它的值也是8可能的。Shader中有gl_ModelViewMatrix等一大批内置的uniform变量(当然在OpenGL3.0/GLSL1.3后它们也被BS了)。

我们通常在shaderProgram链接后获取一个uniform变量的位置,然后向这个位置传送数据(glGetUniformLocation/glUniform)。但是要特别注意的一点是:我们只有在启用了一个shaderProgram后,才能做这样的事情:

  1. //SendUniform函数,其中一个重载版本
  2. void ZWGLSL::SendUniform(const GLchar *UniformName, GLfloat x)
  3. {
  4. GLint location = glGetUniformLocation(m_nShaderProgram,UniformName);
  5. glUniform1f(location, x);
  6. }
  7. inline void Enable() { glUseProgram(m_nShaderProgram); };
  8. inline void Disable(){ glUseProgram(0); };
  9. //传送数据, m_UniformData为一个float数据:
  10. ZWShader.Enable();
  11. ZWShader.SendUniform("uniformName",  m_UniformData);
  12. //Render Something
  13. ZWShader.Disable();
 

可能是不同的shaderProgram都有一套用于分配的“位置”吧,为了不混淆就so了(擦,怎么想起了相对内存地址来了- -)

有些时候,我们身不由己——我们给一个渲染对象类关联一个shader相关类的指针,shaderProgram启用与否完全交给这个渲染对象类——我们还是得在上层为shader指定uniform数据。这时候,可以在shader类指针之外,再关联一个map<uniform位置,uniform数据>(当然了这个“数据”还得根据uniform变量类型来细分),我们只把数据传给这个map。当shader在渲染对象类里头被启用之后,立即就把这些数据都通过glUniform传送。——这其实是面向对象设计要关心的内容了。

是不是可以这样呢?

  1. //not that good!
  2. //somewhere:
  3. glUseProgram(nHandle);
  4. ...//send uniform
  5. glUniform(data used in rendering)...
  6. glUseProgram(nHandle);
  7. //somewhere else
  8. glUseProgram(nHandle);
  9. ...//Render Something
  10. glUseProgram(nHandle);

坐等可能出现的意想不到的悲剧吧。

3.varying变量

这个就是shader之间传递的变量类型了。不是本文关心的。当然还是要提醒一下,在这边的shader里的varying输出,在那边的varying输入就可能是被栅格化了(说通俗点,被插值了)。

4.fragmentShader输出

其实主要是想带出另一个函数:glBindFragDataLocation。其实目前来说,它在数据传递中的作用,其实只是指定了fragmentShader最终的像素颜色信息所要输出的BUFFER(GL_DRAW_BUFFER0 /GL_DRAW_BUFFER1)。这个是OpenGL3.0/GLSL1.3要鄙视我们熟悉的(gl_FragColor/gl_FragData[])用的——把shader里定义的一个输出的out型变量(用以输出最终像素颜色)绑定给一个输出Buffer。

好了,本文到此。有什么其他的,想到再补充了。也欢迎各位的意见——这才是最重要的。

OpenGL/GLSL数据传递小记(2.x)(转)的更多相关文章

  1. OpenGL/GLSL数据传递小记(3.x)(转)

    OpenGL/GLSL规范在不断演进着,我们渐渐走进可编程管道的时代的同时,崭新的功能接口也让我们有点缭乱的感觉.本文再次从OpenGL和GLSL之间数据的传递这一点,记录和介绍基于OpenGL3.x ...

  2. OpenGL进阶(十一) - GLSL4.x中的数据传递

    in out 对于 vertex shader,每个顶点都会包含一次,它的主要工作时处理关于定点的数据,然后把结果传递到管线的下个阶段. 以前版本的GLSL,数据会通过一些内建变量,比如gl_Vert ...

  3. OpenGL笔记<4> 数据传递二

    Sending data to a shader using uniform Preface 上一节我们介绍了通过顶点属性量进行数据传递,今天我们介绍一下通过uniform变量来进行数据传递的方法. ...

  4. OpenGL 笔记<3> 数据传递 一

    Sending data to a shader using vertex attributes and vertex buffer object 上次我们说到着色器的编译和连接,后面的事情没有做过多 ...

  5. EXTJS中grid的数据特殊显示,不同窗口的数据传递

    //EXTJS中grid的数据特殊显示renderer : function(value, metaData, record, rowIndex, colIndex, store, view) { v ...

  6. Activity系列讲解---数据传递

    在Android中,不同的Activity实例可能运行在一个进程中,也可能运行在不同的进程中.因此需要一种特别的机制帮助我们在Activity之间传递消息.Android中通过Intent对象来表示一 ...

  7. vue2.0 组件之间的数据传递

    组件间的数据传递// 父组件<template><div class="order"><dialog-addpro v-on:closedialog= ...

  8. ASP.NET MVC3中Controller与View之间的数据传递总结

    一.  Controller向View传递数据 1.       使用ViewData传递数据 我们在Controller中定义如下: ViewData["Message_ViewData& ...

  9. ASP.NET MVC3中Controller与View之间的数据传递

    在ASP.NET MVC中,经常会在Controller与View之间传递数据,因此,熟练.灵活的掌握这两层之间的数据传递方法就非常重要.本文从两个方面进行探讨: 一.  Controller向Vie ...

随机推荐

  1. csu1808

    csu1808 题意 n 个点间有 m 条地铁,每条地铁可能属于不同的线路,每条地铁有权值即通过时花费的时间,如果乘坐第 i 条地铁来到地铁站 s,再乘坐第 j 条地铁离开,需要花费额外的时间 \(| ...

  2. lua的深拷贝和浅拷贝

    --- Deep copies a table into a new table. -- Tables used as keys are also deep copied, as are metata ...

  3. 几个有用的PHP.ini配置项-路径和目录

    几个有用的PHP.ini配置项-路径和目录 路径和目录1.include_path = string作用域:PHP_INI_ALL默认值:NULL此参数指定的路径是include().require( ...

  4. MySQL索引,如何正确创建MySQL索引?

    索引可以提高数据的检索效率,也可以降低数据库的IO成本,并且索引还可以降低数据库的排序成本.排序分组操作主要消耗的就是CPU资源和内存,所以能够在排序分组操作中好好的利用索引将会极大地降低CPU资源的 ...

  5. eclipse进行Debug的时候,发出“java breakpoint unable to install breakpoint”错误

    错误情况图: 问题的解决方法: 直接点击忽略掉:Don't tell me again 来自网上的答案~~ I had the same error message in Eclipse 3.4.1, ...

  6. python 常用的模块(hashlib)转

    摘要算法简介 Python的hashlib提供了常见的摘要算法,如MD5,SHA1等等. 什么是摘要算法呢?摘要算法又称哈希算法.散列算法.它通过一个函数,把任意长度的数据转换为一个长度固定的数据串( ...

  7. 使用老版本的java api提交hadoop作业

    还是使用之前的单词计数的例子 自定义Mapper类 import java.io.IOException; import org.apache.hadoop.io.LongWritable; impo ...

  8. Hdu 2243 考研路茫茫——单词情结 (AC自己主动机+矩阵)

    哎哟喂.中文题. . .不说题意了. 首先做过POJ 2778能够知道AC自己主动机是能够求出长度为L的串中不含病毒串的数量的. POJ 2778的大概思路就是先用全部给的病毒串建一个AC自己主动机. ...

  9. Django——Model的使用

    Model使用 首先安装MySQL的python连接驱动,windows下安装可下下载,对应python-2.7: https://code.google.com/p/soemin/downloads ...

  10. JavaScript创建块级作用域

    1.JavaScript创建块级作用域 (1)方法一:ES6 (2)方法二:闭包 2.示例 <!DOCTYPE html> <html lang="zh"> ...