To provide an generic and extendable solution to adopting ViewportFrames as a means to render an object on top of a 3D environment.
This is intended to replace older solutions such as rbx-XRayAdornment. Our previous issues with this old xray module is that it made it really hard to balance the state of the target object with the actual rendering process.
There was some difficulty in making a good “only-render-when-obstructed” highlighted object. All attempts were messy and oddly coupled with the creation of the XRayAdornment itself.
Our philosophy is to separate highlight state from highlight render rules.
Check out our other annotated examples in the examples directory!
local ObjectHighlighter = require(game.ReplicatedStorage:FindFirstChild("ObjectHighlighter"))
local targetModel = game.Workspace.MyModel --Replace this with the path to your model
-- This screen gui will contain our ViewportFrames
local myScreenGui = Instance.new("ScreenGui")
myScreenGui.Name = "ObjectHighlighter"
myScreenGui.Parent = game.Players.LocalPlayer.PlayerGui
local myRenderer = ObjectHighlighter.createRenderer(myScreenGui)
local myHighlight = ObjectHighlighter.createFromTarget(targetModel)
-- Apply our highlight object to our Renderer stack.
-- We can add as many highlight objects to a renderer as we need
-- Our renderer will not render until it steps
Feel free to post here or DM me or @GollyGreg any questions, comments, issues or feedback
Saw a handful of people making these style setups when viewport frames where first being tested, really cool to see that you packaged your implementation up for distribution!
Although I’ll probably make my own it makes me happy to see someone release it for the community in such an accessible format so more games can enjoy the benefits. Great for creating games where you can see your team through walls (like L4D) and if asymmetric the “enemy” team can see players at all times.
Also solid for FPS style games where medics need to be able to see where low health team members are and doing a color-slide from orange-red as those team members lose more health.
CloneTrooper1019 did this as well for The Stalker 2. He open-sourced the code on Pastebin. What he did was replicate the characters to a Table and later placed them in the Camera and scaled and positioned them based on the character’s position relative to the camera’s CFrame. I could only make an educated guess that the developers of Polyguns did it in a fashion similar to this.
I made an extremely rough prototype of a “Digital Threat” scope, inspired by Apex Legends.
It checks if the object is within a GUI (the red dot gui) using WorldToScreenPoint, and then does a single raycast to check if the part is obscured.
If the part is in the gui and visible, it updates the highlight and makes it visible.
Plenty of bugs to work out here, but I wanted to show a cool usage idea.
Edit: Forgot to include my code
onBeforeRender = function(_, _)
onRender = function(_, worldPart, viewportPart, highlight)
--Only update within frame
local v,oS = Cam:WorldToScreenPoint(worldPart.CFrame.Position)
if oS and insideGUI(v, gui) then
--Only update if not obscured
local blockingPart = workspace:FindPartOnRay(Ray.new(Cam.CFrame.Position,(worldPart.CFrame.Position - Cam.CFrame.Position).unit * 500), Cam)
if blockingPart and not blockingPart:IsDescendantOf(worldPart.Parent) then
viewportPart.Transparency = 1
viewportPart.Transparency = worldPart.Transparency
viewportPart.CFrame = worldPart.CFrame
viewportPart.Color = highlight.color
--not within gui
viewportPart.Transparency = 1
onAdded = function(_, viewportPart, highlight)
local function clearTextures(instance)
if instance:IsA("MeshPart") then
instance.TextureID = ""
elseif instance:IsA("UnionOperation") then
instance.UsePartColor = true
elseif instance:IsA("SpecialMesh") then
instance.TextureId = ""
local function colorObject(instance)
if instance:IsA("BasePart") then
instance.Color = highlight.color
for _, object in pairs(viewportPart:GetDescendants()) do