Parallax Corrected 3D SurfaceGUIs

Hi, can you add these to the editable demo place ?

3 Likes

I moved the code to this place here, and it is also uncopylocked so you can download it. And this one works in game this time.

3 Likes

Hello again,

I’ve gone through the code and made a bunch of optimsations to the module, allowing more surfaces to be rendered, dynamic frame rates which adjust depending on how far the surface is away from the camera, max and min frame rates so that the user has more control over which surfaces to prioritise.

As for how much it improved the module, Here’s an example of 6561 of them running on an iPhone 13:





The footage above had the updates capped to 200 surfaces per frame, this one below is capped at 1000 updates per frame:

4 Likes

As someone who needs exactly 6,561 Parallax-corrected UIs in my game, I have to say…impressive.

9 Likes

Alright, Finally I think I’ve got a good version of this module.

I added a load more optimisation controls and automatic culling (since before it was broken). This example below uses 2-3 Frames for each window and bunches 9 windows into one surface to cut down on the amount of calculations required. :slightly_smiling_face: It took a lot of math

The place file is here

10 Likes

This is amazing, could you create one that allows meshparts on a frame?
If you made that a module, you could single handedly make parrallex maped mesh enviorments, functionality such as bulletholes, or advanced interiors, etc

I would if I could, but since the module uses Images and UI elements to render stuff I don’t think it would be possible to do it with meshes. I think what you’re looking for is viewportframes since they allow you to render a completely separate workspace, but since they’re kinda laggy I decided to make it using images instead. I am experimenting with editablemesh though and I’ll try to do what I can to make them work with the module. But for now we’re gonna have to settle with flat images.

Also if roblox added shaders this would be way easier :sob:

3 Likes

Do you mind re-uploading a new place file? The current ‘optimised’ one seems to require the person to keep re-downloading studio. I can’t edit it.

If you’re referring to this place, I downloaded its .rbxl file:
testing.rbxl (119.8 KB)

Thanks a lot for this. Still struggling to set this up smh; not sure where to run the scripts the OP mentions above; command prompt or?

Sorry for taking so long to reply, but the local scripts should ideally be placed in starter player scripts because the effect only works on the client and needs to run at runtime. The module you can have anywhere you want, also ideally in replicated storage.

an Update after like months. I fixed a lot of the math and made the code more performant. The rotated surfaces should work, same with the rotated images. So hopefully everything will work a lot better.

10 Likes

Hello, this resource is awesome! Roblox must have been using this technology for their event portals. I found a few issues though, which I had to fix.

  • ParallaxWindow folder is destroyed on CharacterAdded
  • When parts stream out, the SurfaceGui associated with the part is destroyed
  • No proper support for UIStroke or Face UICorner
  • No type annotation
  • Poorly organized code in my opinion

Patched ParallaxWindow (30.8 KB)

In addition to this, I’ll release Fusion Components for ParallaxWindow!

ParallaxWindow Fusion Components.rbxl (134.9 KB)

Source Code
--[[

	avocado (@avodey) -w-
	
	Creates the visual you see in the video!
	
	1/7/2025

]]

local RunService = game:GetService("RunService")
local Replicated = game:GetService("ReplicatedStorage")
local TweenService = game:GetService("TweenService")

local Fusion = require(Replicated.Fusion)
local scoped, Children = Fusion.scoped, Fusion.Children

local scope = scoped(Fusion, {
	ParallaxWindow = require(Replicated.ParallaxWindow),
	ParallaxFrame = require(Replicated.ParallaxFrame),
})

local start = tick()
local void = script.Parent:WaitForChild("Void")

scope:ParallaxWindow {
	CornerRadius = UDim.new(1, 0),
	LightInfluence = 0,
	Brightness = 5,
	
	[Children] = scope:Computed(function(_, scope)
		local children = {}
		
		local count = 18
		local spread = 1
		for i = 1, count do
			local layer: Fusion.Scope = scope:innerScope()
			
			local wave = layer:Value(i)
			local spring = layer:Spring(layer:Value(0), 20, 0.2)
			
			local size = 1 - (i - 1) / (count + 1) * 0.5

			table.insert(layer, RunService.RenderStepped:Connect(function()
				local time = tick() - start + i / count
				wave:set(math.cos(time * 1.5))
			end))
			
			table.insert(layer, void.ClickDetector.MouseClick:Connect(function()
				task.wait((i - 1) / count * 0.25)
				spring:addVelocity(50)
			end))
			
			table.insert(children, layer:ParallaxFrame {
				Adornee = void,
				Face = Enum.NormalId.Left,
				Brightness = 3,

				Position = layer:Computed(function(use)
					return -Vector3.new(i * spread + use(spring))
				end),

				[Children] = {
					layer:New "Frame" {
						AnchorPoint = Vector2.one / 2,
						Position = UDim2.fromScale(0.5, 0.5),
						Size = UDim2.fromScale(size, size),
						BackgroundTransparency = 1,

						[Children] = {
							layer:New "UIStroke" {
								Color = Color3.new(0, 1, 0),
								Transparency = (i - 1) / count,
								Thickness = 2,
							},
							
							layer:New "UICorner" {
								CornerRadius = UDim.new(1, 0)
							}
						}
					},
				},
			})
		end
		
		return children
	end)
}

If you find any issues with these components, with the ParallaxWindow modifications, or with the original module, please let me know! I’m using this resource, so it would be beneficial to know of any fixes that should be implemented.

8 Likes

This is great. This was really the first module I’ve ever done so I used someone else’s module as a frame for it, which hopefully explains why its so poorly organised. I’m glad someone came along and fixed many of the problems that I didn’t fix. I’m not so sure about the module actually being performant as it uses multiple CanvasGroups, which for some reason tank the rendering performance, but hopefully the module is still useful!

1 Like

umm, is all this stuff needed, in your .rbxl file ?

Fusion is not required to run ParallaxWindow, but you need it to use my components that drive it.

Hey, cheesyr from hidden devs here, I was scrolling around resources and found this, I am actually EXTREMELY impressed!!! Not gonna lie, I’m gonna try making a Portal game with this!

In a project I am making, I’m using parallax to create a 3D effect on cards. When inspecting them, the card is facing exactly in front of the camera, and this causes a bug where the parallax elements sometimes begin to flicker.

I found that this is because in the function calcParallax, when the camera facing is exactly in front of the surface, horOffset and verOffset sometimes gets calculated to nan.

I added these lines of code in calcParallax to fix. (I don’t actually know the underlying problem with why its calculating nan tbh)

-- check if the offsets are nan
if horOffset ~= horOffset then horOffset = 0 end
if verOffset ~= verOffset then verOffset = 0 end
5 Likes

Is that Hatsune Miku? If so, cool.

And also if so, how legal is that? Lol.

Hello! I don’t use the @bluebxrrybot account anymore.

I can reproduce this issue, thanks for bringing it up! :3

I found that the issue is caused when the container’s 3D position is exactly the same to the surface 3D position. The distance between them is zero, which is used for calculating some dot products. The result is NaN because the Wall vectors were multiplied by zero, causing the issue.

Your fix works, but I decided to do something simplier to fix the issue:

if imageDist == 0 then
	horizOffset, vertOffset = 0, 0
end

This is what the patched version looks like:

MODULE ParallaxWindow.lua (29.6 KB)

PLACE ParallaxNanFix.rbxl (119.6 KB)


NOTE: FrameSettings.Position now uses the Z axis to change depth instead of the X axis. I was annoyed because of the confusing behavior, so I changed it.

7 Likes