diff options
| author | yum <yum.food.vr@gmail.com> | 2026-03-24 00:16:32 -0700 |
|---|---|---|
| committer | yum <yum.food.vr@gmail.com> | 2026-03-24 00:16:32 -0700 |
| commit | e19bf2d07048bf1b6a9c636380c52f44336c81e8 (patch) | |
| tree | 0a70e23ff0410dd3f331e05d4163c36ffc3f0d53 | |
| parent | 51930cd07ac1a17a05b9848ac8d6639170b3d571 (diff) | |
Pull in letter grid gimmick from 2ner
| -rwxr-xr-x | 3ner.cginc | 13 | ||||
| -rwxr-xr-x | 3ner.shader | 29 | ||||
| -rwxr-xr-x | Scripts/gen_atlas | 421 | ||||
| -rw-r--r-- | disinfo.cginc | 98 | ||||
| -rwxr-xr-x | features.cginc | 4 | ||||
| -rwxr-xr-x | globals.cginc | 25 | ||||
| -rw-r--r-- | letter_grid.cginc | 85 | ||||
| -rwxr-xr-x | math.cginc | 9 |
8 files changed, 683 insertions, 1 deletions
@@ -20,6 +20,7 @@ #include "ray_marching.cginc" #include "vertex.cginc" #include "matcap.cginc" +#include "letter_grid.cginc" v2f vert(appdata v) { #if defined(SHADOW_CASTER_PASS) && !defined(_SHADOW_CASTER) @@ -311,6 +312,13 @@ float4 frag(v2f i, uint facing : SV_IsFrontFace Pbr pbr = getPbr(i); +#if defined(_LETTER_GRID) + LetterGridOutput letter_grid_output = LetterGrid(i); + pbr.albedo.rgb = lerp(pbr.albedo.rgb, letter_grid_output.albedo, letter_grid_output.albedo.a); + pbr.metallic = lerp(pbr.metallic, letter_grid_output.metallic, letter_grid_output.albedo.a); + pbr.roughness = lerp(pbr.roughness, letter_grid_output.roughness, letter_grid_output.albedo.a); +#endif + [branch] if (_Mode == 1) { clip(pbr.albedo.a - 0.5); @@ -322,6 +330,11 @@ float4 frag(v2f i, uint facing : SV_IsFrontFace BrdfData bd; float4 lit = brdf(pbr, light_data, bd); + +#if defined(_LETTER_GRID) + lit.rgb += letter_grid_output.emission * letter_grid_output.albedo.a; +#endif + return apply_debug_view(i, pbr, light_data, bd, lit); } diff --git a/3ner.shader b/3ner.shader index b2ae500..710d456 100755 --- a/3ner.shader +++ b/3ner.shader @@ -1035,7 +1035,34 @@ Shader "yum_food/3ner" [HideInInspector] m_end_Kintsugi_Domain_Warping("Domain Warping", Float) = 0 //endex [HideInInspector] m_end_Kintsugi("Kintsugi", Float) = 0 - //endex + //endex + + //ifex _Letter_Grid_Enabled==0 + [HideInInspector] m_start_Letter_Grid("Letter grid", Float) = 0 + [ThryToggle(_LETTER_GRID)] _Letter_Grid_Enabled("Enable", Float) = 0 + _Letter_Grid_Texture("Glyph texture", 2D) = "black" {} + _Letter_Grid_Tex_Res_X("Glyph X resolution", Float) = 16 + _Letter_Grid_Tex_Res_Y("Glyph Y resolution", Float) = 8 + _Letter_Grid_Res_X("Cell X resolution", Range(1, 4)) = 1 + _Letter_Grid_Res_Y("Cell Y resolution", Range(1, 4)) = 1 + _Letter_Grid_Data_Row_0("Cell data row 0", Vector) = (0, 0, 0, 0) + _Letter_Grid_Data_Row_1("Cell data row 1", Vector) = (0, 0, 0, 0) + _Letter_Grid_Data_Row_2("Cell data row 2", Vector) = (0, 0, 0, 0) + _Letter_Grid_Data_Row_3("Cell data row 3", Vector) = (0, 0, 0, 0) + _Letter_Grid_UV_Scale_Offset("UV scale/offset", Vector) = (1, 1, 0, 0) + _Letter_Grid_Padding("Padding", Float) = 0.02 + _Letter_Grid_Color("Color", Color) = (1, 1, 1, 1) + _Letter_Grid_Metallic("Metallic", Range(0, 1)) = 0 + _Letter_Grid_Roughness("Roughness", Range(0 ,1)) = 0.5 + _Letter_Grid_Emission("Emission", Range(0 ,1)) = 0.0 + _Letter_Grid_Mask("Mask", 2D) = "white" {} + _Letter_Grid_Global_Offset("Global offset", Float) = 0 + _Letter_Grid_Screen_Px_Range("Screen px range (from msdfgen)", Float) = 10 + _Letter_Grid_Min_Screen_Px_Range("Minimum screen px range", Float) = 1 + _Letter_Grid_Blurriness("Blurriness", Float) = 0.5 + _Letter_Grid_Alpha_Threshold("Alpha threshold", Range(0, 1)) = 0.5 + [HideInInspector] m_end_Letter_Grid("Letter grid", Float) = 0 + //endex [HideInInspector] m_end_Gimmicks("Gimmicks", Float) = 0 diff --git a/Scripts/gen_atlas b/Scripts/gen_atlas new file mode 100755 index 0000000..a3fac72 --- /dev/null +++ b/Scripts/gen_atlas @@ -0,0 +1,421 @@ +#!/usr/bin/env -S uv run --script +# /// script +# requires-python = ">=3.10" +# dependencies = ["Pillow"] +# /// + +import argparse +import subprocess +import os +import sys +import json +from PIL import Image, ImageDraw +import random + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +MSDF_REPO = "https://github.com/Chlumsky/msdf-atlas-gen.git" +MSDF_DIR = os.path.join(SCRIPT_DIR, "msdf-atlas-gen") +MSDF_BUILD_DIR = os.path.join(MSDF_DIR, "build") + +def get_msdf_binary(): + """Return path to msdf-atlas-gen binary, building from source if needed.""" + if sys.platform == "win32": + binary = os.path.join(MSDF_BUILD_DIR, "bin", "Debug", "msdf-atlas-gen.exe") + else: + binary = os.path.join(MSDF_BUILD_DIR, "bin", "msdf-atlas-gen") + if os.path.isfile(binary): + return binary + print("msdf-atlas-gen not found, acquiring from source...") + if not os.path.isdir(MSDF_DIR): + subprocess.run( + ["git", "clone", "--recursive", MSDF_REPO, MSDF_DIR], + check=True, + ) + else: + print("Source directory exists, skipping clone.") + os.makedirs(MSDF_BUILD_DIR, exist_ok=True) + subprocess.run( + [ + "cmake", "..", + "-DCMAKE_BUILD_TYPE=Release", + "-DMSDF_ATLAS_USE_VCPKG=OFF", + "-DMSDF_ATLAS_USE_SKIA=OFF", + "-DMSDF_ATLAS_NO_ARTERY_FONT=ON", + ], + cwd=MSDF_BUILD_DIR, + check=True, + ) + subprocess.run( + ["cmake", "--build", ".", "--config", "Release", "-j", str(os.cpu_count() or 4)], + cwd=MSDF_BUILD_DIR, + check=True, + ) + if not os.path.isfile(binary): + # Try Release subdir on Windows + alt = os.path.join(MSDF_BUILD_DIR, "bin", "Release", "msdf-atlas-gen.exe") + if os.path.isfile(alt): + return alt + raise FileNotFoundError(f"Build succeeded but binary not found at {binary}") + return binary + +# Define the character ranges we want to include +CHAR_RANGES = [ + (32, 126), # Printable ASCII + # Add more ranges here as needed, e.g.: + (0x0080, 0x00ff), # latin + (0x0100, 0x017f), # latin + (0x0180, 0x024f), # latin + (0x0250, 0x02af), # ipa extensions + (0x0370, 0x03ff), # greek and coptic + (0x0400, 0x04ff), # cyrillic + (0x0500, 0x052f), # cyrillic supplement + (0x0530, 0x058f), # armenian + (0x0590, 0x05ff), # hebrew + (0x0600, 0x06ff), # arabic +] + +def calculate_grid_size(char_ranges): + """Calculate the smallest square grid that fits the highest character code""" + max_char = max(end for _, end in char_ranges) + grid_size = 1 + while grid_size * grid_size < max_char: + grid_size += 1 + return grid_size + +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 = [ + get_msdf_binary(), + "-font", font_path, + "-type", type, + "-format", "png", + "-imageout", f"{output_filename}.png", + "-size", str(font_size), + "-pxrange", "4", + "-dimensions", str(resolution), str(resolution), + "-chars", chars_str, + "-uniformgrid", + "-uniformcols", str(grid_size), + "-uniformcell", str(cell_size), str(cell_size), + "-errorcorrection", "auto-full", + "-scanline", + #"-angle", "15d", + "-edgecoloring", "distance" + ] + + 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 + if result.stdout: + print("msdf-atlas-gen output:") + print(result.stdout) + + # Rearrange the atlas to include gaps for non-printable characters + print("Rearranging atlas...") + 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}") + print(f"Error output: {e.stderr}") + return False + +def draw_grid_lines(image, resolution): + """Draw red grid lines on the image""" + draw = ImageDraw.Draw(image) + grid_size = calculate_grid_size(CHAR_RANGES) + + # Draw vertical lines + for x in range(grid_size): + line_x = x * resolution // grid_size + draw.line([(line_x, 0), (line_x, resolution-1)], fill=(255, 0, 0), width=1) + + # Draw horizontal lines + for y in range(grid_size): + line_y = y * resolution // grid_size + draw.line([(0, line_y), (resolution-1, line_y)], fill=(255, 0, 0), width=1) + + # Draw the final borders + 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, type="msdf", font_name=""): + """Rearrange the atlas to include gaps for non-printable characters""" + # 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 + + # Track current position in the source atlas + source_index = 0 + + # Process each character range + for start, end in CHAR_RANGES: + for ascii_code in range(start, end + 1): + # Calculate source position (original atlas) + source_x = (source_index % grid_size) * cell_size + source_y = (source_index // grid_size) * cell_size + + # Calculate target position (new atlas) + target_x = ((ascii_code + 1) % grid_size) * cell_size + target_y = ((ascii_code + 1) // grid_size) * cell_size + + # Extract and paste the glyph + glyph = original.crop(( + source_x, + source_y, + source_x + cell_size, + source_y + cell_size + )) + new_atlas.paste(glyph, (target_x, target_y)) + source_index += 1 + + # Draw the grid lines only if requested + if draw_grid: + draw_grid_lines(new_atlas, resolution) + + # Calculate actual used dimensions + used_resolution = cell_size * grid_size + # Crop to used dimensions and resize back to requested resolution + used_atlas = new_atlas.crop((0, 0, used_resolution, used_resolution)) + final_atlas = used_atlas.resize((resolution, resolution), Image.LANCZOS) + + # 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', 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 + if not os.path.isfile(args.font_path): + print(f"Error: Font file not found at {args.font_path}") + return + + generate_atlas(args.font_path, args.resolution, draw_grid=args.grid, type=args.type, font_size=args.font_size) + +if __name__ == "__main__": + main() diff --git a/disinfo.cginc b/disinfo.cginc new file mode 100644 index 0000000..d991e67 --- /dev/null +++ b/disinfo.cginc @@ -0,0 +1,98 @@ +#include "globals.cginc" + +#ifndef __DISINFO_INC +#define __DISINFO_INC + +/* + * A small font rendering library. + * + * Sample usage: + * + * fixed4 frag(v2f i) : SV_Target + * { + * float2 uv = i.uv; + * int2 cell_pos; + * float2 cell_uv; + * float2 res = int2(4, 4); + * if (!getBoxLoc(uv, 0.1, 0.9, res, cell_pos, cell_uv)) { + * return float4(0, 0, 0, 1); + * } + * float2 duv = float2(ddx(i.uv.x), ddy(i.uv.y)) / 4; + * float4 font_color = renderInBox(67, cell_uv, duv); + * + * return font_color; + * } + */ + +// Returns false if `uv` does not fall within `bounds`. +bool remapUVSmaller(float2 uv, float2 bottom_left, float2 top_right, + out float2 uvr) { + if (!(uv.x > bottom_left.x && uv.x < top_right.x && + uv.y > bottom_left.y && uv.y < top_right.y)) { + return false; + } + + uvr = uv - bottom_left; + uvr = uvr / (top_right - bottom_left); + + return true; +} + +// bounds: the left/right/top/bottom bounds of the inner UV region, +// respectively. +// Always returns true. +bool remapUVBigger(float2 uv, float2 bottom_left, float2 top_right, + out float2 uvr) { + uvr = uv * (top_right - bottom_left) + bottom_left; + + return true; +} + +bool getBoxLoc(float2 uv, float2 bottom_left, float2 top_right, + int2 res, float padding, out int2 cell_pos, out float2 cell_uv) +{ + float2 box_uv; + if (!remapUVSmaller(uv, bottom_left, top_right, box_uv)) { + return false; + } + + // The integer index of the cell pointed to by `uv`, on the interval + // [0, res.x - 1] * [0, res.y - 1] + cell_pos = fmod(floor(box_uv * res), res); + + float2 box_sz = 1.0 / float2(res); + float2 cell_bot_left = (cell_pos - padding) * box_sz; + float2 cell_top_right = (cell_pos + 1 + padding) * box_sz; + if (!remapUVSmaller(box_uv, cell_bot_left, cell_top_right, cell_uv)) { + // This should never happen Clueless + return true; + } + + return true; +} + +// `c` is a character encoded as ASCII. +float4 renderInBox(int c, float2 uv, float2 cell_uv, texture2D font, int2 font_res) +{ + int letter_idx = c; + int2 letter_pos = int2( + font_res.x - (letter_idx % font_res.x), + letter_idx / font_res.x); + letter_pos.x = font_res.x - letter_pos.x; + letter_pos.y = (font_res.y - 1) - letter_pos.y; + float2 letter_box_sz = 1.0 / float2(font_res); + float2 letter_bot_left = letter_pos * letter_box_sz; + float2 letter_top_right = (letter_pos + 1) * letter_box_sz; + float2 letter_uv; + remapUVBigger(cell_uv, letter_bot_left, letter_top_right, letter_uv); + +#if 0 + float4 font_color = font.Sample(linear_clamp_s, letter_uv); +#else + float4 font_color = font.SampleLevel(linear_repeat_s, letter_uv, 0); +#endif + return font_color; +} + +#endif // __DISINFO_INC + diff --git a/features.cginc b/features.cginc index c94f7ab..61b59e0 100755 --- a/features.cginc +++ b/features.cginc @@ -226,4 +226,8 @@ #pragma shader_feature_local _CLOTH //endex +//ifex _Letter_Grid_Enabled==0 +#pragma shader_feature_local _LETTER_GRID +//endex + #endif // __FEATURES_INC diff --git a/globals.cginc b/globals.cginc index 4873108..e567177 100755 --- a/globals.cginc +++ b/globals.cginc @@ -568,4 +568,29 @@ float4 _Vertex_Deformation_Slot_15_Vector_3; float4 _Cloth_Sheen; #endif // _CLOTH +#if defined(_LETTER_GRID) +texture2D _Letter_Grid_Texture; +float4 _Letter_Grid_Texture_TexelSize; +float _Letter_Grid_Tex_Res_X; +float _Letter_Grid_Tex_Res_Y; +float _Letter_Grid_Res_X; +float _Letter_Grid_Res_Y; +float4 _Letter_Grid_Data_Row_0; +float4 _Letter_Grid_Data_Row_1; +float4 _Letter_Grid_Data_Row_2; +float4 _Letter_Grid_Data_Row_3; +float4 _Letter_Grid_UV_Scale_Offset; +float _Letter_Grid_Padding; +float4 _Letter_Grid_Color; +float _Letter_Grid_Metallic; +float _Letter_Grid_Roughness; +float _Letter_Grid_Emission; +texture2D _Letter_Grid_Mask; +float _Letter_Grid_Global_Offset; +float _Letter_Grid_Screen_Px_Range; +float _Letter_Grid_Min_Screen_Px_Range; +float _Letter_Grid_Blurriness; +float _Letter_Grid_Alpha_Threshold; +#endif // _LETTER_GRID + #endif // __GLOBALS_INC diff --git a/letter_grid.cginc b/letter_grid.cginc new file mode 100644 index 0000000..61c147b --- /dev/null +++ b/letter_grid.cginc @@ -0,0 +1,85 @@ +#ifndef __LETTER_GRID_INC +#define __LETTER_GRID_INC + +#include "disinfo.cginc" +#include "features.cginc" +#include "globals.cginc" +#include "interpolators.cginc" +#include "math.cginc" +#include "texture_utils.cginc" + +#if defined(_LETTER_GRID) + +struct LetterGridOutput { + float4 albedo; + float metallic; + float roughness; + float3 emission; +}; + +LetterGridOutput LetterGrid(v2f i) { + LetterGridOutput output; + + int2 cell_pos; + int2 font_res = int2(round(_Letter_Grid_Tex_Res_X), round(_Letter_Grid_Tex_Res_Y)); + int2 grid_res = int2(round(_Letter_Grid_Res_X), round(_Letter_Grid_Res_Y)); + float2 cell_uv; // uv within each letter cell + + float4 scoff = _Letter_Grid_UV_Scale_Offset; + float2 uv = ((i.uv01.xy - 0.5) - scoff.zw) * scoff.xy + 0.5; + + bool in_box = getBoxLoc(uv, 0, 1, grid_res, _Letter_Grid_Padding, cell_pos, cell_uv); + + // Extract char from _Letter_Grid_Data_Row_0 et al using cell_pos. + cell_pos.y = (grid_res.y - cell_pos.y) - 1; + float c = lerp( + lerp( + _Letter_Grid_Data_Row_0[cell_pos.x], + _Letter_Grid_Data_Row_1[cell_pos.x], + cell_pos.y), + lerp( + _Letter_Grid_Data_Row_2[cell_pos.x], + _Letter_Grid_Data_Row_3[cell_pos.x], + cell_pos.y - 2), + cell_pos.y/2); + c += _Letter_Grid_Global_Offset; + + float3 msd = renderInBox(c, uv, cell_uv, _Letter_Grid_Texture, font_res).rgb; + float sd = median(msd); + + // Calculate screen pixel range + float screen_px_range; + { + float2 tex_size = float2(_Letter_Grid_Texture_TexelSize.zw); + float2 real_cell_size = floor(tex_size / grid_res); // size of cell in texels + float2 unit_range = _Letter_Grid_Screen_Px_Range / real_cell_size; + float2 screen_tex_size = 1 / fwidth(cell_uv); + screen_px_range = max(0.5 * dot(unit_range, screen_tex_size), _Letter_Grid_Min_Screen_Px_Range); + } + + float screen_px_distance = screen_px_range * (sd - _Letter_Grid_Alpha_Threshold); + float smooth_range = (length(grid_res) / sqrt(screen_px_range)) * _Letter_Grid_Blurriness; + float op = smoothstep(-smooth_range, smooth_range, screen_px_distance); + + // Sample mask if enabled + #if defined(_LETTER_GRID_MASK) + float mask = _Letter_Grid_Mask.Sample(linear_repeat_s, i.uv01.xy).r; + #else + float mask = 1.0; + #endif + + op *= mask; + + // Apply blending to output + output.albedo = float4(_Letter_Grid_Color.rgb, op * in_box); + output.metallic = _Letter_Grid_Metallic; + output.roughness = _Letter_Grid_Roughness; + output.emission = _Letter_Grid_Color.rgb * _Letter_Grid_Emission; + + return output; +} + +#endif // _LETTER_GRID + +#endif // __LETTER_GRID_INC + @@ -240,4 +240,13 @@ float voronoi_edge_distance(float3 x) { return (d2 - d1) / (2.0 * max(1e-4, length(p2 - p1))); } +float median(float3 x) { + // Get the min and max. + float x_min= min(min(x.r, x.g), x.b); + float x_max = max(max(x.r, x.g), x.b); + + // Compute (x.r + x.g + x.b) - (x_min + x_max). This gives us the median. + return (x.r + x.g + x.b) - (x_min + x_max); +} + #endif // __MATH_INC |
