Volumetric Clouds: Phase 2!

Hey Developers,

Im happy to introduce Phase 2 to my Clouds Module! Phase 2 was promised to come yesterday, but due to some issues it had to be today. (Sorry!)

Whats new? Take a look for yourself!

  • ShadowCast Property. (Clouds can now Cast Shadows)
  • CloudType Property.
  • Speed Property.
  • Height Property (How far they are from the ground)
  • Size Property
  • Bug Fixes!

Pretty Exiting! Here is the release picture.

Did I just see Rain Clouds?

You sure did! This is the new CloudType property taking effect! The Cloud type property can change the overall look of the clouds.

Here is the new Initialization Script:

local CloudsModule = require(script.Parent.CloudsModule)

---

CloudsModule.Clouds:new(
	{
		Tallness = 40; -- Default Value = 40
		Offset = 0; -- Default Value = 0
		Density = 0.2; -- Default Value = 0.2
		Speed = 5; -- Default Value = 5
		Height = 500; -- Default Value = 500
		Size = 3.572; -- Default Value = 3.572
		ShadowCast = true; -- Default Value = true
		CloudType = "Cumulus" -- Default Value = "Cumulus"
	}
)

API

API

The API Contains all the Properties of the Clouds Module.

Tallness Property

The tallness property is the first property inside the table, and changes how tall the clouds are. The taller they are the more it lags. (Possibly fixed in Phase 3). The value you set is taken in studs.

Offset

The Offset Property adds a certain amount of offset to each cloud layer to make it look more procedural. The Value you set in is how great the offset can be in studs.

Density

The Density Property changes how dense the clouds look. This effects the transparency of the clouds. (Density must be between 0 - 1)

Speed

The Speed Property changes the speed of the clouds. The value you enter in is taken as SPS (Studs per Second).

Height

The Height Property changes the distance from the cloud to the ground. The value you enter in is taken as studs.

Size

The Size Property changes the size of all the clouds. There is no exact measurement for what value you enter in.

ShadowCast

The ShadowCast Property is a bool value and changes if the clouds can cast shadows or not.

CloudType

The CloudType Property changes depending on what string value you put in. There are currently 2 CloudTypes:

Cumulus

Stratus

Get the Module!

Here

86 Likes

Suggestion:

local Cloud = require(Module)

local CloudObject = Cloud.new(Settings)
CloudObject:SetEnabled(true/false)
5 Likes

Could you elaborate on how this module works, I.e., how the clouds are made? also, how performant is this?

4 Likes

Suggestions:

1. Cloud Types

I’d suggest looking at this website for data on all the cloud types, and add them all in one update: https://www.weather.gov/jetstream/basicten

2. Properties

I’d suggest something more like this:
New Properties Table
Type = “Cirrus” -- Add the types here
Style = “Realistic” -- Maybe a future feature, would determine the style of the cloud.
VerticalSize = 10 -- The height (‘Tallness’) of the cloud.
Layer = 50 -- The height the cloud spawns at.
Cover = 0.2 -- The percent of the sky that would be covered in clouds.
Density = 0.1 -- The density of the clouds spawned.
Offset = 0 -- the offset of the cloud in a Perrin noise function.
CanCastShadow = true -- If the clouds can cast volumetric shadows.

You may notice that some properties have been removed;
Tallness Was replaced with VerticalSize to make it sound more formal.
Height was replaced with Layer to move easily distinguish it.
Speed was replaced with Offset. It’s easy to add a while loop to change the offset of a Perlin noise function, and more customizable.
Size was removed entirely, and partially replaced by Cover and Density.
ShadowCast was also replaced with CanCastShadows To sound more formal.

Now, to go over the new properties in more detail;
Style Could be added as a later update. This would be either Realistic or Cartoony, and maybe more in the future. Self-explanatory.
VerticalSize would be the Y size of the clouds, or how tall they are; a replacement for Tallness.
Layer would determine the Y Position of the cloud, the height of it.
Cover would be a value between 0 and 1 That determines the amount of cloud cover there is.
Density would become how dark the clouds are.
Seed — I’m assuming you’re using a perlin noise algorithm to generate the clouds. This would be the base used for that generation.
Offset — This is where it gets complicated. Right now, it would just be a 2D perlin map of clouds. This could slide that down, moving either the X, Y, or Z axis on your perlin noise algorithm down so that you could make it look like the clouds are moving. An example of the offset and the seed could be: math.noise(x, seed, offset). This, however, isn’t how you have to set it up.
If you need a better explanation, just ask. Lol.

3. Making clouds

Using `CloudsModule.Clouds:new()` seems unnecessary. I would suggest first renaming this to CloudLayers, and then doing as follows: `CloudLayers.new(PropertiesTable)`. This would definitely improve the process of making layers. Also, as @ihavoc101 suggested, you should add a SetEnabled function.

Other

I‘ll add any more feedback whenever I see fit! I think this is enough to start with, though. Thanks for making this amazing module! P.S. If you want, I can help edit the module to use these properties and send you the revised code. I know this is a lot to do. ;)
4 Likes

Amazing Feedback! I find this to be incredibly helpful, and I love how the properties are much more formal and to the point. Thank you for your feedback. Will most likely add in Phase 3!

8 Likes

No problem! I enjoy contributing to the community and helping out with resources (especially ones I may use)!

1 Like

Actually, I think it may work better with a NoiseOffset property, for making the noise randomize, and a PositionOffset property, for moving the clouds themselves.

Revamped your module. I honestly didn’t look at how it worked. I just barely read through your module and rewrote it in a more intuitive manner.

Code:

-- // Constants \\ --
local RunService = game:GetService("RunService")

local Container = workspace:FindFirstChild("DynamicClouds") or Instance.new("Folder", workspace)
Container.Name = "DynamicClouds"

-- // Main Module \\ --
local MainModule = {
	Class = "Cumulus";
	Height = 25;
	Offset = 0;
	Density = 0.2;
	Speed = 5;
	Elevation = 450;
	Size = 3.572;
	CastShadow = true;
}
MainModule.__index = MainModule


function MainModule.new(Class, Properties)
	local self = setmetatable({}, MainModule)
	self.Class = Class or "Cumulus"
	for i,v in pairs(Properties or {}) do
		if MainModule[i] then
			self[i] = v
		end
	end
	return self
end

function MainModule:Propagate(Duration)
	local CloudObject = false
	if self.Class == "Cumulus" then
		CloudObject = script.CumulusCloud:Clone()
	elseif self.Class == "Stratus" then
		CloudObject = script.StratusCloud:Clone()
	end
	assert(CloudObject, "Invalid DynamicCloud Class!")
	
	local Coloration = 100
	local Randomizer = Random.new()
	
	CloudObject.Position = Vector3.new(0, self.Elevation, 0)
	for i = 1, self.Height do
		local OffsetX = math.random(-self.Offset, self.Offset)
		local OffsetZ = math.random(-self.Offset, self.Offset)
		
		local Cloud = CloudObject:Clone()
		Cloud.Position = Cloud.Position + Vector3.new(OffsetX, 5, OffsetZ)
		Cloud.Texture1.Transparency = (1 - self.Density)
		Cloud.Texture2.Transparency = (1 - self.Density)
		Cloud.Texture1.Color3 = Color3.fromRGB(Coloration, Coloration, Coloration)
		Cloud.Texture2.Color3 = Color3.fromRGB(Coloration, Coloration, Coloration)
		Cloud.CastShadow = self.CastShadow
		Cloud.Parent = workspace
		
		Cloud.Size = Vector3.new(Randomizer:NextNumber(2.9, self.Size), 0.15, Randomizer:NextNumber(2.9, self.Size))
		
		Coloration += 10
		CloudObject = Cloud

		RunService.RenderStepped:Connect(function()
			Cloud.CFrame = Cloud.CFrame * CFrame.new(0, 0, (self.Speed / 10))
		end)
		
		game.Debris:AddItem(Cloud, Duration or 250)
	end
end

return MainModule
1 Like

Could you explain the OOP stuff you’re doing here? I’m not very good with metatables and stuff… .-.

Hmm.

Simply put:

local MainModule = {
	Class = "Cumulus";
	Height = 25;
	Offset = 0;
	Density = 0.2;
	Speed = 5;
	Elevation = 450;
	Size = 3.572;
	CastShadow = true;
}
MainModule.__index = MainModule

That makes default variables for the clouds and sets the __index for mainmodule to itself.

local self = setmetatable({}, MainModule)

This creates the object and sets it to inherit mainmodule.
Now, every namecall method you make under MainModule can be references.

Edit: I might release a fork of this because mine can be ran on client without issue, but the Maid requires an model.

1 Like

What were you trying to say here, lol?

Ah, Maids prevent memory leak. Basically keeping your code efficient performance-wise.

You should totally make a github repo for this, so we can look at the source as we please without hopping into studio to try it out, not to mention you wouldn’t have to make a new post or edit your post everytime you changed it lol

1 Like

Maid’s don’t “prevent” memory leaks, they’re just a more abstract way of doing it.

This:

local connection = something:Connect(someone)

connection:Disconnect()

Also ‘prevents’ memory leaks

Indeed. I’d rather use a maid module than merely throw things into a table though.

I might help this guy improve his module, if he lets me. Since I will be using this in my game, it may pay off.

P.S. And I’ll look into Maids

1 Like

I tried this module in studio and lagged my behind off using the sample code lol, I think that should 100% not happen because I have a decent computer; here’s reason(s) why this could be happening:

1, you’re connecting a new heartbeat for every cloud, i’ve tested it myself via microprofiler and firing more frame events is significantly more expensive than 1 loop that handles them all.

1a, make sure you use += for positioning clouds lol, it’s a bit micro but not indexing an extra time is going to help a little some if you’ve got a ton of clouds.

1b, here’s a more significant optimization: don’t construct a new cframe / position everytime, make sure you have a single one pre-constructed at all times, even if you need to have a method function to update said value, it’s still better. Now with all this said and in-mind, here’s an exaple of what i’d do for moving your clouds:

RunService.Heartbeat:Connect(function() -- a single heartbeat
    for Cloud, Rate in pairs(CloudsTableHere) do 
         Cloud.Position += Rate
    end
end)

That should yield significantly better performance compared to your current heartbeat setup.

Also a small gripe, for each cloud type you’re repeating your entire cloud construction in the code, what you can do instead is one construction that just uses a different mesh that you’d swap out before hand based on cloud type

Okay, I got to my computer and looked at the code. It appears that this module doesn’t wok the way I envisioned it in the backend, so I may be rewriting it. :wink:

Edit: As an explanation, I thought this was making real, volumetric clouds with math.noise and density algorithms (Sebastian Lague has a good video on this). Then I learned it’s just cloning a model…

1 Like

Unfortunately, to my knowledge, roblox studio is very limiting and dosen’t allow you to write shader code. In other words the Sebastian League video is impossible in roblox studio (again to my knowledge).

3 Likes

maybe not make the clouds completely flat in the bottom would make it way cooler
+

1 Like