2D Screen Position to 3D World Position

I’m trying to convert a 2D position on the user’s screen to a 3D World Position. Currently, there is only a function for doing the opposite which is WorldToScreenPoint. Does anyone know any methods I could use to translate a 2D position on the screen to a 3D World Position? Also, it needs to be possible to do this with an IgnoreList because objects such as the user’s camera would get in the way of finding this position.

3 Likes

You should be able to use ScreenPointToRay right? or ViewportPointToRay depending on your use case, another way to do this without rays at all is using a custom screen to world translation by using a function, the one that i used to convert the user’s mouse position to world position in view port frames, like:

local function LocalPos(Pos,Viewport)
	return Pos - Viewport.AbsolutePosition --- Return Position - vpf.AbsPos
end

local function ScreenToWorldSpace(Pos, viewport, camera, Depth, Gui_Inset)
 Pos =  LocalPos(Vector2.new(Pos.X, Pos.Y ), viewport) -- Make mouse position "local"
 local Cam_Size = Vector2.new(viewport.AbsoluteSize.X , viewport.AbsoluteSize.Y - (Gui_Inset or 0))
 local Height = Cam_Size.Y
 local Width = Cam_Size.X	
 local AspectRatio = (Cam_Size.X/Cam_Size.Y)
 local Cam_Pos = camera.CFrame.Position 
 local Scale = (Depth or 1) 
 local fov  =math.rad(camera.FieldOfView)
 local Tangent = math.tan((fov/2));
 local fx = ((2 * Scale) * (Pos.x /(Width-1)) -(Scale*1))
 local fy = ((2 * Scale) * (Pos.y/(Height-1)) -(Scale*1))
 local NX = ((AspectRatio * Tangent * fx ))
 local NY = (-Tangent * fy)
 local NZ = -Scale 
 local Translatedcf = (camera.CFrame) * CFrame.new(Vector3.new(NX, NY, NZ))  -- rotate rel to camera
 return CFrame.new( Translatedcf.Position, camera.CFrame.Position  ) -- rotate to face camera
end 

local cf  = ScreenToWorldSpace(MousePos, ScreenGui, Camera)
13 Likes

A 3D position can have a 2D position because you can eliminate depth and just be left with X and Y coordinates, but a 2D position does not have depth. When a 2D position is converted to a 3D one, the depth part is formed from joining two points together and forming a unit ray. This unit ray needs to be extended outward if you want to get a hit point; here, you can just use regular raycasting and ignore list features. The functions for this and code samples are above (posted before me).

1 Like

Mouse has a Target property that allows you to get 3D world positions easily, along with an IgnoreList being applied. With ScreenPointToRay I find the 3D point is being placed on the camera which is not what I want.

1 Like

Target is implemented as a raycast as well. The code and functions that Jay supplied are almost exactly how the Target/Hit/whatever properties are calculated internally. It’s the equivalent of something like this (you can’t use this code because of API restrictions, though):

local UserInputService = game:GetService("UserInputService")
local Mouse = game:GetService("Players").LocalPlayer:GetMouse()

UserInputService.InputChanged:Connect(function (inputObject, gameProcessedEvent)
    local inputPosition = inputObject.Position
    -- ViewportPointToRay would probably be more accurate to use here
    local mouseUnitRay = workspace.CurrentCamera:ScreenPointToRay(inputPosition.X, inputPosition.Y)
    -- Long rays are expensive, so I set it to 500
    local mouseRay = Ray.new(mouseUnitRay.Origin, mouseUnitRay.Direction * 500)
    local target, hit = workspace:FindPartOnRay(mouseRay, Mouse.TargetFilter)

    Mouse.Target = target
    Mouse.Hit = hit
end)

Those two functions are what you want to convert 2D positions to 3D coordinates. ScreenPointToRay accounts for Gui space (and thus the inset of 36 pixels for the topbar), Viewport is for the screen (so it goes according to the CurrentCamera).

9 Likes

@Jaycbee05 Not sure how I would implement that with an IgnoreList but thank you anyways.

@colbert2677 After some slight modification, the code you provided is simple and works perfectly. Thank you.

local screenPoint = Vector2.new(10,10) -- 2D screen position

local mouseUnitRay = workspace.CurrentCamera:ScreenPointToRay(screenPoint.X, screenPoint.Y)
local mouseRay = Ray.new(mouseUnitRay.Origin, mouseUnitRay.Direction * 500)
local target, hit = workspace:FindPartOnRay(mouseRay, mouse.TargetFilter)

print(hit) --> 3D world position
1 Like

if you were wondering the equivalent, using rays would be something like:

local FRONT = Vector3.new(0, 0, -1)
local cf, rot =  ScreenToWorldSpace(Vector2.new(Mouse.X, Mouse.Y) ,ScreenGui, game.Workspace.Camera)
local FowardDir = cf:VectorToWorldSpace(-FRONT)
local mouseRay  = Ray.new(cf.Position, FowardDir * 500)

but i would say using the “built-in” option is better anyways or at least easier

This worked well for positioning an object with particles to a gui position, since viewport frames don’t show particle emitters.

Hello, I have a problem. I want to get the 3d CFrame that the middle of the screen is pointing /looking at. Please help. Thank you!!