OpenGL Transform feed back 粒子系统

来源:互联网 发布:淘宝天下天下网商区别 编辑:程序博客网 时间:2024/06/08 14:12

在原来的OpenGL渲染的pipeline并没有提供较多的交互接口,当调用Draw函数之后很难再绘制过程对已经装配的图元进行修改。然而,在绘制过程中存在这样的需求,尤其是需要根据之前装配好的图元来更新随后的操作。举个简单的例子,当一个场景中存在两个相对运动的物体时,后一个物体需要根据前一个物体的运动来决定自己的运动轨迹,因此需要有一种feed back来提升交互能力。Transform feed back就是这样一种有效的机制,Transform feed back在顶点处理好之后,图元装配和光栅化之前给程序员提供的一个接口。主要利用一个feed back缓存返回之前顶点的状态,在下一步处理中就可以根据顶点信息有效的计算下一步的运动轨迹。

Transform feed back缓存和其他的缓存一样,也存在特定的函数进行管理。首先,利用glGenTransformFeedBacks创建一个feed back数组,数组中包含的是用于管理对应的feed back的ID号,需要注意的是在创建ID号时并不会创建对应的对象,对象的创建会等到绑定的时候才会创建。绑定操作由glBindTransformFeedback完成,这个函数主要实现创建feed back对象,同时将该对象和对应的ID号码绑定;如果ID号码已经存在,则将对应的对象激活。同时OGL还提供了对应的函数用于判断feed back对象是否存在以及清除对象,这两个操作由glIsTransformFeedback以及glDeleteTransformFeedbacks两个函数完成。

创建并绑定了feed back对象之后,需要向feed back提交内存缓存,需要注意的是feed back仅仅提供一个接口,其本身并不会生成内存。和顶点队列类似,需要由其他类型的缓存为之提供背后的内存支持。将背后的内存和feed back绑定起来有两种函数调用方式,第一种是glBindBufferBase,该函数将根据提供的feed back对象的target号,将该target当做一个数组,同时将buffer中的内存与该数组中从index开始的所有内存对应起来。另外一个函数是glBindBufferRange,该函数和glBindBufferBase类似,唯一的不同是将buffer也当做一个数组,仅仅将buffer+offset到buffer+offset+size区域的内存给feed back使用。

前面主要讲了feed back的对象管理以及缓存支持,然而,feed back还没有和具体的顶点信息关联起来,那么如何使之关联并返回对应的属性值呢?这就需要用到glTransformFeedbackVaryings函数,该函数的原型如下:

void glTransformFeedbackVaryings(GLuint program​, GLsizei count​, const char **varyings​, GLenum bufferMode​);
其中program和对应的渲染程序管理,而count则用于指示有多少个feed back变量需要与feed back对象引出接口,varyings则向函数提供需要引出接口的变量的名称,最后一个参数用于管理变量存在于feed back缓存中的模式,一种是多个变量在feed back缓存中交替出现,另一种是为每一个变量关联一个对应的feed back缓存对象。需要注意的是feed back与program是对应的,当发生program切换的同时伴随feed back队列的变换。

在完成了变量与feed back缓存对象关联之后就可以开始启用transform特性并将反馈数据上传给程序员。操作feed back的命令包括begin,pause,resume以及end。下面就以红宝书上的例子《粒子系统》来详细讲解transform feed back机制的应用。

粒子系统中包括两个部分,一部分主要完成目标的渲染,第二部分主要实现粒子的更新。而粒子的位置以及速度可能相对渲染的目标发生偏移。为了有效的在这两者之间建立联系,程序使用了transform feed back来实现粒子相对目标的偏移。首先让粒子和目标在两个不同的位置空间下,我们使用一组渲染语言得到目标的世界坐标以及最终人眼坐标。

        "#version 410\n"        "uniform mat4 model_matrix;\n"        "uniform mat4 projection_matrix;\n"        "layout (location = 0) in vec4 position;\n""layout (location = 1) in vec3 normal;\n"        "out vec4 world_space_position;\n"        "out vec3 vs_fs_normal;\n"        "void main(void)\n"        "{\n"        "    vec4 pos = (model_matrix * (position * vec4(1.0, 1.0, 1.0, 1.0)));\n"        "    world_space_position = pos;\n"        "    vs_fs_normal = normalize((model_matrix * vec4(normal, 0.0)).xyz);\n"        "    gl_Position = projection_matrix * pos;\n"        "}\n";
渲染语言的顶点坐标如上所示。其中world_space_position返回的目标的世界坐标,而gl_Position则用于表示最终的人眼空间中的坐标。粒子的相对运动是根据自身在世界坐标中相对目标的位置来决定的,因此需要将世界坐标系反馈到程序员,程序员需要将这一部分内容传递到粒子的绘制过程中。为了捕获这种反馈,需要设置对应的feed back。代码如下:

static const char * varyings2[] =    {        "world_space_position"    };    glTransformFeedbackVaryings(render_prog, 1, varyings2, GL_INTERLEAVED_ATTRIBS);
到这一步就目标就和对应的feed back对象关联起来了,在后续的绘制过程中就可以利用对应的feed back返回目标的顶点信息,绘制过程如下:

    glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, geometry_vbo);    glBeginTransformFeedback(GL_TRIANGLES);//开始接受三角形重绘的返回信息    object.Render();    glEndTransformFeedback();
从代码中可以看到,对应的feed back缓存最终是由geometry_tbo提供内存支持的。进一步回溯可以发现geometry_tbo是一个纹理缓存,并且已经由OGL分配了内存但是没有初始化。纹理缓存的初始化包括两部分,第一部分包括生成buffer对象geometry_vbo,并为这个对象分配内存,第二步创建geometry_tbo对象,并将其与geometry_vbo关联起来。需要注意的是glBindTexture函数调用之后,后续对纹理缓存的操作都是基于geometry_tex对象。
    glGenBuffers(1, &geometry_vbo);    glGenTextures(1, &geometry_tex);    glBindBuffer(GL_TEXTURE_BUFFER, geometry_vbo);    glBufferData(GL_TEXTURE_BUFFER, 1024 * 1024 * sizeof(vmath::vec4), NULL, GL_DYNAMIC_COPY);    glBindTexture(GL_TEXTURE_BUFFER, geometry_tex);    glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, geometry_vbo);
下面讨论粒子系统的更新部分代码,含义相同的分布不再具体分析。首先看顶点着色器部分的代码。

    static const char update_vs_source[] =            "#version 410\n"            "uniform mat4 model_matrix;\n"            "uniform mat4 projection_matrix;\n""uniform int triangle_count;\n""layout (location = 0) in vec4 position;\n""layout (location = 1) in vec3 velocity;\n""out vec4 position_out;\n"            "out vec3 velocity_out;\n"            "uniform samplerBuffer geometry_tbo;\n"            "uniform float time_step = 0.02;\n"            "bool intersect(vec3 origin, vec3 direction, vec3 v0, vec3 v1, vec3 v2, out vec3 point)\n"            "{\n"            "    vec3 u, v, n;\n"            "    vec3 w0, w;\n"            "    float r, a, b;\n"            "\n"            "    u = (v1 - v0);\n"            "    v = (v2 - v0);\n"            "    n = cross(u, v);\n"            "\n"            "    w0 = origin - v0;\n"            "    a = dot(n, w0);\n"//ori点到平面的距离的负值            "    b = dot(n, direction);\n"//运动方向与平面的距离"\n""    r = a / b;\n""    if (r < 0.0 || r > 1.0)\n""        return false;\n""\n""    point = origin + r * direction;\n"            "\n""    float uu, uv, vv, wu, wv, D;\n""\n""    uu = dot(u, u);\n""    uv = dot(u, v);\n""    vv = dot(v, v);\n""    w = point - v0;\n""    wu = dot(w, u);\n""    wv = dot(w, v);\n""    D = uv * uv - uu * vv;\n""\n""    float s, t;\n""\n""    s = (uv * wv - vv * wu) / D;\n""    if (s < 0.0 || s > 1.0)\n""        return false;\n""    t = (uv * wu - uu * wv) / D;\n""    if (t < 0.0 || (s + t) > 1.0)\n""        return false;\n""\n"            "    return true;\n"            "}\n"            "\n"            "vec3 reflect_vector(vec3 v, vec3 n)\n"            "{\n"            "    return v - 2.0 * dot(v, n) * n;\n"            "}\n"            "\n"            "void main(void)\n"            "{\n"            "    vec3 accelleration = vec3(0.0, -0.3, 0.0);\n"            "    vec3 new_velocity = velocity + accelleration * time_step;\n"            "    vec4 new_position = position + vec4(new_velocity * time_step, 0.0);\n"            "    vec3 v0, v1, v2;\n"            "    vec3 point;\n"            "    int i;\n"            "    for (i = 0; i < triangle_count; i++)\n"            "    {\n"            "        v0 = texelFetch(geometry_tbo, i * 3).xyz;\n"            "        v1 = texelFetch(geometry_tbo, i * 3 + 1).xyz;\n"            "        v2 = texelFetch(geometry_tbo, i * 3 + 2).xyz;\n"            "        if (intersect(position.xyz, new_position.xyz - position.xyz, v0, v1, v2, point))\n"            "        {\n"            "            vec3 n = normalize(cross(v1 - v0, v2 - v0));\n"            "            new_position = vec4(point + reflect_vector(new_position.xyz - point, n), 1.0);\n"            "            new_velocity = 0.8 * reflect_vector(new_velocity, n);\n"            "        }\n"            "    }\n"            "    if (new_position.y < -40.0)\n"            "    {\n"            "        new_position = vec4(-new_position.x * 0.3, position.y + 80.0, 0.0, 1.0);\n"            "        new_velocity *= vec3(0.2, 0.1, -0.3);\n"            "    }\n"            "    velocity_out = new_velocity * 0.9999;\n"            "    position_out = new_position;\n"            "    gl_Position = projection_matrix * (model_matrix * position);\n"            "}\n";
在该顶点着色器语言中,首先需要找到与渲染部分相关联的部分——接收到的世界坐标系的数据。在上面提到glBindTexture函数调用之后,对纹理的操作都是基于geometry_tbo。在该顶点着色器语言中同样也定义了一个纹理缓存,只是定义是由内置的变量声明来实现的,变量的类型是samplerBuffer。定义之后就将实现了通过顶点着色器访问纹理缓存的能力。在执行过程中,利用内置的函数texelFetch获取纹理缓存中的顶点信息,三个顶点组合成一个基本图元——三角形。得到了顶点信息之后就需要判断粒子是否与三个顶点围成的三角形区域相交。判断是否相交是由intersect函数实现的,该函数首先判断当前点是否与三个顶点组成的平面相交。首先将点当做是射线,如果平面上的点到起点与起点到终点的角度是钝角,那么其运动方向是远离平面,所以两者的符号是相反的,因此a/b小于0,反之,当两者的夹角是锐角时也要判断本次运动的距离是否足够到达平面,因此当a/b大于1时同样没有和平面发生接触。经过上面的运算之后,得到的只是射线与平面是否相交,我们所要判断的不仅仅是是否相交还要判断焦点是否在三角形区域内。接下来,更新线和面的接触点。并判断该点是否在三角形内。判断方法是利用v0作为起始点,利用v0到v1,和v0到v2来组合成,具体的推导过程参考网页http://www.cnblogs.com/graphics/archive/2010/08/05/1793393.html。

如果发生了碰撞,那么利用反射原理将粒子给反射出去,这一功能由reflect_vector实现。

v - dot(v, n) * n <span style="font-family: Arial, Helvetica, sans-serif;"> </span><span style="font-family: Arial, Helvetica, sans-serif;">-  * dot(v, n) * n</span>

第一个减号将速度v分解成和法向量n垂直的运动方向,第二个减号则将运动方向反射出去。接下来需要设置捕获粒子的下一次运动速度和运动的位置。

    static const char * varyings[] =    {        "position_out", "velocity_out"    };//用于捕获例子的位置信息和速度信息    glTransformFeedbackVaryings(update_prog, 2, varyings, GL_INTERLEAVED_ATTRIBS);
接下来生成顶点信息,共有两组顶点信息,第一组用于向OGL提交当前的顶点信息,另一组则用于返回feed back函数返回的顶点信息。

    glGenVertexArrays(2, vao);    glGenBuffers(2, vbo);    for (i = 0; i < 2; i++)    {        glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, vbo[i]);        glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, point_count * (sizeof(vmath::vec4) + sizeof(vmath::vec3)), NULL, GL_DYNAMIC_COPY);        if (i == 0)        {            struct buffer_t {                vmath::vec4 position;                vmath::vec3 velocity;            } * buffer = (buffer_t *)glMapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, GL_WRITE_ONLY);            for (j = 0; j < point_count; j++)            {                buffer[j].velocity = random_vector();                buffer[j].position = vmath::vec4(buffer[j].velocity + vmath::vec3(-0.5f, 40.0f, 0.0f), 1.0f);                buffer[j].velocity = vmath::vec3(buffer[j].velocity[0], buffer[j].velocity[1] * 0.3f, buffer[j].velocity[2] * 0.3f);            }            glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER);        }        glBindVertexArray(vao[i]);        glBindBuffer(GL_ARRAY_BUFFER, vbo[i]);//将buffer array中的数据存放到顶点序列中        glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(vmath::vec4) + sizeof(vmath::vec3), NULL);        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(vmath::vec4) + sizeof(vmath::vec3), (GLvoid *)sizeof(vmath::vec4));        glEnableVertexAttribArray(0);        glEnableVertexAttribArray(1);    }
最后是粒子的绘制过程。

    if ((frame_count & 1) != 0)    {        glBindVertexArray(vao[1]);        glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, vbo[0]);    }    else    {        glBindVertexArray(vao[0]);        glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, vbo[1]);    }    glBeginTransformFeedback(GL_POINTS);    glDrawArrays(GL_POINTS, 0, min(point_count, (frame_count >> 3)));    glEndTransformFeedback();
最后来一个效果图。

0 0
原创粉丝点击