草庐IT

c++ - 粒子系统的点 Sprite

coder 2023-05-03 原文

点精灵是构建粒子系统的最佳选择吗?

新版OpenGL和最新图形卡的驱动程序中是否存在点精灵?还是应该使用vbo和glsl来做?

最佳答案

点精灵确实非常适合粒子系统。但是它们与VBO和GLSL没有任何关系,这意味着它们是完全正交的功能。无论是否使用点精灵,都必须始终使用VBO上载几何图形,无论它们只是点,预制精灵还是其他任何东西,并且始终必须通过一组着色器将此几何图形放置(在现代OpenGL中)当然)。

话虽如此,点子精灵在现代OpenGL中得到了很好的支持,只是不像旧的固定功能方法那样自动获得。不支持点衰减功能,这些功能可让您根据点到相机的距离来缩放点的大小,而您必须在顶点着色器中手动执行此操作。以同样的方式,您必须使用特殊的输入变量gl_PointCoord在适当的片段着色器中手动对点进行纹理处理(即当前片段在整个点的[0,1]平方中的位置)。例如,基本的点精灵管道可能看起来像这样:

...
glPointSize(whatever);              //specify size of points in pixels
glDrawArrays(GL_POINTS, 0, count);  //draw the points

顶点着色器:
uniform mat4 mvp;

layout(location = 0) in vec4 position;

void main()
{
    gl_Position = mvp * position;
}

片段着色器:
uniform sampler2D tex;

layout(location = 0) out vec4 color;

void main()
{
    color = texture(tex, gl_PointCoord);
}

就这样。当然,这些着色器只是对纹理精灵进行最基本的绘制,但是它们是进一步功能的起点。例如,要根据精灵到相机的距离来计算精灵的大小(也许是为了为其提供固定的世界空间大小),您必须glEnable(GL_PROGRAM_POINT_SIZE)并写入顶点着色器中的特殊输出变量gl_PointSize:
uniform mat4 modelview;
uniform mat4 projection;
uniform vec2 screenSize;
uniform float spriteSize;

layout(location = 0) in vec4 position;

void main()
{
    vec4 eyePos = modelview * position;
    vec4 projVoxel = projection * vec4(spriteSize,spriteSize,eyePos.z,eyePos.w);
    vec2 projSize = screenSize * projVoxel.xy / projVoxel.w;
    gl_PointSize = 0.25 * (projSize.x+projSize.y);
    gl_Position = projection * eyePos;
}

这将使所有点精灵具有相同的世界空间大小(并因此具有不同的屏幕空间大小(以像素为单位))。

但是,在现代OpenGL中仍得到完美支持的点精灵也有其缺点。最大的缺点之一是它们的剪切行为。点在其中心坐标处被剪裁(因为剪裁是在栅格化之前完成的,因此在点被“放大”之前)。因此,如果该点的中心在屏幕外部,则可能会一直到达查看区域的其余部分不会显示,因此,最糟糕的是,一旦该点离开屏幕的一半,它就会突然消失。但是,只有当点精灵太大时,才可以注意到(或调音)。如果它们是非常小的粒子,无论如何都不能覆盖多于几个像素,那么这将不是什么大问题,我仍将粒子系统视为点精灵的典型用例,只是不要将它们用于大型广告牌。

但是,如果这是一个问题,那么除了将所有子图预先构建为CPU上的单个四分之一的天真方法之外,现代OpenGL还提供了许多其他方法来实现点子图。您仍然可以将它们渲染为充满点的缓冲区(从而可以将它们从基于GPU的粒子引擎中提取出来)。然后,要实际生成四边形几何图形,可以使用几何图形着色器,它使您可以从单个点生成四边形。首先,您仅在顶点着色器中执行modelview转换:
uniform mat4 modelview;

layout(location = 0) in vec4 position;

void main()
{
    gl_Position = modelview * position;
}

然后,几何着色器完成其余工作。它将点的位置与通用[0,1] -quad的4个角相结合,并完成了到剪贴空间的转换:
const vec2 corners[4] = { 
    vec2(0.0, 1.0), vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(1.0, 0.0) };

layout(points) in;
layout(triangle_strip, max_vertices = 4) out;

uniform mat4 projection;
uniform float spriteSize;

out vec2 texCoord;

void main()
{
    for(int i=0; i<4; ++i)
    {
        vec4 eyePos = gl_in[0].gl_Position;           //start with point position
        eyePos.xy += spriteSize * (corners[i] - vec2(0.5)); //add corner position
        gl_Position = projection * eyePos;             //complete transformation
        texCoord = corners[i];                         //use corner as texCoord
        EmitVertex();
    }
}

在片段着色器中,您当然会使用自定义的texCoord变量而不是gl_PointCoord进行纹理化,因为我们不再绘制实际点。

另一个可能的方法(可能是更快的方法,因为我记得几何着色器以其速度较慢而著称)将使用实例渲染。这样,您就有了一个额外的VBO,它仅包含单个通用2D方形的顶点(即[0,1]正方形),而您的旧VBO仅包含点位置。然后,您要做的是多次绘制该单个四边形(有实例),同时从VBO点获取单个实例的位置:
glVertexAttribPointer(0, ...points...);
glVertexAttribPointer(1, ...quad...);
glVertexAttribDivisor(0, 1);            //advance only once per instance
...
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, count);  //draw #count quads

然后在顶点着色器中,将每个点的位置与实际的角/四角位置(这也是该顶点的纹理坐标)组合起来:
uniform mat4 modelview;
uniform mat4 projection;
uniform float spriteSize;

layout(location = 0) in vec4 position;
layout(location = 1) in vec2 corner;

out vec2 texCoord;

void main()
{
    vec4 eyePos = modelview * position;            //transform to eye-space
    eyePos.xy += spriteSize * (corner - vec2(0.5)); //add corner position
    gl_Position = projection * eyePos;             //complete transformation
    texCoord = corner;
}

这实现了与基于几何着色器的方法相同的效果,即正确裁剪的点精灵具有一致的世界空间大小。如果您实际上想模仿实际点精灵的屏幕空间像素大小,则需要付出更多的计算努力。但这是一项练习,并且与从点精灵着色器到世界到屏幕的转换完全相反。

关于c++ - 粒子系统的点 Sprite ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17397724/

有关c++ - 粒子系统的点 Sprite的更多相关文章

  1. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  2. 电脑0x0000001A蓝屏错误怎么U盘重装系统教学 - 2

      电脑0x0000001A蓝屏错误怎么U盘重装系统教学分享。有用户电脑开机之后遇到了系统蓝屏的情况。系统蓝屏问题很多时候都是系统bug,只有通过重装系统来进行解决。那么蓝屏问题如何通过U盘重装新系统来解决呢?来看看以下的详细操作方法教学吧。  准备工作:  1、U盘一个(尽量使用8G以上的U盘)。  2、一台正常联网可使用的电脑。  3、ghost或ISO系统镜像文件(Win10系统下载_Win10专业版_windows10正式版下载-系统之家)。  4、在本页面下载U盘启动盘制作工具:系统之家U盘启动工具。  U盘启动盘制作步骤:  注意:制作期间,U盘会被格式化,因此U盘中的重要文件请注

  3. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  4. ruby - 使用 `+=` 和 `send` 方法 - 2

    如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

  5. kvm虚拟机安装centos7基于ubuntu20.04系统 - 2

    需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc

  6. ruby - 如何计算 Liquid 中的变量 +1 - 2

    我对如何计算通过{%assignvar=0%}赋值的变量加一完全感到困惑。这应该是最简单的任务。到目前为止,这是我尝试过的:{%assignamount=0%}{%forvariantinproduct.variants%}{%assignamount=amount+1%}{%endfor%}Amount:{{amount}}结果总是0。也许我忽略了一些明显的东西。也许有更好的方法。我想要存档的只是获取运行的迭代次数。 最佳答案 因为{{incrementamount}}将输出您的变量值并且不会影响{%assign%}定义的变量,我

  7. ruby - 在没有基准或时间的情况下用 Ruby 测量用户时间或系统时间 - 2

    因为我现在正在做一些时间测量,我想知道是否可以在不使用Benchmark类或命令行实用程序time的情况下测量用户时间或系统时间。使用Time类只显示挂钟时间,而不显示系统和用户时间,但是我正在寻找具有相同灵active的解决方案,例如time=TimeUtility.now#somecodeuser,system,real=TimeUtility.now-time原因是我有点不喜欢Benchmark,因为它不能只返回数字(编辑:我错了-它可以。请参阅下面的答案。)。当然,我可以解析输出,但感觉不对。*NIX系统的time实用程序也应该可以解决我的问题,但我想知道是否已经在Ruby中实

  8. ruby - 以毫秒为单位获取当前系统时间 - 2

    在Ruby中,以毫秒为单位获取自纪元(1970)以来的当前系统时间的正确方法是什么?我试过了Time.now.to_i,好像不是我想要的结果。我需要结果显示毫秒并且使用long类型,而不是float或double。 最佳答案 (Time.now.to_f*1000).to_iTime.now.to_f显示包含十进制数字的时间。要获得毫秒数,只需将时间乘以1000。 关于ruby-以毫秒为单位获取当前系统时间,我们在StackOverflow上找到一个类似的问题:

  9. arrays - Ruby 数组 += vs 推送 - 2

    我有一个数组数组,想将元素附加到子数组。+=做我想做的,但我想了解为什么push不做。我期望的行为(并与+=一起工作):b=Array.new(3,[])b[0]+=["apple"]b[1]+=["orange"]b[2]+=["frog"]b=>[["苹果"],["橙子"],["Frog"]]通过推送,我将推送的元素附加到每个子数组(为什么?):a=Array.new(3,[])a[0].push("apple")a[1].push("orange")a[2].push("frog")a=>[[“苹果”、“橙子”、“Frog”]、[“苹果”、“橙子”、“Frog”]、[“苹果”、“

  10. += 的 Ruby 方法 - 2

    有没有办法让Ruby能够做这样的事情?classPlane@moved=0@x=0defx+=(v)#thisiserror@x+=v@moved+=1enddefto_s"moved#{@moved}times,currentxis#{@x}"endendplane=Plane.newplane.x+=5plane.x+=10putsplane.to_s#moved2times,currentxis15 最佳答案 您不能在Ruby中覆盖复合赋值运算符。任务在内部处理。您应该覆盖+,而不是+=。plane.a+=b与plane.a=

随机推荐