Skip to content

Commit

Permalink
update 4.3.6, add sanhok, start on debug stitch
Browse files Browse the repository at this point in the history
  • Loading branch information
cgcostume committed Jun 29, 2018
1 parent a7eabe0 commit 52c0e49
Show file tree
Hide file tree
Showing 16 changed files with 256 additions and 9 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
tools/
*.psd
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# PlayerUnknown's Battlegrounds | Terrain Maps

PlayerUnknown's Battlegrounds currently features three maps: Erangel, Miramar, and Sahok. This repository provides information and scripts for extracting elevation and normal maps from the game's sources.
PlayerUnknown's Battlegrounds currently features three maps: Erangel, Miramar, and Sanhok. This repository provides information and scripts for extracting elevation and normal maps from the game's sources.

Please note that all preview images are downscaled to 8bit 512px × 512px and should not be used for rendering (normal data is downsampled using bicubic resampling).

Expand All @@ -10,22 +10,26 @@ Please note that all preview images are downscaled to 8bit 512px × 512px a

| Miramar Height Map | Miramar Normal Map |
|--------------------|--------------------|
| <img src="https://github.com/cgcostume/pubg-maps/blob/master/miramar/pubg_miramar_height_l16_preview.png" width="100%" alt="pubg_miramar_elevation_preview"> | <img src="https://github.com/cgcostume/pubg-maps/blob/master/miramar/pubg_miramar_normal_rg8_preview.png" width="100%" alt="pubg_erangel_normal_preview"> |
| <img src="https://github.com/cgcostume/pubg-maps/blob/master/miramar/pubg_miramar_height_l16_preview.png" width="100%" alt="pubg_miramar_elevation_preview"> | <img src="https://github.com/cgcostume/pubg-maps/blob/master/miramar/pubg_miramar_normal_rg8_preview.png" width="100%" alt="pubg_miramar_normal_preview"> |

| Sanhok Height Map | Sanhok Normal Map |
|--------------------|--------------------|
| <img src="https://github.com/cgcostume/pubg-maps/blob/master/sanhok/pubg_sanhok_height_l16_preview.png" width="100%" alt="pubg_sanhok_elevation_preview"> | <img src="https://github.com/cgcostume/pubg-maps/blob/master/sanhok/pubg_sanhok_normal_rg8_preview.png" width="100%" alt="pubg_sanhok_normal_preview"> |

## How-To/DIY

Please note that the following steps might change with respect to the PUBG version, asset provisioning and structure.

1. **Download** the UE Viewer by Gildor's Homepage (`umodel.exe`) - google for it, the sha256 hash of my file is (`56FAB4D29AC7B7800FA6B480D2C2BDA4A7FEC91CCE8B00A8DB77B71849047E96`) and it seems to be legit.
2. **Locate** your PUBG directory, e.g., `C:\Program Files (x86)\Steam\steamapps\common\PUBG`.
3. **Open** `TslGame-WindowsNoEditor_erangel_heightmap.pak`, `TslGame-WindowsNoEditor_desert_heightmap.pak`, or `TslGame-WindowsNoEditor_savage_heightmap.pak` (or any other of the pak files...). Please note that the pak files were AES encrypted recently (try google the AES key, e.g., on reddit or gildor's forum).
3. **Open** `pakchunk5000-WindowsNoEditor_heightmap.pak` (erangel), `pakchunk5100-WindowsNoEditor_heightmap.pak` (miramar|desert), or `pakchunk5200-WindowsNoEditor_heightmap.pak` (sanhok|savage). Please note that the pak files were AES encrypted recently (try google the AES key, e.g., on reddit or gildor's forum).
4. **Filter** for `HeightMap` or `Texture2D_` (optional step)
5. **Export** all height maps. This should create a `UmodelExport\Maps\Desert\Art\Heightmap`, `UmodelExport\Maps\Erangel\Art\Heightmap`, or `UmodelExport\Maps\Savage\Art\Heightmap` folder in your current working directory.
5. **Export** all height maps. This should create a `UmodelExport\Maps\Erangel\Art\Heightmap`, `UmodelExport\Maps\Desert\Art\Heightmap`, or `UmodelExport\Maps\Savage\Art\Heightmap` folder in your current working directory.
6. **Run** `pubg-tga-slice.py` for extracting and encoding the relevant tile data into losless 16bit and 8bit pngs:
```
.\pubg-tga-slice.py -p .\UmodelExport\ -m erangel
.\pubg-tga-slice.py -p .\UmodelExport\ -m miramar
.\pubg-tga-slice.py -p .\UmodelExport\ -m sahok
.\pubg-tga-slice.py -p .\UmodelExport\ -m sanhok
```
That's it. If the script exits without errors there shoud be 8192px &times; 8192px losless height and normal maps.

Expand Down
Binary file added erangel/pubg_erangel_height_l16_lod0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified erangel/pubg_erangel_height_l16_preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added erangel/pubg_erangel_normal_rg8_lod0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified erangel/pubg_erangel_normal_rg8_preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified miramar/pubg_miramar_height_l16_lod0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified miramar/pubg_miramar_height_l16_preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified miramar/pubg_miramar_normal_rg8_lod0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified miramar/pubg_miramar_normal_rg8_preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
241 changes: 241 additions & 0 deletions pubg-tga-slice-debug.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@

import argparse
import math
import numpy
import os
import sys

from PIL import Image

# parse arguments

parser = argparse.ArgumentParser()
parser.add_argument('-p', '--umodel_export_path', help = 'umodel export path')
parser.add_argument('-o', '--output_path', help = 'working directory for extracting and stitching assets', default = '.')
parser.add_argument('-m', '--map', help = 'map identifier, either erangel, miramar, or savage', default = 'erangel')
# parser.add_argument('-l', '--lod', help = 'level-of-detail, either 0, 1, or 2', default = '0')
parser.add_argument('-c', '--compress', help = 'compression level, number between 0 and 10', default = '0')

args = parser.parse_args()

# pakFileNamesByMapNames = {
# 'erangel' : 'TslGame-WindowsNoEditor_erangel_heightmap.pak',
# 'miramar' : 'TslGame-WindowsNoEditor_desert_heightmap.pak' }

tslHeightmapPathsByMap = {
'erangel' : r'Maps\Erangel\Art\Heightmap',
'miramar' : r'Maps\Desert\Art\Heightmap',
'sanhok' : r'Maps\Savage\Art\Heightmap', }


mapIdentifier = args.map.lower()
if mapIdentifier not in {'erangel', 'miramar', 'sanhok' }:
sys.exit('unknown map identifier \'' + mapIdentifier + '\'')


smallMap = mapIdentifier == 'sanhok'
numTiles = 64 if smallMap else 256


assert os.path.isdir(args.umodel_export_path)
umodel_heightmap_path = os.path.abspath(os.path.join(args.umodel_export_path, tslHeightmapPathsByMap[mapIdentifier]))
assert os.path.isdir(umodel_heightmap_path)

output_path = args.output_path
if not os.path.isdir(output_path):
os.makedirs(output_path)
assert os.path.isdir(output_path)


normal_semantic = 'normal_rg8'
height_semantic = 'height_l16'

channel_semantic_8 = 'channel_r8'
channel_semantic_16 = 'channel_r16'


lod = 0 # int(args.lod)
compress = int(args.compress)

# slicing data

tile_width = { 0: 512, 1: 256, 2: 128 }[lod]
tile_height = { 0: 512, 1: 256, 2: 128 }[lod]

# tile_channels = 4
# tile_offset = int({ 0: 0, 1: 512 * 512 * 4 * (1.0), 2: 512 * 512 * 4 * (1.0 + 0.25)}[lod])

tile_scale = 8 if smallMap else 16;
tile_size = int(tile_width * tile_height)
# tile_size_with_mipmaps = int(tile_size * tile_channels * (1.00 + 0.25 + 0.0625))


def extract_tiles(asset_path, offsets, height_target, normal_target):

tile_indices = [[0, 1, 2, 3]] if smallMap else [[0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3]]
num_indices = len(numpy.array(tile_indices).flatten())

offset_scale = 2 if smallMap else 4

sequence_index = 0
for i, indices in enumerate(tile_indices):
for tile_index in indices:

if smallMap:
tile_path = asset_path % (offsets[0], offsets[1], tile_index)
else:
tile_path = asset_path % (offsets[0], offsets[1], i, tile_index)

try:
tile = Image.open(tile_path)
except:
sequence_index += 1
continue

tile_r, tile_g, tile_b, tile_a = tile.split()

# extract normal

channel_r = numpy.asarray(tile_b).flatten()
channel_g = numpy.asarray(tile_a).flatten()

channel_b = numpy.resize(numpy.uint8(), tile_size)
channel_b.fill(255)

rgb = numpy.dstack((channel_r, channel_g, channel_b)).flatten()

# # extract elevation

channel_l = numpy.left_shift(numpy.asarray(tile_r, numpy.uint16()).flatten(), 8)
channel_l = channel_l + numpy.asarray(tile_g).flatten()

# refine stitching sequence

x = offsets[0] * offset_scale + int(i % 2) * 2 + int(tile_index % 2)
y = offsets[1] * offset_scale + int(i / 2) * 2 + int(tile_index / 2)

ordered_index = y * 16 + x

# paste data

target_x = (ordered_index % 16) * tile_width
target_y = math.floor(ordered_index / 16) * tile_height

# write normal tile

normal_tile = Image.frombytes('RGB', (tile_width, tile_height), rgb)
normal_target.paste(normal_tile, (target_x, target_y))

# write height tile

height_tile = Image.frombytes('I;16', (tile_width, tile_height), numpy.asarray(channel_l, order = 'C'))
height_target.paste(height_tile, (target_x, target_y))

# display progress

progress = (offsets[0] * 4 + offsets[1]) * num_indices + sequence_index + 1

print ('processing', str(progress).rjust(2 if smallMap else 3, '0'), 'of', numTiles,
flush = True, end = ('\r' if progress < numTiles else '\n'))

sequence_index += 1

# ubulk.close()

def extract_tiles_channel(asset_path, offsets, channel_target, tile_index, channel):

num_indices = 4 if smallMap else 16
offset_scale = 2 if smallMap else 4

sequence_index = 0
for i in ([0] if smallMap else [0, 1, 2, 3]):

if smallMap:
tile_path = asset_path % (offsets[0], offsets[1], tile_index)
else:
tile_path = asset_path % (offsets[0], offsets[1], i, tile_index)

try:
tile = Image.open(tile_path)

tile_r, tile_g, tile_b, tile_a = tile.split()
channel_l = numpy.asarray([tile_r, tile_g, tile_b, tile_a][channel]).flatten()
# channel_l = numpy.left_shift(numpy.asarray(tile_r, numpy.uint16()).flatten(), 8)
# channel_l = channel_l + numpy.asarray(tile_g).flatten()

except:
channel_l = numpy.random.rand(tile_size)

# refine stitching sequence

x = offsets[0] * offset_scale + int(i % 2) * 2
y = offsets[1] * offset_scale + int(i / 2) * 2

ordered_index = y * 16 + x

# paste data

target_x = (ordered_index % 16) * tile_width
target_y = math.floor(ordered_index / 16) * tile_height

# write normal tile

# normal_tile = Image.frombytes('RGB', (tile_width, tile_height), rgb)
# normal_target.paste(normal_tile, (target_x, target_y))

# write channel tile

# channel_tile = Image.frombytes('I;16', (tile_width, tile_height), numpy.asarray(channel_l, order = 'C'))
channel_tile = Image.frombytes('L', (tile_width, tile_height), channel_l)
channel_target.paste(channel_tile, (target_x, target_y))

# display progress

progress = (offsets[0] * 4 + offsets[1]) * num_indices + sequence_index + 1

print ('processing', str(progress).rjust(2 if smallMap else 3, '0'), 'of', numTiles,
flush = True, end = ('\r' if progress < numTiles else '\n'))

sequence_index += 1

# ubulk.close()



print ('extracting', numTiles, 'tiles (normal and height data) ...')

# normal_composite = Image.new("RGB", (tile_width * tile_scale, tile_height * tile_scale))
# height_composite = Image.new("I", (tile_width * tile_scale, tile_height * tile_scale))
channel_composite = Image.new("L", (tile_width * tile_scale, tile_height * tile_scale))

for p in range(64):

probe_index = int(p / 4)
probe_channel = p % 4

for indices in [(0, 0), (0, 1), (0, 2), (0, 3), (1, 0), (1, 1), (1, 2), (1, 3), (2, 0), (2, 1), (2, 2), (2, 3), (3, 0), (3, 1), (3, 2), (3, 3)]:
# example '.\UmodelExport\Maps\Erangel\Art\Heightmap\Heightmap_x0_y0_00_sharedAssets\Texture2D_0.tga'

path = 'Heightmap_x%d_y%d_'
if not smallMap:
path = path + '0%d_'
path = path + 'sharedAssets\Texture2D_%d.tga'

asset_path = os.path.join(umodel_heightmap_path, path)
# extract_tiles(asset_path, (indices[0], indices[1]), height_composite, normal_composite)
extract_tiles_channel(asset_path, (indices[0], indices[1]), channel_composite, probe_index, probe_channel)


map_size_info = ['8k', '4k', '2k'][(lod + 1) if smallMap else lod]

# normal_stitched_path = os.path.join(output_path, 'pubg_' + mapIdentifier + '_' + normal_semantic + '_lod' + str(lod) + '.png')
# print (normal_stitched_path, 'saving', map_size_info, 'normal map ... hang in there')
# normal_composite.save(normal_stitched_path, 'PNG', compress_level = min(9, compress), optimize = compress == 10)

# height_stitched_path = os.path.join(output_path, 'pubg_' + mapIdentifier + '_' + height_semantic + '_lod' + str(lod) + '.png')
# print (height_stitched_path, 'saving', map_size_info, 'height map ... hang in there')
# height_composite.save(height_stitched_path, 'PNG', compress_level = min(9, compress), optimize = compress == 10)

channel_stitched_path = os.path.join(output_path, 'pubg_' + mapIdentifier + '_' + channel_semantic_8 + '_' + str(probe_index) + '_' + str(probe_channel) + '_lod' + str(lod) + '.png')
print (channel_stitched_path, 'saving', map_size_info, 'channel map ... hang in there')
channel_composite.save(channel_stitched_path, 'PNG', compress_level = min(9, compress), optimize = compress == 10)
8 changes: 4 additions & 4 deletions pubg-tga-slice.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@
tslHeightmapPathsByMap = {
'erangel' : r'Maps\Erangel\Art\Heightmap',
'miramar' : r'Maps\Desert\Art\Heightmap',
'sahok' : r'Maps\Savage\Art\Heightmap', }
'sanhok' : r'Maps\Savage\Art\Heightmap', }


mapIdentifier = args.map.lower()
if mapIdentifier not in {'erangel', 'miramar', 'sahok' }:
if mapIdentifier not in {'erangel', 'miramar', 'sanhok' }:
sys.exit('unknown map identifier \'' + mapIdentifier + '\'')

smallMap = mapIdentifier == 'sahok'
smallMap = mapIdentifier == 'sanhok'
numTiles = 64 if smallMap else 256


Expand Down Expand Up @@ -158,5 +158,5 @@ def extract_tiles(asset_path, offsets, height_target, normal_target):
normal_composite.save(normal_stitched_path, 'PNG', compress_level = min(9, compress), optimize = compress == 10)

height_stitched_path = os.path.join(output_path, 'pubg_' + mapIdentifier + '_' + height_semantic + '_lod' + str(lod) + '.png')
print (normal_stitched_path, 'saving', map_size_info, 'height map ... hang in there')
print (height_stitched_path, 'saving', map_size_info, 'height map ... hang in there')
height_composite.save(height_stitched_path, 'PNG', compress_level = min(9, compress), optimize = compress == 10)
Binary file added sanhok/pubg_sanhok_height_l16_lod0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added sanhok/pubg_sanhok_height_l16_preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added sanhok/pubg_sanhok_normal_rg8_lod0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added sanhok/pubg_sanhok_normal_rg8_preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 52c0e49

Please sign in to comment.