Making portals with viewport frames

Trying to make it so you can create portals anywhere on the map. I already completed the seemless transition but the viewport frame camera rendering is what is confusing me, I’ve tried using :ToObjectSpace() but it doesnt seem to do the trick.
This is what happens.
image
The camera cframe seems to not be the same as the opposing portal. and I can’t find a way to correct this.

This is the script I have for the blue portal.
`local cam = Instance.new(“Camera”,script)
cam.CameraType = “Scriptable”
cam.CFrame = script.Parent.Adornee.CFrame
script.Parent.ViewportFrame.CurrentCamera = cam

game:GetService(“RunService”).RenderStepped:Connect(function()
script.Parent.Parent.Parent.Parent.RemoteEvent2.Script.Portal2.ViewportFrame.CurrentCamera.CFrame = script.Parent.Adornee.CFrame * script.Parent.Parent.Parent.Parent.RemoteEvent2.Script.Portal2.Adornee.CFrame:toObjectSpace(workspace.CurrentCamera.CFrame)
end)

It changes the red portal viewport camera to the blue portals adornee cframe times its own cframe relative to the cframe of the players camera.

Any help is appreciated!

3 Likes

That cool. I want to learn this.

1 Like

function ViewportWindow:RenderFrame(camCF, surfaceCF, surfaceSize,other)
local PI2 = math.pi/2
local Y_SPIN = CFrame.Angles(0, math.pi, 0)

local ZERO = Vector3.new(0, 0, 0)
local UNIT_Y = Vector3.new(0, 1, 0)
local UNIT_NZ = Vector3.new(0, 0, -1)
local XZ = Vector3.new(1, 0, 1)
local YZ = Vector3.new(0, 1, 1)
local camera = game.Workspace.CurrentCamera

camCF = camCF or camera.CFrame
local surfaceCF1, surfaceSize1 = self:GetSurfaceInfo(other)
if not (surfaceCF and surfaceSize) then
	surfaceCF, surfaceSize = self:GetSurfaceInfo()
end

–local camOffset = surfaceCF:Inverse() * camera.CFrame

surfaceSize = SurfSizechec(surfaceSize)
surfaceSize1 = SurfSizechec(surfaceSize1)
local tc = surfaceCF * Vector3.new(0, surfaceSize.y/2, 0)
local bc = surfaceCF * Vector3.new(0, -surfaceSize.y/2, 0)

local cross = camCF.LookVector:Cross(surfaceCF.UpVector)
local right = cross:Dot(cross) > 0 and cross.Unit or camCF.RightVector

local levelCamCF = CFrame.fromMatrix(camCF.p, right, surfaceCF.UpVector, right:Cross(surfaceCF.UpVector))
local levelCamCFInv = levelCamCF:Inverse()

local csbc = levelCamCFInv * bc
local cstc = levelCamCFInv * tc
local v1 = (csbc*YZ).Unit
local v2 = (cstc*YZ).Unit
local alpha = math.sign(v1.y)*math.acos(v1:Dot(UNIT_NZ))
local beta = math.sign(v2.y)*math.acos(v2:Dot(UNIT_NZ))

local fh = 2*math.tan(math.rad(camera.FieldOfView)/2)
local hPrime = math.tan(beta) - math.tan(alpha)
local refHeight = hPrime / fh

--

local c2p = surfaceCF:VectorToObjectSpace(surfaceCF.p - camCF.p)
local c2pXZ = c2p * XZ
local c2pYZ = c2p * YZ

local dpX = c2pXZ.Unit:Dot(UNIT_NZ)
local camXZ = (surfaceCF:VectorToObjectSpace(camCF.LookVector) * XZ)

local scale = camXZ.Unit:Dot(c2pXZ.Unit) / UNIT_NZ:Dot(c2pXZ.Unit)
local tanArcCos = math.sqrt(1 - dpX*dpX) / dpX

--

local w, h = 1, (surfaceSize.x / surfaceSize.y)
local dx = math.sign(c2p.x*c2p.z)*tanArcCos
local dy = c2pYZ.y / c2pYZ.z * h
local d = math.abs(scale * refHeight * h)

local ncf = (surfaceCF - surfaceCF.p)  * Y_SPIN * CFrame.new(0, 0, 0, w, 0, 0, 0, h, 0, dx, dy, d)
local c = {ncf:GetComponents()}

local max = c[1] - 1
for i = 1, #c do max = math.max(math.abs(c[i]), max) end

ncf = CFrame.new(c[1], c[2], c[3], c[4]/max, c[5]/max, c[6]/max, c[7]/max, c[8]/max, c[9]/max, c[10]/max, c[11]/max, c[12]/max)

--


-- can set w and h to 1 and use this as an alternative scaling method, but I find the above is better
local ratioXY = (surfaceSize.x / surfaceSize.y)
local ratioYX = (surfaceSize.y / surfaceSize.x)

if (ratioXY > ratioYX) then
	self.SurfaceGUI.CanvasSize = Vector2.new(1024, 1024 * ratioYX)
else
	self.SurfaceGUI.CanvasSize = Vector2.new(1024 * ratioXY, 1024)
end


local ncamCF = ncf + camCF.p
--
self.Camera.FieldOfView = camera.FieldOfView
self.Camera.CFrame = ncamCF 

self.Camera.Focus = ncamCF * CFrame.new(0, 0, camCF:PointToObjectSpace(surfaceCF.p).z)

return ncamCF
end

this is from egomoose(edited by me), it helped me a lot when i was stuck on my portal effect.
don’t forget to make the SurfaceSize = SurfaceSize * Y_Spin

1 Like

Do you mind releasing a rbxl file with this working. I’m still confused

It is a sample of Egomoose’s code, you can find it uncopylocked on his profile

1 Like


Works great thanks :smiley: I modified it abit so I can be more customizable and fixed a issue where it stops working when you respawn.

1 Like

Just a word of future-proofing caution: That sample code above contains both Egomoose’s original portal solution, which scales the viewport frame CanvasSize, as well as the portal code that I wrote from a similar demo, which is all of those nasty trig calls that are used to compute the CFrame.new(0, 0, 0, w, 0, 0, 0, h, 0, dx, dy, d) matrix. The caution here is that my version relies on being able to set Camera.CFrame to a non-orthonormal CFrame. That CFrame I’m using is far from what’s officially allowed in a CFrame; it has shear, non-uniform scaling, and translation. This is unavoidable with this approach, because neither perspective projection matrices nor their inverses are valid CFrames. In fact, neither can even be stored as a CFrame because they have values in the 4th row of the 4x4 matrix. It’s only because the correction involves an inverse perspective projection combined with a forward one that the result is even able to be represented as a CFrame at all.

Right now, Roblox lets you do this, but this is not fully guaranteed to be future proof. If they ever decide that it’s necessary to orthonormalize this CFrame in the setter, this trick will no longer work, and you will need to use Egomoose’s original method that does not rely on improper CFrames. The benefit of my version, is that it pixelates less at close view distances and oblique view angles, because it’s always using the full 1024x1024 resolution of the VPF to the fullest, no cropping.

2 Likes
-- Script for viewport camera rendering between portals

-- Function to create viewport camera for blue portal
local cam = Instance.new("Camera", script)
cam.CameraType = "Scriptable"
cam.CFrame = script.Parent.Adornee.CFrame
script.Parent.ViewportFrame.CurrentCamera = cam

-- RenderStepped event to update red portal's viewport camera
game:GetService("RunService").RenderStepped:Connect(function()
    -- Calculate the transformation needed for the red portal's camera
    script.Parent.Parent.Parent.Parent.RemoteEvent2.Script.Portal2.ViewportFrame.CurrentCamera.CFrame = script.Parent.Adornee.CFrame * script.Parent.Parent.Parent.Parent.RemoteEvent2.Script.Portal2.Adornee.CFrame:toObjectSpace(workspace.CurrentCamera.CFrame)
end)

I am not the best scripter, apologies.

From your description, it appears that the camera CFrame for the opposing portal doesn’t match the CFrame of the original portal, and you’re struggling to rectify this discrepancy. It’s crucial to ensure that the camera view transitions smoothly between portals to provide a seamless gameplay experience.

The script you provided for the blue portal involves creating a new camera, setting its type to “Scriptable,” and initializing its CFrame to match the CFrame of the blue portal’s adornee. Then, during each render step, you’re attempting to update the CFrame of the viewport camera for the red portal. You’re achieving this by calculating the transformation needed based on the relative positions of the two portals and the player’s camera.

However, despite your efforts, it seems that the transformation isn’t producing the desired result, leading to misalignment between the portal cameras.

To address this issue, you might want to revisit how you’re calculating the transformation between the two portals’ CFrames. Ensure that you’re correctly accounting for any rotations or translations needed to align the camera viewpoints accurately. Double-check the usage of ToObjectSpace() and ensure that it’s applied correctly within the context of your script.

Additionally, consider debugging your code by adding print statements or visual indicators to track the values of relevant variables and confirm that the transformations are being calculated as expected.

Overall, refining the calculation of camera transformations and carefully verifying each step of your script should help you achieve the desired result of seamless viewport frame camera rendering for portals in your game.