Applying Textures to Roblox Parts Without Losing Your Mind
Most people coming into Roblox development think textures work the same way they do in Blender or Unity. They don't. I spent a week trying to figure out why my clean PBR material looked completely flat in-engine before I understood that Roblox handles texture application differently depending on what kind of part you are working with. A Roblox Texture is fundamentally different from a standard diffuse map in other engines. When you apply a texture to a BasePart, Roblox uses it as an override of the built-in Material system. The engine blends the texture color with the material's existing properties like Specular, Reflectance, and Roughness. This is why a concrete texture applied with the Concrete material still looks somewhat reflective compared to what you might expect. There are three places you can apply textures: the Texture property on any BasePart, the Decal object placed on a Surface, and the NormalMap channel which requires setting the NormalMapType to CustomNormalMap. The Decal system is the one most beginners abuse. It is limited to 1024x1024 resolution and it does not respect PBR workflows at all. If you are doing serious work, use the Texture property instead.
MeshParts operate entirely differently. They use UV coordinates baked into the mesh file itself. If your mesh was not UV unwrapped properly, the texture will stretch, repeat incorrectly, or appear backward. I learned this the hard way with a custom stair model that had zero UV layout work behind it. Every single texture I tried looked horizontally compressed on the riser surfaces. The fix was importing the mesh into Blender, assigning a UV unwrap there, and re-exporting as an OBJ or FBX before bringing it back into Roblox Studio. There is no workaround inside Roblox for bad UVs on MeshParts.
Resolution limits and format expectations
Roblox accepts PNG, JPG, and DDS files for textures. The engine handles transparency only in PNG and DDS. JPEG will discard alpha channels silently, which means if your texture has a rounded shape on a transparent background, it comes in as a square block and you will waste twenty minutes trying to debug it. Always use PNG with alpha when you need transparency. The maximum texture resolution for BasePart textures is 4096x4096. For Decals it is 1024x1024. You can upload a 4K texture and Roblox will downscale it on servers depending on the viewer device. This means your texture should look acceptable at half resolution, because that is what most mobile players are actually seeing. I once uploaded a 4096 brick wall texture that looked incredible on my desktop monitor and appeared muddy and low detail on a phone. Reducing it to 2048 beforehand solved the problem because the engine stopped doing its own aggressive compression pass. DDS files with BC7 compression are the best choice if your workflow supports them. They preserve alpha channels, support mipmaps natively, and Roblox's server-side texture compression is less destructive on DDS than on PNG. The downside is that you need an external tool like NVIDIA Texture Tools or the FreeDDS plugin for Photoshop to generate them, which adds a step most people skip.
Get the Full Details

Normal maps and the roughness trap
This is where people consistently get tripped up. Applying a normal map to a part does nothing unless you set the NormalMapType property to CustomNormalMap and point the NormalMap property at your image. The default NormalMapType is None, so a normal map sitting in your texture property is completely ignored. I have seen forums full of people complaining that their normal maps are not working when they never actually enabled the second property. Normal maps in Roblox follow the standard RGB convention where red is X, green is Y, and blue is Z. Do not use an inverted normal map unless you intentionally want the bumps to appear as dents. Also, the normal map strength is controlled by the part's Scale property multiplier, not a separate slider in the inspector. If your normal map looks too subtle, you need to increase the part scale value. If it looks absurdly deep, decrease it. There is no standalone intensity control. Roughness and metallic values come from the BasePart's underlying Material setting, not from the texture file itself unless you are using a specially formatted texture atlas. Roblox does not automatically extract roughness maps from standard PBR texture sets the way Unity or Unreal does. You get one texture controlling diffuse color, and the material preset controlling everything else. If you need per-surface roughness variation, you are out of luck with standard parts. MeshParts with custom materials assigned via the Roblox Material API is the only real path, and even that is limited.
Common pipeline mistakes
Color space is a frequent source of confusion. Roblox textures are treated as sRGB by default. If you are working in a linear workflow and your base color textures were authored in linear space, they will look washed out when applied in Roblox. Conversely, if your normal map was saved in linear space by mistake, the lighting calculation breaks because normal maps should never be color corrected. The rule of thumb is simple: base color goes through sRGB, everything else stays linear. Roblox does not give you per-property color space controls like a proper DCC pipeline does. UV tiling can be controlled with the Texture tile properties (TileX and TileY) on BaseParts, but these only affect procedural and single-image texture assignments. Decals do not have tiling properties. If you need a texture to repeat across a large floor, apply it as a Part Texture with TileX and TileY set to the repeat count, not as a Decal. I used to put giant floor decals everywhere thinking it would be cleaner, and then spent hours realizing they were not repeating and I had to rebuild the entire setup. When you change a texture on a live game server, other clients do not see the change instantly without a proper ReplicatedStorage sync or a remote event triggering a client-side update. Texture changes made through script on the server require the client to download the new asset, which introduces a brief pop-in. This is usually only noticeable in tight loops or when textures cycle rapidly during gameplay.
What Roblox Texture handling does poorly
It is worth being honest about the limitations. The material system is not truly PBR. You cannot assign a full set of PBR maps to a BasePart. You get diffuse through the Texture property, roughness and metallic through the Material dropdown, and a normal map if you go through the CustomNormalMap route. That is it. No height maps, no ambient occlusion maps baked into the material, no clearcoat. If your project requires sophisticated surface variation, you are better off using MeshParts with multiple small planes arranged together, each with a different material, or investing time in custom shader work through the Roblox mesh and material API which is more complex and less documented. Another limitation nobody mentions until it hurts them: cached textures. Once a texture is loaded by the client, it stays in memory and is reused. Changing a texture ID on a part after it has already been rendered does not always trigger a reload. I ran into this when iterating on a texture swap system where players could change their hat colors. The first few switches worked fine, then after about ten changes the texture stops updating because the client is reusing the cached version. The workaround is forcing a client reload of the asset or using distinct texture IDs for every variant so the engine treats each one as a separate load. If you are working on something that demands proper PBR material authoring and you need full control over roughness, metallic, and normal maps independently, consider exporting your mesh as a high quality FBX, texturing it in Substance Painter or Blender, and importing it as a MeshPart with custom materials rather than trying to force a BasePart texture system to do more than it was built to do. The MeshPart route requires more upfront work but saves time once the pipeline is established.
