I would like to use UserInputService to detect which parts inside a ViewportFrame were clicked. With the addition of a WorldModel, raycasts are possible. However, I’m looking for some example code on how to do this using both UserInputService and Raycasting.

I also customized the function used with UserInputService and GuiService for GUI inset.

local GuiService = game:GetService("GuiService")
local UserInputService = game:GetService("UserInputService")
local function RaycastInViewportFrame(viewportFrame, raycastDistance, raycastParams)
raycastDistance = raycastDistance or 1000
local camera = viewportFrame.CurrentCamera
local worldModel = viewportFrame:FindFirstChildWhichIsA("WorldModel")
local mousePosition = UserInputService:GetMouseLocation() - GuiService:GetGuiInset() - viewportFrame.AbsolutePosition -- account for viewportframe offset
local relativePosition = ((mousePosition - (viewportFrame.AbsoluteSize/2)) * Vector2.new(1, -1))/(viewportFrame.AbsoluteSize/2) -- get the relative position of the click with center of viewportFrame as origin: -1 is left/bottom and 1 is right/top for X and Y respectively
local projectedY = math.tan(math.rad(camera.FieldOfView)/2)*raycastDistance -- the projected height of a 2D frame raycastDistance studs from the camera with same aspect ratio
local projectedX = projectedY * (viewportFrame.AbsoluteSize.X/viewportFrame.AbsoluteSize.Y) -- projected width from aspect ratio
local projectedPosition = Vector2.new(projectedX, projectedY) * relativePosition -- the projected position of the input on the similar frame
local worldPosition = (camera.CFrame * CFrame.new(projectedPosition.X, projectedPosition.Y, -raycastDistance)).Position -- the 3d position of said projected position
local part = visualizeRay(camera.CFrame.Position, (worldPosition - camera.CFrame.Position).Unit * raycastDistance)
part.Parent = workspace
local part = visualizeRay(camera.CFrame.Position, (worldPosition - camera.CFrame.Position).Unit * raycastDistance)
part.Parent = worldModel
local part = visualizeRay(camera.CFrame.Position, (worldPosition - camera.CFrame.Position).Unit * raycastDistance)
part.Parent = VPF
return worldModel:Raycast(camera.CFrame.Position, (worldPosition - camera.CFrame.Position).Unit * raycastDistance, raycastParams)
end

Edit: Whoops left the visualization functions within it, here it is anyway since it helps see how it works

visualizeRay

local function visualizeRay(pos, vector)
local distance = vector.Magnitude
local p = Instance.new("Part")
p.Anchored = true
p.CanCollide = false
p.Size = Vector3.new(0.5, 0.5, distance)
p.BrickColor = BrickColor.Random()
p.CanQuery = false
p.CFrame = CFrame.lookAt(pos, pos+vector)*CFrame.new(0, 0, -distance/2-5)
game.Debris:AddItem(p,1)
return p
end

This is an odd solution, but it is a solution nonetheless. For anyone else wanting to use this, you would need to remove any references to the visualizeRay functions as well as the part variables.