Colour Cube

Revision as of 15:37, 27 April 2023 by Burninrubber0 (talk | contribs) (i.e. to e.g.)

ColourCube resources are used by both the EnvironmentSettings and PostFX to change the colour and tone of the world.

ColourCubes used in Burnout Paradise.

A ColourCube is a set of textures forming a CLUT, which is usually colour corrected to give a different art style to the game at a given time (e.g., the red filter when wrecking a vehicle in versions 1.0-1.3 of Burnout Paradise).

Some time after the February 22, 2007 build of Paradise, ColourCubes were added to the EnvironmentSettings folder and were used specifically to give the game a unique art style.

In the 1.4 update to Burnout Paradise, the ColourCubes were changed to a standard CLUT, possibly due to the extended time cycle conflicting with the ColourCube's art style. In the 1.6 update, these were again updated to use a default RGB CLUT.

Structures

rw::graphics::postfx::ColourCube

32-bit

Offset Length Type Name Description Comments
0x0 0x4 uint32_t m_size The number of textures and their width/height. Used to calculate file size (m_size^3 * 3 + 16)
0x4 0x4 uint8_t* m_pixels Pointer to the texture data.

64-bit

Offset Length Type Name Description Comments
0x0 0x4 uint32_t m_size The number of textures and their width/height. Used to calculate file size (m_size^3 * 3 + 16)
0x4 0x4 padding
0x8 0x8 uint8_t* m_pixels Pointer to the texture data.

Data

In Burnout Paradise: The Ultimate Box on PC and Burnout Paradise Remastered on PC and PS4, data is in a standard RGB24 format (that is, each pixel takes up 3 bytes, one for each color). Textures in the PS3, Xbox 360, and Switch versions of the game are also RGB24, but swizzled. There is a functional implementation of an Xbox 360 to PC texture converter on GitHub.

Xbox 360 Swizzle Algorithm

The following algorithm assumes that the data is stored as a cube of size m_size*m_size*m_size pixels, accessed according to a certain algorithm. The algorithm is different for Burnout Paradise 32*32*32 cubes and Need for Speed: Hot Pursuit 16*16*16 cubes.

To find the 3 bytes (R, G and B) of pixel data for position (x,y,z) in the PC m_pixels data, 3 bytes (R, G and B) can be read from the Xbox 360 m_pixels data at the offset calculated as follows where x, y and z are represented as a number of bits, x0 to x4, y0 to y4 and z0 to z4 (all values between 0 and m_size - 1).

x y z offset (m_size = 16)
xxxx
3210
yyyy
3210
zzzz
3210
o0=x0
o1=x1
o2=y0
o3=x2
o4=x3
o5=y1
o6=y2
o7=z0
o8=y3^z2
o9=z1
o10=z2
o11=z3
x y z offset (m_size = 32)
xxxxx
43210
yyyyy
43210
zzzzz
43210
o0=x0
o1=x1
o2=y0
o3=x2
o4=x3
o5=x4^y3^z2
o6=y1
o7=y2
o8=z0
o9=y3^z2
o10=z1
o11=y4
o12=z2
o13=z3
o14=z4

Example Ruby implementation:

class Fixnum
  # calculates the number of bytes
  def pixels
    self * 3
  end
end

# calculate offset, in pixels, of where to find the (x,y,z) pixel in the Xbox 360 data
# pixel offset in PC file is m_size * m_size * z + m_size * y + x
def calc_offset(m_size, x, y, z)
  case m_size
  when 16
    calc_offset16(x, y, z)
  when 32
    calc_offset32(x, y, z)
  else
    raise "Unsupported m_size: #{m_size}"
  end
end

def calc_offset16(x, y, z)
  x0 = (x >> 0) & 0b1
  x1 = (x >> 1) & 0b1
  x2 = (x >> 2) & 0b1
  x3 = (x >> 3) & 0b1
  y0 = (y >> 0) & 0b1
  y1 = (y >> 1) & 0b1
  y2 = (y >> 2) & 0b1
  y3 = (y >> 3) & 0b1
  z0 = (z >> 0) & 0b1
  z1 = (z >> 1) & 0b1
  z2 = (z >> 2) & 0b1
  z3 = (z >> 3) & 0b1

  o=(z3<<11)|
    (z2<<10)|
    (z1<<9)|
    ((y3<<8)^(z2<<8))|
    (z0<<7)|
    (y2<<6)|
    (y1<<5)|
    (x3<<4)|
    (x2<<3)|
    (y0<<2)|
    (x1<<1)|
    (x0<<0)
end

def calc_offset32(x, y, z)
  x0 = (x >> 0) & 0b1
  x1 = (x >> 1) & 0b1
  x2 = (x >> 2) & 0b1
  x3 = (x >> 3) & 0b1
  x4 = (x >> 4) & 0b1
  y0 = (y >> 0) & 0b1
  y1 = (y >> 1) & 0b1
  y2 = (y >> 2) & 0b1
  y3 = (y >> 3) & 0b1
  y4 = (y >> 4) & 0b1
  z0 = (z >> 0) & 0b1
  z1 = (z >> 1) & 0b1
  z2 = (z >> 2) & 0b1
  z3 = (z >> 3) & 0b1
  z4 = (z >> 4) & 0b1

  o=(z4<<14)|
    (z3<<13)|
    (z2<<12)|
    (y4<<11)|
    (z1<<10)|
    ((z2<<9)^(y3<<9))|
    (z0<<8)|
    (y2<<7)|
    (y1<<6)|
    ((z2<<5)^(y3<<5)^(x4<<5))|
    (x3<<4)|
    (x2<<3)|
    (y0<<2)|
    (x1<<1)|
    (x0<<0)
end

# read m_size and m_pixels data from Xbox 360 file
# ...

# now, convert the m_pixels data
data="".b
m_size.times do |z|
  m_size.times do |y|
    m_size.times do |x|
      offset = calc_offset(x, y, z)
      pixel_data = m_pixels[offset.pixels...(offset + 1).pixels]
      data << pixel_data
    end
  end
end

# write "converted to PC" data to file
# ...