diff options
| author | yum <yum.food.vr@gmail.com> | 2024-12-05 18:20:03 -0800 |
|---|---|---|
| committer | yum <yum.food.vr@gmail.com> | 2024-12-05 18:20:03 -0800 |
| commit | 2356cdf85d2c52f70052828bb5a18419a30d4de9 (patch) | |
| tree | 161c83eecbed18a239143bf37c896779d5c95c36 /Third_Party/gen_atlas | |
| parent | a116fda8c034ab13bf8b1cf1b4cbdc4ba9eba6b0 (diff) | |
Add second mask for rim lighting & matcap
Diffstat (limited to 'Third_Party/gen_atlas')
| -rw-r--r-- | Third_Party/gen_atlas | 239 |
1 files changed, 225 insertions, 14 deletions
diff --git a/Third_Party/gen_atlas b/Third_Party/gen_atlas index b3a8eff..d814c09 100644 --- a/Third_Party/gen_atlas +++ b/Third_Party/gen_atlas @@ -5,6 +5,7 @@ import subprocess import os import json from PIL import Image +import random # Define the character ranges we want to include CHAR_RANGES = [ @@ -21,22 +22,70 @@ def calculate_grid_size(char_ranges): grid_size += 1 return grid_size -def generate_atlas(font_path, resolution, draw_grid=False): +ATLAS_TYPES = [ + "hardmask", # binary image + "softmask", # anti-aliased image + "sdf", # signed distance field + "psdf", # perpendicular distance field + "msdf", # multi-channel signed distance field + "mtsdf" # combined MSDF and true SDF +] + +def calculate_font_size(resolution, base_size=None): + """Calculate the font size based on resolution, scaling from 512 resolution""" + if base_size is None: + base_size = 32 # Default size at 512 resolution + return (base_size * resolution) // 512 + +def generate_atlas(font_path, resolution, draw_grid=False, type="msdf", font_size=None): + """Generates a font atlas using various distance field techniques. + + This function creates a font atlas using msdf-atlas-gen, then rearranges the + characters to include gaps for non-printable characters. The output is saved + as 'atlas.png' in the current directory. + + Args: + font_path (str): Path to the input font file (.ttf/.otf) + resolution (int): Width and height of the output atlas in pixels + draw_grid (bool, optional): If True, draws red grid lines on the output. + Defaults to False. + type (str, optional): Atlas type to generate. See ATLAS_TYPES. + font_size (int, optional): Base font size in pixels at 512 resolution. + Will be scaled for other resolutions. + + Returns: + bool: True if atlas generation succeeded, False if an error occurred. + + Raises: + subprocess.CalledProcessError: If msdf-atlas-gen fails to execute. + """ + # Get font name from path + font_name = os.path.splitext(os.path.basename(font_path))[0] + # Calculate grid size based on character ranges grid_size = calculate_grid_size(CHAR_RANGES) cell_size = resolution // grid_size + # Calculate font size if not specified, scaling from 512 resolution + if font_size is None: + font_size = calculate_font_size(resolution) + else: + font_size = calculate_font_size(resolution, font_size) + # Convert character ranges to command-line format chars_str = ", ".join(f"[{start}, {end}]" for start, end in CHAR_RANGES) + # Update the output filename to include resolution + output_filename = f"atlas_{font_name}_{resolution}_{type}" + cmd = [ "msdf-atlas-gen/build/bin/Debug/msdf-atlas-gen.exe", "-font", font_path, - "-type", "msdf", + "-type", type, "-format", "png", - "-imageout", "atlas.png", - "-size", "72", - "-pxrange", "5", + "-imageout", f"{output_filename}.png", + "-size", str(font_size), + "-pxrange", "4", "-dimensions", str(resolution), str(resolution), "-chars", chars_str, "-uniformgrid", @@ -50,6 +99,13 @@ def generate_atlas(font_path, resolution, draw_grid=False): try: print("Running msdf-atlas-gen...") + print("Command:", end=" ") + for arg in cmd: + if arg.startswith('-'): + print(f"\n {arg}", end=" ") + else: + print(arg, end=" ") + print() result = subprocess.run(cmd, check=True, capture_output=True, text=True) # Print the output @@ -59,7 +115,10 @@ def generate_atlas(font_path, resolution, draw_grid=False): # Rearrange the atlas to include gaps for non-printable characters print("Rearranging atlas...") - rearrange_atlas(resolution, cell_size, cell_size, draw_grid) + rearrange_atlas(resolution, cell_size, cell_size, draw_grid, type, font_name) + + # Generate or update Unity meta file + generate_unity_meta(output_filename, resolution) return True except subprocess.CalledProcessError as e: print(f"Error generating atlas: {e}") @@ -85,10 +144,12 @@ def draw_grid_lines(image, resolution): draw.line([(resolution-1, 0), (resolution-1, resolution-1)], fill=(255, 0, 0), width=1) draw.line([(0, resolution-1), (resolution-1, resolution-1)], fill=(255, 0, 0), width=1) -def rearrange_atlas(resolution, cell_width, cell_height, draw_grid=False): +def rearrange_atlas(resolution, cell_width, cell_height, draw_grid=False, type="msdf", font_name=""): """Rearrange the atlas to include gaps for non-printable characters""" - original = Image.open("atlas.png") - new_atlas = Image.new('RGB', (resolution, resolution), (0, 0, 0)) + # Update input and output filenames to match generate_atlas format + input_filename = f"atlas_{font_name}_{resolution}_{type}.png" + original = Image.open(input_filename) + new_atlas = Image.new('RGBA', (resolution, resolution), (0, 0, 0, 255)) grid_size = calculate_grid_size(CHAR_RANGES) cell_size = resolution // grid_size @@ -127,16 +188,166 @@ def rearrange_atlas(resolution, cell_width, cell_height, draw_grid=False): used_atlas = new_atlas.crop((0, 0, used_resolution, used_resolution)) final_atlas = used_atlas.resize((resolution, resolution), Image.LANCZOS) - # Save the rearranged atlas, overwriting the original - final_atlas.save("atlas.png") + # Save with the same filename format (no change needed since input/output are the same) + final_atlas.save(input_filename) print("Atlas rearranged successfully!") +def generate_unity_meta(basename, resolution): + """Generate or update Unity meta file for the atlas texture.""" + meta_path = f"{basename}.png.meta" + existing_guid = None + + # Try to read existing GUID if meta file exists + if os.path.exists(meta_path): + with open(meta_path, 'r') as f: + for line in f: + if 'guid: ' in line: + existing_guid = line.split('guid: ')[1].strip() + break + + # Generate new GUID if none exists + guid = existing_guid or ''.join('%x' % random.randrange(16) for _ in range(32)) + + meta_template = f'''fileFormatVersion: 2 +guid: {guid} +TextureImporter: + internalIDToNameTable: [] + externalObjects: {{}} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: {resolution} + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {{x: 0.5, y: 0.5}} + spritePixelsToUnits: 100 + spriteBorder: {{x: 0, y: 0, z: 0, w: 0}} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: {resolution} + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 2 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: {resolution} + resizeAlgorithm: 0 + textureFormat: 3 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 1 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: {resolution} + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {{}} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: +''' + + with open(meta_path, 'w') as f: + f.write(meta_template) + def main(): parser = argparse.ArgumentParser(description='Generate a font atlas using msdf-atlas-gen') parser.add_argument('font_path', help='Path to the font file (.ttf/.otf)') parser.add_argument('resolution', type=int, help='Total atlas resolution (width=height)') - parser.add_argument('--grid', action='store_true', help='Draw grid lines on the output atlas') - + parser.add_argument('--grid', type=bool, default=False, help='Draw grid lines on the output atlas') + parser.add_argument('--type', type=str, default="msdf", choices=ATLAS_TYPES, + help='Type of atlas to generate') + parser.add_argument('--font-size', type=int, help='Base font size in pixels at 512 resolution. Will be scaled for other resolutions.') args = parser.parse_args() # Verify font file exists @@ -144,7 +355,7 @@ def main(): print(f"Error: Font file not found at {args.font_path}") return - generate_atlas(args.font_path, args.resolution, args.grid) + generate_atlas(args.font_path, args.resolution, draw_grid=args.grid, type=args.type, font_size=args.font_size) if __name__ == "__main__": main() |
