Excellent, thanks!
I thought you might like to see an experiment I did, to see how many vbquads I could play with. It uses a pair of textures to store the positions and velocities of each quad, updating them using a shader, so everything stays on the GPU where it can be nice and fast.
This runs with 90000 quads at 175 fps on my GeForce 8800. (I haven't tried it on any other card - I can't guarantee it'll work at all).
Unfortunately Demoniak crashes when I try to use more vbquads - if you want to reproduce this, increase the size of tex_star0 and tex_star1 to 316x316, and increase vbq_grains to 99856. It would be really cool if you could find out why this is happening. I'd like to try it with 1000000 quads!
Interestingly, it actually runs slower with a vertex pool than with vbquads. (I'm also stretching the quads to create motion blur, which you can't do with a vertex pool.)
Anyway, it's quite pretty in action:
<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?>
<hyperion version="1.0">
<scene name="Swarm"
clear_color_buffer="TRUE"
display_fps="TRUE"
show_ref_grid="FALSE"
display_hyperion_logo="FALSE"
vsync="FALSE" >
<window_size width="1024" height="768" />
<background_color r="0" g="0" b="0" />
<wait_screen image="startup.jpg" />
</scene>
<camera name="cam_default" fov="1.0" navigation_mode="FIXED" near_plane="1" far_plane="1000" >
<position x="0" y="0" z="5" />
<lookat x="0" y="0" z="0" w="1" />
</camera>
<texture name="tex_star0" type="TEXTURE_RECTANGLE_2D" use_fbo_for_rtt="TRUE" pixel_format="RGBA_32F" filtering_mode="NONE" addressing_mode="MIRROR" >
<size width="300" height="300" />
</texture>
<texture name="tex_star1" type="TEXTURE_RECTANGLE_2D" use_fbo_for_rtt="TRUE" pixel_format="RGBA_32F" filtering_mode="NONE" addressing_mode="MIRROR" >
<size width="300" height="300" />
</texture>
<mesh name="vbq_grains0" shape_type="VB_QUAD" render="FALSE" polygon_mode="SOLID"
group="content" display_bounding_sphere="FALSE"
lighting="FALSE" texturing="TRUE" back_face_culling="FALSE" use_vbo="TRUE" >
<vb_quad num_vb_quads="90000" />
<attach_material name="mat_grains" />
<blending_params active="TRUE" src_factor="BLENDING_FACTOR_SRC_ALPHA" dst_factor="BLENDING_FACTOR_ONE" />
</mesh>
<material name="mat_star">
<add_texture
texture_unit="0"
/>
</material>
<hud name="hud_star"
group="simulation"
material_name="mat_star"
texturing="TRUE"
render="FALSE" >
</hud>
<material name="mat_grains" opacity="0.999" shader_program_name="shd_grains" >
<add_texture
texture_unit="0"
/>
<add_texture
texture_unit="1"
/>
</material>
<primitive name="prm_grain" shape_type="QUAD" render="FALSE" texturing="FALSE" lighting="FALSE" display_tripod="FALSE" >
<quad width="1.0" height="1.0" billboarding="FALSE" />
</primitive>
<shader_program name="shd_stars" >
<constant_1i name="Star" value="0" />
<constant_2f name="Attractor" />
<raw_data><![CDATA[
[Vertex_Shader]
void main(void)
{
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_FrontColor = gl_Color;
}
[Pixel_Shader]
#extension GL_ARB_texture_rectangle : enable
uniform sampler2DRect Star;
uniform vec2 Attractor;
vec2 Force(vec4 star, vec2 attractor, vec2 uid)
{
vec2 offset = star.xy-attractor;
float dist = length(offset)+1.;
attractor += offset.yx/dist * vec2(1.,-1.)*max(0.,dist-8.)*(0.5+uid.y);
offset = star.xy-attractor;
float oof = 1.+4.*uid.x;
return offset * -oof/dot(offset,offset);
}
void main(void)
{
vec4 star = texture2DRect(Star, gl_TexCoord[0].st);
vec2 uid = (gl_TexCoord[0].st-(gl_TexCoord[0].ts/256.))/256.;
vec2 acc = Force(star,Attractor,uid);
star.zw += acc;
star.xy += star.zw;
star.zw *= 0.978+0.02*uid.y;
float maxv = 2.+2.*(1.-uid.y);
if (dot(star.zw,star.zw)>maxv*maxv)
{
star.zw = normalize(star.zw)*maxv;
}
if (any(lessThan(star.xy,vec2(-768.,-576.))) || any(greaterThan(star.xy,vec2(768.,576.))))
{
star.xy = (fract((gl_TexCoord[0].st/128.)+(star.wz*31.7)+(star.yx*71.3))-0.5)*vec2(1024.,768.);
star.zw = 0.;
}
gl_FragColor = star;
}
]]></raw_data>
</shader_program>
<shader_program name="shd_grains" >
<constant_1i name="Star" value="0" />
<constant_1i name="StarP" value="1" />
<raw_data><![CDATA[
[Vertex_Shader]
#extension GL_ARB_texture_rectangle : enable
uniform sampler2DRect Star;
uniform sampler2DRect StarP;
void main(void)
{
gl_FrontColor = gl_Color;
vec4 position = gl_Vertex;
vec4 sim = texture2DRect(Star, gl_MultiTexCoord1.xy);
//motion blur - leading verts come from current sim, trailing come from previous
vec2 simp = texture2DRect(StarP, gl_MultiTexCoord1.xy).xy;
vec2 mot = sim.xy-simp;
float len = length(mot);
gl_FrontColor.a /= 1.+len*0.5;
if (dot(mot, position.xy) < 0.)
{
//trailing vert
const float exaggerate = 2.;
sim.xy -= mot*exaggerate;
}
//apply sim info to vert pos
float radius = 1.;
position.xy = position.xy*radius + sim.xy;
gl_Position = gl_ModelViewProjectionMatrix * position;
gl_TexCoord[1] = gl_Vertex;
}
[Pixel_Shader]
#extension GL_ARB_texture_rectangle : enable
uniform sampler2DRect Star;
uniform sampler2DRect StarP;
void main(void)
{
vec4 colour = gl_Color;
colour.a *= (1.-dot(gl_TexCoord[1].xy,gl_TexCoord[1].xy));
gl_FragColor = colour;
}
]]></raw_data>
</shader_program>
<script name="Initialise" run_mode="EXECUTE_ONCE" >
<raw_data><![CDATA[
function SwarmInit()
HYP_Input.SetDefaultInputManagementState(0);
swarm_sim = {HYP_Texture.GetIdFromName("tex_star0"),HYP_Texture.GetIdFromName("tex_star1")};
swarm_page = 1;
swarm_nextframe = 0;
swarm_timer = 0;
swarm_grains = HYP_Mesh.GetNumFaces("vbq_grains0")/2;
SwarmInitUVs();
swarm_fbo = HYP_Scene.FboGetDefault();
HYP_Scene.FboBind(swarm_fbo);
HYP_Scene.FboAttachTexture(swarm_sim[1], 0);
HYP_Scene.FboAttachTexture(swarm_sim[2], 1);
HYP_Scene.FboBind(0);
SwarmInitQuads();
HYP_Material.AttachTexture("mat_star", swarm_sim[1], 0);
HYP_Material.SetShaderProgram("mat_star", "shd_stars");
local w,h = HYP_Texture.GetDimensions(swarm_sim[1]);
local grains = math.min(swarm_grains, w*h)-1;
for i=0,grains do
HYP_Texture.SetValueTex2DFloatRgba(swarm_sim[1],i, (math.random()-0.5)*1024,(math.random()-0.5)*768,(math.random()-0.5)*2,(math.random()-0.5)*2);
end
end
function SwarmInitUVs()
local w,h = HYP_Texture.GetDimensions(swarm_sim[1]);
HYP_Hud.SetVertexTexCoord("hud_star", 0, 0, 0, 0);
HYP_Hud.SetVertexTexCoord("hud_star", 1, w, 0, 0);
HYP_Hud.SetVertexTexCoord("hud_star", 2, 0, h, 0);
HYP_Hud.SetVertexTexCoord("hud_star", 3, w, h, 0);
HYP_Hud.SetSize("hud_star", w,h);
HYP_Object.SetPosition("hud_star", -(1024-w)/2, -(768-h)/2);
HYP_Object.Update("hud_star");
end
function SwarmInitQuads()
HYP_Scene.RemoveObject("prm_grain");
local vbqgrains0 = HYP_Object.GetIdFromName("vbq_grains0");
local w,h = HYP_Texture.GetDimensions(swarm_sim[1]);
local c = 0;
for y=0,h-1 do
for x=0,w-1 do
if c<swarm_grains then
SwarmAddQuad(vbqgrains0, x,y);
end
end
end
local ps = (100/768)*math.tan(HYP_Camera.GetFOV("cam_default")*math.pi/180);
HYP_Object.SetRenderState(vbqgrains0,1);
HYP_Object.SetPosition(vbqgrains0, 0,0,-100);
HYP_Object.SetScale(vbqgrains0, ps,ps,ps);
end
function SwarmAddQuad(vbq, x, y)
HYP_Primitive.SetQuadVertexPosition( "prm_grain", 0, -1,-1,0);
HYP_Primitive.SetQuadVertexPosition( "prm_grain", 1, 1,-1,0);
HYP_Primitive.SetQuadVertexPosition( "prm_grain", 2, -1, 1,0);
HYP_Primitive.SetQuadVertexPosition( "prm_grain", 3, 1, 1,0);
local r = 0.25+0.50*math.random();
local g = 0.50+0.50*math.random();
local b = 0.75+0.25*math.random();
local a = 0.85;
HYP_Primitive.SetQuadVertexColor( "prm_grain", 0, r,g,b,a);
HYP_Primitive.SetQuadVertexColor( "prm_grain", 1, r,g,b,a);
HYP_Primitive.SetQuadVertexColor( "prm_grain", 2, r,g,b,a);
HYP_Primitive.SetQuadVertexColor( "prm_grain", 3, r,g,b,a);
HYP_Primitive.SetQuadVertexTexCoords("prm_grain", 0, 0, 0,0);
HYP_Primitive.SetQuadVertexTexCoords("prm_grain", 1, 0, 1,0);
HYP_Primitive.SetQuadVertexTexCoords("prm_grain", 2, 0, 0,1);
HYP_Primitive.SetQuadVertexTexCoords("prm_grain", 3, 0, 1,1);
HYP_Primitive.SetQuadVertexTexCoords("prm_grain", 0, 1, x,y);
HYP_Primitive.SetQuadVertexTexCoords("prm_grain", 1, 1, x,y);
HYP_Primitive.SetQuadVertexTexCoords("prm_grain", 2, 1, x,y);
HYP_Primitive.SetQuadVertexTexCoords("prm_grain", 3, 1, x,y);
HYP_Mesh.VBQuad_AddQuad(vbq, "prm_grain");
end
function SwarmUpdate()
swarm_timer = swarm_timer + HYP_GetTimeStep();
if swarm_timer >= swarm_nextframe then
swarm_nextframe = math.max(swarm_nextframe+(1/60),swarm_timer);
HYP_Scene.FboBind(swarm_fbo);
local x,y = HYP_Input.GetMousePos();
HYP_GPUShader.SetConstant_2f("shd_stars", "Attractor", x-512,384-y);
HYP_Material.AttachTexture("mat_star", swarm_sim[(2-swarm_page)], 0);
HYP_Scene.FboSetRenderDestination(swarm_page);
HYP_Object.Render("hud_star");
HYP_Material.AttachTexture("mat_star", swarm_sim[1+swarm_page], 0);
HYP_Material.AttachTexture("mat_grains", swarm_sim[1+swarm_page], 0);
HYP_Material.AttachTexture("mat_grains", swarm_sim[2-swarm_page], 1);
swarm_page = 1-swarm_page;
HYP_Scene.FboBind(0);
end
end
SwarmInit();
]]></raw_data>
</script>
<script name="Update" run_mode="EXECUTE_EACH_FRAME" >
<raw_data><![CDATA[
SwarmUpdate();
]]></raw_data>
</script>
</hyperion>