Trompe L'oeil effect

So, basically I’m trying to make something like this:
trompe loeil effect
But I literally have no idea of how to do it.
Maybe this can be possible by Raycasting with the part and use weird ViewportFrame and math stuff (specially trigonometry I think), but I dont know where to start. Any ideas?

1 Like

Doesn’t look like ViewportFrames work in SurfaceGuis unfortunately. I don’t think it’s possible with ViewportFrames in a ScreenGui because they’d have to be skewed to account for some rotations, not just scaled and rotated.

But actually, if you put a SurfaceGui into ScreenGui (or PlayerGui when it is in-game) and you set the Adornee property to a part, you can put ViewportFrames in parts. That is exactly how Roblox Studio portals are made.

4 Likes

Yes they do! I’ve tried them before.

You do need to place the SurfaceGui in PlayerGui though, otherwise it just renders as white, but it does work and does display on the part you have the Adornee set to.

Viewportframes are a little limited. Shadows and some materials do not work. If you add a selectionbox instance in a viewportframe, it will get rendered in workspace instead. Cameras objects don’t replicate from server to client.

You would need to use client scripts.

You would need to project the part on to the walls. Starting at the camera point, going through a point in the part, and hitting a wall surface. You might make a very thin part, floating very close to the wall, and put the viewportframe there.

Raycasting would be a good way to handle simple use cases, meaning walls that are flat without furniture and other small parts / high number of parts to deal with.

Otherwise, if not raycasy, you would need to envision a pyramid with the camera at the tip, and find every part that is touching the pyramid. I doubt that will be feasible. Making a pyramid and finding touching parts will be ok. Finding out whether to project onto surfaces could soon become difficult and/or lead to too many viewportframe instances that could hurt performance.

You would need to set the lighting in the viewportframe to match the room.

I just wonder… when you project a rectangle onto a wall or floor, you might get a trapezoid or other shape. Not a rectangle. The wall is not orthogonal to the line going from the camera to the wall. While you can probably stretch the viewportframe, I don’t think you can make it not rectangular. I cannot see if that is a showstopper.

I am not sure if the render quality will be good enough, even if it is possible.

I made an open source game called “mad penguins” that use viewportframes to make pancake versions of the penguins in the game. The code is not pretty, but if you did not make a viewportframe in a script before, you are welcome to see if it helps you get going. It is simple, compared to what you are proposing, I think.

1 Like

This could be useful
image
How did you do it?

It should be uncooylocked so you can download a copy and see :slight_smile:

Pretty sure its impossible.

Summary

This text will be hidden

Guys I dont know what did I just do but I made this thing:


Here is the code:

local cameraResolution = cam.ViewportSize
local cam2 = workspace.Camera2
local wall = workspace.wall
local sphere = workspace.sphere
sphere.Transparency = 0
task.wait(2)
sphere.Transparency = 1
local function getSurfaceInfo(part, normal)
    local partCF: CFrame, partSize = part.CFrame, part.Size
    local back = -Vector3.FromNormalId(normal)
    local axis = (math.abs(back.Y) == 1) and Vector3.new(back.Y, 0, 0) or Vector3.yAxis
    local right = CFrame.fromAxisAngle(axis, math.pi / 2) * back
    local top = back:Cross(right).Unit
    local cf = partCF * CFrame.fromMatrix(-back * partSize / 2, right, top, back)
    local size = Vector2.new((partSize * right).Magnitude, (partSize * top).Magnitude)
    return cf, size
end
local surfaceCF, surfaceSize = getSurfaceInfo(wall, Enum.NormalId.Front)
local rPoint = surfaceCF:PointToObjectSpace(cam.CFrame.Position)
local sX, sY = rPoint.X / surfaceSize.X, rPoint.Y / surfaceSize.Y
local scaleX = 1 + math.abs(sX) * 2
local scaleY = 1 + math.abs(sY) * 2
local scale = math.sqrt(scaleX * scaleX + scaleY * scaleY)
local rDist = (cam.CFrame.Position - surfaceCF.Position):Dot(surfaceCF.LookVector)
local newFov = 2 * math.atan2(surfaceSize.Y / 2, rDist)
local clampedFov = math.clamp(math.deg(newFov), 1, 120)
local pDist = surfaceSize.Y / 2 / math.tan(math.rad(clampedFov) / 2)
local adjust = rDist / pDist
local factor = (newFov > math.rad(120) and adjust or 1) / scale
local scaleCF = CFrame.new(0, 0, 0, factor, 0, 0, 0, factor, 0, 0, 0, 1)
cam2.CFrame = CFrame.new(cam.CFrame.Position) * (surfaceCF - surfaceCF.Position) * CFrame.Angles(0, math.pi, 0) * scaleCF
cam2.FieldOfView = clampedFov
local surfaceCF, surfaceSize = getSurfaceInfo(wall, Enum.NormalId.Front)
local vs = game.StarterGui.SurfaceGui.ViewportFrame.sphere
vs.Position = sphere.Position
vs.Parent.Parent.CanvasSize = Vector2.new(cam.ViewportSize.Y * (cam.ViewportSize.X / cam.ViewportSize.Y), cam.ViewportSize.Y)
game.StarterGui.SurfaceGui.ViewportFrame.Position = UDim2.fromScale(0.5 - sX, 0.5 - sY)
vs.Parent.Size = UDim2.fromScale(scale, scale)

I think I’m doing well, also here is an image of the Explorer if someone needs it:
image

5 Likes

Nvm guys, I completely solved this theory. It is actually possible with some math stuff, ViewportFrames and Camera calculations, but it is possible. Here is the final result:


Thanks to all of you anyway, it was so useful everything you provided me :slightly_smiling_face:.

9 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.