3D Pixel Art Guide - Part 2
posted on 18 May 2026, updated on 20 May 2026

3D Pixel Art Guide by Bernard Perbal is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
How should an isometric Pixel Art object be colored? Here are the standard techniques I use to maintain consistent shading and coloring throughout my 3D artwork. Following these guidelines helps produce clean, visually pleasing results and ensures that all elements remain coherent within the scene.
Naturally, lighting conditions can sometimes vary depending on the environment or the mood of a scene. Certain situations require special treatment and must therefore be handled on a case-by-case basis. There are also many other valid approaches and artistic conventions, each with their own advantages and stylistic choices.
As a starting point, I assume a virtual light source positioned above the scene and slightly to the left. With this setup, the brightest part of an object is usually its top horizontal surface. This brightest tone becomes the reference color from which all other shades are derived.
The different surfaces of the object are then shaded according to their orientation relative to the light source. Horizontal surfaces receive the most light, while vertical planes appear darker overall.
Among the vertical surfaces, the planes facing toward the left receive more light and therefore appear brighter than those facing toward the right. This simple rule helps reinforce depth and volume while preserving the readability of the object.
Special attention must also be paid to the object's edges. Outer edges are generally highlighted with brighter colors in order to clearly separate adjacent surfaces and improve the silhouette of the object.
Inner edges, which occur less frequently, are usually rendered with darker tones. Even so, they should remain clearly visible and sufficiently contrasted so that the structure of the object remains easy to read.

We are going to use a graphics application that supports layers and opacity levels, such as Adobe Photoshop, in order to build a template that can be used to maintain consistent lighting and shading across objects. This template will be constructed using several separate layers organized as follows:

On the right-side surface, notice that a 15% opacity layer is applied twice. Be careful: applying two 15% opacity layers does not result in a total opacity of 30%.
In reality, the actual combined value is 27.75%:
- the first layer blocks 15% of the light, leaving 85%
- the second layer blocks 15% of what remains, so: 85% * 85% = 72.25% white remaining
- therefore the total black coverage is: 100 - 72.25 = 27.75%
Be careful not to recreate these opacity masks for every new object. The purpose of this template is simply to change the color of the 'reference color' layer, then use your application's Eyedropper tool to sample the generated colors for the various components (edges, left and right vertical surfaces) and apply them directly to your objects.
Here are a few examples of black, gray, and white cubes. Note that for a black object, you should not use pure black (RGB: 0, 0, 0) as the base color. This both helps distinguish the object's outline more clearly and allows brightness variations on the left and right vertical surfaces:

It can also be useful to add an extra layer to color the inner edges of an object. This layer should be placed over the right surface and filled with black set to 20% opacity.

When working with a cylinder, or any other curved object, the color shading must vary according to the orientation of the surface. To achieve this effect, intermediate RGB values are calculated between the color of a surface facing left and the color of a surface facing right.
In practice, the curved surface usually extends slightly beyond these two perpendicular reference planes. Additional gradient values must therefore also be calculated for these extended areas.
In the following example, the distance between the left-facing and right-facing reference planes of the cylinder is 14 pixels. These pixels are indexed from 0 to 13. The curved surface also extends by 1 extra pixel on each side: pixel -1 on the left and pixel 14 on the right.

All intermediate RGB values can easily be generated using a small calculation function implemented in any programming language. As an example, we provide an Excel spreadsheet along with a simple macro.
To generate the RGB table, simply enter:
- the distance between the two reference planes (14 pixels in this example),
- the number of extension pixels on the left and right sides (3 values are used here to increase the available color choices for the 1-pixel gaps on both sides),
- and the two RGB colors corresponding to the left and right reference surfaces calculated previously from the template above.
When clicking the "Create RGB Table" button, the spreadsheet automatically generates all RGB values for the complete range of indexes. In this example:
- index -1 corresponds to the left extension,
- indexes 0 to 13 correspond to the main cylindrical surface,
- and index 14 corresponds to the right extension.

For a more realistic and mathematically accurate result, you can choose the colors corresponding to indexes -2 and 15, or -3 and 16, from the Excel table, depending on the context and the desired visual feel:

Here are two examples of color gradients for which this Excel macro can be used:


The source code of the Excel macro is provided below. Since the algorithm is very simple, it can easily be rewritten in any programming language.
Sub CreateTable()
Dim ColumnRange As String
Dim RowCount1 As Long
Dim RowCount2 As Long
Dim CurrentRow As Long
Dim i As Long
Dim IncR As Double
Dim IncG As Double
Dim IncB As Double
Dim HighlightR As Long
Dim HighlightG As Long
Dim HighlightB As Long
ColumnRange = "A:E"
CurrentRow = 6
' Highlight color
HighlightR = 245
HighlightG = 245
HighlightB = 210
IncR = (Range("B4").Value - Range("B3").Value) / (Range("B1").Value - 1)
IncG = (Range("C4").Value - Range("C3").Value) / (Range("B1").Value - 1)
IncB = (Range("D4").Value - Range("D3").Value) / (Range("B1").Value - 1)
RowCount1 = Range("B1").Value
RowCount2 = Range("B2").Value
' Clear contents, text and background colors from row 6 and below
With ActiveSheet.Range(ColumnRange).Rows(CurrentRow & ":" & Rows.Count)
.ClearContents
.Interior.Pattern = xlNone
.Font.ColorIndex = xlAutomatic
End With
' Main loop
For i = -RowCount2 To RowCount1 + RowCount2 - 1
Cells(CurrentRow, 1).Value = i
' RGB
Cells(CurrentRow, 2).Value = Clamp_Byte(Range("B3").Value + i * IncR)
Cells(CurrentRow, 3).Value = Clamp_Byte(Range("C3").Value + i * IncG)
Cells(CurrentRow, 4).Value = Clamp_Byte(Range("D3").Value + i * IncB)
Cells(CurrentRow, 5).Value = RGB_To_Hex(Cells(CurrentRow, 2).Value, _
Cells(CurrentRow, 3).Value, Cells(CurrentRow, 4).Value)
' Column A background color
If i < 0 Or i >= RowCount1 Then
Cells(CurrentRow, 1).Interior.Color = RGB(HighlightR, HighlightG, HighlightB)
End If
CurrentRow = CurrentRow + 1
Next i
End Sub
Function Clamp_Byte(v As Integer) As Integer
Clamp_Byte = Application.Max(Application.Min(v, 255), 0)
End Function
Function RGB_To_Hex(r As Integer, _
g As Integer, _
b As Integer) As String
RGB_To_Hex = LCase("'" & _
Right("0" & Hex(r), 2) & _
Right("0" & Hex(g), 2) & _
Right("0" & Hex(b), 2))
End Function