Since the release of EditableImages, I always wanted to know what they are capable of, so this is my attempt at making a texture dissolver. It has many limitations and is by no means perfect, but I feel it is significant enough to share with the community.
I have been working on this for some time now. I want to spend more time working on my other projects, so I likely won’t be working on this anymore.
What is Dissolver?
Dissolver is a module that allows you to create DissolverObjects that dissolve a MeshPart’s texture with customizable settings such as dissolve time, texture resolution, FPS, and more. This allows the creation of various interesting visual effects.
Is it Performant?
Using the power of Parallel LuaU, buffers, caching, and the newly released EditableImage:ReadPixelsBuffer()
and EditableImage:WritePixelsBuffer()
functions, even multiple DissolverObjects running at the same time won’t put a dent in your FPS.
The video above shows 56 MeshParts dissolving at the same time using the default settings. Even my old 2014 laptop is still able to run at 60 FPS.
NOTE: This does not mean that you should abuse it! If too much MeshParts are dissolving at the same time, it can still impact performance.
How to Use
First of all, put the module somewhere that the client can access (such as ReplicatedStorage) and require the module on the client. It does NOT run on the server!
Then, create a DissolverSetting using Dissolver.NewSetting()
.
local Dissolver = require(path.to.module)
local Setting = Dissolver.NewSetting(
1, -- The time it takes to dissolve.
256, -- The texture resolution. Anything higher than 512 is not recommended.
20, -- Noise resolution. The higher the number, the more zoomed out the noise is.
Color3.fromRGB(255, 255, 255), -- Edge color
0.05 -- Size of the colored edge. Ranges from 0 to 1.
)
After that, create a DissolverObject using Dissolver.NewDissolver()
and pass the MeshPart you want to dissolve along with your settings.
local DissolverObject = Dissolver.NewDissolver(
MeshPart, -- The MeshPart you want to dissolve.
Setting -- The DissolverSetting you made.
)
It should take a couple of seconds to compute the textures. After thats done, experiment with the various methods available on the DissolverObject.
DissolverObject.DissolveFinished:Connect(function()
print("Dissolve finished!")
end)
DissolverObject:NormalizedDissolve(1)
And you’re done! There are also other methods you can use such as DissolverObject:SetTime()
and DissolverObject:StopDissolve()
. To see the full list of features, see the documentation below.
IMPORTANT NOTES
When you first create a DissolverObject, all the textures are computed and cached. This usually takes a few seconds depending on your settings. When you create another DissolverObject on another MeshPart with the same texture and settings, the textures does not need to be computed again.
When you no longer need to dissolve a texture, use DissolverObject:Remove()
to remove the DissolverObject. If you want to get rid of a cached texture to free up memory, you can use Dissolver.RemoveFromCache()
to remove the cached textures.
If you want to precompute the textures so that you can use them later, you can use Dissolver.PrecomputeImages()
and pass the same arguments you passed with Dissolver.NewDissolver()
.
You should not make multiple active DissolverObjects on one MeshPart. There should be at most one DissolverObject active on a MeshPart at any given time.
Limitations
- Until EditableImage gets out of beta, this is STUDIO ONLY and requires you to enable it in Beta Features.
- In order to maximize performance, textures need to be first computed and then cached. When there are multiple different textures and settings, it can result in long computation times and VERY high memory usage.
- Due to SurfaceApperance limitations, the colored edge DOES NOT glow.
- If the original texture resolution is larger than the specified resolution, the texture will get downscaled to match the specified resolution.
- The UV of the mesh will affect how the dissolved texture will look. Some MeshParts might not look good with this.
Documentation
Dissolver Global Settings
These are settings found within the Dissolver module itself.
PrintMessages: boolean
Prints the progress of texture computation. Should be used for debugging only.
Default: false
FPS: number
The FPS that Dissolver will run at. Higher values means a smoother dissolve but more textures will need to be computed. Anything above 60 is discourged.
NOTE: This value should only be an integer. Unintended behavior may occur otherwise.
Default: 30
NumberOfWorkers: number
How many Actors will be created for parallelism.
Default: 64
PixelsPerWorker: number
How many pixels each Actor will process at once
Default: 4096
Dissolver Library Functions
function Dissolver.NewSetting(
Time: number?, Default: 1
Resolution: number?, Default: 256
NoiseResolution: number?, Default: 20
EdgeColor: Color3?, Default: Color3.fromRGB(255, 255, 255)
EdgeSize: number? Default: 0.05
) -> DissolverSetting
Creates a new DissolverSetting. Leaving arguments nil will use the default values.
For the sake of performance and memory, a resolution of above 512 is discouraged.
Time cannot be below 0.
Resolution cannot be below 1.
function Dissolver.PrecomputeImages(
Source: string | Color3 | MeshPart,
Setting: DissolverSetting
) -> {buffer}
Not to be confused with Dissolver.ComputeImages()
Computes the textures from the given Source and Setting and caches them for later use. This function does nothing if the textures are already cached.
NOTE: Computing textures will yield your code for up to several seconds.
If the Source is an ImageId, the computed texture will be that image.
If the Source is a Color3, the computed texture will be a solid color.
If the Source is a MeshPart, the computed texture will be the MeshPart’s TextureID.
If the TextureID of the MeshPart is blank, the Color of the MeshPart will be used instead.
function Dissolver.NewDissolver(
MeshPart: MeshPart,
Setting: DissolverSetting
) -> DissolverObject
Creates a DissolverObject for the passed MeshPart.
If the texture is not already cached, they will be automatically computed and cached using Dissolver.PrecomputeImages()
. If this is the case, your code will yield for up to several seconds.
If the MeshPart does not have a SurfaceApperance, one will automatically be inserted into the MeshPart. Then, an EditableImage will be inserted into the SurfaceApperance.
NOTE: If your MeshPart already have a SurfaceApperance, make sure that the MeshPart’s TextureID matches with the ColorMap. Unintended behavior may occur if this is not adressed.
NOTE: You should not make multiple active DissolverObjects on one MeshPart. There should be at most one DissolverObject active on a MeshPart at any given time.
function Dissolver.RemoveFromCache(
Source: string | Color3 | MeshPart,
Setting: DissolverSetting
) -> nil
Removes the textures associated with the given Source and Setting from the cache. Useful for freeing memory if the textures are not going to be used.
function Dissolver.ComputeSingleImage(
Source: string | Color3,
Setting: DissolverSetting,
Threshold: number
) -> buffer
Returns a single texture in the form of a buffer using the specified Source, Setting, and Threshold.
Threshold should be in the range of 0 to 1.
A threshold of 0 means no dissolve (texture is not changed).
A threshold of 1 means fully dissolved (texture is fully transparent).
NOTE: This function can yield.
function Dissolver.ComputeImages(
Source: string | Color3,
Setting: DissolverSetting,
) -> {buffer}
Not to be confused with Dissolver.PrecomputeImages()
Returns an array of texture buffers ordered from least dissolved to most dissolved using the specified Source and Setting.
Textures are NOT cached when using this function. If you are looking for that functionality, use Dissolver.PrecomputeImages().
NOTE: Computing textures will yield your code for up to several seconds.
DissolverObject Properties
[READ ONLY] Images: {buffer}
A reference to the array of cached textures used by this DissolverObject.
[READ ONLY] Time: number
The current dissolve progress in the range of 0 to the maximum time specified in the settings.
[READ ONLY] Setting: DissolverSetting
A reference to the settings used by this DissolverObject.
[READ ONLY] MeshPart: MeshPart
A reference to the MeshPart being dissolved.
[READ ONLY] SurfaceAppearance: SurfaceAppearance
A reference to the SurfaceApperance belonging to the MeshPart.
[READ ONLY] EditableImage: EditableImage
A reference to the EditableImage that was created by the DissolverObject.
DissolverObject Events
DissolveFinished: RBXScriptSignal
Fires when a dissolve process finishes, is interrupted by starting another dissolve process, or stopped using DissolverObject:Stop()
. Does not fire if this object is destroyed using DissolverObject:Remove()
.
DissolverObject Methods
function DissolverObject:SetTime(
Time: number
) -> nil
Sets the current dissolve progress.
Time should be in the range of 0 to the maximum time specified in the settings.
function DissolverObject:SetNormalTime(
NormalizedTime: number
) -> nil
Sets the current dissolve progress using normalized time.
Time should be in the range of 0 to 1.
function DissolverObject:Dissolve(
TargetTime: number
) -> nil
Dissolves the MeshPart texture until the target time is reached.
Time should be in the range of 0 to the maximum time specified in the settings.
If a dissolve is already in progress, it will be canceled and the most recent dissolve request will run instead.
This method does not yield.
function DissolverObject:NormalizedDissolve(
TargetNormalizedTime: number
) -> nil
Dissolves the MeshPart texture until the target normalized time is reached.
Time should be in the range of 0 to 1.
If a dissolve is already in progress, it will be canceled and the most recent dissolve request will run instead.
This method does not yield.
function DissolverObject:StopDissolve() -> nil
Stops the current dissolve process.
function DissolverObject:Remove() -> nil
Disconnects all events, stops the current dissolve process, and destroys the EditableImage and SurfaceApperance created by the DissolverObject. (If the SurfaceApperance is already present prior to the creation of the DissolverObject, it will not be destroyed.)
This method can be used to destroy the DissolverObject once it is not needed.