这里写自定义目录标题
- tf里面的一些术语
- 什么是transform feedback
- transform feedback的作用
- transform feedback对象
- transform feedback缓存
- 函数
- (创建tf)glCreateTransformFeedbacks(GLsizei n,GLuint *ids)
- (绑定tf)glBindTransformFeedback(GLenum target,Gluint id)
- (判断tf)gllsTransformFeedback(GLenum id)
- (删除tf)glDeleteTransformFeedbacks(GLsizei n,const GLuint*ids)
- (缓存对象整个绑定到tf缓存绑定点)glTransformFeedbackBufferBase(Gluint xfb,GLuint index,GLuint buffer)
- (缓存对象部分绑定到tf缓存绑定点)glTransformFeedbackBufferRange(GLuint xfb,GLuint index,GLuint buffer,GLintptr,GLsizeiptr size)
- (绑定接口) void glBindBufferBase( GLenum target, GLuint index, GLuint buffer )
- void glBindBufferRange(GLenum target,GLuint first,const GLuint*buffers,const GLintptr*offsets,const GLsizeiptr*sizes)
- (绑定部分接口) void glBindBuffersRange(GLenum target,GLuint first,GLsizei count,const GLuint*buffers,const GLintptr*offsets,const GLsizeiptr*sizes)
- (API配置tf变量)glTransformFeedbackVaryings(GLuint program,GLsizei count,const,GLchar**varyings,GLenum bufferMode)
- 通过着色器配置tf的变量
- (启动tf)glBeginTransformFeedback(GLenum primitiveMode)
- (暂停tf)glPauseTransformFeedback(void)
- (重启tf)glResumeTransformFeedback(void)
- (结束tf)glEndTransformFeedback(void)
- glDrawTransformFeedback(GL_POINTS, m_transformFeedback[m_currVB]);
- 最终实例
- 粒子效果核心流程
tf里面的一些术语
绑定点:应该是一个tf对象的索引 每个对象都有它自己的索引 也就是tf对象的索引0和tf对象的索引0不是一个绑定点
缓存:类似VBO的可以存储顶点的 用glGenBuffer创建的
什么是transform feedback
transform feedback是OpenGL管线中 顶点处理阶段结束之后 图元装配和光栅化之前的一个步骤
最小的OpenGL管线是一个顶点着色器加上transform feedback的组合 不一定要用到片元着色器(这是为什么呢 为什么不是一个顶点着色器就行了)
transform feedback的作用
正常是一个顶点被传递到图元装配阶段时 会把他自己包含的所有数据记录到相应的缓存中 但是transform feedback可以重新捕获这些顶点 做一些处理 然后将他们部分或全部属性传递到缓存对象中
transform feedback对象
tf对象主要用于管理将顶点捕捉到缓存对象的相关状态
可以同时给tf绑定多个缓存 也可也绑定缓存对象的多个子块 也可也将tf对象的不同子块同时绑定到不同的tf缓存绑定点
自己的话来说:
你先创建了一个tf对象名称 把他绑定到为当前tf 这个时候你再把一个vbo绑定给tf 这个vbo就接受tf管理了
transform feedback状态
tf状态是封装在一个tf对象中
tf状态主要包括:
1.所有用于记录顶点数据的缓存对象 这些缓存对象连接在tf缓存绑定点 (就是一些VBO)
2.用于标识缓存对象的充满程度的计数器
3.用于标识tf当前是否启用的状态量
transform feedback缓存绑定点
首先每个绑定点对应于一个缓存或一部分缓存,他总是独立享有指定的缓存,不能与其他绑定点交叉。
对于顶点着色器和几何着色器而言绑定点似乎可以理解为地址。我们可以通过这个绑定点找到他所指向的那部分缓存
transform feedback缓存
顶点 几何着色器的输出记录在这个缓存中 实际上就是创建了一个VBO 然后把他绑在了一个tf对象上 然后这个VBO就属于tf对象了
函数
(创建tf)glCreateTransformFeedbacks(GLsizei n,GLuint *ids)
glGenTransformFeedbacks( GLsizei n, GLuint* ids ) 一样
创建n个新的tf对象,并且将生成的名称记录到数组ids中
系统会内置一个默认的tf对象 这个默认的tf对象id是0
(绑定tf)glBindTransformFeedback(GLenum target,Gluint id)
将一个名称为id的tf对象绑定到目标target上 目标的值必须时GL_TRANSFORM_FEEDBACK
他做了三个工作:
1、如果第一次绑定这个名称,将创建一个tranform feedback对象,使用默认状态值对他进行初始化,并设为当前transform feedback;
2、否则单纯的帮该transform feedback激活为当前;
3、如果id为0,则相当于重新回到默认的transform feedback对象上(解除所有值钱绑定的transform feedback对象)
(判断tf)gllsTransformFeedback(GLenum id)
如果id时一个已有的tf对象的名称 那么返回GL_TRUE 否则返回GL_FALSE
(删除tf)glDeleteTransformFeedbacks(GLsizei n,const GLuint*ids)
删除n个保存在ids数组中的tf对象 如果tf对象处于启用状态 我们调用该函数 只有在本次tf结束之后才会删除对象
(缓存对象整个绑定到tf缓存绑定点)glTransformFeedbackBufferBase(Gluint xfb,GLuint index,GLuint buffer)
和glBindBufferBase()应该类似
将名为buffer的缓存对象绑定到名为xfb的tf对象上 索引通过index设置 如果index为0 buffer将被绑定到默认的tf对象的绑定点
这里的index必须是当前绑定到GL_TRANSFORM_FEEDBACK上的tf对象的缓存绑定点索引 (这个还没理解 tf对象的缓存绑定点索引去哪确定)
可以用GL_MAXTRANSFORM_FEEDBACK_BUFFERS的值来查询 index值必须小于这个值大于0
(缓存对象部分绑定到tf缓存绑定点)glTransformFeedbackBufferRange(GLuint xfb,GLuint index,GLuint buffer,GLintptr,GLsizeiptr size)
和glBindBufferRange()应该类似
将一个缓存对象的不同区域绑定给不同的tf绑定点 这些区域不能交叠
简单来说 就是一个tf对象的多个索引可以绑定到一个缓存对象的不同区域
将缓存对象buffer的一部分绑定到xfb的tf对象的绑定索引index上 offset和size的单位均为字节 他们设定了要绑定的缓存对象的范围
例如:
Gluint buffer;
glCreateBuffers(1,&buffer);
glNamedBufferStorage(buffer,1024*1024,NULL,0);//1MB空间
glTransformFeedbackBufferRange(xfb, //对象 //这个是哪来的
0, //索引0
buffer, //缓存名称
0, //数据范围的起始地址
512*1024);//缓存的前半部分
glTransformFeedbackBufferRange(xfb,1,buffer,512*1024,512*1024);
(绑定接口) void glBindBufferBase( GLenum target, GLuint index, GLuint buffer )
tf方面
和glTransformFeedbackBufferBase感觉一样 可能这个可以绑定的更多些
这个函数主要是将一整个buffer缓存对象绑定给当前管理tf的tf对象 然后给这个tf对象一个索引值 就是不知能干嘛? 原话是:将缓冲区对象的缓冲区绑定到target指定的目标数组的索引所索引的索引处的绑定点
target必须为GL_TRANSFORM_FEEDBACK_BUFFER(本篇中必须为这个) 或者 GL_UNIFORM_BUFFER
index必须为当前绑定的transform feedback对象的缓存绑定点索引
buffer是glGenbuffer创建的类似于VBO的可以存储顶点的缓存
uniform缓冲对象方面
可以将一个uniform缓冲对象绑定到一个绑定点上 例如
glBindBufferBase(GL_UNIFORM_BUFFER, 2, uboExampleBlock);
// 或
glBindBufferRange(GL_UNIFORM_BUFFER, 2, uboExampleBlock, 0, 152);
将uboExampleBlock缓冲对象绑定到2这个绑定点上 然后2这个绑定点会跟着色器里面的一个uniform块绑定上 例如:
将lights 的uniform块绑定到2号绑定点上
unsigned int lights_index = glGetUniformBlockIndex(shaderA.ID, "Lights");//获取已定义的uniform块的位置值索引
glUniformBlockBinding(shaderA.ID, lights_index, 2);
这样 uboExampleBlock缓冲对象就和lights的uniform块链接上了
每个目标都代表 缓冲区绑定点的索引数组,以及可以由 其他的 缓冲区操作功能(例如glBindBuffer或glMapBufferRange)使用的单个常规绑定点。 除了将缓冲区绑定到索引缓冲区绑定目标之外,glBindBufferBase还将缓冲区绑定到目标指定的通用缓冲区绑定点。
例子
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, m_particleBuffer[i]);m_particleBuffer[]是一个VBO数组 索引0应该是在
void glBindBufferRange(GLenum target,GLuint first,const GLuintbuffers,const GLintptroffsets,const GLsizeiptr*sizes)
这个没有多写!!! 下面那个函数多了个s
和glTransformBufferRange一样
简单来说 就是一个tf对象的多个索引可以绑定到一个缓存对象的不同区域
(绑定部分接口) void glBindBuffersRange(GLenum target,GLuint first,GLsizei count,const GLuintbuffers,const GLintptroffsets,const GLsizeiptr*sizes)
glBindBufferRange循环版
(白话说一下就是 在buffers缓存对象中offsets和sizes确定了一个范围 将这个范围绑定到target所指定的first开始的索引绑定点上)
绑定来自一个或多个缓存的多个范围值 对应于target所指定的目标绑定点(这句话应该是可以将一个缓存对象的多个区域绑定给tf对象的不同索引 或者将多个缓存对象的多个区域绑定给tf对象的不同索引)
first表示绑定缓存范围的第一个索引值
count表示要绑定的数量
对应于数组中buffers参数的count个缓存名称 offsets参数的count个起始地址偏移量,sizes参数的count个绑定范围的大小 下面代码有解释
每一个范围数据都通过offsets和sizes中的而对应元素指定的 从first开始计数
如果buffers为NULL 那么offsets和sizes将被忽略 target的索引绑定点所有的绑定关系会被删除
可以通过绑定多个缓存(每个缓存都绑定到不同的索引位置)使得图元可以输入到多个缓存中
glBindBuffersRange()等价于多个glBindBufferRange()(多了个s)
for(int = 0;i < count;i++)//就是这个对应于上面 offsets参数的count个起始地址偏移量等等
{
if(buffers != NULL)
{
glBindBufferRange(target, //缓冲区目标
/*符号常量必须为GL_ATOMIC_COUNTER_BUFFER,GL_TRANSFORM_FEEDBACK_BUFFER,GL_UNIFORM_BUFFER或GL_SHADER_STORAGE_BUFFER*/
first+i, //应该就是索引 绑定点
buffers[i], //绑定点的缓冲区对象名称 //这里面存的就是一些顶点 这个缓存对象要被绑定给target缓冲区目标的firt+i索引上
offsets[i], //起始偏移量 这一部分范围是属于buffers的
sizes[i]); //(大概就是读多少字节)当用作索引目标时 可以从缓冲区对象读取的数据量
}
else
{
glBindBufferBase(target,first + i ,0);
}
}
(API配置tf变量)glTransformFeedbackVaryings(GLuint program,GLsizei count,const,GLchar**varyings,GLenum bufferMode)
varyings是一个字符串数组 记录所有需要输出到片元着色器和通过tf获取的变化量
program指定所用程序
count设置varyings数组中所包含的字符串的数量 (后面这个对不对还不清楚)也存储了所有捕获的变量的名称
在调用该函数之后需要直接调用glLinkProgram()因为我们在调用glTransformFeedbackVaryings中所选择的变量只有程序对象再一次被链接的时候才会起作用
bufferMode设置捕获变量的模式:
分离模式——GL_SEPARATE_ATTRIBS 所有的变量是一个接着一个记录在绑定到当前tf对象的第一个绑定点的缓存对象里
例子
const GLchar* Varyings[4];//这里的名字就是和几何着色器里面的输出的向量要名字一样 这样才能对应上
{
Varyings[0] = "Type1";
Varyings[1] = "Position1";
Varyings[2] = "Velocity1";
Varyings[3] = "Age1";
}
glTransformFeedbackVaryings(m_shaderProg, 4, Varyings, GL_SEPARATE_ATTRIBS);//指定要输入进缓存的属性
交叉模式——GL_INTERLEAVED_ATTRIBS 每个变量都会记录到一个单独的缓存对象中
如果我们想 把foo和bar保存到一个缓存中 baz保存到另一个缓存中 在该模式下可以调用相关的内置变量 gl_SkipComponents1,gl_SkipComponents2,gl_SkipComponents3,gl_SkipComponents4,gl_NextBuffer 如果OpenGL遇到任何一个,gl_SkipComponents变量 就会在tf缓存中留出一个指定数量(1,2,3,4)的空隙 gl_SkipComponents1是跳过一个浮点数间隔 gl_SkipComponents3就是跳过3个浮点数间隔 但是跳过的分量依旧会增加tf过程中铺货的数据量的总数
如果遇到gl_NextBuffer那么他会将变量传递到当前绑定的下一个tf缓存中
如果bufferMode为GL_SEPARATE_ATTRIBS时我们遇到gl_NextBuffer或者在GL_INTERLEAVED_ATTRIBS模式下 一行中遇到两个或多个gl_NextBuffer的示例 那么他将会直接跳过当前绑定点(索引)并且在当前绑定的缓存中不会记录任何的数据
例子
在tf缓存中留出空隙
static const char * const vars[] =
{
"foo",
"gl_SkipComponents2",
"bar",
"gl_SkipComponents3",
"baz"
};
glTransformFeedbackVaryings(m_shaderProg, sizeof(vars)/sizeof(vars[0]), vars, GL_INTERLEAVES_ATTRIBS);//指定要输入进缓存的属性
glLinkProgram(prog);
将tf输出到不同的缓存中(gl_NextBuffer)
static const char* const cars[] =
{
"foo","bar" //记录到缓存0当中的变量
"gl_NextBuffer",//移动到绑定点1 因为是GL_INTERLEAVED_ATTRIBS的缘故所以是移动到下一个绑定点 而不是跳过
"baz" //记录到缓存1当中的变量
};
glTransformFeedbackBaryings(prog,
sizeof(vars)/sizeof(vars[0]),
vars,
GL_INTERLEAVED_ATTRIBS);
glLinkProgram(prog);
将tf输出到不同的缓存中(gl_NextBuffer和gl_SkipComponents同时用)
static const char * const vars[] =
{
//记录foo,然后是1个浮点数间隔,bar,然后是2个浮点数间隔
"foo","gl_SkipComponents1","bar","gl_SkipComponents2",
//移动到下一个绑定点1
"gl_NextBuffer",
//流出4个浮点数的间隔,然后记录baz,然后再流出2个浮点数的间隔
"gl_SkipComponents4","baz","gl_SkipComponents2",
//移动到下一个绑定点2
"gl_NextBuffer",
//直接移动到绑定点3,因此不会向绑定点2写入任何内容
"gl_NextBuffer",
//记录iron,留出3个浮点数的间隔,然后记录copper
"iron","gl_SkipComponents3","copper"
};
glTransformFeedbackVaryings(prog,sizeof(vars)/sizeof(vars[0]),vars,GL_INTERLEAVED_ATTRIBS);
glLinkProgram(prog);
结果:(一个空是一个浮点数间隔)
通过着色器配置tf的变量
xfb_buffer设置变量对应的缓存
xfb_offset设置变量在缓存中的位置(偏移值)
xfb_stride设置数据从一个顶点到下一个的排列方式(跨幅)
我们是否捕获某个变量的数据 取决于是否给该变量设置或全局设置xfb_offset
顶点之间的数据对齐通过xfb_stride来声明缓存中的数据跨幅来完成
在顶点数据块中创建”洞“(忽略某些数据)的方法:直接每个要捕捉的变量的精确偏移值(有洞应该是像上面那张图那样 有些空余的间隔)
跨幅和偏移量的设置必须是4的倍数 除非其中包含double类型的数据 此时必须设置为8的倍数 一个缓存中应该只有一个跨幅设置 此时该缓存对应的所有的xfb_stride都需要是一致的
例如:layout(xfb_buffer = 1,xfb_strid = 40) out; 之后对这个缓存的数据再使用xfb_offset时 会直接采用之前的默认跨幅值
例子
一个缓存条件的tf着色器定义
//一个缓存中的不同变量位置布局
layout(xfb_offset = 0) out vec4 foo;//默认xfb_buffer为0
layout(xfb_offset = 16) out vec3 bar;
layout(xfb_offset = 28) out vec4 barz;
//也可也在块中完成同样的操作
layout(xfb_offset = 0) out//表示偏移地址对应所有变量
{
vec4 foo;
vec3 bar;
vec4 barz;
}captured;
多个缓存条件的tf着色器定义 也就是有多个buff的情况
//多个缓存的布局
//因为是在不同的缓存中 所以他们相对于不同缓存的位置都是初始位置0
layout(xfb_buffer = 0 , xfb_offset = 0) out vec4 foo;//必须有xfb_offset
layout(xfb_buffer = 1 ,xfb_offset = 0) out vec3 bar;
layout(xfb_buffer = 2 ,xfb_offset = 0) out vec4 barz;
多个缓存条件的tf着色器定义
多个缓存中的布局(有洞)有洞应该是本来16字节 给他开辟了20字节
//这里应该是foo占16字节 但是给他开辟了20字节 boo从0开始 bar从20开始 就是foo[0]到foo[1]中间垮了40字节 bar也一样
layout(xfb_buffer = 0 ,xfb_stride = 40, xfb_offset = 0) out vec4 foo;
layout(xfb_buffer = 0 ,xfb_stride = 40, xfb_offset = 20) out vec3 bar;
layout(xfb_buffer = 1 ,xfb_stride = 40, xfb_offset = 16) out vec4 barz;
layout(xfb_buffer = 2,xfb_stride = 44) out
{
layout(xfb_offset = 0) vec4 iron;
layout(xfb_offset = 28) vec4 copper;
vec zinc;//没有xfb_offset 不做捕获
}
(启动tf)glBeginTransformFeedback(GLenum primitiveMode)
开始并设置transform feedback将要记录的图元类型,primitiveMode必须是:GL_POINTS, GL_LINES、GL_TRIANGLES三个之一。
在之后的绘制命令中类型需要与之相符,或者几何着色器中输出类型需要与之相符。因为tf的工作方式 只有完整的图元才能被写入到缓存中 所以如果不符合下图的规则 则可能获取不到数据
primitiveMode需要与准备绘制的图元类型相符
可以兼容的绘制模式:
(暂停tf)glPauseTransformFeedback(void)
暂停tf对变量的记录 必须用glResumeTransformFeedback重启 不能用glBeginTransformFeedback
其实tf暂停之后 他依旧是启用状态 但是暂时不会向tf缓存传递任何数据 所以应该这个时候也不能恢复正常绘制
(重启tf)glResumeTransformFeedback(void)
重新启用一个之前glPauseTransformFeedback暂停的tf
(结束tf)glEndTransformFeedback(void)
已经完成所有tf图元渲染 切换回正常渲染模式
glDrawTransformFeedback(GL_POINTS, m_transformFeedback[m_currVB]);
第一个参数:绘制的图元类型,第二个参数:当前被绑定到顶点缓冲区上的transform feedback对象 绑定的这个对象是会值函数的目标缓冲区,将要处理的顶点的个数来自于上次在这个函数中被绑定到GL_TRANSFORM_FEEDBACK目标上的transform feedback对象
这个函数不需要被告知有多少顶点需要渲染,它仅仅检查输入缓存中的数据并将之前写入到这个缓存中的顶点数据全部渲染出来(只有当它被绑定作为一个transform feedback缓存时才行)
当我们向transform feedback对象#1中写入的时候,就从transform feedback对象#0中得到顶点的个数来进行绘制,反之亦然。现在的输入将作为以后的输出。
最终实例
//配置tf
GLuint tfVBO,TBO,tfVAO;
glGenBuffers(1, &tfVBO);//创建了一个用于存储tf顶点的缓冲区
glGenVertexArrays(1,&tfVAO);
glBindVertexArray(tfVAO);
glBindBuffer(GL_ARRAY_BUFFER, tfVBO);//创建专门用于tf写入的顶点缓冲区
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), NULL, GL_DYNAMIC_DRAW);//给这个缓冲区开辟一下空间
glGenTransformFeedbacks(1, &TBO);//创建tf对象
glBindTransformFeedback(GL_TRANSFORM_FEEDBACK,TBO);//把TBO设置为当前管理tf缓冲区的对象
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, tfVBO);//把tfBuff缓冲区链接给TBO 现在 tfBuffer受TBO管理 并且为0号索引
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);//设置一下顶点数据怎么用
glEnableVertexAttribArray(0);
const GLchar* Varyings[1];
Varyings[0] = "tfPos";//tfPos会被写入缓冲区
glLinkProgram(shader.ID);//先链接一下别的着色器 再链接要链接的着色器 要不然无法链接 我也不知道为什么
glTransformFeedbackVaryings(tfshader.ID,1, Varyings, GL_INTERLEAVED_ATTRIBS);//规定一下tfPos怎么写入
glLinkProgram(tfshader.ID);
while (!glfwWindowShouldClose(window))
{
processInput(window);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
/*写入到tf缓存中*/
tfshader.use();//启用tf专用着色器
glBindBuffer(GL_ARRAY_BUFFER,VBO);//绑定存有顶点数据的VBO
glBindVertexArray(VAO);
glBindBufferBase(GL_TRANSFORM_FEEDBACK, 0, tfVBO);//将tfVBO链接上tf对象 这样等会绘制的时候就是把VBO的数据写入tfVBO
glEnable(GL_RASTERIZER_DISCARD);//防止绘制出图像
glBeginTransformFeedback(GL_POINTS);//开启tf
glDrawArrays(GL_POINTS, 0, 6);//将数据写入
glEndTransformFeedback();//关闭tf
glDisable(GL_RASTERIZER_DISCARD);//可以绘制图像
/*打印出tf顶点缓存中的数据进行验证*/
glBindBuffer(GL_ARRAY_BUFFER, tfVBO);
const int tfnum = sizeof(vertices) / 4;
GLfloat feedback[tfnum];
glGetBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(feedback), feedback);
for (int i = 0; i < tfnum; i++)
{
printf("第%d:%f\n", i, feedback[i]);
}
GLuint red = glGetSubroutineIndex(shader.ID, GL_FRAGMENT_SHADER, "red");//这个是关于子程序的应用
GLuint blue = glGetSubroutineIndex(shader.ID, GL_FRAGMENT_SHADER, "blue");
shader.setVec4("Plane", glm::vec4(0.0, 0.0, -1.0, -0.1));
shader.use();
/*绘制场景*/
glEnable(GL_PROGRAM_POINT_SIZE);
glEnable(GL_DEPTH_TEST);
glBindVertexArray(tfVAO);
glBindBuffer(GL_ARRAY_BUFFER, tfVBO);//绑定刚刚写入数据的tfVBO
glUniformSubroutinesuiv(GL_FRAGMENT_SHADER, 1, &blue);
//glEnable(GL_CLIP_DISTANCE0);//开启裁剪模式
glDrawArrays(GL_POINTS, 0, 6);
tfshader顶点着色器代码
#version 330 core
layout(location = 0) in vec3 Pos;
out vec3 tfPos;
void main()
{
tfPos = vec3(Pos)+vec3(0.0,1.0,0.0);
}
其他部分就正常代码
例子 这个例子实现了把三个点通过不停的交换缓冲 网上移动
在这里插入代码片//配置tf
GLuint tfVBO[2],TBO,tfVAO[2]; //每个都创建两个是为了实现交替写入 因为tf缓冲不能同时写入和输出
//两个vbo是为了一个用来提供数据写入 一个用来被写入
//其实也可能不要两个VAO
glGenBuffers(2, tfVBO);//创建了两个用于存储tf顶点的缓冲区 用来替换
glGenVertexArrays(2,tfVAO);
for (int i = 0; i < 2; i++)
{
glBindVertexArray(tfVAO[i]);
glBindBuffer(GL_ARRAY_BUFFER, tfVBO[i]);//创建专门用于tf写入的顶点缓冲区
if(i == 0)
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW);//给这个缓冲区开辟一下空间
else
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), NULL, GL_DYNAMIC_DRAW);//这里
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);//设置一下顶点数据怎么用
glEnableVertexAttribArray(0);
}
glGenTransformFeedbacks(1, &TBO);//创建tf对象
glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, TBO);//把TBO设置为当前管理tf缓冲区的对象
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, tfVBO[save]);//把tfBuff缓冲区链接给TBO 现在 tfBuffer受TBO管理 并且为0号索引
const GLchar* Varyings[1];
Varyings[0] = "tfPos";//tfPos会被写入缓冲区
glLinkProgram(shader.ID);//先链接一下别的着色器 再链接要链接的着色器 要不然无法链接 我也不知道为什么
glTransformFeedbackVaryings(tfshader.ID, 1, Varyings, GL_INTERLEAVED_ATTRIBS);//规定一下tfPos怎么写入
glLinkProgram(tfshader.ID);
while (!glfwWindowShouldClose(window))
{
processInput(window);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
/*写入到tf缓存中*/
tfshader.use();//启用tf专用着色器
glBindVertexArray(tfVAO[draw]);
glBindTransformFeedback(GL_TRANSFORM_FEEDBACK,TBO);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, tfVBO[save]);//将tfVBO链接上tf对象 这样等会绘制的时候就是把VBO的数据写入tfVBO
glEnable(GL_RASTERIZER_DISCARD);//防止绘制出图像
glBeginTransformFeedback(GL_POINTS);//开启tf
glDrawArrays(GL_POINTS, 0, 6);//将数据写入
glEndTransformFeedback();//关闭tf
glDisable(GL_RASTERIZER_DISCARD);//可以绘制图像
/*打印出tf顶点缓存中的数据进行验证*/
cout << "之后draw";
glBindBuffer(GL_ARRAY_BUFFER, tfVBO[draw]);
const int tfnum = sizeof(vertices) / 4;
GLfloat feedback[tfnum];
glGetBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(feedback), feedback);
for (int i = 0; i < tfnum; i++)
{
printf("第%d:%f\n", i, feedback[i]);
}
cout << "save";
glBindBuffer(GL_ARRAY_BUFFER, tfVBO[save]);
const int tum = sizeof(vertices) / 4;
glGetBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(feedback), feedback);
for (int i = 0; i < tum; i++)
{
printf("第%d:%f\n", i, feedback[i]);
}
GLuint red = glGetSubroutineIndex(shader.ID, GL_FRAGMENT_SHADER, "red");//这个是关于子程序的应用
GLuint blue = glGetSubroutineIndex(shader.ID, GL_FRAGMENT_SHADER, "blue");
shader.setVec4("Plane", glm::vec4(0.0, 0.0, -1.0, -0.1));
shader.use();
/*绘制场景*/
glEnable(GL_PROGRAM_POINT_SIZE);
glEnable(GL_DEPTH_TEST);
glBindVertexArray(tfVAO[save]);
glBindBuffer(GL_ARRAY_BUFFER, tfVBO[save]);//绑定刚刚写入数据的tfVBO
glUniformSubroutinesuiv(GL_FRAGMENT_SHADER, 1, &blue);
//glEnable(GL_CLIP_DISTANCE0);//开启裁剪模式
glDrawArrays(GL_POINTS, 0, 6);
int temp = save;
save = draw;
draw = temp;
glFlush();
glfwSwapBuffers(window);
glfwPollEvents();
}
顶点着色器 tfshader.vs
#version 330 core
layout(location = 0) in vec3 Pos;
out vec3 tfPos;
void main()
{
tfPos = vec3(Pos)+vec3(0.0,0.1,0.0);
}
粒子效果核心流程
就是创建出两个vbo 假设vbo里面存了初始数据 就一开始用vbo1假绘制 然后把这些被着色器更新的值存进vbo2里面 用vbo2绘制出真粒 然后用vbo2假绘制 把更新的值存在vbo1中 然后再用vbo1绘制出真粒子