To start this off, I’ve seen some “blocky” games with this
texture blur problem, and I want to share my experience with how I personally went about solving this.
This is an example of four blocks I’ve created, each with their own method of displaying a “texture”.
The two cubes on the right display the outcome we want, each one having their pros and cons.
Part -> SurfaceGui -> ImageLabel -> Properties-ResampleMode -> "Pixelated"
For the Surface UI method it’s actually pretty easy. All you have to do is take a part !preferably a cube!, and add the instance SurfaceGui, after that you can assign it a face. After this you simply want to add an ImageLabel instance under the SurfaceGui instance. “Edit the ImageLabel to preference”. The key property to edit is the ResampleMode property within the ImageLabel, simply change it to “Pixelated”.
"Also I recommend a transparency of 0 for the parent part to prevent Z-Fighting"
The only con I’ve run into with the SurfaceGui method is that the part doesn’t display properly within ViewportFrames. !Not a bug!
Although the pro of using SurfaceGui is that the corners of each pixel are crisp and sharp. If that is the style you want to achieve.
As for the Mesh UV method, the pros of using a mesh is that you can display it via ViewportFrames and might be softer on your eyes because of MSAA I assume.
I’ve seen other developers take a mesh and structure the UV like the example image. !Please ignore the random edges!
The cons of using Mesh UV is that you increase face count at an alarming rate for open-world games, and that MSAA!I assume! might just get in the way of your preferred style. The last con I forgot to add states that you may need to upload larger images for mesh texture due to Roblox tendency to compress the color on smaller image files.
Pretty cool, but this approach limits you to blocks or blocky meshes. It’s also not the most performant way of achieving this. IMO, a better way to get nearly pixel perfect textures, without unnecessarily uploading 1024x1024 images, is to import and upscale them using editable images.
Here's a script that does this with image labels. But it can be applied to mesh part textures as well.
local assetId = "rbxassetid://100362955533266"; -- change the asset id here
-- If target res is a Vector2, the function will stretch the output image to fit the desired resolution
-- If target res is a number, it will stretch the longest side of the image and keep the aspect ratio
local function NearestNeighbour(imageIn:EditableImage, targetRes:Vector2|number)
local sizeIn = imageIn.Size;
local sizeOut; do
if (typeof(targetRes)=="Vector2") then
sizeOut = targetRes;
elseif (sizeIn.X > sizeIn.Y) then
sizeOut = Vector2.new(targetRes, sizeIn.Y/sizeIn.X * targetRes);
else
sizeOut = Vector2.new(sizeIn.X/sizeIn.Y * targetRes, targetRes);
end
end
local sizeInX = sizeIn.X;
local sizeInY = sizeIn.Y;
local sizeOutX = sizeOut.X;
local sizeOutY = sizeOut.Y;
local _ = (sizeOutX < 0 or sizeOutY < 0 or sizeOutX > 1024 or sizeOutY > 1024) and error("Invalid target resolution "..sizeOutX.." x "..sizeOutY);
local bufferIn = imageIn:ReadPixelsBuffer(Vector2.zero, sizeIn);
local bufferOut = buffer.create(sizeOutX*sizeOutY*4);
local scale = sizeOut/sizeIn;
local scaleX = scale.X;
local scaleY = scale.Y;
for yIn = 0, sizeInY-1 do
for xIn = 0, sizeInX-1 do
local color = buffer.readi32(bufferIn, (yIn*sizeInX+xIn)*4);
for yOut = math.max(math.round(yIn*scaleY), 0), math.min(math.round((yIn+1)*scaleY), sizeOutY)-1 do
for xOut = math.max(math.round(xIn*scaleX), 0), math.min(math.round((xIn+1)*scaleX), sizeOutX)-1 do
buffer.writei32(bufferOut, (yOut*sizeOutX+xOut)*4, color);
end
end
end
end
local imageOut = AssetService:CreateEditableImage({Size = sizeOut});
imageOut:WritePixelsBuffer(Vector2.zero, sizeOut, bufferOut);
return imageOut;
end
-- Load the asset id into an editable image and upscale it to 1024 pixels
local imageIn = game:GetService("AssetService"):CreateEditableImageAsync(assetId);
local imageOut = NearestNeighbour(imageIn, 1024);
local sizeIn = imageIn.Size;
local sizeOut = imageOut.Size;
print(sizeIn)
print(sizeOut)
-- Apply these images to a gui
local sgui = Instance.new("ScreenGui");
local labelIn = Instance.new("ImageLabel");
labelIn.Size = UDim2.fromOffset(500, 500);
labelIn.ZIndex = 10;
labelIn.AnchorPoint = Vector2.zero;
labelIn.ImageContent = Content.fromObject(imageIn);
labelIn.Parent = sgui;
local labelOut = Instance.new("ImageLabel");
labelOut.Size = UDim2.fromOffset(500, 500);
labelOut.Position = UDim2.fromScale(1,0);
labelOut.AnchorPoint = Vector2.new(1,0);
labelOut.ImageContent = Content.fromObject(imageOut);
labelOut.Parent = sgui;
sgui.Parent = game:GetService("Players").LocalPlayer:WaitForChild("PlayerGui");
EDIT: Expanded this script by a lot. This idea made me want to write an entire nearest neighbour implementation, so I edited this post with the result.