Announcement

Collapse
No announcement yet.

How to place single GPU particles at specified locations?

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    How to place single GPU particles at specified locations?

    Hello everyone,
    I would like to place single GPU particles (from the same emitter) at specified location.
    I've built a data table containing coordinates for single points (from csv file) where I would like to place particles, and I'm able to extract from this table splitted x,y and z values.
    I'm even able to create all the particles needed for spawning in a single emitter shot, and to set them to stay still and to never die (disappear) but I can't get the right way to place single particles to the desired position.
    I've made several tests with sprites (and everything is working fine!) but I get very poor performances even with 2.000 / 3.000 sprites.
    GPU particles are very optimized and I can get 60 fps even with 70.000 particles.

    My final project is to render a Point cloud using GPU particles.

    Can anyone help me?

    Thank you.

    (Sorry for my english)

    #2
    The easiest way to go about this is probably to convert your csv file into a bitmap image. Then you can use the bitmap image as a lookup table in the particle material and use it to offset particle position. Dunno what tools or programming experience you have, but I used Mathematica to convert a CSV to a bitmap. If you don't want to get that, here's some suggestions on how to do it with python. Make sure all your x,y,z values are scaled to between 0 and 1.
    Click image for larger version

Name:	buncloud.bmp
Views:	1
Size:	48.1 KB
ID:	1093413
    Here's a 128x128 image made from 16384 vertices from the Stanford bunny.

    Import your bitmap. Open the asset. Under Level Of Detail, set the Texture Group to ColorLookupTable, under Compression, set Compression Settings to VectorDisplacementmap(RGBA8).

    In your Particle system, under Initial Location, set the Min X as 0 and Max X as the number of points. Set distribute over NPoints to the number of points as well, and set the distribute threshold to 1. This way, you can use the particle's x position as an index for your lookup table. Here's what the material setup looks like.

    Click image for larger version

Name:	PointCloudMaterial.jpg
Views:	1
Size:	236.4 KB
ID:	1093414

    Here's the point cloud it creates.

    Click image for larger version

Name:	ScreenShot00001.png
Views:	1
Size:	653.6 KB
ID:	1093415

    Let me know if you have any questions.

    Comment


      #3
      WOW xnihil0zer0! This suggestion open a whole new path for the project!
      Thank you so much! I'm testing right now and I will post an image of the result as soon as possible!

      Comment


        #4
        So I found a flaw in my method. Distributing N particles over N points does not guarantee that all points are occupied. While the particles will be found at integer points, many are occupied multiple times and many are not occupied at all. You can up the spawn to increase the probability that a spot will be occupied, but it takes many times the number of points to be confident they will all be filled.

        If it is ok that the cloud does not draw instantly, you can guarantee that all spots are filled by using velocity. Remove the initial location, set the initial velocity x to the number of points divided by the emitter duration. Set the spawn to the same as the initial velocity x. Set the lifetime to the emitter duration. It will take the duration of your emitter to completely draw the point cloud. I've found I can decrease the emitter duration down to 0.25 seconds but below that, with a large number of points, large velocities cause position inaccuracies due to floating point errors.

        Comment


          #5
          I just found a method with guaranteed placement that can be drawn within two ticks. Set spawn rate, lifetime and initial velocity to 0. Add a spawn per unit module. Set it up like this.

          Click image for larger version

Name:	SPUParticles.jpg
Views:	1
Size:	45.4 KB
ID:	1094211

          Then make a blueprint based on your particle system. Wait one tick, then move your particle system 1 unit per point like this:

          Click image for larger version

Name:	SPUCloudBP.jpg
Views:	1
Size:	262.9 KB
ID:	1094214

          Here's the resulting cloud:

          Click image for larger version

Name:	SPUbuncloud.jpg
Views:	1
Size:	76.4 KB
ID:	1094215

          Comment


            #6
            Nice very interesting method right there! I was doing some messing around before to spawn star systems from data on the Milky way and this would be quite an interesting way to test that out too.

            Comment


              #7
              Trying to get it working

              [MENTION=27525]xnihil0zer0[/MENTION]: I attempted to implement the updated method from your most recent post. But I'm not getting anything displayed.

              I imagine (I hope!) there must be something obvious that I missed. I wonder if you or anyone would like to take a look at my project and advise?

              Here is the work in progress. To try it out you can either clone it with Git or just download the zip file.

              Thanks!

              Comment


                #8
                @Michael Geary Looking at your project, you have BunnyParticles in your level, but you need BunnyPrints in the level instead. BunnyParticles only emits when it's moved, which is what BunnyPrints does. Second, see the warning in your particle system. You need a fixed relative bounding box when using GPU particles, and you'll need a big one or you'll only be able to see the particles from limited angles. In the cascade editor toolbar, to the right of the Bounds button there is a downward facing triangle, click it select Set Fixed Bounds. Then in the main Particle System details tab, scroll down to Bounds and set them all to something like Min: -100000, Max:100000. Also, BunnyPrints should be at 0,0,0 in your level, because the material uses the x location of each particle. In the material, use the Constant3Vector added right before World Position Offset to control the location of the cloud. You can change it to a collection or dynamic parameter if you want to move it around with blueprints.

                Comment


                  #9
                  [MENTION=27525]xnihil0zer0[/MENTION] - thanks for the helpful tips! That definitely got me farther along.

                  I took BunnyParticles out of the level and put BunnyPrints there instead, and I added the fixed bounds as you mentioned.

                  I was a little uncertain about the 0,0,0 location for BunnyPrints. I had a Make Vector in the blueprint following your screenshot, with X=16384 and Y=Z=0. I changed the X to 0 as well.

                  I don't see a World Position Offset in the blueprint. The Make Vector feeds into the SetWorldLocation. Is there a World Position Offset here somewhere too?

                  I don't care about moving the point cloud around at this point (pun intended) - for starters I would just like to get it to display a bunny at all. :-)

                  In any case I do see something new after these changes. It's not a bunny but it's something at least. If I go down below the floor, I see this:

                  Click image for larger version

Name:	2016-06-28 (2).png
Views:	1
Size:	200.5 KB
ID:	1109188

                  Thanks for helping with my dumb questions and errors. My goal is to get a working demo of your technique that people can download and try out without having to make the same mistakes as me. :-) The new version of the project is on GitHub at the same URL.
                  Attached Files

                  Comment


                    #10
                    @Michael Geary When you place bunny prints in your level, look at its transform in the details tab. Set that to 0,0,0. Then, after the first tick, you want the blueprint to move it to 16384,0,0, so change that back. It will then place 16384 particles, one at each integer value of x.

                    The world position offset is in materials, not blueprints. You need to make a material based on the one in my first post in this thread and set your particle material to that.

                    Comment


                      #11
                      [MENTION=27525]xnihil0zer0[/MENTION] - D'oh! So I forgot to create the material. How silly.

                      I set up a BunnyMaterial and made it the particle material, along with the other changes you mentioned. The material screenshot doesn't show all the settings for the individual nodes, of course, but most of them were fairly obvious. A few I had trouble with:

                      Your Texture Sample has two input pins, UVs and Level. Mine ended up with three: UVs, Tex, and Level. This would seem to tell me I have one of the Texture Sample settings wrong, but after trying all of them I can't get rid of the Tex pin. The settings I have for Texture Sample are:

                      MipValueMode: MipLevel
                      Sampler Source: From texture asset
                      Const Coordinate: 0 (grayed)
                      Const Mip Value: -1
                      Texture: thumbnail shows the BunnyCloud texture
                      Sampler Type: Linear Color
                      Is Default Meshpaint Texture: unchecked

                      Also, there is a Multiply(,-1) node near the right that takes its A input from the Particle Position. It overlaps the Add node to its right, so it's hard to tell where its output goes. Since the Multiply(,1000) node below them goes to the Add node's B input, I assumed that the Multiply(,-1) goes to that Add node's A input.

                      Finally, the BunnyMaterial itself has a number of settings. I set them rather unscientifically: I noticed that your DefaultParticle2 had four of the input pins enabled and the others grayed out, so I tweaked settings until mine looked like that. (Cargo cult programming at its finest!) The settings I have are:

                      Phys material: None
                      Material Domain: Surface
                      Blend Mode: Masked
                      Decal Blend Mode: Translucent (grayed out)
                      Shading Model: Unlit
                      Two Sided: unchecked
                      Use Material Attributes: unchecked
                      Subsurface Profile: None (grayed out)

                      The remaining settings after the Material group are all on their defaults.

                      With these changes, I still have that colorful bar artifact instead of a bunny. The main thing that's changed is that bar is now horizontal instead of sticking down at an angle.

                      Of course another option would be if you have a working project and would like to share it. Then you wouldn't be hearing my dumb questions. :-) But no problem either way, I will continue to investigate and try to see what I missed, and of course welcome any further tips. The GitHub repo is updated with this latest code.

                      Thanks!
                      Last edited by Michael Geary; 06-28-2016, 08:21 PM.

                      Comment


                        #12
                        [MENTION=27525]xnihil0zer0[/MENTION]: Thank you in BIG LETTERS for your kind and patient assistance with this. Your last couple of tips in a PM got it working!

                        As you mentioned, the two remaining problems were simply:

                        • That Multiply(,-1) node I was asking about? I had a silly typo and it was doing Multiply(,1). It's funny, if this were code in any programming language, I have to think I would have noticed an expression like (foo*1) and wondered why I was multiplying by 1! :-) But in the "blueprint-style" material editor I didn't notice it.

                        • I had the BunnyMaterial hooked into the BunnyParticles the wrong way. Instead of being attached to the particle system as a whole, it needs to be connected to the emitter's Required tab.

                        With those two changes, it works!

                        I don't know how to thank xnihil0zer0 enough, both for creating this very clever technique and for the assistance here and in PMs.
                        Last edited by Michael Geary; 06-30-2016, 08:28 PM.

                        Comment


                          #13
                          I just realized this technique has a limitation that makes it unsuitable for my use case. Because the X/Y/Z particle coordinates come from a texture sample lookup, they are limited to an integer range of 0-255 on each axis. So you only have 256 discrete positions available in each direction. This results in some pretty severe quantization/aliasing errors.

                          You can see this effect if you load and run the project and press the up arrow to move "inside" the bunny, then look around. You'll find a lot of places where the particles line up in neat horizontal/vertical/diagonal lines, like this:

                          Click image for larger version

Name:	bunnydots.png
Views:	1
Size:	616.0 KB
ID:	1111519

                          I experimented using a .png file with 16-bit depth instead of 8-bit as in the original, but when I import it into UE4 and try to set the compression settings, it still only offers me "VectorDisplacementmap(RGBA8)". I was hoping to see something like an RGBA16 option there. (I must confess that I didn't actually test this further to see if I was getting 16-bit precision.)

                          Even 16 bit resolution wouldn't be perfect. That would give 65,536 discrete positions in each axis. Maybe good enough, but I bet there would still be some aliasing. What I really want to do is take an array of Float3's (or Vector3's or whatever Unreal likes to call them) and use that as the lookup table instead of the TextureSample that our material uses now.

                          One roundabout way to accomplish this would be generate not just one lookup texture, but two or even three or four, where one texture has the low-order 8 bits for each axis, the next texture has the next-higher-order 8 bits, and if we want more than 16 bits of precision we keep going from there. Then instead of just the single texture lookup that the material uses now, we also have lookups for each additional 8 bits of precision, doing some bit shifting after the lookups to combine each 8-bit slice into the final coordinate.

                          As I think about it, I'm pretty sure that could be made to work. I'm just wondering if there might be a much simpler approach using a single linear array of Float3 coordinates. If I were writing this from scratch I know that's how I would want to do it, but I'm trying to find a way that works in Unreal's material system.

                          Any ideas are most welcome! In the meantime I may experiment with this multiple-texture idea.
                          Last edited by Michael Geary; 07-05-2016, 05:28 AM.

                          Comment


                            #14
                            @Michael Geary You can use up to 4096 float3s in an array within a custom code node. Set output as CMOT float3, input as x.

                            const float3 pointcloud[4096]={{1234.5678,1234.5678,1234.5678},...{1234.5678,1234.5678,1234.5678}};
                            return pointcloud[x];

                            just make sure you have plenty of newlines in the data or it will fail with a source line too long error.

                            I have been looking into a way of automating the image creation from data using CanvasRenderTarget2Ds. Shouldn't be too much of a stretch to extend it to multiple render targets to increase precision. I'll post something soon.

                            Comment


                              #15
                              [MENTION=27525]xnihil0zer0[/MENTION] - Neat, that custom node data could come in handy for something. 4096 isn't enough for my project, so will probably stick with textures for the point data itself.

                              For the increased precision, instead of the multiple textures I'm experimenting with an EXR texture, which has 32-bit floats for the RGBA data. As a quick and dirty test, I converted the bunny cloud bitmap to an EXR file, imported it and set the texture's Compression Settings to HDR instead of VectorDisplacementmap, and it worked. Of course there wasn't any visible difference yet, because the point coordinates had already been quantized to 0-255, but it looks promising so far.

                              Comment

                              Working...
                              X