OpenGL/GLSL数据传递小记(2.x)(转)
本篇记录一下关于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没使用过显卡也没支持):
- shader代码可以用文本文件保存,可以在程序中用字符串保存,但最终还是必须在程序中以字符串的形式传入Shader对象中;
- 创建Shader对象(glCreateShader——成功时返回非0的无符号Handle值,指涉Shader对象);
- 把shader代码传入shader对象(glShaderSource——注意此函数的参数,字符流地址参量是GLchar*,不支持宽字符。执行后可删除内存上保存的shader代码字符串副本);
- 编译Shader对象——正如我们编译任何代码一样(glCompileShader——应该以GL_COMPILE_STATUS为参调用glGetShaderiv检查编译是否成功。如果代码出现问题会在这个阶段报错,debug时可用glGetError查看更具体的错误类型);
- 按上步骤把一个“过程”中需要的各类型shader编译好(通常每种类型最多建一个shader对象——因为对于一个渲染管道(流程)来说,顶点、单几何体、像素都只处理一遍,不可能返回。shaders只是插入这个流程中取代对应的固定管道处理的“外挂”[在抹除固定管道处理、全shader时代来临之前可以这么说],在一个渲染流程中起作用的shaders姑且统称为一个shader过程);
- OpenGL通过一个名为shaderProgram的对象来与shader交互。也可以说shaders通过这个对象连接到我们的OpenGL应用程序中,它具体地指涉shader过程。宏观概念上类似于我们平时写的“程序”,不过它是基于GPU的;
- 创建这么一个shaderProgram对象(glCreateProgram——成功时返回非0的无符号Handle值,指涉Shader程序对象);
- 把之前创建的shaders,一个一个地Attach到这个shaderProgram对象上(glAttachShader——当然理论上可以attach多于一个的同类型完整的shader,譬如vertexShader。但是在一个特定的渲染流程中只允许其中一个起作用,你明白的);
- 链接shaderProgram——正如我们链接任何程序一样(glLinkProgram——应该以GL_LINK_STATUS为参调用glGetProgramiv检查链接是否成功。)
- 至此一个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:
- //为shader中的attribute变量attribName绑定到一个位置
- GLuint nHandle = glCreateProgram()
- glAttachShader(nHandle, nShaderHandle1);
- ....
- glBindAttribLocation(nHandle, 2, "attribName");
- glLinkProgram(nHandle);
在我的ZWGLSL类中,封装后提供给上层的shader装载API是setXXShader(建立各类型shader对象)和Load(类似上面的代码,建立shaderProgram,attach,bind和link)。上层怎么指定一个attribute的位置呢?当然可以把glLinkProgram独立起来——不过这样就拆开了上面的代码,上层需要多个调用,个人不喜欢。于是便在类内提供一个map容器:
- //Load函数大致:
- GLuint nHandle = glCreateProgram()
- glAttachShader(nHandle, nShaderHandle1);
- ....
- for(std::map<GLuint, const GLchar *>::iterator p = m_LocAttribMap.begin();p != m_LocAttribMap.end(); ++p)
- {
- glBindAttribLocation(m_nShaderProgram, p->first, p->second);
- }
- glLinkProgram(nHandle);
- //BindAttributeLocation函数:
- void ZWGLSL::BindAttributeLocation(const GLchar *AttributeName, GLuint nLocation)
- {
- m_LocAttribMap.insert(std::pair<GLuint, const GLchar *>(nLocation, AttributeName));
- }
- //外部调用一个shader的装载代码(需要绑定一个attribute变量的位置的情形):
- m_ZWShader.SetVertexShader("XXX.vert");
- m_ZWShader.SetFragmentShader("XXX.frag");
- m_ZWShader.BindAttributeLocation("attribName", 2);
- m_ZWShader.Load();
我们利用这个“位置”(上面为2)来指定需要传给shader里的attribue变量的数据(见下文)。
另一种获得这个“位置”的方法:我们不要去显式设定这个位置,而是去获取它。通常,如果shader里有attribute变量,且我们没有为它绑定一个位置(见1上文),那么shaderProgram在链接后,会自动为它分配一个位置。我们可以在任何需要的时候获取(查询)这个位置:glGetAttribLocation,就不必局限于在shaderProgram的创建和链接之间去绑定了:
- //GetAttributeLocation函数:
- GLint ZWGLSL::GetAttributeLocation(const GLchar *AttributeName)
- {
- return glGetAttribLocation(m_nShaderProgram, AttributeName);
- }
- //外部调用一个shader的装载代码:
- m_ZWShader.SetVertexShader("XXX.vert");
- m_ZWShader.SetFragmentShader("XXX.frag");
- m_ZWShader.Load();
- ....
- //获取一个attribute变量的位置
- GLint nAttribLoc = m_ZWShader.GetAttributeLocation("attribName");
- if(-1 != nAttribLoc )
- {
- //使用
- }
这里返回一个有符号的int值,因为当要查询的这个变量在shader中不存在,或者它没有作用(非活动的:non-active),就会返回-1,否则才是它的位置。(当我们在shader里定义了一个变量,但是代码里却没见它有什么作为,就说它是非活动的。glGetActiveAttrib可返回那些活动的attributes。)
使用atribute变量的“位置”为它传递数据:
a)传统的glVertex3f类逐点绘制下(使用glVertexAttrib3f函数,以“位置”为首参):
- //nAttribLoc是获得的一个vec3的attribute变量的位置
- glBegin(GL_QUADS);
- glNormal3f(vNormal.x, vNormal.y, vNormal.z);
- glVertexAttrib3f(nAttribLoc, vAttribData.x, vAttribData.y, vAttribData.z);
- glTexCoord2d(0.0, 0.0);
- glVertex3d(pt1.x, pt1.y, pt1.z);
- glNormal3f(vNormal.x, vNormal.y, vNormal.z);
- glVertexAttrib3f(nAttribLoc, vAttribData.x, vAttribData.y, vAttribData.z);
- glTexCoord2d(0.0, 1.0);
- glVertex3d(pt2.x, pt2.y, pt2.z);
- .......
- glEnd();
b)VBO【学一学,VBO】下:
- //nAttribLoc是获得的一个vec3的attribute变量的位置
- glBindBuffer(GL_ARRAY_BUFFER, nPosVBO);
- glEnableClientState(GL_VERTEX_ARRAY);
- glVertexPointer(3, GL_FLOAT, 0, NULL);
- glBindBuffer(GL_ARRAY_BUFFER, nNormVBO);
- glEnableClientState(GL_NORMAL_ARRAY);
- glNormalPointer(GL_FLOAT, 0, NULL);
- glBindBuffer(GL_ARRAY_BUFFER, nTexcoordVBO);
- glEnableClientState(GL_TEXTURE_COORD_ARRAY);
- glTexCoordPointer(2, GL_FLOAT, 0, NULL);
- glBindBuffer(GL_ARRAY_BUFFER, nAttrbDataVBO);
- glEnableVertexAttribArray(nAttribLoc);
- glVertexAttribPointer(nAttribLoc, 3, GL_FLOAT, GL_FALSE, 0, NULL);
- glDrawElements/glDrawArrays
- glDisableVertexAttribArray(nAttribLoc);
- glDisableClientState(GL_TEXTURE_COORD_ARRAY);
- glDisableClientState(GL_NORMAL_ARRAY);
- glDisableClientState(GL_VERTEX_ARRAY);
- 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后,才能做这样的事情:
- //SendUniform函数,其中一个重载版本
- void ZWGLSL::SendUniform(const GLchar *UniformName, GLfloat x)
- {
- GLint location = glGetUniformLocation(m_nShaderProgram,UniformName);
- glUniform1f(location, x);
- }
- inline void Enable() { glUseProgram(m_nShaderProgram); };
- inline void Disable(){ glUseProgram(0); };
- //传送数据, m_UniformData为一个float数据:
- ZWShader.Enable();
- ZWShader.SendUniform("uniformName", m_UniformData);
- //Render Something
- ZWShader.Disable();
可能是不同的shaderProgram都有一套用于分配的“位置”吧,为了不混淆就so了(擦,怎么想起了相对内存地址来了- -)
有些时候,我们身不由己——我们给一个渲染对象类关联一个shader相关类的指针,shaderProgram启用与否完全交给这个渲染对象类——我们还是得在上层为shader指定uniform数据。这时候,可以在shader类指针之外,再关联一个map<uniform位置,uniform数据>(当然了这个“数据”还得根据uniform变量类型来细分),我们只把数据传给这个map。当shader在渲染对象类里头被启用之后,立即就把这些数据都通过glUniform传送。——这其实是面向对象设计要关心的内容了。
是不是可以这样呢?
- //not that good!
- //somewhere:
- glUseProgram(nHandle);
- ...//send uniform
- glUniform(data used in rendering)...
- glUseProgram(nHandle);
- //somewhere else
- glUseProgram(nHandle);
- ...//Render Something
- 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)(转)的更多相关文章
- OpenGL/GLSL数据传递小记(3.x)(转)
OpenGL/GLSL规范在不断演进着,我们渐渐走进可编程管道的时代的同时,崭新的功能接口也让我们有点缭乱的感觉.本文再次从OpenGL和GLSL之间数据的传递这一点,记录和介绍基于OpenGL3.x ...
- OpenGL进阶(十一) - GLSL4.x中的数据传递
in out 对于 vertex shader,每个顶点都会包含一次,它的主要工作时处理关于定点的数据,然后把结果传递到管线的下个阶段. 以前版本的GLSL,数据会通过一些内建变量,比如gl_Vert ...
- OpenGL笔记<4> 数据传递二
Sending data to a shader using uniform Preface 上一节我们介绍了通过顶点属性量进行数据传递,今天我们介绍一下通过uniform变量来进行数据传递的方法. ...
- OpenGL 笔记<3> 数据传递 一
Sending data to a shader using vertex attributes and vertex buffer object 上次我们说到着色器的编译和连接,后面的事情没有做过多 ...
- EXTJS中grid的数据特殊显示,不同窗口的数据传递
//EXTJS中grid的数据特殊显示renderer : function(value, metaData, record, rowIndex, colIndex, store, view) { v ...
- Activity系列讲解---数据传递
在Android中,不同的Activity实例可能运行在一个进程中,也可能运行在不同的进程中.因此需要一种特别的机制帮助我们在Activity之间传递消息.Android中通过Intent对象来表示一 ...
- vue2.0 组件之间的数据传递
组件间的数据传递// 父组件<template><div class="order"><dialog-addpro v-on:closedialog= ...
- ASP.NET MVC3中Controller与View之间的数据传递总结
一. Controller向View传递数据 1. 使用ViewData传递数据 我们在Controller中定义如下: ViewData["Message_ViewData& ...
- ASP.NET MVC3中Controller与View之间的数据传递
在ASP.NET MVC中,经常会在Controller与View之间传递数据,因此,熟练.灵活的掌握这两层之间的数据传递方法就非常重要.本文从两个方面进行探讨: 一. Controller向Vie ...
随机推荐
- Java IO 学习(五)跟踪三个文件IO方法的调用链
假设我们想要用Java读取一个二进制文件,有好几种方式,本文会选取其中比较典型的三种方式进行详细分析 0. 准备工作 安装openjdk-1.8.0.141(普通的jdk中涉及IO的很多代码是闭源的, ...
- 用了GradientDrawable后,当点击控件时,控件大小发生变化
android新手:发现一个很奇怪的问题,用了GradientDrawable后,当点击控件时,程序自动使我的一些控件大小保持一致,为什么呢,我就是不想它们保持一致啊 改了好久好久:GradientD ...
- (3)WPF 布局
一.布局原则 二.布局过程 三.布局容器 核心布局面板 布局属性
- HDU 1203 【01背包/小数/概率DP】
I NEED A OFFER! Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Tot ...
- Codeforces Round #445 D. Restoration of string【字符串】
D. Restoration of string time limit per test 2 seconds memory limit per test 256 megabytes input sta ...
- Cookie和Session在Node.JS中的实践(二)
Cookie和Session在Node.JS中的实践(二) cookie篇在作者的上一篇文章Cookie和Session在Node.JS中的实践(一)已经是写得算是比较详细了,有兴趣可以翻看,这篇是s ...
- sqldeveloper 英文设置
在软件ide\bin目录下找到sqldeveloper.conf或ide.conf,加入 C:\Program Files (x86)\sqldeveloper\ide\bin AddVMOption ...
- python查询mangodb
from bson.objectid import ObjectId import pymongo #查询代码 #连接本机mongodb conn=pymongo.Connection() #指定 ...
- Map泛型集合-显示企鹅信息
package collection; /** * 宠物类 * @author * */ public class Pet { private String name; private String ...
- CRUD_PreparedStatement
package songyan.jdbc.crud; import java.sql.Connection; import java.sql.PreparedStatement; import jav ...