Announcement

Collapse
No announcement yet.

Free Particle Editor Module for Spiral Galaxies

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

    Free Particle Editor Module for Spiral Galaxies

    [All code in this post that was created by me is released under the CC0 license]

    I wanted to create a realistic galaxy for my game, but trying to render all of the stars as static meshes soon proved to be too slow. So, I thought I could create the desired effect using particles, but unfortunately I could not get the particles to spawn at the right locations to create a good looking galaxy.

    So I wrote my own location module for the particle editor, so instead of using a sphere or cube-like distribution, the particles are now placed to form a galaxy:

    Click image for larger version

Name:	ParticleEditor.jpg
Views:	1
Size:	237.4 KB
ID:	1155991

    As you can see, it uses a realistic density distribution of a spiral galaxy to place the particles. The size, angle, etc. is fully customizable with parameters.

    If you want to use this, you have to edit the engine source code (as far as I know). So you can not use this with a blueprint-only project.
    Here is a zip-file that you can extract into your engine folder: GalaxyParticleModule.zip (maybe you have to add the ParticleModuleLocationGalaxy.h to the engine-solution in Viusual Studio).

    Here is the source code for the new ParticleModuleLocationGalaxy.h:
    Code:
    #pragma once
    #include "Particles/Location/ParticleModuleLocation.h"
    #include "ParticleModuleLocationGalaxy.generated.h"
    
    UCLASS(editinlinenew, hidecategories = Object, meta = (DisplayName = "Galaxy Location"))
    class UParticleModuleLocationGalaxy : public UParticleModuleLocation
    {
    	GENERATED_UCLASS_BODY()
    	
    	/** The random seed(s) to use for looking up values in StartLocation */
    	UPROPERTY(EditAnywhere, Category = RandomSeed)
    	struct FParticleRandomSeedInfo RandomSeedInfo;
    
    	/** The radius of the galaxy. Retrieved using EmitterTime. */
    	UPROPERTY(EditAnywhere, Category = Galaxy)
    	struct FRawDistributionFloat Radius;
    
    	/** The angle used to determine the twist of the spiral arms. */
    	UPROPERTY(EditAnywhere, Category = Galaxy)
    	float DeltaAngle = 4;
    
    	UPROPERTY(EditAnywhere, Category = Galaxy)
    	float EllipseA = 25;
    
    	UPROPERTY(EditAnywhere, Category = Galaxy)
    	float EllipseB = 15;
    
    	/** The height of the galaxy disc. */
    	UPROPERTY(EditAnywhere, Category = Galaxy)
    	FRawDistributionFloat DiscHeight;
    
    	UPROPERTY(EditAnywhere, Category = Galaxy)
    	float FalloffFactor = 1;
    
    	/** Initializes the default values for this property */
    	void InitializeDefaults();
    
    	// Begin UObject Interface
    #if WITH_EDITOR
    	virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
    #endif // WITH_EDITOR
    	virtual void PostInitProperties() override;
    	// End UObject Interface
    
    	//Begin UParticleModule Interface
    	virtual void Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase) override;
    	virtual uint32 RequiredBytesPerInstance(FParticleEmitterInstance* Owner = NULL) override;
    	virtual uint32 PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData) override;
    	virtual FParticleRandomSeedInfo* GetRandomSeedInfo() override
    	{
    		return &RandomSeedInfo;
    	}
    	virtual void EmitterLoopingNotify(FParticleEmitterInstance* Owner) override;
    	//End UParticleModule Interface
    	
    	void SpawnEx(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, struct FRandomStream* InRandomStream, FBaseParticle* ParticleBase) override;
    };
    And here is the code that has to be added to the ParticleModules_Location.cpp (original file copyright of EPIC):
    Code:
    #include "Particles/Location/ParticleModuleLocationGalaxy.h"
    
    /*-----------------------------------------------------------------------------
    UParticleModuleLocationGalaxy implementation.
    -----------------------------------------------------------------------------*/
    UParticleModuleLocationGalaxy::UParticleModuleLocationGalaxy(const FObjectInitializer& ObjectInitializer)
    	: Super(ObjectInitializer)
    {
    	bSpawnModule = true;
    	bSupportsRandomSeed = true;
    	bRequiresLoopingNotification = true;
    	bUpdateModule = false;
    }
    
    void UParticleModuleLocationGalaxy::InitializeDefaults()
    {
    	if (!Radius.Distribution)
    	{
    		UDistributionFloatUniform* DistributionRadius = NewNamedObject<UDistributionFloatUniform>(this, TEXT("DistributionRadius"));
    		DistributionRadius->Min = 0;
    		DistributionRadius->Max = 150.0f;
    		Radius.Distribution = DistributionRadius;
    	}
    
    	if (!DiscHeight.Distribution)
    	{
    		UDistributionFloatUniform* DistributionHeight = NewNamedObject<UDistributionFloatUniform>(this, TEXT("DistributionHeight"));
    		DistributionHeight->Min = -70;
    		DistributionHeight->Max = 70;
    		DiscHeight.Distribution = DistributionHeight;
    	}
    }
    
    void UParticleModuleLocationGalaxy::PostInitProperties()
    {
    	Super::PostInitProperties();
    	if (!HasAnyFlags(RF_ClassDefaultObject | RF_NeedLoad))
    	{
    		InitializeDefaults();
    	}
    }
    
    #if WITH_EDITOR
    void UParticleModuleLocationGalaxy::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
    {
    	InitializeDefaults();
    	Super::PostEditChangeProperty(PropertyChangedEvent);
    }
    #endif // WITH_EDITOR
    
    void UParticleModuleLocationGalaxy::Spawn(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, FBaseParticle* ParticleBase)
    {
    	FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this));
    	SpawnEx(Owner, Offset, SpawnTime, (Payload != NULL) ? &(Payload->RandomStream) : NULL, ParticleBase);
    }
    
    void UParticleModuleLocationGalaxy::SpawnEx(FParticleEmitterInstance* Owner, int32 Offset, float SpawnTime, struct FRandomStream* InRandomStream, FBaseParticle* ParticleBase)
    {
    	SPAWN_INIT;
    	UParticleLODLevel* LODLevel = Owner->SpriteTemplate->GetCurrentLODLevel(Owner);
    	check(LODLevel);
    	bool hasStream = InRandomStream != NULL;
    
    	// Determine the radius, disc height and start center
    	float radius = Radius.GetValue(Owner->EmitterTime, Owner->Component, InRandomStream);
    	float discHeight = DiscHeight.GetValue(Owner->EmitterTime, Owner->Component, InRandomStream);
    	FVector center = StartLocation.GetValue(Owner->EmitterTime, Owner->Component, 0, InRandomStream);
    
    	// Scale the radius to reduce the density of stars near the edge of the galaxy
    	float factor = hasStream ? InRandomStream->GetFraction() : FMath::SRand();
    	radius *= (1 - (1 - factor) * FalloffFactor);
    
    	// Calculate position on an elliptical star path -> creates the spiral arms
    	FVector offset(0, 0, discHeight);
    	float discEllipseA = EllipseA * radius;
    	float discEllipseB = EllipseB * radius;
    	float angle = DeltaAngle * radius;
    	bool useXasRandom = hasStream ? InRandomStream->GetFraction() > 0.5 : FMath::SRand() > 0.5;
    
    	if (useXasRandom) {
    		offset.X = hasStream ? InRandomStream->FRandRange(-discEllipseA, discEllipseA) : FMath::FRandRange(-discEllipseA, discEllipseA);
    		offset.Y = FMath::Sqrt((1 - FMath::Square(offset.X) / FMath::Square(discEllipseA)) * FMath::Square(discEllipseB));
    		bool switched = hasStream ? InRandomStream->GetFraction() > 0.5 : FMath::SRand() > 0.5;
    		if (switched) {
    			offset.Y *= -1;
    		}
    	}
    	else {
    		offset.Y = hasStream ? InRandomStream->FRandRange(-discEllipseB, discEllipseB) : FMath::FRandRange(-discEllipseB, discEllipseB);
    		offset.X = FMath::Sqrt((1 - FMath::Square(offset.Y) / FMath::Square(discEllipseB)) * FMath::Square(discEllipseA));
    		bool switched = hasStream ? InRandomStream->GetFraction() > 0.5 : FMath::SRand() > 0.5;
    		if (switched) {
    			offset.X *= -1;
    		}
    	}
    	offset = offset.RotateAngleAxis(angle, FVector::UpVector);
    
    	// combine the vectors and transform the result
    	offset += center;
    	Particle.Location += Owner->EmitterToSimulation.TransformVector(offset);
    }
    
    uint32 UParticleModuleLocationGalaxy::RequiredBytesPerInstance(FParticleEmitterInstance* Owner)
    {
    	return RandomSeedInfo.GetInstancePayloadSize();
    }
    
    uint32 UParticleModuleLocationGalaxy::PrepPerInstanceBlock(FParticleEmitterInstance* Owner, void* InstData)
    {
    	return PrepRandomSeedInstancePayload(Owner, (FParticleRandomSeedInstancePayload*)InstData, RandomSeedInfo);
    }
    
    void UParticleModuleLocationGalaxy::EmitterLoopingNotify(FParticleEmitterInstance* Owner)
    {
    	if (RandomSeedInfo.bResetSeedOnEmitterLooping == true)
    	{
    		FParticleRandomSeedInstancePayload* Payload = (FParticleRandomSeedInstancePayload*)(Owner->GetModuleInstanceData(this));
    		PrepRandomSeedInstancePayload(Owner, Payload, RandomSeedInfo);
    	}
    }
    Here is a nice looking result using only particles (rendering with a steady 120fps):



    Click image for larger version

Name:	Result.jpg
Views:	1
Size:	484.0 KB
ID:	1155992


    Have fun!
    Last edited by Cultrarius; 07-31-2015, 06:20 PM.
    Particle Editor Extension: Marketplace Page | Documentation
    Code plugin which extends your particle effect toolbox with new possibilities.


    SVG Importer Plugin: Marketplace Page | Gumroad Page
    Import SVG (scalable vector graphics) files as rasterized rendering or as multi-channel signed distance field.

    #2
    This looks great! can't wait to give it a try. Thanks!!
    [FREE] Procedural Bridge Blueprint, [FREE] Spline Enabled Ivy BP

    Comment


      #3
      I will be happy to see your results

      For anyone willing to use this: you will most certainly want to use it with GPU particles, as you want to simulate as many stars as possible. However, a GPU emitter can NOT create particles with different starting colors, so once you found a nice pattern, you have to duplicate the emitter to create a new one with different particle colors.

      If you wonder how I created the nebula: I took the volumetric clouds from the content examples folder and made them blue.

      Also, I was really surprised how easy it was to add a new particle editor module, great work there EPIC!
      Particle Editor Extension: Marketplace Page | Documentation
      Code plugin which extends your particle effect toolbox with new possibilities.


      SVG Importer Plugin: Marketplace Page | Gumroad Page
      Import SVG (scalable vector graphics) files as rasterized rendering or as multi-channel signed distance field.

      Comment


        #4
        I am new to UE and I´ve downloaded the 4.9 branch added the galaxy solution but, as I am still learning the particle system, could you make a tutorial with the full creation of the galaxy?

        Comment


          #5
          Create your own galaxy tutorial

          OK, here is a quick tutorial for everyone who wants to create a kickass galaxy for their game
          1. Import the code posted above into your engine source folder
            Unzip the file into the UE4 github project folder or place the code there manually.

          2. Compile your UE4 project
            If you encounter any compile-time problems, make sure you added the ParticleModuleLocationGalaxy.h file to your Visual Studio solution:

            Click image for larger version

Name:	VisStudio Explorer.jpg
Views:	1
Size:	68.7 KB
ID:	1082498

          3. Create a new particle system

            Click image for larger version

Name:	New particle system.png
Views:	1
Size:	48.3 KB
ID:	1082499

          4. Right-click on the emitter and set TypeData to "GPU Sprites"
            We want to have a lot of stars in our galaxy and this is only possible with GPU sprites.

            Click image for larger version

Name:	GPU sprites.png
Views:	1
Size:	28.7 KB
ID:	1082500

          5. Create a particle material
            I just used the "radial gradient" material from the starter content. It is a really simple material but gets the job done. Make sure "Responsive AA" is enabled, otherise the stars smear when the galaxy is moving on the screen.

            Click image for larger version

Name:	Material.png
Views:	1
Size:	209.4 KB
ID:	1082501

          6. Set up the basic particle modules
            Here are the settings for the basic emitter modules:

            Click image for larger version

Name:	Required Module.png
Views:	1
Size:	60.7 KB
ID:	1082502 Click image for larger version

Name:	Burst spawn.png
Views:	1
Size:	45.7 KB
ID:	1082503 Click image for larger version

Name:	Delete lifetime.png
Views:	1
Size:	32.8 KB
ID:	1082504 Click image for larger version

Name:	Remove velocity and color.png
Views:	1
Size:	36.0 KB
ID:	1082505 Click image for larger version

Name:	Shiny color.png
Views:	1
Size:	168.5 KB
ID:	1082506

          7. Add the galaxy location module
            Right now the particle editor shows just a blob of particles, which does not look very interesting. Now we add the galaxy location module and set it up. Play around with the values (especially the delta angle and the ellipse values) until you are satisfied with the shape of the galaxy. You do NOT want to change these values later, because we duplicate emitters and then you have to change these values on each emitter separately.

            Click image for larger version

Name:	Add galaxy module.png
Views:	1
Size:	46.1 KB
ID:	1082507 Click image for larger version

Name:	Galaxy settings.png
Views:	1
Size:	324.8 KB
ID:	1082508

          8. Copy the emitter to add stars with different color/size/location
            To add some variation to your galaxy you have to duplicate the emitter.

            Click image for larger version

Name:	Duplicate emitter.png
Views:	1
Size:	30.3 KB
ID:	1082509

            For example, let's add some big blue stars, but only to the fringes of our galaxy, not to the core.
            This time, we spawn only 500 particles with a size of 50 (twice as big as the other stars) and a shiny blue color. The trick to get them to only spawn at a certain distance is to modify the "start radius" min value. You also have to set the falloff-factor to 0, otherwise they are partially pulled back into the core of the galaxy. Just play around with the value to see what looks good.

            Click image for larger version

Name:	Blue fringe stars.png
Views:	1
Size:	518.3 KB
ID:	1082510

          9. Add nebulas and other stuff to the galaxy
            I used the cloud-particles from the content examples folder to create a nebula, the process to add them is them same as above. You can also add other stuff now, e.g. lights or some blinking stars, stars with cross-shapes, etc.

          10. PROFIT!


          As an additional tip, the galaxy looks best in-game when slightly rotating, because then the player can really see the spatial positions of the individual stars as they move relative to each other.
          Particle Editor Extension: Marketplace Page | Documentation
          Code plugin which extends your particle effect toolbox with new possibilities.


          SVG Importer Plugin: Marketplace Page | Gumroad Page
          Import SVG (scalable vector graphics) files as rasterized rendering or as multi-channel signed distance field.

          Comment


            #6
            Thank you very much. Very appreciated.

            Comment


              #7
              Here is a quick video update from the final result:



              I think it really shows how important a little bit of movement is for the human perception to see spatial details.
              Particle Editor Extension: Marketplace Page | Documentation
              Code plugin which extends your particle effect toolbox with new possibilities.


              SVG Importer Plugin: Marketplace Page | Gumroad Page
              Import SVG (scalable vector graphics) files as rasterized rendering or as multi-channel signed distance field.

              Comment


                #8
                Just found this thread, and I got to say.. that is an amazing addition
                Wouldn't it be great if this was added to the main source code!

                Def. Bookmarking this if I ever need a solar system effect

                Comment


                  #9
                  This is great, awesome work!
                  Storyteller - An immersive VR audiobook player

                  Dungeon Survival - WIP First person dungeon crawler with a focus on survival and environmental gameplay ala roguelikes

                  Comment


                    #10
                    Originally posted by Luos View Post
                    Just found this thread, and I got to say.. that is an amazing addition
                    Wouldn't it be great if this was added to the main source code!
                    Thanks, glad you like it

                    I mean I could try to open a pull request to EPIC, but on the other hand the use case is pretty specific, so I don't know if they would want this as a general addition into their particle editor modules.
                    Particle Editor Extension: Marketplace Page | Documentation
                    Code plugin which extends your particle effect toolbox with new possibilities.


                    SVG Importer Plugin: Marketplace Page | Gumroad Page
                    Import SVG (scalable vector graphics) files as rasterized rendering or as multi-channel signed distance field.

                    Comment


                      #11
                      Originally posted by Cultrarius View Post
                      Thanks, glad you like it

                      I mean I could try to open a pull request to EPIC, but on the other hand the use case is pretty specific, so I don't know if they would want this as a general addition into their particle editor modules.
                      I also doubt that with Niagara particle editor on the very long horizon.. they'd add new content to cascade.
                      But you could always try right?

                      Comment


                        #12
                        Maybe you could try looking into making it a plugin. Or is it not possible to add new particle modules from plugins?

                        Comment


                          #13
                          Originally posted by manoelneto View Post
                          Maybe you could try looking into making it a plugin. Or is it not possible to add new particle modules from plugins?
                          plugins do just that, plug in something.
                          so it should be possible

                          Comment


                            #14
                            This is pretty **** brilliant!

                            Im curious, was this all C++? Or were there blueprints involved?

                            And if it wasnt in BP, do you have any advice or suggestions for someone who is endeavoring to go that route in producing such results?
                            Production Manager at Goodnight Games

                            Goodnight Games Social Media: Twitter | Facebook | Instagram |Youtube

                            Subscribe to Newsletter

                            Comment


                              #15
                              How expensive is this on performance? Pretty curious about that. Thanks!
                              Solar Purge IndieDB - http://www.indiedb.com/games/solar-purge

                              Solar Purge Forum - https://forums.unrealengine.com/show...metric-Shooter

                              Comment

                              Working...
                              X