How to make 3D mirrors using a ViewportFrame

You can view the code for the DUMB mirror at the very very bottom. This is just a short explainer.

Warning: Since it is impossible to crop Roblox Viewport Frames without using other non standard methods I will not be showing this(someone else has done this but I hate the idea of using it as it is not a good idea for production games).I believe It is possible to get this working with dynamic mirrors but for now I will need to find a better workaround. Dynamic mirrors in this context I refer to where the player is able to move their camera relative to mirror

This type of mirror is most suited to instances where the player and mirror are static and the player is not moving in relation to the orientation mirror.

This is a Resource/Tutorial Hybrid. I have come back to Roblox after a while I have been busy ever since I left school. (Life catches up) and I was going through some of my old projects when I found that I had attempted to make a game similar to Beware horror car game.

One of the core mechanics is being able to see the headlights of the enemies.
image
For reference.
I set out to make a mirror and succeeded and since this project is never going to get completed I have decided to opensource the code.

You may already know how a mirror works.
image
As you can see the black circle is your eye
The Black lines are the light that goes from the mirror and hits your eyes
The Red lines are the reflected lines.

If you extrapolate the red lines into the mirror you get this
image
This “FakeEye” is where you you would be if you were looking at the original without a mirror.
So what we can do is put a Camera at this position, render the camera onto the part and boom we have a mirror.

We are not done yet?

You may be thinking that we are done but NO we are not done. What makes mirrors different to portals is that they reflect. They only reflect in the plane parallel to the surface of the mirror. (You may think this would be easy as negating the position but that is not the case. I have seen many implementations of mirrors in Roblox NONE are 100% correct. Take this for example:
How to make a mirror not using a viewport frame? - Help and Feedback / Scripting Support - DevForum | Roblox
image

To understand why this is a big problem try to look in a mirror.
You will notice that:
A. Your left and right side is flipped
B. Your top and bottom is right way up
How does a mirror know to flip your left and right and not your top and bottom.
Even if you lay on your side the mirror always only flips your left and right relative to your head. Not your top and bottom.

As you can see in the image above the bat is placed incorrectly and it should be flipped. This is what many mirrors in Roblox fail to do. For static mirrors this is only a visual discrepancy. But for a mirrors that you need in a car you will notice this as it moves over the ground.

The way we can fix this is by mirroring the positions of all the objects in one axis then rotating 180 degrees in Y axis. This is to account for the fact that mirrors are also back to front. (That is why)
The camera rotation needs to be inversed as it the opposite rotation wise. This would look like you are doubling the rotation when it only looks doubled because we also inverted the objects.
Then the camera needs to also be inverted in X axis. The way I do that here is kinda not well done.

Then you have it completed!

Limitations

Now the reason why I call it “Dumb” mirror is that is does not take into account the position of the camera(eye) at the time of rendering. So it looks like you are standing directly in front of the mirror

This is fine for a car mirror as the player will be in first person the offset from the car mirror to the players head does not move. It will also be fine for first person mirrors for example a hand held mirror

Here is the open sourced dumb mirror for a rear view in a car.

local MirrorSwap = -(Vector3.xAxis)
local rad =math.deg
local RunService = game:GetService("RunService")

local CollectionService = game:GetService("CollectionService")

local MirrorPart = workspace.Car.MirrorPart

local PlayerGui = game.Players.LocalPlayer.PlayerGui

local SurfaceGui = Instance.new("SurfaceGui")
SurfaceGui.Adornee = MirrorPart
SurfaceGui.Brightness = 10
SurfaceGui.LightInfluence = 0
SurfaceGui.Parent = PlayerGui


local ViewportFrame = Instance.new("ViewportFrame")
ViewportFrame.Size = UDim2.fromScale(-1,-1)
ViewportFrame.AnchorPoint = Vector2.one/2
ViewportFrame.Position = UDim2.fromScale(0.5,0.5)
ViewportFrame.Ambient = Color3.new(0,0,0)
ViewportFrame.LightDirection = -Vector3.yAxis
ViewportFrame.BackgroundColor3 = Color3.new(0,0,0)
ViewportFrame.Parent = SurfaceGui


local Camera = Instance.new("Camera")
Camera.CameraType = Enum.CameraType.Scriptable
Camera.FieldOfView = 30
Camera.CFrame = CFrame.new(Vector3.new(0,20,0))

Camera.Parent = ViewportFrame

ViewportFrame.CurrentCamera = Camera

local LightList = CollectionService:GetTagged("Reflect")

for i,v in ipairs(LightList) do
	local clone = v:Clone()
	clone.Position = v.Position * Vector3.new(-1,1,1)
	clone.Orientation += Vector3.new(0,180,0) 
	clone.Parent = ViewportFrame
	LightList[i] = clone
end

RunService.RenderStepped:Connect(function(deltaTime)
	Camera.CFrame = MirrorPart.CFrame:Inverse():ToWorldSpace(CFrame.new(0,0,0))
	Camera.CFrame += -Camera.CFrame.Position + (MirrorPart.Position * Vector3.new(-1,1,1))
end)

A couple of prerequisites/caveats:

  • It is running locally as it is only a visual element of the game. Visual elements should always be created on client side for server performance.
  • You would need part to run this on so where your mirror will be displayed
  • I decided to use collection service to get a list of specific elements to reflect. Having a lot of objects reflect in the mirror (escpecially the ones that move) can be bad for performance. I have tagged a list using a plugin called tag editor. Tag Editor Plugin - Help and Feedback / Creations Feedback - DevForum | Roblox
  • Of course you would need things to appear in the reflection.
  • This does not take into account Mesh reflection. To reflect a mesh you need to invert the size so that the X is flipped negative. Have not tested this. I do not know how decals and textures render just yet.
  • Mirror cannot rotate in relation to the players camera. Both have to remain “locked” together.
  • Terrain is not reflectable. If you need a reflection I recomend creating a low-res low-poly mesh that matches your terrain in physical world. Then applying same steps.
  • Parts do not update. Updating them is simple as getting the current CFrame and applying same logic.

Currently working on improvements for this to allow the dynamic movement of the players camera and also for use with portals. (Portals are basically 2 mirrors without the invert and with outputs of the mirrors flipped)

27 Likes

I have just noticed now that Wedges do not appear correctly. I this this would be a side effect of the part rotation. Strange how they don’t work and corner wedges and cylinders do. Will get that sorted (probably)
Let me know if you need anything.