本篇记录一下关于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. Xamarin XAML语言教程Progress属性设置进度条进度

    Xamarin XAML语言教程Progress属性设置进度条进度 在图12.19~12.21中我们看到的是没有实现加载的进度条,即进度条的当前进度为0,如果开发者想要修改当前进度,可以使用两种方式: ...

  2. Beginning iOS 8 Programming with Swift-TableView

    UITableView控件使用 使用UITableView,在控件库中,拖拽一个Table View到ViewController中,在Controller的后台代码中需要继承UITableViewD ...

  3. haproxy代理kibana、nginx代理kibana并实现登录验证

    在使用ELK进行日志统计的时候,由于Kibana自身并没有身份验证的功能,任何人只要知道链接地址就可以正常登录到Kibana控制界面,由于日常的查询,添加和删除日志都是在同一个web中进行,这样就有极 ...

  4. [COCI2017-2018 Contest5] Birokracija

    题目描述 Mirko has become CEO of a huge corporation. This corporation consists of ​N people, labeled fro ...

  5. cf 546C Soldier and Cards

    题目链接:C. Soldier and Cards Two bored soldiers are playing card war. Their card deck consists of exact ...

  6. RTU模式与ASCII模式有什么不同

    所有设备必须必须实现 RTU 模式.ASCII 传输模式是选项,即默认设置必须为 RTU 模式. 当设备使用RTU (Remote Terminal Unit) 模式在 Modbus 串行链路通信, ...

  7. WCF IIS上部署服务

    一.选择应用程序池:.Net Framework 4.0集成模式 二.IIS Access is denied:程序所在文件夹给予Everyone权限 三.HTTP 错误 500.21 - Inter ...

  8. log4j教程 8、日志格式化

    Apache log4j 提供了各种布局对象,每一个对象都可以根据各种布局格式记录数据.另外,也可以创建一个布局对象格式化测井数据中的特定应用的方法. 所有的布局对象 - Appender对象收到 L ...

  9. 机器学习第4课:多变量线性回归(Linear Regression with Multiple Variables)

    4.1  多维特征 目前为止,我们探讨了单变量/特征的回归模型,现在我们对房价模型增加更多的特征, 例如房间数楼层等,构成一个含有多个变量的模型,模型中的特征为(x1,x2,...,xn).

  10. spring boot 引用外部配置文件

    java -jar xx.jar -Dspring.config.location=/data/apps/xx/application-prod.properties