Roblox Animation

How to Fake Blender-Style Reflections in Roblox Studio

You dragged Reflectance from 0 to 1 expecting the room to show up on your sphere and got clouds instead. Roblox reflects the skybox, not your map — here are the three layers that get you close to a Blender render, including a script that mirrors your scenery under the floor.

by Gustavo Cantino10 min readBeginner

AI-assisted draft, reviewed, tested and edited by a human before publishing. See our Editorial Policy. · Reviewed by Gustavo Cantino, 9/20/26 Editorial Policy

Typographic plate with the Roblox Animation category in large type, the beginner level below it, and a field of vertical lime green bars on a dark background.

Reflections like Blender's don't exist in Roblox, and it isn't an option hidden in some menu: it's a difference between engines. In Blender, Cycles traces the path of light ray by ray and EEVEE uses screen space reflection — both of them actually compute what sits around the object. The Roblox renderer has to run on an entry-level Android phone, so it does neither: what it calls a reflection is, in most cases, a lookup into the skybox (the sky box that wraps your whole map).

Roblox has no ray tracing — and what it has instead

That's where you lose an afternoon. You click a sphere, find the Reflectance property, drag it from 0 to 1 expecting the floor and the wall to appear on it — and you get clouds. Seal that sphere inside a windowless room: still clouds. It doesn't know a wall exists next to it, only that a sky exists. Then the compensating starts: material to Neon, then to Glass, Reflectance pushed to 1 on everything, and the whole map ends up looking like chrome plastic.

Before anything else, two panels. The Explorer is the right-hand panel that lists every object in the game as a tree; Properties sits right below it and shows the properties of the object you selected. If either one isn't showing, open the View tab in the ribbon at the top of Studio and click Explorer and Properties. Everything below depends on those two panels being open.

Another half hour saved: the quality level of the lighting system lives on Lighting, an object that already ships inside the place and shows up in the Explorer. Select it and, in Properties, find the Technology property. It's a dropdown with Voxel, ShadowMap and Future. Decent reflection only happens on Future. And it only changes through that dropdown — a script doesn't change it:

Script in ServerScriptService — do NOT do this
-- Doesn't work. Technology is only changed from the Properties panel, in Studio.
game:GetService("Lighting").Technology = Enum.Technology.Future

With Lighting still selected, also find EnvironmentSpecularScale and EnvironmentDiffuseScale, two numbers from 0 to 1. If Specular sits at 0, the sky neither lights nor reflects on anything, and you can drag Reflectance around all day without seeing a difference. Set both to 1 to test.

The three reflection layers, from cheapest to most expensive

Layer 1 — Reflectance (takes 2 minutes, costs nothing in performance). Click a Part in the Explorer, go to Properties and find Reflectance. It's a number from 0 to 1: 0 is matte, 1 is a sky mirror. For brushed metal, 0.15 to 0.3 already gives the right sheen. Above 0.5 the object turns into a disco ball. This layer only reflects the skybox, so it depends on a Sky existing inside Lighting. Check the Explorer: if there's no object called Sky in there, hover over Lighting, click the plus sign that appears beside it and insert a Sky. A skybox with clouds, mountains and a horizon gives a far more convincing reflection than the default flat blue sky — and it is literally the image that will show up on your "metal".

A rule worth gold for interiors: the more enclosed the scene, the lower the Reflectance. A windowless room with Reflectance 0.4 looks wrong, because Roblox reflects sky coming straight through the ceiling. Indoors, stay between 0.05 and 0.12.

Layer 2 — SurfaceAppearance, which is where your Blender material comes in. This is the real bridge between the two programs. In Blender, your material is probably a Principled BSDF with Base Color, Metallic, Roughness and Normal inputs. Roblox has an object that accepts exactly those four maps: the SurfaceAppearance, which only works inside a MeshPart (the Part that came from an imported model, not the plain box).

The path is this. In Blender, export or bake the four textures as PNG. Roblox accepts images up to 1024x1024 — send something bigger and it gets downscaled, so export at that size and you'll see the final result instead of a surprise. In Studio, open View > Asset Manager, click Images, then Add Images, and pick the files. They enter the moderation queue and only appear once approved; until then the texture stays blank, and that isn't a bug.

With the MeshPart selected in the Explorer, insert a SurfaceAppearance inside it (plus sign next to the MeshPart's name). In the SurfaceAppearance's Properties you'll see ColorMap, MetalnessMap, RoughnessMap and NormalMap. Paste each image's ID into the matching field: Base Color becomes ColorMap, Metallic becomes MetalnessMap, Roughness becomes RoughnessMap, Normal becomes NormalMap. A field left empty is treated as a neutral value (not metallic, very rough). The RoughnessMap is a grayscale image where black is mirror and white is matte — and it's the one that makes a wet metal floor look wet. The MetalnessMap is the opposite of what it sounds like: white is metal, black is not metal. A gray middle ground on both usually reads more believable than either extreme.

Layer 3 — duplicating geometry, which is how a real mirror and a real reflective floor get made. Neither of the two previous layers reflects a single object from your map. If you want to see the character reflected in the floor, or a puddle reflecting the building, the way that has always worked is making a copy of the scenery upside down, below the floor, and leaving the floor translucent on top. The eye buys it instantly. You can do it by hand, duplicating the model, rotating it 180 degrees and dropping it down — but missing the axis by half a stud already ruins the effect, and that's why the script below exists.

The script that builds the floor reflection

The artifact does all of layer 3 in one click. It looks for a Model in Workspace with the name you choose, clones it, mirrors each Part around a Y height you provide, strips out everything that isn't visual (scripts, sounds, lights, ProximityPrompts), makes the pieces translucent and turns off collision, shadow and raycast on the copy.

To use it: in the Explorer, select the scenery pieces you want reflected, right-click and choose Group so they become a Model; rename that Model to Scenery. Then hover over ServerScriptService in the Explorer (a folder that already exists in the tree), click the plus sign and insert a Script. Delete its contents and paste the artifact in.

One number is missing: FLOOR_HEIGHT. It's the Y of the floor's top face, not the center of the Part. Select the floor and look at Position and Size in Properties: if Position Y is 0.5 and Size Y is 1, the top face is at 1. Put 1 in there.

Hit Play (F5). The script creates a Model called FakeReflection in Workspace. Tune MIN_TRANSPARENCY — 0.55 is a reasonable start; closer to 0.7 you get the discreet reflection of a polished floor. What's still missing is the floor on top: a thin Part covering everything, Transparency around 0.5, Reflectance 0.1 and a dark color. Without that Part on top, the effect looks like a glitched duplicate map, not a reflection.

To keep the reflection saved instead of generating it every time: with the game running, click FakeReflection in the Explorer, Ctrl+C, hit Stop and Ctrl+V. The clipboard survives Stop and the copy stays permanently in your map.

The mistake that doubles your map and kills FPS on mobile

The expensive mistake is pointing the script at the entire map. A 2,000-part scene mirrored becomes 4,000 parts for the GPU to draw, and because the copy is translucent, each one costs more than an opaque piece — transparency forces the renderer to draw in order, one by one. On PC you might not even notice; on a phone the game stutters and the player leaves. That's why the artifact ships with PART_LIMIT at 400 and simply refuses to run above that, printing the warning to the Output (open it at View > Output).

The way out isn't raising the limit: it's mirroring less. Put inside the Scenery Model only what is genuinely visible in the reflection — the walls and objects near that floor, not the whole map and not the roof. A floor reflection only shows the first few meters around it anyway.

The second mistake, more annoying to track down: forgetting to turn off CanCollide and CanQuery on the copy. If you duplicate by hand with Ctrl+D instead of using the script, the mirrored copy stays solid, and the player falls through the floor and walks into an invisible wall underneath it; worse, weapon and tool raycasts hit the reflection and the shot dies in nothing. The script already forces CanCollide = false, CanQuery = false, CanTouch = false and CastShadow = false on every cloned piece.

Third: a model with text or asymmetric detail. Mirroring flips position and rotation, but it doesn't flip the mesh itself — a sign reading "SHOP" will appear backwards in the reflection, and a mesh with detail on one side only will show it on the wrong side. For signs and lettering, pull them out of the Scenery Model and accept that that piece doesn't reflect.

What to measure next

Measure the part count before and after. Open View > Command Bar (the little bar you type commands into, usually at the bottom) and run:

Command Bar (View > Command Bar)
print(#workspace:GetDescendants())

Write the number down before generating the reflection and again after. If the difference is more than a few hundred, you mirrored too much scenery and it's time to pull things out of the Scenery Model.

Then test where it hurts: the Test tab has the device emulator (Device). Pick a phone, hit Play and walk across the reflective floor. If the camera drags when you look down, the culprit is stacked transparency — push MIN_TRANSPARENCY to 0.7 or cut half the mirrored pieces.

Finally, compare against your Blender reference side by side, one image per screen. Almost always what's missing isn't reflection: it's the RoughnessMap. An object with uniform roughness looks like a plastic toy in any engine, Blender included. Varying the roughness in the texture fixes more than raising Reflectance.

Ready-to-use artifact

Script in ServerScriptService
lua
--!strict
-- Fake floor reflection: duplicates a Model, mirrors it below the floor and makes it translucent.
-- Where it lives: Script (RunContext Legacy) inside ServerScriptService.

local Workspace = game:GetService("Workspace")

-- ========= CONFIGURATION =========
local NOME_DO_MODELO: string = "Cenario"    -- exact name of the Model in the Workspace
local ALTURA_DO_PISO: number = 0            -- Y of the TOP FACE of the floor
local TRANSPARENCIA_MINIMA: number = 0.55   -- 0 = opaque, 1 = invisible
local LIMITE_DE_PARTS: number = 400         -- above this the script warns and does not duplicate
-- ================================

-- Mirrors a CFrame around a horizontal plane (Y = planoY).
-- Inverts the position in Y and reflects the rotation (M * R * M), which remains
-- a valid rotation: Roblox does not accept negative scale inside a CFrame.
local function espelharCFrame(cf: CFrame, planoY: number): CFrame
	local x, y, z, r00, r01, r02, r10, r11, r12, r20, r21, r22 = cf:GetComponents()
	return CFrame.new(
		x, (2 * planoY) - y, z,
		 r00, -r01,  r02,
		-r10,  r11, -r12,
		 r20, -r21,  r22
	)
end

-- Strips from the copy everything that is not visual: script, sound, light, prompt, humanoid.
local function limparCopia(raiz: Instance): ()
	for _, obj in ipairs(raiz:GetDescendants()) do
		if obj:IsA("LuaSourceContainer")
			or obj:IsA("Sound")
			or obj:IsA("ParticleEmitter")
			or obj:IsA("Light")
			or obj:IsA("Humanoid")
			or obj:IsA("ClickDetector")
			or obj:IsA("ProximityPrompt") then
			obj:Destroy()
		end
	end
end

local function contarParts(raiz: Instance): number
	local total = 0
	for _, obj in ipairs(raiz:GetDescendants()) do
		if obj:IsA("BasePart") then
			total += 1
		end
	end
	return total
end

local original = Workspace:FindFirstChild(NOME_DO_MODELO)
if not original or not original:IsA("Model") then
	warn(("[Reflection] Could not find a Model named '%s' inside the Workspace."):format(NOME_DO_MODELO))
	return
end

local quantidade = contarParts(original)
if quantidade > LIMITE_DE_PARTS then
	warn(("[Reflection] '%s' has %d parts; the map would go to %d. Reduce the Model or raise LIMITE_DE_PARTS.")
		:format(NOME_DO_MODELO, quantidade, quantidade * 2))
	return
end

local antigo = Workspace:FindFirstChild("ReflexoFalso")
if antigo then
	antigo:Destroy()
end

local reflexo = original:Clone()
reflexo.Name = "ReflexoFalso"
limparCopia(reflexo)

for _, obj in ipairs(reflexo:GetDescendants()) do
	if obj:IsA("BasePart") then
		obj.Anchored = true      -- does not fall or get pushed
		obj.CanCollide = false   -- the player does not bump into the copy
		obj.CanQuery = false     -- tool raycast ignores it
		obj.CanTouch = false
		obj.CastShadow = false   -- a shadow below the floor only costs GPU
		obj.Transparency = math.max(obj.Transparency, TRANSPARENCIA_MINIMA)
		obj.CFrame = espelharCFrame(obj.CFrame, ALTURA_DO_PISO)
	end
end

reflexo.Parent = Workspace
print(("[Reflection] 'ReflexoFalso' created with %d parts mirrored at Y = %.2f.")
	:format(quantidade, ALTURA_DO_PISO))
In this article

Keep reading