Ever wanted to turn your roblox models into 3D 8-bit art? Well now you can with this neat plugin!
This plugin allows you to pixelate any desired model you wish. This plugin is recommended for larger models as you are limited with 1x1x1 blocks as your pixels. If you wish to do a small model (like a sword) than you can just scale up the model, pixelate it, and then scale it back down again.
Here are some examples!
Giant sphere
Build time: 144s
Scan size: 250, 250, 250
Blocks: 194,372
Utility truck
Build time: 38s
Scan size: 125, 125, 125
Blocks: 15,485
M1 Garand
Build time: 105s
Scan size: 223, 223, 223
Blocks: 6,476
This system uses raycasting. Meaning this will work on any BaseParts! Such as;
- MeshParts
- Unions
- Parts
- WedgeParts
- etc
Please note that this system goes off by collision. Meaning that if you have a part that has a SpecialMesh in it, then the result will be dependent to the shape of your part rather than the mesh.
This plugin works by scanning all 6 sides of the model with rays, and if the ray hits an object within the model a cube will be created!
If you are going to use this plugin on a model that has Unions or MeshParts and you want an accurate result, then make sure that the Unions/MeshParts have their CollisionFidelity is set the Precise, or else your pixelate model may not look the same.
While generating models may cause a bit a of lag, performance with these models in-game is quite optimized.
Generating a sphere that is 25x25x25 studs, will give you about 1.9k parts. As that may seem a lot of parts, that’s actually the lowest you can go with out getting holes in your model.
If you are going to be pixelating a model that is 200x200x200 studs or below, your game will most likely not lag. Since this system uses raycasting, the models will be hollow, and they will be made up of cubes which has only a few polygons. So in reality, this system is quite optimized. Just what ever you do, DO NOT UNANCHOR THE MODEL. Or else your potato laptop will turn into a baked potato.
I do not recommend pixelating models that are larger than 400x400x400 studs, as you may encounter some heavy lag, or even a crash.
If roblox had a built in mesh making system, then this system would work flawlessly!
Using this plugin is easier done than said.
Once you have installed the plugin and you are in studio, you can open up the plugin in the plugins tab. And then select the Model Pixelator plugin
Now before we go ahead and pixelate a model, you will need to make sure that your model has a PrimaryPart. If you model does not have a primary part, then select your model, go to your properties window, and click on PrimaryPart, and then click on a part that is in the middle of your model to set it.
If your model doesn’t have a centered part, then you go to the Model Pixelator window, and increase the ExtraScanSize value to a few studs.
From there you can select you model, and then click PIXELATE.
If you’d like to know what the settings do, well then here’s some info;
-
Extra scan size - Increasing the scanning size for your model (can be useful for models with an offset primary part)
-
Keep materials - When set to true, your pixelated model will have the same materials as the original model. When set to false, your model will be plastic
-
Keep transparency - When set to true, your model will have the same transparent areas as the original model. When set to false, your model will have no transparent areas.
-
Delete old model - Deletes the original model after you make a pixelated clone of it.
-
Debug mode - Shows the scanning area and the origin and direction of the ray.
Plugin:
Source code:
source
local plugin = plugin -- Removes that annoying blue line you see 24/7
local Toolbar = plugin:CreateToolbar("Model Pixelator")
local PluginButton = Toolbar:CreateButton("Model pixelator", "Model pixelator plugin", "rbxassetid://5749290814")
local ChangeHistoryService = game:GetService("ChangeHistoryService")
local Selection = game:GetService("Selection")
local CoreGui = game:GetService("CoreGui")
local widgetInfo = DockWidgetPluginGuiInfo.new(
Enum.InitialDockState.Float, -- Widget will be initialized in floating panel
false, -- Widget will be initially enabled
true, -- Don't override the previous enabled state
200, -- Default width of the floating window
270, -- Default height of the floating window
150, -- Minimum width of the floating window (optional)
150 -- Minimum height of the floating window (optional)
)
local PluginWidget = plugin:CreateDockWidgetPluginGui("TestWidget", widgetInfo)
PluginWidget.Name = "Model Pixelator Widget"
PluginWidget.Title = "Model Pixelator"
local PixelatingProcessGui = script.Parent:WaitForChild("PixelatingProcessGui")
local WidgetFrame = script.Parent:WaitForChild("WidgetFrame")
WidgetFrame.Parent = PluginWidget
local SettingsFrame = WidgetFrame:WaitForChild("SettingsFrame")
local PixelateButton = WidgetFrame:WaitForChild("PixelateButton")
CancelButton = PixelatingProcessGui:WaitForChild("CancelButton")
local WarningMessageText = WidgetFrame:WaitForChild("WarningMessage")
local ExtraScanSizeTextBox = SettingsFrame.ExtraScanSize.TextBox
local KeepMaterialsButton = SettingsFrame.KeepMaterials.TextButton
local KeepTransparencyButton = SettingsFrame.KeepTransparency.TextButton
local DeleteOldModelButton = SettingsFrame.DeleteOldModel.TextButton
local DebugModeButton = SettingsFrame.DebugMode.TextButton
local ModelToPixelate = nil -- The model to pixelate | A model that is bigger than (6, 6, 6) studs is recommended.
local ExtraScanSize = 10 -- (in studs) Increases the area scanning size (can be usefull be model's primarypart is not centered)
local UseModelMaterials = true -- Use the same materials that the model has
local UseModelTransparency = true -- Use the same transparent areas that the model has
local DebugMode = false -- Show the debugging parts and areas
local DeleteOldModel = true -- Deletes then old model when pixelating is finished
local Pixelating = false
local CanPixelate = false
local Canceled = true
PluginButton.Click:Connect(function()
if PluginWidget.Enabled == false and not Pixelating then
PluginWidget.Enabled = true
else
PluginWidget.Enabled = false
end
end)
plugin.Unloading:Connect(function()
local OldGui = PixelatingProcessGui:Destroy()
Canceled = true
end)
ExtraScanSizeTextBox.FocusLost:Connect(function()
if tonumber(ExtraScanSizeTextBox.Text) and tonumber(ExtraScanSizeTextBox.Text) > 0 then
ExtraScanSize = tonumber(ExtraScanSizeTextBox.Text)
elseif tonumber(ExtraScanSizeTextBox.Text) and tonumber(ExtraScanSizeTextBox.Text) < 1 then
ExtraScanSize = 1
ExtraScanSizeTextBox.Text = 1
else
ExtraScanSize = 25
ExtraScanSizeTextBox.Text = 25
end
end)
KeepMaterialsButton.MouseButton1Click:Connect(function()
if UseModelMaterials == false then
UseModelMaterials = true
KeepMaterialsButton.Text = "True"
else
UseModelMaterials = false
KeepMaterialsButton.Text = "False"
end
end)
KeepTransparencyButton.MouseButton1Click:Connect(function()
if UseModelTransparency == false then
UseModelTransparency = true
KeepTransparencyButton.Text = "True"
else
UseModelTransparency = false
KeepTransparencyButton.Text = "False"
end
end)
DeleteOldModelButton.MouseButton1Click:Connect(function()
if DeleteOldModel == false then
DeleteOldModel = true
DeleteOldModelButton.Text = "True"
else
DeleteOldModel = false
DeleteOldModelButton.Text = "False"
end
end)
DebugModeButton.MouseButton1Click:Connect(function()
if DebugMode == false then
DebugMode = true
DebugModeButton.Text = "True"
else
DebugMode = false
DebugModeButton.Text = "False"
end
end)
CancelButton.MouseButton1Click:Connect(function()
Canceled = true
plugin:SelectRibbonTool(Enum.RibbonTool.Move, UDim2.new(0, 0, 0, 0))
Selection:Set({nil})
PixelatingProcessGui.Parent = script.Parent
Pixelating = false
plugin:Deactivate()
end)
PixelateButton.MouseButton1Click:Connect(function()
if CanPixelate and not Pixelating then
Pixelating = true
Canceled = false
plugin:Activate(true)
PluginWidget.Enabled = false
Selection:Set({nil})
PixelatingProcessGui.FinishedText.Visible = false
PixelatingProcessGui.ScanningSizeText.Visible = true
PixelatingProcessGui.StatsText.Visible = true
PixelatingProcessGui.PartCountText.Visible = true
CancelButton.Visible = true
PixelatingProcessGui.Parent = CoreGui
PixelateModel()
PixelatingProcessGui.FinishedText.Visible = true
PixelatingProcessGui.ScanningSizeText.Visible = false
PixelatingProcessGui.StatsText.Visible = false
PixelatingProcessGui.PartCountText.Visible = false
plugin:SelectRibbonTool(Enum.RibbonTool.Move, UDim2.new(0, 0, 0, 0))
CancelButton.Visible = false
wait(3)
PixelatingProcessGui.Parent = script.Parent
Pixelating = false
Canceled = true
plugin:Deactivate()
end
end)
Selection.SelectionChanged:Connect(function()
if #Selection:Get() == 1 then
local SelectedObjects = Selection:Get()
local FirstModel
for i, child in ipairs(SelectedObjects) do
FirstModel = child
end
if FirstModel:IsA("Model") then
PixelateButton.Visible = true
WarningMessageText.Visible = false
if FirstModel.PrimaryPart ~= nil then
ModelToPixelate = FirstModel
CanPixelate = true
else
PixelateButton.Visible = false
WarningMessageText.Visible = true
CanPixelate = false
WarningMessageText.Text = "Model does not have a PrimaryPart"
end
else
PixelateButton.Visible = false
WarningMessageText.Visible = true
CanPixelate = false
WarningMessageText.Text = FirstModel.Name .. " is not a Model"
end
elseif #Selection:Get() < 1 then
PixelateButton.Visible = false
WarningMessageText.Visible = true
CanPixelate = false
WarningMessageText.Text = "Select a model"
elseif #Selection:Get() > 1 then
PixelateButton.Visible = false
WarningMessageText.Visible = true
CanPixelate = false
WarningMessageText.Text = "Please select only 1 model"
end
end)
function PixelateModel()
--== FUNCTIONS ==--
local ModelSize = ModelToPixelate:GetExtentsSize()
local ScanningSize = 0
if ModelSize.X > ModelSize.Y and ModelSize.X > ModelSize.Z then
ScanningSize = ModelSize.X + ExtraScanSize
elseif ModelSize.Y > ModelSize.Z and ModelSize.Y > ModelSize.X then
ScanningSize = ModelSize.Y + ExtraScanSize
elseif ModelSize.Z > ModelSize.X and ModelSize.Z > ModelSize.Y then
ScanningSize = ModelSize.Z + ExtraScanSize
else
ScanningSize = ModelSize.X + ExtraScanSize
end
local BlockCount = 0
local BlockSize = 1
local DebugBrick1
local DebugBrick2
local DebugSquare
local function round(n)
return math.ceil(n - 0.5)
end
ChangeHistoryService:SetWaypoint("Start pixelating Model")
local PixelsGroup = Instance.new("Model")
PixelsGroup.Name = "Pixelated " .. ModelToPixelate.Name
PixelsGroup.Parent = ModelToPixelate
local function CreateBlock(PartColour, PartMaterial, PartPosition, PartTransparency)
--print("Created block" .. BlockCount)
local Block = Instance.new("Part")
Block.Name = "PixelBlock"
Block.Anchored = true
Block.Size = Vector3.new(BlockSize, BlockSize, BlockSize)
Block.TopSurface = Enum.SurfaceType.Smooth
Block.BottomSurface = Enum.SurfaceType.Smooth
Block.Color = PartColour
Block.Position = Vector3.new(round(PartPosition.X), round(PartPosition.Y), round(PartPosition.Z))
if PartMaterial and UseModelMaterials then
Block.Material = PartMaterial
else
Block.Material = Enum.Material.SmoothPlastic
end
if PartTransparency and UseModelTransparency then
Block.Transparency = PartTransparency
end
Block.Parent = PixelsGroup
return Block
end
local function ScanFaceAndPixelate(Origin, Direction)
--print("ORIGIN")
--print(Origin)
--print("DIRECTION")
--print(Direction)
--print(Canceled)
if DebugBrick1 then
DebugBrick1.Position = Origin
end
if DebugBrick2 then
DebugBrick2.Position = Direction
end
local Params = RaycastParams.new()
Params.FilterType = Enum.RaycastFilterType.Whitelist
Params.FilterDescendantsInstances = {ModelToPixelate}
local function CastRay(OffsetX, OffsetZ)
local ScanRay = workspace:Raycast(Origin + Vector3.new(OffsetX, 0, OffsetZ), Direction + Vector3.new(OffsetX, 0, OffsetZ), Params)
if ScanRay then
local Hit = ScanRay.Instance
if Hit and Hit:IsA("BasePart") and Hit.Name ~= "PixelBlock" then
--print("RAY HIT POS")
--print(ScanRay.Position)
CreateBlock(Hit.Color, Hit.Material, ScanRay.Position, Hit.Transparency)
BlockCount = BlockCount + 1
PixelatingProcessGui.PartCountText.Text = "Blocks: " .. BlockCount
end
end
end
CastRay(0, 0)
CastRay(0.25, 0.25)
CastRay(0.5, 0.5)
CastRay(-0.5, -0.5)
CastRay(-0.25, -0.25)
CastRay(-0.25, 0.25)
CastRay(-0.5, 0.5)
CastRay(0.5, -0.5)
CastRay(0.25, -0.25)
end
--== MAIN ==--
if ModelToPixelate and ModelToPixelate.PrimaryPart then
local OriginalModelToPixelateCFrame = ModelToPixelate:GetPrimaryPartCFrame()
local CurrentPos = Vector3.new(ScanningSize / 2, ScanningSize / 2, ScanningSize / 2)
if DebugMode then
DebugBrick1 = CreateBlock(Color3.fromRGB(255, 0, 0), nil, CurrentPos, 0.5)
DebugBrick1.Shape = Enum.PartType.Ball
DebugBrick1.Name = "DebugPart"
DebugBrick1.Parent = workspace
DebugBrick1.CanCollide = false
DebugBrick2 = DebugBrick1:Clone()
DebugBrick2.Color = Color3.fromRGB(0, 0, 255)
DebugBrick2.Parent = workspace
end
-- Begin pixelating the model
local function ScanModel()
local CurrentPos = Vector3.new(ScanningSize / 2, ScanningSize / 2, ScanningSize / 2)
for index = 1, ScanningSize do
if not Canceled then
wait()
for index = 1, ScanningSize do
if not Canceled then
--print(CurrentPos)
CurrentPos = CurrentPos - Vector3.new(BlockSize, 0, 0)
ScanFaceAndPixelate(CurrentPos, CurrentPos - Vector3.new(0, ScanningSize * 2, 0))
end
end
CurrentPos = Vector3.new(ScanningSize / 2, ScanningSize / 2, CurrentPos.Z) - Vector3.new(0, 0, BlockSize)
end
end
end
if DebugMode then
DebugSquare = Instance.new("Part")
DebugSquare.Material = Enum.Material.SmoothPlastic
DebugSquare.Anchored = true
DebugSquare.CanCollide = false
DebugSquare.Transparency = 1
DebugSquare.Position = Vector3.new(0, 0, 0)
DebugSquare.Size = Vector3.new(ScanningSize, ScanningSize, ScanningSize)
DebugSquare.CastShadow = false
local DebugSquareSelectionBox = Instance.new("SelectionBox")
DebugSquareSelectionBox.Color3 = Color3.fromRGB(255, 157, 0)
DebugSquareSelectionBox.LineThickness = 0.1
DebugSquareSelectionBox.SurfaceColor3 = Color3.fromRGB(255, 157, 0)
DebugSquareSelectionBox.SurfaceTransparency = 0.75
DebugSquareSelectionBox.Adornee = DebugSquare
DebugSquareSelectionBox.Parent = DebugSquare
DebugSquare.Name = "DebugAreaSquare"
DebugSquare.Parent = workspace
end
print("Starting... | Scanning size: " .. ScanningSize)
PixelatingProcessGui.ScanningSizeText.Text = "Scanning size: " .. ScanningSize .. ", " .. ScanningSize .. ", " .. ScanningSize
local ScanTimer = os.time()
--print("Scanning side 1 out of 6 | Front")
PixelatingProcessGui.StatsText.Text = "Pixelating sides (1/6)"
ModelToPixelate:SetPrimaryPartCFrame(CFrame.Angles(0, 0, 0))
ScanModel()
--print("Scanning side 2 out of 6 | Right")
PixelatingProcessGui.StatsText.Text = "Pixelating sides (2/6)"
ModelToPixelate:SetPrimaryPartCFrame(CFrame.Angles(math.rad(90), 0, 0))
ScanModel()
--print("Scanning side 3 out of 6 | Back")
PixelatingProcessGui.StatsText.Text = "Pixelating sides (3/6)"
ModelToPixelate:SetPrimaryPartCFrame(CFrame.Angles(math.rad(180), 0, 0))
ScanModel()
--print("Scanning side 4 out of 6 | Left")
PixelatingProcessGui.StatsText.Text = "Pixelating sides (4/6)"
ModelToPixelate:SetPrimaryPartCFrame(CFrame.Angles(math.rad(270), 0, 0))
ScanModel()
--print("Scanning side 5 out of 6 | Top")
PixelatingProcessGui.StatsText.Text = "Pixelating sides (5/6)"
ModelToPixelate:SetPrimaryPartCFrame(CFrame.Angles(0, 0, math.rad(90)))
ScanModel()
--print("Scanning side 6 out of 6 | Bottom")
PixelatingProcessGui.StatsText.Text = "Pixelating sides (6/6)"
ModelToPixelate:SetPrimaryPartCFrame(CFrame.Angles(0, 0, math.rad(270)))
ScanModel()
ModelToPixelate:SetPrimaryPartCFrame(OriginalModelToPixelateCFrame)
if DebugBrick1 then
DebugBrick1:Destroy()
end
if DebugBrick2 then
DebugBrick2:Destroy()
end
if DebugSquare then
DebugSquare:Destroy()
end
PixelsGroup.Parent = workspace
if DeleteOldModel and ModelToPixelate then
ModelToPixelate:Destroy()
end
Selection:Set({PixelsGroup})
ChangeHistoryService:SetWaypoint("Pixelate Model")
print(ModelToPixelate.Name .. " has been successfully pixelated in " .. os.time() - ScanTimer .. " seconds! | " .. BlockCount .. " parts created")
else
error("ModelToPixelate is nil or ModelToPixelate has no set PrimaryPart")
end
end
print("Successfully loaded Ethanthegrand14's 'Model Pixelator'")
rbxm file:
Model pixelator.rbxm (18.7 KB)
I am open for feedback and questions, and I hope you enjoy this plugin!
Thank you!