├── Scripts ├── Editor │ ├── README.md │ ├── xatlas.dll │ ├── README.md.meta │ ├── XatlasSource~ │ │ └── xatlas │ │ │ ├── .gitignore │ │ │ ├── .appveyor.yml │ │ │ ├── LICENSE │ │ │ ├── source │ │ │ └── xatlas │ │ │ │ ├── main.cpp │ │ │ │ └── xatlas.h │ │ │ ├── README.md │ │ │ └── premake5.lua │ ├── PackOnBeforeRender.cs.meta │ ├── TriggerPackingOnImport.cs.meta │ ├── XatlasPackerBakeryPatch.cs.meta │ ├── xatlas.dll.meta │ ├── TriggerPackingOnImport.cs │ ├── PackOnBeforeRender.cs │ └── XatlasPackerBakeryPatch.cs ├── Editor.meta ├── xatlasLightmap.asmdef.meta ├── LMBlur.shader.meta ├── UnwrapUV.shader.meta ├── xatlas.cs.meta ├── XatlasLightmapPacker.cs.meta ├── NativeCollectionUtilities.cs.meta ├── xatlasLightmap.asmdef ├── UnwrapUV.shader ├── LMBlur.shader ├── xatlas.cs ├── NativeCollectionUtilities.cs └── XatlasLightmapPacker.cs ├── LICENSE.meta ├── README.md.meta ├── package.json.meta ├── Scripts.meta ├── package.json ├── .github └── workflows │ └── trigger-discord-notify.yml ├── LICENSE ├── .gitignore └── README.md /Scripts/Editor/README.md: -------------------------------------------------------------------------------- 1 | Source: https://github.com/guycalledfrank/xatlasLib -------------------------------------------------------------------------------- /Scripts/Editor/xatlas.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/z3y/XatlasLightmap/HEAD/Scripts/Editor/xatlas.dll -------------------------------------------------------------------------------- /LICENSE.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 438ebf5a6514ff94ba828dcd78963d20 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 39dde3cfa932c564d9896ee39cb01241 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1939922b6327e844a8494d0856f500db 3 | PackageManifestImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Scripts.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4648fb1ec07a71d45a3c43343faf7cb9 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Scripts/Editor/README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4adf6470b03dad247a01cd3a605b459f 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Scripts/Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e437cdf368291fd46af933085c7bcfed 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Scripts/Editor/XatlasSource~/xatlas/.gitignore: -------------------------------------------------------------------------------- 1 | /bin 2 | /build 3 | /models 4 | /source/examples 5 | /source/test 6 | /source/thirdparty 7 | /source/viewer 8 | /source/xatlas/xatlas_c.h 9 | /source/xatlas.natvis 10 | -------------------------------------------------------------------------------- /Scripts/xatlasLightmap.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 96fc23fcec7f11d41bfbbe240e4602dc 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.z3y.xatlaslightmap", 3 | "description": "Xatlas lightmap uv packer for bakery lightmapper", 4 | "version": "0.2.0-beta.2", 5 | "unity": "2019.4", 6 | "displayName": "Xatlas Lightmap", 7 | "hideInEditor": false 8 | } 9 | -------------------------------------------------------------------------------- /Scripts/LMBlur.shader.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d34a2ca9e62b1fa47a67f09bf5782687 3 | ShaderImporter: 4 | externalObjects: {} 5 | defaultTextures: [] 6 | nonModifiableTextures: [] 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Scripts/UnwrapUV.shader.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bb5eba9a0d1d8064580d55e57dc2c4c3 3 | ShaderImporter: 4 | externalObjects: {} 5 | defaultTextures: [] 6 | nonModifiableTextures: [] 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Scripts/xatlas.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e6c2ba9c5b209dd47a3beb0e853d7a5d 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/XatlasLightmapPacker.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f401f063eb6e67c42a1ed6122f745813 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Editor/PackOnBeforeRender.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5f835d06340eb80429863ac046792475 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/NativeCollectionUtilities.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 58c31d8e9bd67ec4ca1a9a30c5499474 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Editor/TriggerPackingOnImport.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 85f4a97b2bc2e684aa2ec418dc356890 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Scripts/Editor/XatlasPackerBakeryPatch.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a667d936b95b7ab4987e604d3c973af8 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /.github/workflows/trigger-discord-notify.yml: -------------------------------------------------------------------------------- 1 | name: Notify Discord on Release 2 | 3 | on: 4 | release: 5 | types: [published] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | notify: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Notify Discord 13 | uses: z3y/discord-notify-action@main 14 | with: 15 | webhook_url: ${{ secrets.DISCORD_WEBHOOK_URL }} 16 | -------------------------------------------------------------------------------- /Scripts/Editor/XatlasSource~/xatlas/.appveyor.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - Visual Studio 2022 3 | 4 | environment: 5 | matrix: 6 | - ACTION: vs2015 7 | - ACTION: vs2017 8 | - ACTION: vs2019 9 | - ACTION: vs2022 10 | 11 | platform: 12 | - Win32 13 | - x64 14 | 15 | configuration: 16 | - Release 17 | 18 | install: 19 | - bin\premake5.exe %ACTION% 20 | 21 | build: 22 | project: build\$(ACTION)\xatlas.sln 23 | -------------------------------------------------------------------------------- /Scripts/Editor/xatlas.dll.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b8634534a2e8ffe4baac4d43d534577e 3 | PluginImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | iconMap: {} 7 | executionOrder: {} 8 | defineConstraints: [] 9 | isPreloaded: 0 10 | isOverridable: 0 11 | isExplicitlyReferenced: 0 12 | validateReferences: 1 13 | platformData: 14 | - first: 15 | Any: 16 | second: 17 | enabled: 0 18 | settings: {} 19 | - first: 20 | Editor: Editor 21 | second: 22 | enabled: 1 23 | settings: 24 | DefaultValueInitialized: true 25 | userData: 26 | assetBundleName: 27 | assetBundleVariant: 28 | -------------------------------------------------------------------------------- /Scripts/xatlasLightmap.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xatlasLightmap", 3 | "rootNamespace": "", 4 | "references": [ 5 | "GUID:d8b63aba1907145bea998dd612889d6b", 6 | "GUID:2665a8d13d1b3f18800f46e256720795", 7 | "GUID:290dd5870d0ead646bcb6ea5c6a60af5", 8 | "GUID:a1653399f63795746b1857281d1e400d" 9 | ], 10 | "includePlatforms": [], 11 | "excludePlatforms": [], 12 | "allowUnsafeCode": true, 13 | "overrideReferences": false, 14 | "precompiledReferences": [], 15 | "autoReferenced": true, 16 | "defineConstraints": [], 17 | "versionDefines": [ 18 | { 19 | "name": "com.stresslevelzero.static-batching", 20 | "expression": "1.0", 21 | "define": "SLZ_BATCHING" 22 | } 23 | ], 24 | "noEngineReferences": false 25 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 z3y 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Scripts/Editor/XatlasSource~/xatlas/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2020 Jonathan Young 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This .gitignore file should be placed at the root of your Unity project directory 2 | # 3 | # Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore 4 | # 5 | /[Ll]ibrary/ 6 | /[Tt]emp/ 7 | /[Oo]bj/ 8 | /[Bb]uild/ 9 | /[Bb]uilds/ 10 | /[Ll]ogs/ 11 | /[Mm]emoryCaptures/ 12 | 13 | # Asset meta data should only be ignored when the corresponding asset is also ignored 14 | !/[Aa]ssets/**/*.meta 15 | 16 | # Uncomment this line if you wish to ignore the asset store tools plugin 17 | # /[Aa]ssets/AssetStoreTools* 18 | 19 | # Autogenerated Jetbrains Rider plugin 20 | [Aa]ssets/Plugins/Editor/JetBrains* 21 | 22 | # Visual Studio cache directory 23 | .vs/ 24 | 25 | # Gradle cache directory 26 | .gradle/ 27 | 28 | # Autogenerated VS/MD/Consulo solution and project files 29 | ExportedObj/ 30 | .consulo/ 31 | *.csproj 32 | *.unityproj 33 | *.sln 34 | *.suo 35 | *.tmp 36 | *.user 37 | *.userprefs 38 | *.pidb 39 | *.booproj 40 | *.svd 41 | *.pdb 42 | *.mdb 43 | *.opendb 44 | *.VC.db 45 | 46 | # Unity3D generated meta files 47 | *.pidb.meta 48 | *.pdb.meta 49 | *.mdb.meta 50 | 51 | # Unity3D generated file on crash reports 52 | sysinfo.txt 53 | 54 | # Builds 55 | *.apk 56 | *.unitypackage 57 | 58 | # Crashlytics generated file 59 | crashlytics-build.properties 60 | 61 | -------------------------------------------------------------------------------- /Scripts/Editor/TriggerPackingOnImport.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | /*using System.Collections.Generic; 3 | using UnityEditor; 4 | using UnityEditor.SceneManagement; 5 | using UnityEngine; 6 | using z3y; 7 | 8 | public class TriggerPackingOnImport : AssetPostprocessor 9 | { 10 | private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) 11 | { 12 | var scene = EditorSceneManager.GetActiveScene(); 13 | 14 | if (scene == null) 15 | { 16 | return; 17 | } 18 | 19 | 20 | var rootGameObjects = scene.GetRootGameObjects(); 21 | var instances = new List(); 22 | foreach (var gameObject in rootGameObjects) 23 | { 24 | var xatlasLightmapPackers = gameObject.GetComponentsInChildren(false); 25 | if (xatlasLightmapPackers is null || xatlasLightmapPackers.Length == 0) 26 | { 27 | continue; 28 | } 29 | instances.AddRange(xatlasLightmapPackers); 30 | } 31 | 32 | for (int i = 0; i < instances.Count; i++) 33 | { 34 | instances[i].Execute(true, false); 35 | } 36 | 37 | 38 | } 39 | } 40 | */ 41 | #endif -------------------------------------------------------------------------------- /Scripts/Editor/PackOnBeforeRender.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using UnityEditor; 6 | using UnityEditor.SceneManagement; 7 | using UnityEngine; 8 | 9 | 10 | namespace z3y 11 | { 12 | public static class PackOnBeforeRender 13 | { 14 | [InitializeOnLoadMethod] 15 | public static void RegisterPackOnBeforeRender() 16 | { 17 | ftRenderLightmap.OnPreFullRender += PackOpenScene; 18 | } 19 | 20 | public static void PackOpenScene(object sender, EventArgs e) 21 | { 22 | var scene = EditorSceneManager.GetActiveScene(); 23 | 24 | if (scene == null) 25 | { 26 | return; 27 | } 28 | 29 | var rootGameObjects = scene.GetRootGameObjects(); 30 | var instances = new List(); 31 | foreach (var gameObject in rootGameObjects) 32 | { 33 | var xatlasLightmapPackers = gameObject.GetComponentsInChildren(false); 34 | if (xatlasLightmapPackers is null || xatlasLightmapPackers.Length == 0) 35 | { 36 | continue; 37 | } 38 | instances.AddRange(xatlasLightmapPackers); 39 | } 40 | 41 | for (int i = 0; i < instances.Count; i++) 42 | { 43 | if (instances[i].autoUpdateUVs) 44 | { 45 | instances[i].Execute(false, true); 46 | } 47 | } 48 | 49 | } 50 | } 51 | } 52 | #endif -------------------------------------------------------------------------------- /Scripts/UnwrapUV.shader: -------------------------------------------------------------------------------- 1 | Shader "Hidden/z3y/UnwrapUV" 2 | { 3 | Properties 4 | { 5 | _MainTex ("Texture", 2D) = "white" {} 6 | } 7 | SubShader 8 | { 9 | Tags { "RenderType"="Opaque" } 10 | LOD 100 11 | Cull Off 12 | 13 | Pass 14 | { 15 | CGPROGRAM 16 | #pragma vertex vert 17 | #pragma fragment frag 18 | // make fog work 19 | #pragma multi_compile_fog 20 | 21 | #include "UnityCG.cginc" 22 | 23 | struct appdata 24 | { 25 | float4 vertex : POSITION; 26 | float2 uv : TEXCOORD0; 27 | float2 uv1 : TEXCOORD1; 28 | }; 29 | 30 | struct v2f 31 | { 32 | float2 uv : TEXCOORD0; 33 | UNITY_FOG_COORDS(1) 34 | float4 vertex : SV_POSITION; 35 | }; 36 | 37 | sampler2D _MainTex; 38 | float4 _MainTex_ST; 39 | 40 | v2f vert (appdata v) 41 | { 42 | v2f o; 43 | o.vertex = UnityObjectToClipPos(v.vertex); 44 | o.uv = TRANSFORM_TEX(v.uv, _MainTex); 45 | 46 | float4 uv = float4(0,0,0,1); 47 | uv.xy = v.uv1.xy * 2 - 1; 48 | uv.y *= _ProjectionParams.x; 49 | o.vertex = uv; 50 | //UNITY_TRANSFER_FOG(o,o.vertex); 51 | return o; 52 | } 53 | 54 | fixed4 frag (v2f i) : SV_Target 55 | { 56 | // sample the texture 57 | // fixed4 col = tex2D(_MainTex, i.uv); 58 | return 1; 59 | } 60 | ENDCG 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XatlasLightmap 2 | 3 | A tool for the most efficient lightmap packing in Unity. 4 | 5 | ## How it works 6 | It generates unique uv2 per mesh, instead of using lightmap UV offsets, which when combined with static batching doesn't create any extra cost. Additional vertex streams are used to set uv2 in editor, which requires modifications to bakery scripts. Lightmap Scale is also calculated differently which allows it to have perfectly uniform texel density when lightmap scale is set to 1. 7 | 8 | ## How to use 9 | - Add package with git `https://github.com/z3y/XatlasLightmap.git` 10 | - Patch Bakery `Tools > XatlasLightmap > PatchBakery` (only once) 11 | - Add a `XatlasLightmapPacker` script on a GameObject 12 | - Select a BakeryLightmapGroup asset with Packing Mode set to Original UV 13 | - Generate Lightmap UVs on the model importer or create custom UV2 14 | - Enable mesh Read/Write on the model importer 15 | - Pack 16 | 17 | [Share your lightmaps](https://github.com/z3y/XatlasLightmap/issues/6) 18 | 19 | ## Limitations 20 | 21 | - Nested lightmap groups arent supported yet 22 | - Doesn't work with GPU instancing, since every mesh ends up being unique. Only use with static batching. 23 | - Model reimports will break the mesh until packed again. 24 | - Since the packing is so efficient and the padding gets applied correctly most shaders will have slight bleeding at 2px, it is recommended to use a centroid interpolator for the lightmap UV. 25 | - Using bicubic sampling requires more padding 26 | - Lightmaps might fail to bake properly first time 27 | - Only works with Bakery 28 | - Sometimes on build it can fail to get UV2 because Read/Write is disabled, even tho its not needed for an editor script. This also doesn't affect the build size. The original mesh is never referenced, only the combined static mesh gets included. 29 | 30 | ## Examples 31 | 32 | Sponza: 33 | 34 | ![Screenshot 2023-03-18 164316](https://user-images.githubusercontent.com/33181641/227739457-d5bd302d-ba14-4e1f-a745-da5942e1215b.png) 35 | 36 | Checker preview: 37 | 38 | ![image](https://github.com/z3y/XatlasLightmap/assets/33181641/ab5af17a-ef49-442e-96cc-d0cd0295acdd) 39 | 40 | 41 | Sponza 2: 42 | 43 | ![image](https://github.com/z3y/XatlasLightmap/assets/33181641/6b791015-51c1-4d12-b0bd-16452dc802bf) 44 | -------------------------------------------------------------------------------- /Scripts/LMBlur.shader: -------------------------------------------------------------------------------- 1 | Shader "Hidden/z3y/LMBlur" 2 | { 3 | Properties 4 | { 5 | _MainTex ("Lightmap", 2D) = "white" {} 6 | 7 | _Mask ("Blur Mask", 2D) = "white" {} 8 | 9 | _Size ("Size", Float) = 1 10 | 11 | } 12 | SubShader 13 | { 14 | Tags { "RenderType"="Opaque" } 15 | LOD 100 16 | 17 | Pass 18 | { 19 | CGPROGRAM 20 | #pragma vertex vert 21 | #pragma fragment frag 22 | #include "UnityCG.cginc" 23 | 24 | struct appdata 25 | { 26 | float4 vertex : POSITION; 27 | float2 uv : TEXCOORD0; 28 | float2 uv2 : TEXCOORD1; 29 | }; 30 | 31 | struct v2f 32 | { 33 | float2 uv : TEXCOORD0; 34 | float4 vertex : SV_POSITION; 35 | }; 36 | 37 | sampler2D _MainTex; 38 | float4 _MainTex_ST; 39 | float4 _MainTex_TexelSize; 40 | 41 | float2 _lightmapRes; 42 | float _Size; 43 | 44 | sampler2D _Mask; 45 | 46 | v2f vert (appdata v) 47 | { 48 | v2f o; 49 | o.vertex = UnityObjectToClipPos(v.vertex); 50 | o.uv = v.uv2; 51 | return o; 52 | } 53 | 54 | fixed4 frag (v2f i) : SV_Target 55 | { 56 | // sample the texture 57 | //fixed4 lightmap = tex2D(_MainTex, i.uv); 58 | //fixed4 mask = tex2D(_Mask, i.uv); 59 | 60 | //lightmap*= mask; 61 | 62 | float Pi = UNITY_PI*2; // Pi*2 63 | 64 | // GAUSSIAN BLUR SETTINGS {{{ 65 | float Directions = 32.0; // BLUR DIRECTIONS (Default 16.0 - More is better but slower) 66 | float Quality = 6.0; // BLUR QUALITY (Default 4.0 - More is better but slower) 67 | float Size = _Size; // BLUR SIZE (Radius) 68 | // GAUSSIAN BLUR SETTINGS }}} 69 | 70 | float2 Radius = _lightmapRes.xy * Size; 71 | 72 | // Normalized pixel coordinates (from 0 to 1) 73 | float2 uv = i.uv; 74 | // Pixel colour 75 | float4 lightmap = tex2D(_MainTex, uv); 76 | float4 Color = lightmap; 77 | float mask = tex2D(_Mask, uv).r; 78 | 79 | // Blur calculations 80 | for( float d=0.0; d(); 29 | 30 | var handles = new List(); 31 | 32 | try 33 | { 34 | var uvs = new List(); 35 | for (int i = 0; i < meshes.Length; i++) 36 | { 37 | var mesh = meshes[i]; 38 | 39 | var xMesh = new XatlasMesh(); 40 | xMesh.vertexCount = mesh.vertexCount; 41 | GCHandle vHandle = GCHandle.Alloc(mesh.vertices, GCHandleType.Pinned); 42 | handles.Add(vHandle); 43 | var vPtr = vHandle.AddrOfPinnedObject(); 44 | xMesh.vertices = vPtr; 45 | 46 | var lightmapUV = mesh.uv2 ?? mesh.uv; 47 | GCHandle uvHandle = GCHandle.Alloc(lightmapUV, GCHandleType.Pinned); 48 | handles.Add(uvHandle); 49 | var uvPtr = uvHandle.AddrOfPinnedObject(); 50 | xMesh.uvs = uvPtr; 51 | 52 | for (int j = 0; j < mesh.subMeshCount; j++) 53 | { 54 | var indices = mesh.GetIndices(j); 55 | 56 | GCHandle iHandle = GCHandle.Alloc(indices, GCHandleType.Pinned); 57 | handles.Add(iHandle); 58 | var iPtr = iHandle.AddrOfPinnedObject(); 59 | xMesh.indices = iPtr; 60 | xMesh.indexCount = (int)mesh.GetIndexCount(j); 61 | xMesh.submeshIndex = j; 62 | 63 | xatlasMeshes.Add(xMesh); 64 | } 65 | uvs.Add(lightmapUV); 66 | 67 | } 68 | 69 | var meshesArray = xatlasMeshes.ToArray(); 70 | var handle = GCHandle.Alloc(meshesArray, GCHandleType.Pinned); 71 | handles.Add(handle); 72 | var ptr = handle.AddrOfPinnedObject(); 73 | 74 | if (PackLightmap(ptr, meshesArray.Length, true, padding, resolution, bruteForce, 1024, 0) == 0) 75 | { 76 | Debug.Log("Lightmap Packed"); 77 | for (int k = 0; k < meshes.Length; k++) 78 | { 79 | meshes[k].uv2 = uvs[k]; 80 | } 81 | } 82 | 83 | 84 | } 85 | finally 86 | { 87 | foreach (var h in handles) 88 | { 89 | h.Free(); 90 | } 91 | } 92 | 93 | } 94 | } 95 | } 96 | #endif -------------------------------------------------------------------------------- /Scripts/Editor/XatlasPackerBakeryPatch.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Text; 7 | using UnityEditor; 8 | using UnityEngine; 9 | 10 | namespace z3y 11 | { 12 | public class XatlasPackerBakeryPatch 13 | { 14 | const string ftBuildGraphicsPath = "Assets/Editor/x64/Bakery/scripts/ftBuildGraphics.cs"; 15 | 16 | const string Patch0Start = "if (ftRenderLightmap.checkOverlaps)"; 17 | const string Patch1Start = "ftUVGBufferGen.StartUVGBuffer(res, hasEmissive, bakeWithNormalMaps"; 18 | 19 | const string Patch0Text = "for (int i = 0; i < objsToWrite.Count(); i++)\r\n {\r\n var r = objsToWrite[i].GetComponent();\r\n\r\n if (r && r.enlightenVertexStream)\r\n {\r\n objsToWriteVerticesUV2[i] = r.enlightenVertexStream.uv2;\r\n // this can also change\r\n //objsToWriteVerticesUV[i] = r.enlightenVertexStream.uv;\r\n }\r\n }"; 20 | const string Patch1Text = "for (int i = 0; i < objsToWrite.Count(); i++)\r\n {\r\n var r = objsToWrite[i].GetComponent();\r\n\r\n if (r && r.enlightenVertexStream && r.enlightenVertexStream.uv2 != null)\r\n {\r\n objsToWriteUVOverride[i] = r.enlightenVertexStream.uv2;\r\n }\r\n }"; 21 | 22 | [MenuItem("Tools/XatlasLightmap/PatchBakery")] 23 | public static void PatchBakery() 24 | { 25 | if (!File.Exists(ftBuildGraphicsPath)) 26 | { 27 | Debug.LogError("ftBuildGraphics.cs not found"); 28 | return; 29 | } 30 | 31 | var lines = File.ReadLines(ftBuildGraphicsPath); 32 | 33 | bool line0found = false; 34 | bool line1found = false; 35 | var text = new StringBuilder(); 36 | foreach (var line in lines) 37 | { 38 | 39 | var current = line.AsSpan().TrimStart(); 40 | 41 | if (current.StartsWith("//BAKERY PATCH START".AsSpan(), StringComparison.Ordinal)) 42 | { 43 | if (line0found) 44 | { 45 | line1found = true; 46 | } 47 | 48 | line0found = true; 49 | 50 | if (line0found && line1found) 51 | { 52 | Debug.LogError("Patch Already Applied"); 53 | return; 54 | } 55 | } 56 | else if (current.StartsWith(Patch0Start.AsSpan(), StringComparison.Ordinal)) 57 | { 58 | text.AppendLine("//BAKERY PATCH START"); 59 | text.AppendLine(Patch0Text); 60 | text.AppendLine("//BAKERY PATCH END"); 61 | line0found = true; 62 | } 63 | else if (current.StartsWith(Patch1Start.AsSpan(), StringComparison.Ordinal)) 64 | { 65 | text.AppendLine("//BAKERY PATCH START"); 66 | text.AppendLine(Patch1Text); 67 | text.AppendLine("//BAKERY PATCH END"); 68 | line1found = true; 69 | } 70 | 71 | text.AppendLine(line); 72 | } 73 | 74 | if (!line0found || !line1found) 75 | { 76 | Debug.LogError("Patch Failed"); 77 | return; 78 | } 79 | 80 | File.WriteAllText(ftBuildGraphicsPath, text.ToString()); 81 | AssetDatabase.Refresh(); 82 | } 83 | } 84 | } 85 | #endif -------------------------------------------------------------------------------- /Scripts/Editor/XatlasSource~/xatlas/source/xatlas/main.cpp: -------------------------------------------------------------------------------- 1 | #include "xatlas.h" 2 | #include 3 | 4 | #if defined(_WIN32) || defined(_WIN64) 5 | #define EXPORT __declspec(dllexport) 6 | #define CALLCONV __cdecl 7 | #else 8 | #define EXPORT __attribute__((visibility("default"))) 9 | #define CALLCONV 10 | #endif 11 | 12 | extern "C" 13 | { 14 | struct Mesh 15 | { 16 | float *vertices; 17 | float *uvs; 18 | uint32_t *indices; 19 | 20 | uint32_t vertexCount; 21 | uint32_t indexCount; 22 | uint32_t submeshIndex; 23 | }; 24 | 25 | EXPORT float CALLCONV PackLightmap(Mesh *meshes, uint32_t meshLength, bool blockAlign, uint32_t padding, uint32_t resolution, bool bruteForce, uint32_t maxChartSize, float texelSize) 26 | { 27 | 28 | xatlas::Atlas *atlas = xatlas::Create(); 29 | 30 | for (uint32_t i = 0; i < meshLength; i++) 31 | { 32 | Mesh *mesh = &meshes[i]; 33 | 34 | xatlas::UvMeshDecl decl; 35 | decl.vertexCount = mesh->vertexCount; 36 | decl.vertexUvData = mesh->uvs; 37 | decl.vertexStride = sizeof(float) * 2; 38 | decl.indexCount = mesh->indexCount; 39 | decl.indexData = mesh->indices; 40 | decl.indexFormat = xatlas::IndexFormat::UInt32; 41 | 42 | xatlas::AddMeshError err = xatlas::AddUvMesh(atlas, decl); 43 | 44 | if (err == xatlas::AddMeshError::IndexOutOfRange) 45 | { 46 | return 1; 47 | } 48 | else if (err == xatlas::AddMeshError::InvalidIndexCount) 49 | { 50 | return 2; 51 | } 52 | } 53 | 54 | xatlas::PackOptions packOptions; 55 | packOptions.bilinear = true; 56 | packOptions.blockAlign = blockAlign; 57 | packOptions.padding = padding; 58 | packOptions.resolution = resolution; 59 | packOptions.bruteForce = bruteForce; 60 | packOptions.maxChartSize = maxChartSize; 61 | packOptions.texelsPerUnit = texelSize; 62 | 63 | xatlas::ChartOptions chartOptions; 64 | chartOptions.useInputMeshUvs = true; 65 | 66 | xatlas::ComputeCharts(atlas, chartOptions); 67 | 68 | xatlas::PackCharts(atlas, packOptions); 69 | 70 | float max[2] = {1}; 71 | 72 | for (uint32_t i = 0; i < meshLength; i++) 73 | { 74 | Mesh *mesh = &meshes[i]; 75 | 76 | xatlas::Vertex *verts = atlas->meshes[i].vertexArray; 77 | uint32_t vertexCount = atlas->meshes[i].vertexCount; 78 | 79 | float uvX = 0; 80 | float uvY = 0; 81 | 82 | for (uint32_t j = 0; j < vertexCount; j++) 83 | { 84 | uint32_t xref = verts[j].xref; 85 | 86 | uvX = verts[j].uv[0]; 87 | uvY = verts[j].uv[1]; 88 | 89 | max[0] = std::max(max[0], uvX); 90 | max[1] = std::max(max[1], uvY); 91 | } 92 | } 93 | 94 | float scale[2] = {1}; 95 | 96 | scale[0] = 1.0 / max[0]; 97 | scale[1] = 1.0 / max[1]; 98 | 99 | for (uint32_t i = 0; i < meshLength; i++) 100 | { 101 | Mesh *mesh = &meshes[i]; 102 | 103 | xatlas::Vertex *verts = atlas->meshes[i].vertexArray; 104 | uint32_t vertexCount = atlas->meshes[i].vertexCount; 105 | 106 | float uvX = 0; 107 | float uvY = 0; 108 | 109 | for (uint32_t j = 0; j < vertexCount; j++) 110 | { 111 | uint32_t xref = verts[j].xref; 112 | 113 | uvX = verts[j].uv[0] * scale[0]; 114 | uvY = verts[j].uv[1] * scale[1]; 115 | 116 | mesh->uvs[xref * 2] = uvX; 117 | mesh->uvs[xref * 2 + 1] = uvY; 118 | } 119 | } 120 | 121 | xatlas::Destroy(atlas); 122 | 123 | return 0; 124 | } 125 | } -------------------------------------------------------------------------------- /Scripts/NativeCollectionUtilities.cs: -------------------------------------------------------------------------------- 1 | using Unity.Collections; 2 | using Unity.Mathematics; 3 | using UnityEngine; 4 | using Unity.Collections.LowLevel.Unsafe; 5 | 6 | /* 7 | Utility for memcopying data from native to managed containers and vice versa. 8 | 9 | Todo: Is it possible to generic memcopy implementations, where Source, Dest : struct? 10 | If not, you know what to do: *** code generation *** 11 | 12 | This trick was originally learned from a tweet by @LotteMakesStuff 13 | */ 14 | 15 | public static class NativeCollectionUtilities { 16 | 17 | /* float2 <-> Vector2 */ 18 | 19 | public static unsafe void CopyToManaged(NativeArray source, Vector2[] destination) { 20 | fixed (void* vertexArrayPointer = destination) { 21 | UnsafeUtility.MemCpy( 22 | vertexArrayPointer, 23 | NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(source), 24 | destination.Length * (long)UnsafeUtility.SizeOf()); 25 | } 26 | } 27 | 28 | public static unsafe void CopyToNative(Vector2[] source, NativeArray destination) { 29 | if (source.Length != destination.Length) { 30 | throw new System.ArgumentException("Source length is not equal to destination length"); 31 | } 32 | fixed (void* sourcePointer = source) { 33 | UnsafeUtility.MemCpy( 34 | NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(destination), 35 | sourcePointer, 36 | destination.Length * (long)UnsafeUtility.SizeOf()); 37 | } 38 | } 39 | 40 | public static unsafe void CopyToNative(int[] source, NativeArray destination) { 41 | if (source.Length != destination.Length) { 42 | throw new System.ArgumentException("Source length is not equal to destination length"); 43 | } 44 | fixed (void* sourcePointer = source) { 45 | UnsafeUtility.MemCpy( 46 | NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(destination), 47 | sourcePointer, 48 | destination.Length * (long)UnsafeUtility.SizeOf()); 49 | } 50 | } 51 | 52 | /* float3 <-> Vector3 */ 53 | 54 | public static unsafe void CopyToManaged(NativeArray source, Vector3[] destination) { 55 | fixed (void* vertexArrayPointer = destination) { 56 | UnsafeUtility.MemCpy( 57 | vertexArrayPointer, 58 | NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(source), 59 | destination.Length * (long)UnsafeUtility.SizeOf()); 60 | } 61 | } 62 | 63 | public static unsafe void CopyToNative(Vector3[] source, NativeArray destination) { 64 | if (source.Length != destination.Length) { 65 | throw new System.ArgumentException("Source length is not equal to destination length"); 66 | } 67 | fixed (void* sourcePointer = source) { 68 | UnsafeUtility.MemCpy( 69 | NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(destination), 70 | sourcePointer, 71 | destination.Length * (long)UnsafeUtility.SizeOf()); 72 | } 73 | } 74 | 75 | /* float4 <-> Vector4 */ 76 | 77 | public static unsafe void CopyToManaged(NativeArray source, Vector4[] destination) { 78 | fixed (void* vertexArrayPointer = destination) { 79 | UnsafeUtility.MemCpy( 80 | vertexArrayPointer, 81 | NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(source), 82 | destination.Length * (long)UnsafeUtility.SizeOf()); 83 | } 84 | } 85 | 86 | public static unsafe void CopyToNative(Vector4[] source, NativeArray destination) { 87 | if (source.Length != destination.Length) { 88 | throw new System.ArgumentException("Source length is not equal to destination length"); 89 | } 90 | fixed (void* sourcePointer = source) { 91 | UnsafeUtility.MemCpy( 92 | NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(destination), 93 | sourcePointer, 94 | destination.Length * (long)UnsafeUtility.SizeOf()); 95 | } 96 | } 97 | 98 | /* float4 <-> Color */ 99 | 100 | public static unsafe void CopyToManaged(NativeArray source, Color[] destination) { 101 | fixed (void* vertexArrayPointer = destination) { 102 | UnsafeUtility.MemCpy( 103 | vertexArrayPointer, 104 | NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(source), 105 | destination.Length * (long)UnsafeUtility.SizeOf()); 106 | } 107 | } 108 | 109 | public static unsafe void CopyToNative(Color[] source, NativeArray destination) { 110 | if (source.Length != destination.Length) { 111 | throw new System.ArgumentException("Source length is not equal to destination length"); 112 | } 113 | fixed (void* sourcePointer = source) { 114 | UnsafeUtility.MemCpy( 115 | NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(destination), 116 | sourcePointer, 117 | destination.Length * (long)UnsafeUtility.SizeOf()); 118 | } 119 | } 120 | 121 | /* int <-> int */ 122 | 123 | public static unsafe void CopyToManaged(NativeArray source, int[] destination) { 124 | fixed (void* vertexArrayPointer = destination) { 125 | UnsafeUtility.MemCpy( 126 | vertexArrayPointer, 127 | NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(source), 128 | destination.Length * (long)UnsafeUtility.SizeOf()); 129 | } 130 | } 131 | 132 | /* float3 <-> Texture2D */ 133 | 134 | public static void ToTexture2D(NativeArray screen, Texture2D tex, int2 resolution) { 135 | Color[] colors = new Color[screen.Length]; 136 | 137 | for (int i = 0; i < screen.Length; i++) { 138 | var c = screen[i]; 139 | colors[i] = new Color(c.x, c.y, c.z, 1f); 140 | } 141 | 142 | tex.SetPixels(0, 0, (int)resolution.x, (int)resolution.y, colors, 0); 143 | tex.Apply(); 144 | } 145 | 146 | public static void SetToConstant(NativeSlice values, float value) { 147 | for (int i = 0; i < values.Length; i++) { 148 | values[i] = value; 149 | } 150 | } 151 | 152 | public static void ExportImage(Texture2D texture, string folder) { 153 | var bytes = texture.EncodeToJPG(100); 154 | System.IO.File.WriteAllBytes( 155 | System.IO.Path.Combine(folder, string.Format("render_{0}.png", System.DateTime.Now.ToFileTimeUtc())), 156 | bytes); 157 | } 158 | } -------------------------------------------------------------------------------- /Scripts/Editor/XatlasSource~/xatlas/README.md: -------------------------------------------------------------------------------- 1 | ## xatlas 2 | 3 | [![Actions Status](https://github.com/jpcy/xatlas/workflows/build/badge.svg)](https://github.com/jpcy/xatlas/actions) [![Appveyor CI Build Status](https://ci.appveyor.com/api/projects/status/github/jpcy/xatlas?branch=master&svg=true)](https://ci.appveyor.com/project/jpcy/xatlas) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 4 | 5 | xatlas is a small C++11 library with no external dependencies that generates unique texture coordinates suitable for baking lightmaps or texture painting. 6 | 7 | It is an independent fork of [thekla_atlas](https://github.com/Thekla/thekla_atlas), used by [The Witness](https://en.wikipedia.org/wiki/The_Witness_(2016_video_game)). 8 | 9 | ## Screenshots 10 | 11 | #### Example - [Cesium Milk Truck](https://github.com/KhronosGroup/glTF-Sample-Models) 12 | | Viewer | Random packing | Brute force packing | 13 | |---|---|---| 14 | | [![Viewer](https://user-images.githubusercontent.com/3744372/69908461-48cace80-143e-11ea-8b73-efea5a9f036e.png)](https://user-images.githubusercontent.com/3744372/69908460-48323800-143e-11ea-8b18-58087493c8e9.png) | ![Random packing](https://user-images.githubusercontent.com/3744372/68638607-d4db8b80-054d-11ea-8238-845d94789a2d.gif) | ![Brute force packing](https://user-images.githubusercontent.com/3744372/68638614-da38d600-054d-11ea-82d9-43e558c46d50.gif) | 15 | 16 | #### Example - [Godot Third Person Shooter demo](https://github.com/godotengine/tps-demo) 17 | [![Godot TPS](https://user-images.githubusercontent.com/3744372/69908463-48cace80-143e-11ea-8035-b669d1a455f6.png)](https://user-images.githubusercontent.com/3744372/69908462-48cace80-143e-11ea-8946-a2c596ec8028.png) 18 | 19 | #### [Graphite/Geogram](http://alice.loria.fr/index.php?option=com_content&view=article&id=22) 20 | ![Graphite/Geogram](https://user-images.githubusercontent.com/19478253/69903392-c0deb900-1398-11ea-8a52-c211bc7803a9.gif) 21 | 22 | ## How to use 23 | 24 | ### Building 25 | 26 | Premake is used. For CMake support, see [here](https://github.com/cpp-pm/xatlas). 27 | 28 | Integration into an existing build is simple, only `xatlas.cpp` and `xatlas.h` are required. They can be found in [source/xatlas](https://github.com/jpcy/xatlas/blob/master/source/xatlas) 29 | 30 | #### Windows 31 | 32 | Run `bin\premake.bat`. Open `build\vs2019\xatlas.sln`. 33 | 34 | Note: change the build configuration to "Release". The default - "Debug" - severely degrades performance. 35 | 36 | #### Linux 37 | 38 | Required packages: `libgl1-mesa-dev libgtk-3-dev xorg-dev`. 39 | 40 | Install Premake version 5. Run `premake5 gmake`, `cd build/gmake`, `make`. 41 | 42 | ### Bindings 43 | 44 | [Python](https://github.com/mworchel/xatlas-python) 45 | 46 | ### Generate an atlas (simple API) 47 | 48 | 1. Create an empty atlas with `xatlas::Create`. 49 | 2. Add one or more meshes with `xatlas::AddMesh`. 50 | 3. Call `xatlas::Generate`. Meshes are segmented into charts, which are parameterized and packed into an atlas. 51 | 52 | The `xatlas::Atlas` instance created in the first step now contains the result: each input mesh added by `xatlas::AddMesh` has a corresponding new mesh with a UV channel. New meshes have more vertices (the UV channel adds seams), but the same number of indices. 53 | 54 | Cleanup with `xatlas::Destroy`. 55 | 56 | [Example code here.](https://github.com/jpcy/xatlas/blob/master/source/examples/example.cpp) 57 | 58 | ### Generate an atlas (tools/editor integration API) 59 | 60 | Instead of calling `xatlas::Generate`, the following functions can be called in sequence: 61 | 62 | 1. `xatlas::ComputeCharts`: meshes are segmented into charts and parameterized. 63 | 2. `xatlas::PackCharts`: charts are packed into one or more atlases. 64 | 65 | All of these functions take a progress callback. Return false to cancel. 66 | 67 | You can call any of these functions multiple times, followed by the proceeding functions, to re-generate the atlas. E.g. calling `xatlas::PackCharts` multiple times to tweak options like unit to texel scale and resolution. 68 | 69 | See the [viewer](https://github.com/jpcy/xatlas/tree/master/source/examples/viewer) for example code. 70 | 71 | ### Pack multiple atlases into a single atlas 72 | 73 | 1. Create an empty atlas with `xatlas::Create`. 74 | 2. Add one or more meshes with `xatlas::AddUvMesh`. 75 | 3. Call `xatlas::PackCharts`. 76 | 77 | [Example code here.](https://github.com/jpcy/xatlas/blob/master/source/examples/example_uvmesh.cpp) 78 | 79 | ## Technical information / related publications 80 | 81 | [Ignacio Castaño's blog post on thekla_atlas](http://www.ludicon.com/castano/blog/articles/lightmap-parameterization/) 82 | 83 | P. Sander, J. Snyder, S. Gortler, and H. Hoppe. [Texture Mapping Progressive Meshes](http://hhoppe.com/proj/tmpm/) 84 | 85 | K. Hormann, B. Lévy, and A. Sheffer. [Mesh Parameterization: Theory and Practice](http://alice.loria.fr/publications/papers/2007/SigCourseParam/param-course.pdf) 86 | 87 | P. Sander, Z. Wood, S. Gortler, J. Snyder, and H. Hoppe. [Multi-Chart Geometry Images](http://hhoppe.com/proj/mcgim/) 88 | 89 | D. Julius, V. Kraevoy, and A. Sheffer. [D-Charts: Quasi-Developable Mesh Segmentation](https://www.cs.ubc.ca/~vlady/dcharts/EG05.pdf) 90 | 91 | B. Lévy, S. Petitjean, N. Ray, and J. Maillot. [Least Squares Conformal Maps for Automatic Texture Atlas Generation](https://members.loria.fr/Bruno.Levy/papers/LSCM_SIGGRAPH_2002.pdf) 92 | 93 | O. Sorkine, D. Cohen-Or, R. Goldenthal, and D. Lischinski. [Bounded-distortion Piecewise Mesh Parameterization](https://igl.ethz.ch/projects/parameterization/BDPMP/index.php) 94 | 95 | Y. O’Donnell. [Precomputed Global Illumination in Frostbite](https://media.contentapi.ea.com/content/dam/eacom/frostbite/files/gdc2018-precomputedgiobalilluminationinfrostbite.pdf) 96 | 97 | ## Used by 98 | 99 | [ArmorPaint](https://armorpaint.org/index.html) 100 | 101 | [Bakery - GPU Lightmapper](https://assetstore.unity.com/packages/tools/level-design/bakery-gpu-lightmapper-122218) 102 | 103 | [DXR Ambient Occlusion Baking](https://github.com/Twinklebear/dxr-ao-bake) - A demo of ambient occlusion map baking using DXR inline ray tracing. 104 | 105 | [Filament](https://google.github.io/filament/) 106 | 107 | [Godot Engine](https://github.com/godotengine/godot) 108 | 109 | [Graphite/Geogram](http://alice.loria.fr/index.php?option=com_content&view=article&id=22) 110 | 111 | [Lightmaps - An OpenGL sample demonstrating path traced lightmap baking on the CPU with Embree](https://github.com/diharaw/Lightmaps) 112 | 113 | [redner](https://github.com/BachiLi/redner) 114 | 115 | [Skylicht Engine](https://github.com/skylicht-lab/skylicht-engine) 116 | 117 | [toy](https://github.com/hugoam/toy) / [two](https://github.com/hugoam/two) 118 | 119 | [UNIGINE](https://unigine.com/) - [video](https://www.youtube.com/watch?v=S0gR9T1tWPg) 120 | 121 | [Wicked Engine](https://github.com/turanszkij/WickedEngine) 122 | 123 | ## Related projects 124 | 125 | [aobaker](https://github.com/prideout/aobaker) - Ambient occlusion baking. Uses [thekla_atlas](https://github.com/Thekla/thekla_atlas). 126 | 127 | [Lightmapper](https://github.com/ands/lightmapper) - Hemicube based lightmap baking. The example model texture coordinates were generated by [thekla_atlas](https://github.com/Thekla/thekla_atlas). 128 | 129 | [Microsoft's UVAtlas](https://github.com/Microsoft/UVAtlas) - isochart texture atlasing. 130 | 131 | [Ministry of Flat](http://www.quelsolaar.com/ministry_of_flat/) - Commercial automated UV unwrapper. 132 | 133 | [seamoptimizer](https://github.com/ands/seamoptimizer) - A C/C++ single-file library that minimizes the hard transition errors of disjoint edges in lightmaps. 134 | 135 | [simpleuv](https://github.com/huxingyi/simpleuv/) - Automatic UV Unwrapping Library for Dust3D. 136 | 137 | ## Models used 138 | 139 | [Gazebo model](https://opengameart.org/content/gazebo-0) by Teh_Bucket 140 | -------------------------------------------------------------------------------- /Scripts/Editor/XatlasSource~/xatlas/source/xatlas/xatlas.h: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2018-2020 Jonathan Young 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | /* 25 | thekla_atlas 26 | MIT License 27 | https://github.com/Thekla/thekla_atlas 28 | Copyright (c) 2013 Thekla, Inc 29 | Copyright NVIDIA Corporation 2006 -- Ignacio Castano 30 | */ 31 | #pragma once 32 | #ifndef XATLAS_H 33 | #define XATLAS_H 34 | #include 35 | #include 36 | 37 | namespace xatlas { 38 | 39 | enum class ChartType 40 | { 41 | Planar, 42 | Ortho, 43 | LSCM, 44 | Piecewise, 45 | Invalid 46 | }; 47 | 48 | // A group of connected faces, belonging to a single atlas. 49 | struct Chart 50 | { 51 | uint32_t *faceArray; 52 | uint32_t atlasIndex; // Sub-atlas index. 53 | uint32_t faceCount; 54 | ChartType type; 55 | uint32_t material; 56 | }; 57 | 58 | // Output vertex. 59 | struct Vertex 60 | { 61 | int32_t atlasIndex; // Sub-atlas index. -1 if the vertex doesn't exist in any atlas. 62 | int32_t chartIndex; // -1 if the vertex doesn't exist in any chart. 63 | float uv[2]; // Not normalized - values are in Atlas width and height range. 64 | uint32_t xref; // Index of input vertex from which this output vertex originated. 65 | }; 66 | 67 | // Output mesh. 68 | struct Mesh 69 | { 70 | Chart *chartArray; 71 | uint32_t *indexArray; 72 | Vertex *vertexArray; 73 | uint32_t chartCount; 74 | uint32_t indexCount; 75 | uint32_t vertexCount; 76 | }; 77 | 78 | static const uint32_t kImageChartIndexMask = 0x1FFFFFFF; 79 | static const uint32_t kImageHasChartIndexBit = 0x80000000; 80 | static const uint32_t kImageIsBilinearBit = 0x40000000; 81 | static const uint32_t kImageIsPaddingBit = 0x20000000; 82 | 83 | // Empty on creation. Populated after charts are packed. 84 | struct Atlas 85 | { 86 | uint32_t *image; 87 | Mesh *meshes; // The output meshes, corresponding to each AddMesh call. 88 | float *utilization; // Normalized atlas texel utilization array. E.g. a value of 0.8 means 20% empty space. atlasCount in length. 89 | uint32_t width; // Atlas width in texels. 90 | uint32_t height; // Atlas height in texels. 91 | uint32_t atlasCount; // Number of sub-atlases. Equal to 0 unless PackOptions resolution is changed from default (0). 92 | uint32_t chartCount; // Total number of charts in all meshes. 93 | uint32_t meshCount; // Number of output meshes. Equal to the number of times AddMesh was called. 94 | float texelsPerUnit; // Equal to PackOptions texelsPerUnit if texelsPerUnit > 0, otherwise an estimated value to match PackOptions resolution. 95 | }; 96 | 97 | // Create an empty atlas. 98 | Atlas *Create(); 99 | 100 | void Destroy(Atlas *atlas); 101 | 102 | enum class IndexFormat 103 | { 104 | UInt16, 105 | UInt32 106 | }; 107 | 108 | // Input mesh declaration. 109 | struct MeshDecl 110 | { 111 | const void *vertexPositionData = nullptr; 112 | const void *vertexNormalData = nullptr; // optional 113 | const void *vertexUvData = nullptr; // optional. The input UVs are provided as a hint to the chart generator. 114 | const void *indexData = nullptr; // optional 115 | 116 | // Optional. Must be faceCount in length. 117 | // Don't atlas faces set to true. Ignored faces still exist in the output meshes, Vertex uv is set to (0, 0) and Vertex atlasIndex to -1. 118 | const bool *faceIgnoreData = nullptr; 119 | 120 | // Optional. Must be faceCount in length. 121 | // Only faces with the same material will be assigned to the same chart. 122 | const uint32_t *faceMaterialData = nullptr; 123 | 124 | // Optional. Must be faceCount in length. 125 | // Polygon / n-gon support. Faces are assumed to be triangles if this is null. 126 | const uint8_t *faceVertexCount = nullptr; 127 | 128 | uint32_t vertexCount = 0; 129 | uint32_t vertexPositionStride = 0; 130 | uint32_t vertexNormalStride = 0; // optional 131 | uint32_t vertexUvStride = 0; // optional 132 | uint32_t indexCount = 0; 133 | int32_t indexOffset = 0; // optional. Add this offset to all indices. 134 | uint32_t faceCount = 0; // Optional if faceVertexCount is null. Otherwise assumed to be indexCount / 3. 135 | IndexFormat indexFormat = IndexFormat::UInt16; 136 | 137 | // Vertex positions within epsilon distance of each other are considered colocal. 138 | float epsilon = 1.192092896e-07F; 139 | }; 140 | 141 | enum class AddMeshError 142 | { 143 | Success, // No error. 144 | Error, // Unspecified error. 145 | IndexOutOfRange, // An index is >= MeshDecl vertexCount. 146 | InvalidFaceVertexCount, // Must be >= 3. 147 | InvalidIndexCount // Not evenly divisible by 3 - expecting triangles. 148 | }; 149 | 150 | // Add a mesh to the atlas. MeshDecl data is copied, so it can be freed after AddMesh returns. 151 | AddMeshError AddMesh(Atlas *atlas, const MeshDecl &meshDecl, uint32_t meshCountHint = 0); 152 | 153 | // Wait for AddMesh async processing to finish. ComputeCharts / Generate call this internally. 154 | void AddMeshJoin(Atlas *atlas); 155 | 156 | struct UvMeshDecl 157 | { 158 | const void *vertexUvData = nullptr; 159 | const void *indexData = nullptr; // optional 160 | const uint32_t *faceMaterialData = nullptr; // Optional. Overlapping UVs should be assigned a different material. Must be indexCount / 3 in length. 161 | uint32_t vertexCount = 0; 162 | uint32_t vertexStride = 0; 163 | uint32_t indexCount = 0; 164 | int32_t indexOffset = 0; // optional. Add this offset to all indices. 165 | IndexFormat indexFormat = IndexFormat::UInt16; 166 | }; 167 | 168 | AddMeshError AddUvMesh(Atlas *atlas, const UvMeshDecl &decl); 169 | 170 | // Custom parameterization function. texcoords initial values are an orthogonal parameterization. 171 | typedef void (*ParameterizeFunc)(const float *positions, float *texcoords, uint32_t vertexCount, const uint32_t *indices, uint32_t indexCount); 172 | 173 | struct ChartOptions 174 | { 175 | ParameterizeFunc paramFunc = nullptr; 176 | 177 | float maxChartArea = 0.0f; // Don't grow charts to be larger than this. 0 means no limit. 178 | float maxBoundaryLength = 0.0f; // Don't grow charts to have a longer boundary than this. 0 means no limit. 179 | 180 | // Weights determine chart growth. Higher weights mean higher cost for that metric. 181 | float normalDeviationWeight = 2.0f; // Angle between face and average chart normal. 182 | float roundnessWeight = 0.01f; 183 | float straightnessWeight = 6.0f; 184 | float normalSeamWeight = 4.0f; // If > 1000, normal seams are fully respected. 185 | float textureSeamWeight = 0.5f; 186 | 187 | float maxCost = 2.0f; // If total of all metrics * weights > maxCost, don't grow chart. Lower values result in more charts. 188 | uint32_t maxIterations = 1; // Number of iterations of the chart growing and seeding phases. Higher values result in better charts. 189 | 190 | bool useInputMeshUvs = false; // Use MeshDecl::vertexUvData for charts. 191 | bool fixWinding = false; // Enforce consistent texture coordinate winding. 192 | }; 193 | 194 | // Call after all AddMesh calls. Can be called multiple times to recompute charts with different options. 195 | void ComputeCharts(Atlas *atlas, ChartOptions options = ChartOptions()); 196 | 197 | struct PackOptions 198 | { 199 | // Charts larger than this will be scaled down. 0 means no limit. 200 | uint32_t maxChartSize = 0; 201 | 202 | // Number of pixels to pad charts with. 203 | uint32_t padding = 0; 204 | 205 | // Unit to texel scale. e.g. a 1x1 quad with texelsPerUnit of 32 will take up approximately 32x32 texels in the atlas. 206 | // If 0, an estimated value will be calculated to approximately match the given resolution. 207 | // If resolution is also 0, the estimated value will approximately match a 1024x1024 atlas. 208 | float texelsPerUnit = 0.0f; 209 | 210 | // If 0, generate a single atlas with texelsPerUnit determining the final resolution. 211 | // If not 0, and texelsPerUnit is not 0, generate one or more atlases with that exact resolution. 212 | // If not 0, and texelsPerUnit is 0, texelsPerUnit is estimated to approximately match the resolution. 213 | uint32_t resolution = 0; 214 | 215 | // Leave space around charts for texels that would be sampled by bilinear filtering. 216 | bool bilinear = true; 217 | 218 | // Align charts to 4x4 blocks. Also improves packing speed, since there are fewer possible chart locations to consider. 219 | bool blockAlign = false; 220 | 221 | // Slower, but gives the best result. If false, use random chart placement. 222 | bool bruteForce = false; 223 | 224 | // Create Atlas::image 225 | bool createImage = false; 226 | 227 | // Rotate charts to the axis of their convex hull. 228 | bool rotateChartsToAxis = true; 229 | 230 | // Rotate charts to improve packing. 231 | bool rotateCharts = true; 232 | }; 233 | 234 | // Call after ComputeCharts. Can be called multiple times to re-pack charts with different options. 235 | void PackCharts(Atlas *atlas, PackOptions packOptions = PackOptions()); 236 | 237 | // Equivalent to calling ComputeCharts and PackCharts in sequence. Can be called multiple times to regenerate with different options. 238 | void Generate(Atlas *atlas, ChartOptions chartOptions = ChartOptions(), PackOptions packOptions = PackOptions()); 239 | 240 | // Progress tracking. 241 | enum class ProgressCategory 242 | { 243 | AddMesh, 244 | ComputeCharts, 245 | PackCharts, 246 | BuildOutputMeshes 247 | }; 248 | 249 | // May be called from any thread. Return false to cancel. 250 | typedef bool (*ProgressFunc)(ProgressCategory category, int progress, void *userData); 251 | 252 | void SetProgressCallback(Atlas *atlas, ProgressFunc progressFunc = nullptr, void *progressUserData = nullptr); 253 | 254 | // Custom memory allocation. 255 | typedef void *(*ReallocFunc)(void *, size_t); 256 | typedef void (*FreeFunc)(void *); 257 | void SetAlloc(ReallocFunc reallocFunc, FreeFunc freeFunc = nullptr); 258 | 259 | // Custom print function. 260 | typedef int (*PrintFunc)(const char *, ...); 261 | void SetPrint(PrintFunc print, bool verbose); 262 | 263 | // Helper functions for error messages. 264 | const char *StringForEnum(AddMeshError error); 265 | const char *StringForEnum(ProgressCategory category); 266 | 267 | } // namespace xatlas 268 | 269 | #endif // XATLAS_H 270 | -------------------------------------------------------------------------------- /Scripts/Editor/XatlasSource~/xatlas/premake5.lua: -------------------------------------------------------------------------------- 1 | newoption 2 | { 3 | trigger = "asan", 4 | description = "Enable Clang AddressSanitizer" 5 | } 6 | 7 | newoption 8 | { 9 | trigger = "msan", 10 | description = "Enable Clang MemorySanitizer" 11 | } 12 | 13 | newoption 14 | { 15 | trigger = "ubsan", 16 | description = "Enable Clang UndefinedBehaviorSanitizer" 17 | } 18 | 19 | dofile("source/viewer/shaders.lua") 20 | 21 | if _ACTION == nil then 22 | return 23 | end 24 | 25 | local asanEnabled = false 26 | local msanEnabled = false 27 | local ubsanEnabled = false 28 | if _ACTION == "gmake" and _OPTIONS["cc"] == "clang" then 29 | if _OPTIONS["asan"] then 30 | asanEnabled = true 31 | sanitizerEnabled = true 32 | end 33 | if _OPTIONS["msan"] then 34 | msanEnabled = true 35 | sanitizerEnabled = true 36 | end 37 | if _OPTIONS["ubsan"] then 38 | ubsanEnabled = true 39 | sanitizerEnabled = true 40 | end 41 | end 42 | local sanitizerEnabled = asanEnabled or msanEnabled or ubsanEnabled 43 | 44 | function sanitizer() 45 | if asanEnabled then 46 | buildoptions { "-fsanitize=address" } 47 | linkoptions { "-fsanitize=address" } 48 | end 49 | if msanEnabled then 50 | buildoptions { "-fsanitize=memory" } 51 | linkoptions { "-fsanitize=memory" } 52 | end 53 | if ubsanEnabled then 54 | buildoptions { "-fsanitize=undefined" } 55 | linkoptions { "-fsanitize=undefined" } 56 | end 57 | if sanitizerEnabled then 58 | buildoptions { "-fno-omit-frame-pointer" } 59 | end 60 | end 61 | 62 | solution "xatlas" 63 | configurations { "Release", "Debug" } 64 | if _OPTIONS["cc"] ~= nil then 65 | location(path.join("build", _ACTION) .. "_" .. _OPTIONS["cc"]) 66 | else 67 | location(path.join("build", _ACTION)) 68 | end 69 | platforms { "x86_64", "x86" } 70 | startproject "viewer" 71 | filter "platforms:x86" 72 | architecture "x86" 73 | filter "platforms:x86_64" 74 | architecture "x86_64" 75 | filter "configurations:Debug*" 76 | defines { "_DEBUG" } 77 | optimize "Debug" 78 | symbols "On" 79 | filter "configurations:Release" 80 | defines "NDEBUG" 81 | optimize "Full" 82 | filter {} 83 | if sanitizerEnabled then 84 | optimize "Off" 85 | symbols "On" 86 | end 87 | sanitizer() 88 | 89 | local XATLAS_DIR = "source/xatlas" 90 | 91 | project "xatlas_static" 92 | kind "StaticLib" 93 | language "C++" 94 | cppdialect "C++11" 95 | exceptionhandling "Off" 96 | rtti "Off" 97 | warnings "Extra" 98 | enablewarnings { "shadow" } 99 | files { path.join(XATLAS_DIR, "xatlas.*") } 100 | sanitizer() 101 | 102 | project "xatlas" 103 | kind "SharedLib" 104 | language "C++" 105 | cppdialect "C++11" 106 | exceptionhandling "Off" 107 | rtti "Off" 108 | warnings "Extra" 109 | enablewarnings { "shadow" } 110 | defines { "XATLAS_C_API=1", "XATLAS_EXPORT_API=1" } 111 | files { path.join(XATLAS_DIR, "*.*") } 112 | sanitizer() 113 | 114 | local THIRDPARTY_DIR = "source/thirdparty" 115 | local BGFX_DIR = path.join(THIRDPARTY_DIR, "bgfx") 116 | local BIMG_DIR = path.join(THIRDPARTY_DIR, "bimg") 117 | local BX_DIR = path.join(THIRDPARTY_DIR, "bx") 118 | local EIGEN_DIR = path.join(THIRDPARTY_DIR, "eigen") 119 | local EMBREE_DIR = path.join(THIRDPARTY_DIR, "embree3") 120 | local ENKITS_DIR = path.join(THIRDPARTY_DIR, "enkiTS") 121 | local GLFW_DIR = path.join(THIRDPARTY_DIR, "glfw") 122 | local IGL_DIR = path.join(THIRDPARTY_DIR, "libigl") 123 | local MIMALLOC_DIR = path.join(THIRDPARTY_DIR, "mimalloc") 124 | local NATIVEFILEDIALOG_DIR = path.join(THIRDPARTY_DIR, "nativefiledialog") 125 | local OIDN_DIR = path.join(THIRDPARTY_DIR, "oidn") 126 | local OPENFBX_DIR = path.join(THIRDPARTY_DIR, "OpenFBX") 127 | 128 | project "test" 129 | kind "ConsoleApp" 130 | language "C++" 131 | cppdialect "C++11" 132 | exceptionhandling "Off" 133 | rtti "Off" 134 | warnings "Extra" 135 | sanitizer() 136 | includedirs { XATLAS_DIR, THIRDPARTY_DIR } 137 | files "source/test/test.cpp" 138 | links { "tiny_obj_loader", "xatlas_static" } 139 | filter "action:vs*" 140 | files "source/xatlas.natvis" 141 | filter "system:linux" 142 | links { "pthread" } 143 | 144 | project "viewer" 145 | kind "ConsoleApp" 146 | language "C++" 147 | cppdialect "C++14" 148 | exceptionhandling "Off" 149 | rtti "Off" 150 | warnings "Extra" 151 | sanitizer() 152 | files { "source/viewer/*.*", "source/viewer/shaders/*.*" } 153 | includedirs 154 | { 155 | XATLAS_DIR, 156 | THIRDPARTY_DIR, 157 | path.join(BGFX_DIR, "include"), 158 | path.join(BX_DIR, "include"), 159 | EIGEN_DIR, 160 | EMBREE_DIR, 161 | ENKITS_DIR, 162 | path.join(GLFW_DIR, "include"), 163 | path.join(IGL_DIR, "include"), 164 | path.join(MIMALLOC_DIR, "include"), 165 | path.join(NATIVEFILEDIALOG_DIR, "include"), 166 | path.join(OIDN_DIR, "include"), 167 | OPENFBX_DIR 168 | } 169 | links { "bgfx", "bimg", "bx", "cgltf", "enkiTS", "glfw", "imgui", "mimalloc", "nativefiledialog", "objzero", "OpenFBX", "stb_image", "stb_image_resize", "xatlas_static" } 170 | filter "system:windows" 171 | links { "bcrypt", "gdi32", "ole32", "psapi", "uuid"} 172 | filter "system:linux" 173 | links { "dl", "GL", "gtk-3", "gobject-2.0", "glib-2.0", "pthread", "X11", "Xcursor", "Xinerama", "Xrandr" } 174 | filter "action:vs*" 175 | files "source/xatlas.natvis" 176 | includedirs { path.join(BX_DIR, "include/compat/msvc") } 177 | filter { "system:windows", "action:gmake" } 178 | includedirs { path.join(BX_DIR, "include/compat/mingw") } 179 | 180 | group "examples" 181 | local EXAMPLES_DIR = "source/examples" 182 | 183 | project "example" 184 | kind "ConsoleApp" 185 | language "C++" 186 | cppdialect "C++11" 187 | exceptionhandling "Off" 188 | rtti "Off" 189 | warnings "Extra" 190 | sanitizer() 191 | files { path.join(EXAMPLES_DIR, "example.cpp") } 192 | includedirs { XATLAS_DIR, THIRDPARTY_DIR } 193 | links { "stb_image_write", "tiny_obj_loader", "xatlas_static" } 194 | filter "action:vs*" 195 | files "source/xatlas.natvis" 196 | filter "system:linux" 197 | links { "pthread" } 198 | 199 | project "example_c99" 200 | kind "ConsoleApp" 201 | language "C" 202 | cdialect "C99" 203 | warnings "Extra" 204 | sanitizer() 205 | files { path.join(EXAMPLES_DIR, "example_c99.c") } 206 | includedirs { XATLAS_DIR, THIRDPARTY_DIR } 207 | links { "objzero", "xatlas" } 208 | filter "system:linux" 209 | links { "m", "pthread" } 210 | 211 | project "example_uvmesh" 212 | kind "ConsoleApp" 213 | language "C++" 214 | cppdialect "C++11" 215 | exceptionhandling "Off" 216 | rtti "Off" 217 | warnings "Extra" 218 | sanitizer() 219 | files { path.join(EXAMPLES_DIR, "example_uvmesh.cpp") } 220 | includedirs { XATLAS_DIR, THIRDPARTY_DIR } 221 | links { "stb_image_write", "tiny_obj_loader", "xatlas_static" } 222 | filter "action:vs*" 223 | files "source/xatlas.natvis" 224 | filter "system:linux" 225 | links { "pthread" } 226 | 227 | group "thirdparty" 228 | 229 | project "bgfx" 230 | kind "StaticLib" 231 | language "C++" 232 | cppdialect "C++14" 233 | exceptionhandling "Off" 234 | rtti "Off" 235 | sanitizer() 236 | defines { "__STDC_FORMAT_MACROS" } 237 | files 238 | { 239 | path.join(BGFX_DIR, "include/bgfx/**.h"), 240 | path.join(BGFX_DIR, "src/*.cpp"), 241 | path.join(BGFX_DIR, "src/*.h") 242 | } 243 | excludes 244 | { 245 | path.join(BGFX_DIR, "src/amalgamated.cpp") 246 | } 247 | includedirs 248 | { 249 | path.join(BX_DIR, "include"), 250 | path.join(BIMG_DIR, "include"), 251 | path.join(BIMG_DIR, "3rdparty"), 252 | path.join(BIMG_DIR, "3rdparty/astc-codec/include"), 253 | path.join(BIMG_DIR, "3rdparty/iqa/include"), 254 | path.join(BGFX_DIR, "include"), 255 | path.join(BGFX_DIR, "3rdparty"), 256 | path.join(BGFX_DIR, "3rdparty/dxsdk/include"), 257 | path.join(BGFX_DIR, "3rdparty/khronos") 258 | } 259 | filter "configurations:Debug" 260 | defines "BGFX_CONFIG_DEBUG=1" 261 | filter "action:vs*" 262 | defines { "_CRT_SECURE_NO_WARNINGS" } 263 | includedirs { path.join(BX_DIR, "include/compat/msvc") } 264 | excludes 265 | { 266 | path.join(BGFX_DIR, "src/glcontext_glx.cpp"), 267 | path.join(BGFX_DIR, "src/glcontext_egl.cpp") 268 | } 269 | filter { "system:windows", "action:gmake" } 270 | includedirs { path.join(BX_DIR, "include/compat/mingw") } 271 | 272 | project "bimg" 273 | kind "StaticLib" 274 | language "C++" 275 | cppdialect "C++14" 276 | exceptionhandling "Off" 277 | rtti "Off" 278 | sanitizer() 279 | defines 280 | { 281 | "BIMG_DECODE_ENABLE=0" 282 | } 283 | files 284 | { 285 | path.join(BIMG_DIR, "include/bimg/*.h"), 286 | path.join(BIMG_DIR, "src/*.cpp"), 287 | path.join(BIMG_DIR, "src/*.h") 288 | } 289 | includedirs 290 | { 291 | path.join(BX_DIR, "include"), 292 | path.join(BIMG_DIR, "include") 293 | } 294 | filter "action:vs*" 295 | defines { "_CRT_SECURE_NO_WARNINGS" } 296 | includedirs { path.join(BX_DIR, "include/compat/msvc") } 297 | filter { "system:windows", "action:gmake" } 298 | includedirs { path.join(BX_DIR, "include/compat/mingw") } 299 | 300 | project "bx" 301 | kind "StaticLib" 302 | language "C++" 303 | cppdialect "C++14" 304 | exceptionhandling "Off" 305 | rtti "Off" 306 | sanitizer() 307 | defines { "__STDC_FORMAT_MACROS" } 308 | files 309 | { 310 | path.join(BX_DIR, "include/bx/*.h"), 311 | path.join(BX_DIR, "include/bx/inline/*.inl"), 312 | path.join(BX_DIR, "include/tinystl/*.h"), 313 | path.join(BX_DIR, "src/*.cpp") 314 | } 315 | excludes 316 | { 317 | path.join(BX_DIR, "src/amalgamated.cpp"), 318 | path.join(BX_DIR, "src/crtnone.cpp") 319 | } 320 | includedirs 321 | { 322 | path.join(BX_DIR, "3rdparty"), 323 | path.join(BX_DIR, "include") 324 | } 325 | filter "action:vs*" 326 | defines { "_CRT_SECURE_NO_WARNINGS" } 327 | includedirs { path.join(BX_DIR, "include/compat/msvc") } 328 | filter { "system:windows", "action:gmake" } 329 | includedirs { path.join(BX_DIR, "include/compat/mingw") } 330 | 331 | project "cgltf" 332 | kind "StaticLib" 333 | language "C" 334 | sanitizer() 335 | files(path.join(THIRDPARTY_DIR, "cgltf.*")) 336 | filter "action:vs*" 337 | defines { "_CRT_SECURE_NO_WARNINGS" } 338 | 339 | project "enkiTS" 340 | kind "StaticLib" 341 | language "C++" 342 | cppdialect "C++11" 343 | exceptionhandling "Off" 344 | rtti "Off" 345 | sanitizer() 346 | files(path.join(ENKITS_DIR, "*.*")) 347 | 348 | project "glfw" 349 | kind "StaticLib" 350 | language "C" 351 | sanitizer() 352 | files 353 | { 354 | path.join(GLFW_DIR, "include/*.h"), 355 | path.join(GLFW_DIR, "src/context.c"), 356 | path.join(GLFW_DIR, "src/egl_context.c"), 357 | path.join(GLFW_DIR, "src/init.c"), 358 | path.join(GLFW_DIR, "src/input.c"), 359 | path.join(GLFW_DIR, "src/monitor.c"), 360 | path.join(GLFW_DIR, "src/osmesa_context.c"), 361 | path.join(GLFW_DIR, "src/vulkan.c"), 362 | path.join(GLFW_DIR, "src/window.c"), 363 | } 364 | includedirs { path.join(GLFW_DIR, "include") } 365 | filter "system:windows" 366 | defines "_GLFW_WIN32" 367 | files 368 | { 369 | path.join(GLFW_DIR, "src/win32_init.c"), 370 | path.join(GLFW_DIR, "src/win32_joystick.c"), 371 | path.join(GLFW_DIR, "src/win32_monitor.c"), 372 | path.join(GLFW_DIR, "src/win32_thread.c"), 373 | path.join(GLFW_DIR, "src/win32_time.c"), 374 | path.join(GLFW_DIR, "src/win32_window.c"), 375 | path.join(GLFW_DIR, "src/wgl_context.c") 376 | } 377 | filter "system:linux" 378 | defines "_GLFW_X11" 379 | files 380 | { 381 | path.join(GLFW_DIR, "src/glx_context.c"), 382 | path.join(GLFW_DIR, "src/linux*.c"), 383 | path.join(GLFW_DIR, "src/posix*.c"), 384 | path.join(GLFW_DIR, "src/x11*.c"), 385 | path.join(GLFW_DIR, "src/xkb*.c") 386 | } 387 | filter "action:vs*" 388 | defines { "_CRT_SECURE_NO_WARNINGS" } 389 | filter {} 390 | 391 | project "imgui" 392 | kind "StaticLib" 393 | language "C++" 394 | exceptionhandling "Off" 395 | rtti "Off" 396 | sanitizer() 397 | files(path.join(THIRDPARTY_DIR, "imgui/*.*")) 398 | 399 | project "mimalloc" 400 | kind "StaticLib" 401 | language "C" 402 | sanitizer() 403 | includedirs(path.join(MIMALLOC_DIR, "include")) 404 | files(path.join(MIMALLOC_DIR, "src/*.*")) 405 | excludes 406 | { 407 | path.join(MIMALLOC_DIR, "src/alloc-override*"), 408 | path.join(MIMALLOC_DIR, "src/page-queue.c"), 409 | path.join(MIMALLOC_DIR, "src/static.c") 410 | } 411 | 412 | project "nativefiledialog" 413 | kind "StaticLib" 414 | language "C++" 415 | exceptionhandling "Off" 416 | rtti "Off" 417 | sanitizer() 418 | includedirs(path.join(NATIVEFILEDIALOG_DIR, "include")) 419 | files(path.join(NATIVEFILEDIALOG_DIR, "nfd_common.*")) 420 | filter "system:windows" 421 | files(path.join(NATIVEFILEDIALOG_DIR, "nfd_win.cpp")) 422 | filter "system:linux" 423 | files(path.join(NATIVEFILEDIALOG_DIR, "nfd_gtk.c")) 424 | buildoptions(os.outputof("pkg-config --cflags gtk+-3.0")) 425 | filter "action:vs*" 426 | defines { "_CRT_SECURE_NO_WARNINGS" } 427 | 428 | project "objzero" 429 | kind "StaticLib" 430 | language "C" 431 | cdialect "C99" 432 | sanitizer() 433 | files(path.join(THIRDPARTY_DIR, "objzero/objzero.*")) 434 | 435 | project "OpenFBX" 436 | kind "StaticLib" 437 | language "C++" 438 | cppdialect "C++14" 439 | exceptionhandling "Off" 440 | rtti "Off" 441 | sanitizer() 442 | files(path.join(OPENFBX_DIR, "*.*")) 443 | 444 | project "stb_image" 445 | kind "StaticLib" 446 | language "C" 447 | sanitizer() 448 | files(path.join(THIRDPARTY_DIR, "stb_image.*")) 449 | 450 | project "stb_image_resize" 451 | kind "StaticLib" 452 | language "C" 453 | sanitizer() 454 | files(path.join(THIRDPARTY_DIR, "stb_image_resize.*")) 455 | 456 | project "stb_image_write" 457 | kind "StaticLib" 458 | language "C" 459 | sanitizer() 460 | files(path.join(THIRDPARTY_DIR, "stb_image_write.*")) 461 | 462 | project "tiny_obj_loader" 463 | kind "StaticLib" 464 | language "C++" 465 | exceptionhandling "Off" 466 | rtti "Off" 467 | sanitizer() 468 | files(path.join(THIRDPARTY_DIR, "tiny_obj_loader.*")) 469 | 470 | -------------------------------------------------------------------------------- /Scripts/XatlasLightmapPacker.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using Unity.Burst; 8 | using Unity.Collections; 9 | using Unity.Jobs; 10 | using Unity.Mathematics; 11 | using UnityEditor; 12 | using UnityEditor.Build; 13 | using UnityEditor.Build.Reporting; 14 | using UnityEngine; 15 | using UnityEngine.Rendering; 16 | using UnityEngine.SceneManagement; 17 | 18 | namespace z3y 19 | { 20 | // since static batching already creates a copy of the same mesh for every instance 21 | // there is no reason to use lightmap tiling and offset when we can just set different uv2 for each mesh renderer and pack them very efficiently 22 | // this gets merged by static batching creating no additional cost 23 | [ExecuteInEditMode] 24 | public class XatlasLightmapPacker : MonoBehaviour 25 | { 26 | //public GameObject[] rootObjects; // the renderers here would be on the same lightmap group with no uv adjustments (original uv) 27 | public BakeryLightmapGroup lightmapGroup; 28 | public bool autoUpdateUVs = false; 29 | public bool bruteForce = false; 30 | 31 | public bool ignoreScaleInLightmap = false; 32 | 33 | //public int lightmapSize = 1024; 34 | public int LightmapSize() => lightmapGroup == null ? 1024 : lightmapGroup.resolution; 35 | public int padding = 2; 36 | 37 | [Serializable] 38 | public struct LightmapMeshData 39 | { 40 | public LightmapMeshData(Vector2[] uv) 41 | { 42 | lightmapUV = uv; 43 | } 44 | public Vector2[] lightmapUV; 45 | } 46 | 47 | public GameObject[] GetRootGameObjectsFromGroup() 48 | { 49 | var objects = new List(); 50 | var selectors = FindObjectsOfType(); 51 | 52 | 53 | foreach (var selector in selectors ) 54 | { 55 | if (selector.lmgroupAsset != lightmapGroup) 56 | { 57 | continue; 58 | } 59 | 60 | objects.Add(selector.gameObject); 61 | } 62 | 63 | if (selectors.Length == 0) 64 | { 65 | objects.Add(gameObject); 66 | } 67 | 68 | 69 | return objects.ToArray(); 70 | } 71 | 72 | // This should probably be done somewhere else, but im not sure exactly when does unity clear the additionalVertexStreams 73 | // it seems to happen on scene load, entering play mode and reload 74 | 75 | private void OnValidate() 76 | { 77 | if (Application.isPlaying) 78 | { 79 | return; 80 | } 81 | 82 | if (!autoUpdateUVs) 83 | { 84 | ClearVertexStreams(); 85 | return; 86 | } 87 | Execute(false, false); 88 | } 89 | 90 | public void ClearVertexStreams() 91 | { 92 | autoUpdateUVs = false; 93 | Execute(true, false); 94 | } 95 | 96 | public void Execute(bool clearStream, bool regenerateData, bool directlyToUv2 = false) 97 | { 98 | var meshes = new List(); 99 | var rootObjects = GetRootGameObjectsFromGroup(); 100 | GetActiveTransformsWithRenderers(rootObjects, out List renderers, out List filters, out List objects); 101 | 102 | LightmapMeshData[] meshCache = null; 103 | if (!regenerateData) 104 | { 105 | TryReadData(ref meshCache); 106 | } 107 | bool needsSave = meshCache == null; 108 | 109 | if (clearStream || meshCache == null || meshCache.Length == 0 || meshCache.Length != objects.Count) 110 | { 111 | for (int i = 0; i < objects.Count; i++) 112 | { 113 | var m_Renderer = renderers[i]; 114 | 115 | var mf = filters[i]; 116 | var sm = mf.sharedMesh; 117 | 118 | 119 | if (clearStream) 120 | { 121 | m_Renderer.additionalVertexStreams = null; 122 | m_Renderer.enlightenVertexStream = null; 123 | EditorUtility.SetDirty(m_Renderer); 124 | continue; 125 | } 126 | 127 | 128 | var scale = ignoreScaleInLightmap ? 1f : m_Renderer.scaleInLightmap; 129 | int length = sm.vertices.Length; 130 | 131 | 132 | Vector2[] lightmapUV = new Vector2[length]; 133 | #if true 134 | NativeArray verts = new NativeArray(length, Allocator.TempJob); 135 | NativeArray uvs = new NativeArray(length, Allocator.TempJob); 136 | NativeCollectionUtilities.CopyToNative(sm.vertices, verts); 137 | NativeCollectionUtilities.CopyToNative(sm.HasVertexAttribute(VertexAttribute.TexCoord1) ? sm.uv2 : sm.uv, uvs); 138 | NativeArray result = new NativeArray(2, Allocator.TempJob); 139 | try 140 | { 141 | Matrix4x4 modelMatrix = objects[i].transform.localToWorldMatrix; 142 | 143 | result[0] = 0; 144 | result[1] = 0; 145 | 146 | for (int j = 0; j < sm.subMeshCount; j++) 147 | { 148 | var indicies = new NativeArray(sm.GetIndices(j), Allocator.TempJob); 149 | 150 | var areaMultiplier = new CalculateChartsAreaMultiplierJob(verts, uvs, modelMatrix, indicies, result); 151 | areaMultiplier.Run(indicies.Length/3); 152 | 153 | indicies.Dispose(); 154 | } 155 | float area = result[0]; 156 | float uvArea = result[1]; 157 | float finalScale = (math.sqrt(area) / math.sqrt(uvArea)) * scale; 158 | 159 | var scaleJob = new ScaleUVsJob(uvs, finalScale); 160 | scaleJob.Run(uvs.Length); 161 | 162 | NativeCollectionUtilities.CopyToManaged(uvs, lightmapUV); 163 | } 164 | finally 165 | { 166 | result.Dispose(); 167 | uvs.Dispose(); 168 | verts.Dispose(); 169 | } 170 | #else 171 | var area = CalculateArea(sm, objects[i].transform); 172 | scale *= area; 173 | var refUv = sm.uv2 ?? sm.uv; 174 | 175 | for (int j = 0; j < lightmapUV.Length; j++) 176 | { 177 | lightmapUV[j] = refUv[j] * scale; 178 | } 179 | 180 | #endif 181 | var stream = new Mesh 182 | { 183 | name = sm.name, 184 | indexFormat = sm.indexFormat, // thanks haxy 185 | vertices = sm.vertices, 186 | normals = sm.normals, 187 | uv = sm.uv, 188 | uv2 = lightmapUV, 189 | triangles = sm.triangles, 190 | tangents = sm.tangents 191 | }; 192 | 193 | 194 | meshes.Add(stream); 195 | 196 | } 197 | 198 | 199 | if (clearStream) 200 | { 201 | return; 202 | } 203 | 204 | 205 | Xatlas.PackLightmap(meshes.ToArray(), padding, LightmapSize(), bruteForce); 206 | 207 | meshCache = new LightmapMeshData[meshes.Count]; 208 | for (int k = 0; k < meshCache.Length; k++) 209 | { 210 | var m = meshes[k]; 211 | //m.UploadMeshData(true); 212 | meshCache[k] = new LightmapMeshData(m.uv2); 213 | DestroyImmediate(m); 214 | } 215 | 216 | } 217 | 218 | 219 | for (int i = 0; i < meshCache.Length; i++) 220 | { 221 | var m_Renderer = renderers[i]; 222 | var mf = filters[i]; 223 | if (meshCache[i].lightmapUV.Length != mf.sharedMesh.vertices.Length) 224 | { 225 | Debug.LogError("Vertex count not the same + " + mf.sharedMesh.name + " " + meshCache[i].lightmapUV.Length + " original: " + mf.sharedMesh.vertices.Length); 226 | continue; 227 | } 228 | 229 | 230 | var sm = mf.sharedMesh; 231 | var avs = m_Renderer.enlightenVertexStream; 232 | #if SLZ_BATCHING 233 | if (directlyToUv2) 234 | { 235 | var newMesh = Instantiate(sm); 236 | avs = newMesh; 237 | mf.sharedMesh = newMesh; 238 | } 239 | #endif 240 | 241 | // vertices have to be set to match the uv2 length 242 | // it can cause problems when editing and reimporting the geometry but thankfully its non destructive and setting them again or clearing streams fixes it 243 | // reimport should be detected and this data updated so the mesh doesnt look messed up 244 | if (avs == null) 245 | { 246 | avs = new Mesh 247 | { 248 | vertices = mf.sharedMesh.vertices, 249 | uv2 = meshCache[i].lightmapUV 250 | }; 251 | } 252 | else 253 | { 254 | if (avs.vertices == null || avs.vertices.Length != sm.vertices.Length) 255 | { 256 | avs.vertices = mf.sharedMesh.vertices; 257 | } 258 | avs.uv2 = meshCache[i].lightmapUV; 259 | } 260 | 261 | m_Renderer.enlightenVertexStream = avs; 262 | avs.UploadMeshData(false); 263 | } 264 | 265 | if (needsSave) 266 | { 267 | WriteData(meshCache); 268 | } 269 | 270 | } 271 | 272 | public void RePackCharts() 273 | { 274 | autoUpdateUVs = true; 275 | Execute(false, true); 276 | } 277 | public void PackCharts() 278 | { 279 | Execute(false, false); 280 | } 281 | 282 | public void GetActiveTransformsWithRenderers(GameObject[] rootObjs, out List renderers, out List filters, out List obs) 283 | { 284 | var infoMsg = new StringBuilder(); 285 | 286 | var roots = new List(); 287 | 288 | for (int i = 0; i < rootObjs.Length; i++) 289 | { 290 | var o = rootObjs[i]; 291 | roots.AddRange(o.GetComponentsInChildren(false)); 292 | } 293 | 294 | roots.Distinct(); 295 | 296 | renderers = new List(); 297 | filters = new List(); 298 | obs = new List(); 299 | 300 | 301 | foreach (var root in roots) 302 | { 303 | var o = root.gameObject; 304 | if (!o.activeInHierarchy) 305 | { 306 | continue; 307 | } 308 | 309 | if (!GameObjectUtility.GetStaticEditorFlags(o).HasFlag(StaticEditorFlags.ContributeGI)) 310 | { 311 | continue; 312 | } 313 | 314 | var objName = o.name; 315 | 316 | var r = root.GetComponent(); 317 | var f = root.GetComponent(); 318 | 319 | if (!f || !r) 320 | { 321 | continue; 322 | } 323 | 324 | if (r.receiveGI != ReceiveGI.Lightmaps) 325 | { 326 | continue; 327 | } 328 | 329 | if (r.scaleInLightmap == 0) 330 | { 331 | continue; 332 | } 333 | 334 | if (f.sharedMesh == null) 335 | { 336 | //infoMsg.AppendLine($"{objName}: has no shared mesh"); 337 | continue; 338 | } 339 | 340 | 341 | 342 | var sm = f.sharedMesh; 343 | 344 | if (sm.vertices == null) 345 | { 346 | infoMsg.AppendLine($"{objName}: mesh has no vertices"); 347 | continue; 348 | } 349 | 350 | if (sm.uv2 == null && sm.uv == null) 351 | { 352 | infoMsg.AppendLine($"{objName}: mesh has no uvs"); 353 | continue; 354 | } 355 | 356 | var uv = sm.HasVertexAttribute(VertexAttribute.TexCoord1) ? sm.uv2 : sm.uv; 357 | 358 | if (uv.Length != sm.vertices.Length) 359 | { 360 | infoMsg.AppendLine($"{objName}: uv length does not equal vertices length"); 361 | continue; 362 | } 363 | 364 | obs.Add(o); 365 | renderers.Add(r); 366 | filters.Add(f); 367 | } 368 | 369 | var msg = infoMsg.ToString(); 370 | if (!string.IsNullOrEmpty(msg)) 371 | { 372 | Debug.LogWarning(msg); 373 | } 374 | } 375 | 376 | //public RenderTexture rt; 377 | //public Material mt; 378 | //pr Material lmblur; 379 | [HideInInspector] public Texture2D lightmap; 380 | [Range(1,5)] 381 | [HideInInspector] public int radius = 2; 382 | 383 | public int PreprocessOrder => throw new NotImplementedException(); 384 | 385 | public void GaussianPrefilter(Renderer[] renderers) 386 | { 387 | if ( lightmap is null) 388 | { 389 | return; 390 | } 391 | 392 | var rt = new RenderTexture(lightmap.width, lightmap.height, 0, RenderTextureFormat.R8); 393 | 394 | var lmblur = new Material(Shader.Find("Hidden/z3y/LMBlur")); 395 | var mt = new Material(Shader.Find("Hidden/z3y/UnwrapUV")); 396 | 397 | //RenderTexture.active = rt; 398 | var cmd = new CommandBuffer(); 399 | cmd.SetRenderTarget(rt); 400 | //mt.SetPass(0); 401 | 402 | cmd.ClearRenderTarget(true, true, Color.black); 403 | 404 | for (int i = 0; i < renderers.Length; i++) 405 | { 406 | cmd.DrawRenderer(renderers[i], mt); 407 | } 408 | 409 | Graphics.ExecuteCommandBuffer(cmd); 410 | cmd.Dispose(); 411 | 412 | lmblur.SetTexture("_MainTex", lightmap); 413 | lmblur.SetTexture("_Mask", rt); 414 | lmblur.SetFloat("_Radius", radius); 415 | float width = lightmap.width; 416 | float height = lightmap.height; 417 | lmblur.SetVector("_lightmapRes", new Vector4(1.0f / width, 1.0f / height, width, height)); 418 | 419 | var lmrt = new RenderTexture(lightmap.width, lightmap.height, 0, RenderTextureFormat.ARGBFloat); 420 | var tex = new Texture2D(lmrt.width, lmrt.height, TextureFormat.RGBAFloat, false); 421 | 422 | RenderTexture.active = lmrt; 423 | Graphics.Blit(null, lmrt, lmblur); 424 | tex.ReadPixels(new Rect(0, 0, lmrt.width, lmrt.height), 0, 0); 425 | tex.Apply(false); 426 | 427 | RenderTexture.active = null; 428 | var bytes = tex.EncodeToEXR(Texture2D.EXRFlags.CompressZIP); 429 | //var bytes = tex.EncodeToPNG(); 430 | var path = AssetDatabase.GetAssetPath(lightmap); 431 | File.WriteAllBytes(path, bytes); 432 | AssetDatabase.ImportAsset(path); 433 | } 434 | 435 | // this is a bit slow 436 | private float CalculateArea(Mesh mesh, Transform t) 437 | { 438 | if (mesh.uv == null) 439 | { 440 | return 1f; 441 | } 442 | var verts = mesh.vertices; 443 | var uvs = mesh.uv2 ?? mesh.uv; 444 | float area = 0; 445 | float uvArea = 0; 446 | 447 | if (uvs.Length != mesh.vertices.Length) 448 | { 449 | return 1f; 450 | } 451 | 452 | for (int k = 0; k < mesh.subMeshCount; k++) 453 | { 454 | var indices = mesh.GetIndices(k); 455 | for (int j = 0; j < indices.Length; j += 3) 456 | { 457 | var indexA = indices[j]; 458 | var indexB = indices[j + 1]; 459 | var indexC = indices[j + 2]; 460 | 461 | var v1 = verts[indexA]; 462 | var v2 = verts[indexB]; 463 | var v3 = verts[indexC]; 464 | v1 = t.TransformPoint(v1); 465 | v2 = t.TransformPoint(v2); 466 | v3 = t.TransformPoint(v3); 467 | 468 | area += Vector3.Cross(v2 - v1, v3 - v1).magnitude; 469 | 470 | var u1 = uvs[indexA]; 471 | var u2 = uvs[indexB]; 472 | var u3 = uvs[indexC]; 473 | 474 | var d = determinant(u1, u2, u3); 475 | uvArea += math.abs(d); 476 | } 477 | } 478 | 479 | 480 | 481 | area = Mathf.Sqrt(area) / Mathf.Sqrt(uvArea); 482 | 483 | return area; 484 | } 485 | 486 | float determinant(float2 c, float2 c2, float2 c3) 487 | { 488 | float num = c2.y - c3.y; 489 | float num2 = c.y - c3.y; 490 | float num3 = c.y - c2.y; 491 | return c.x * num - c2.x * num2 + c3.x * num3; 492 | } 493 | 494 | [BurstCompile(CompileSynchronously = true, DisableSafetyChecks = true)] 495 | private struct CalculateChartsAreaMultiplierJob : IJobParallelFor 496 | { 497 | public CalculateChartsAreaMultiplierJob(NativeArray verts, NativeArray uvs, Matrix4x4 modelMatrix, NativeArray indices, NativeArray result) 498 | { 499 | this.verts = verts; 500 | this.uvs = uvs; 501 | this.modelMatrix = modelMatrix; 502 | this.indices = indices; 503 | this.result = result; 504 | } 505 | 506 | public NativeArray indices; 507 | public NativeArray verts; 508 | public NativeArray uvs; 509 | public float4x4 modelMatrix; 510 | 511 | public NativeArray result; // length 2 512 | 513 | float determinant(float2 c, float2 c2, float2 c3) 514 | { 515 | float num = c2.y - c3.y; 516 | float num2 = c.y - c3.y; 517 | float num3 = c.y - c2.y; 518 | return c.x * num - c2.x * num2 + c3.x * num3; 519 | } 520 | 521 | public void Execute(int index) 522 | { 523 | int i = index * 3; 524 | var indexA = indices[i]; 525 | var indexB = indices[i + 1]; 526 | var indexC = indices[i + 2]; 527 | 528 | var v1 = verts[indexA]; 529 | var v2 = verts[indexB]; 530 | var v3 = verts[indexC]; 531 | v1 = math.transform(modelMatrix, v1); 532 | v2 = math.transform(modelMatrix, v2); 533 | v3 = math.transform(modelMatrix, v3); 534 | 535 | result[0] += math.length(math.cross(v2 - v1, v3 - v1)); 536 | 537 | var u1 = uvs[indexA]; 538 | var u2 = uvs[indexB]; 539 | var u3 = uvs[indexC]; 540 | 541 | var d = determinant(u1, u2, u3); 542 | result[1] += math.abs(d); 543 | } 544 | } 545 | 546 | [BurstCompile(CompileSynchronously = true)] 547 | private struct ScaleUVsJob : IJobParallelFor 548 | { 549 | public ScaleUVsJob(NativeArray uvs, float scale) 550 | { 551 | this.uvs = uvs; 552 | this.scale = scale; 553 | } 554 | 555 | public NativeArray uvs; 556 | float scale; 557 | public void Execute(int index) 558 | { 559 | uvs[index] *= scale; 560 | } 561 | } 562 | 563 | private void WriteData(LightmapMeshData[] data) 564 | { 565 | using (MemoryStream m = new MemoryStream()) 566 | { 567 | using (BinaryWriter w = new BinaryWriter(m)) 568 | { 569 | w.Write(data.Length); 570 | for (int i = 0; i < data.Length; i++) 571 | { 572 | var uv = data[i].lightmapUV; 573 | 574 | w.Write(uv.Length); 575 | for (int j = 0; j < uv.Length; j++) 576 | { 577 | w.Write(uv[j].x); 578 | w.Write(uv[j].y); 579 | } 580 | } 581 | } 582 | var bytes = m.ToArray(); 583 | 584 | File.WriteAllBytes(GetDataPath(), bytes); 585 | } 586 | } 587 | 588 | private void TryReadData(ref LightmapMeshData[] data) 589 | { 590 | var path = GetDataPath(); 591 | if (!File.Exists(path)) 592 | { 593 | return; 594 | } 595 | 596 | var bytes = File.ReadAllBytes(path); 597 | 598 | using (MemoryStream m = new MemoryStream(bytes)) 599 | { 600 | using (BinaryReader reader = new BinaryReader(m)) 601 | { 602 | var totalLength = reader.ReadInt32(); 603 | 604 | var result = new LightmapMeshData[totalLength]; 605 | 606 | for (int i = 0; i < result.Length; i++) 607 | { 608 | var length = reader.ReadInt32(); 609 | 610 | var uvs = new Vector2[length]; 611 | 612 | for (int j = 0; j < uvs.Length; j++) 613 | { 614 | uvs[j].x = reader.ReadSingle(); 615 | uvs[j].y = reader.ReadSingle(); 616 | } 617 | 618 | result[i].lightmapUV = uvs; 619 | } 620 | 621 | data = result; 622 | } 623 | } 624 | } 625 | 626 | private string GetDataPath() 627 | { 628 | string libraryPath = Path.Combine(Application.dataPath, "../Library/XatlasCache"); 629 | 630 | if (!Directory.Exists(libraryPath)) 631 | { 632 | Directory.CreateDirectory(libraryPath); 633 | } 634 | 635 | var idString = GlobalObjectId.GetGlobalObjectIdSlow(gameObject); 636 | var sceneGuid = AssetDatabase.AssetPathToGUID(gameObject.scene.path); 637 | 638 | return Path.Combine(libraryPath, idString.targetObjectId + sceneGuid); 639 | } 640 | 641 | /*bool IPreprocessCallbackBehaviour.OnPreprocess() 642 | { 643 | //PackCharts(); 644 | return true; 645 | }*/ 646 | } 647 | public class PackDataOnBuild : IProcessSceneWithReport 648 | { 649 | public int callbackOrder => -10000; 650 | public void OnProcessScene(Scene scene, BuildReport report) 651 | { 652 | var rootGameObjects = scene.GetRootGameObjects(); 653 | var instances = new List(); 654 | foreach (var gameObject in rootGameObjects) 655 | { 656 | var xatlasLightmapPackers = gameObject.GetComponentsInChildren(false); 657 | if (xatlasLightmapPackers is null || xatlasLightmapPackers.Length == 0) 658 | { 659 | continue; 660 | } 661 | instances.AddRange(xatlasLightmapPackers); 662 | } 663 | 664 | for (int i = 0; i < instances.Count; i++) 665 | { 666 | instances[i].Execute(false, false, true); 667 | } 668 | } 669 | } 670 | 671 | [CustomEditor(typeof(XatlasLightmapPacker))] 672 | [CanEditMultipleObjects] 673 | public class XatlasLightmapPackerEditor : Editor 674 | { 675 | bool _firstFrame = true; 676 | 677 | 678 | List _meshFilters; 679 | public override void OnInspectorGUI() 680 | { 681 | 682 | if (Application.isPlaying) 683 | { 684 | return; 685 | } 686 | 687 | DrawDefaultInspector(); 688 | for (int i = 0; i < targets.Length; i++) 689 | { 690 | var t = targets[i]; 691 | 692 | var packer = t as XatlasLightmapPacker; 693 | 694 | 695 | if (GUILayout.Button("Pack")) 696 | { 697 | EditorUtility.SetDirty(packer.gameObject); 698 | packer.RePackCharts(); 699 | } 700 | if (GUILayout.Button("Clear")) 701 | { 702 | EditorUtility.SetDirty(packer.gameObject); 703 | packer.ClearVertexStreams(); 704 | } 705 | /*if (GUILayout.Button("Gaussian Prefilter (wip)")) 706 | { 707 | var meshes = new List(); 708 | packer.GetActiveTransformsWithRenderers(packer.GetRootGameObjectsFromGroup(), out List renderers, out List filters, out List objects); 709 | //var lightmap = renderers[0].lightmapIndex; 710 | packer.GaussianPrefilter(renderers.ToArray()); 711 | }*/ 712 | 713 | } 714 | 715 | EditorGUILayout.Space(); 716 | Warnings(); 717 | 718 | _firstFrame = false; 719 | } 720 | 721 | // Mimics the normal map import warning - written by Orels1 722 | private static bool WarningBox(string message, string fixText = "Fix Now") 723 | { 724 | GUILayout.BeginVertical(new GUIStyle(EditorStyles.helpBox)); 725 | EditorGUILayout.LabelField(message, new GUIStyle(EditorStyles.label) { fontSize = 11, wordWrap = true }); 726 | EditorGUILayout.BeginHorizontal(new GUIStyle() { alignment = TextAnchor.MiddleRight }, GUILayout.Height(24)); 727 | EditorGUILayout.Space(); 728 | var buttonPress = GUILayout.Button(fixText, new GUIStyle("button") 729 | { 730 | stretchWidth = false, 731 | margin = new RectOffset(0, 0, 0, 0), 732 | padding = new RectOffset(8, 8, 0, 0) 733 | }, GUILayout.Height(22)); 734 | EditorGUILayout.EndHorizontal(); 735 | GUILayout.EndVertical(); 736 | return buttonPress; 737 | } 738 | 739 | Vector2 _scrollPos; 740 | void Warnings() 741 | { 742 | var packer = target as XatlasLightmapPacker; 743 | if (packer.lightmapGroup == null) 744 | { 745 | return; 746 | } 747 | 748 | var storage = FindObjectOfType(); 749 | if (storage != null) 750 | { 751 | if (!(storage.renderSettingsForceDisableUnwrapUVs || !storage.renderSettingsUnwrapUVs)) 752 | { 753 | EditorGUILayout.HelpBox("Disable Adjust UV Padding in bakery lightmap settings", MessageType.Warning); 754 | } 755 | } 756 | 757 | 758 | if (_firstFrame) 759 | { 760 | var rootObjects = packer.GetRootGameObjectsFromGroup(); 761 | packer.GetActiveTransformsWithRenderers(rootObjects, out List renderers, out List filters, out List objects); 762 | _meshFilters = filters; 763 | } 764 | 765 | var group = packer.lightmapGroup; 766 | 767 | if (group.mode != BakeryLightmapGroup.ftLMGroupMode.OriginalUV) 768 | { 769 | if (WarningBox("Lightmap Group mode not set to Original UV")) 770 | { 771 | group.mode = BakeryLightmapGroup.ftLMGroupMode.OriginalUV; 772 | EditorUtility.SetDirty(group); 773 | } 774 | } 775 | 776 | 777 | // this is cursed but how else do i know in advance if i should draw the scroll view lol 778 | bool hasWarnings = false; 779 | if (_meshFilters == null) 780 | { 781 | return; 782 | } 783 | for (int i = 0; i < _meshFilters.Count; i++) 784 | { 785 | var m = _meshFilters[i].sharedMesh; 786 | 787 | if (!m.HasVertexAttribute(VertexAttribute.TexCoord1)) 788 | { 789 | hasWarnings = true; break; 790 | } 791 | 792 | if (!m.isReadable) 793 | { 794 | hasWarnings = true; break; 795 | } 796 | } 797 | 798 | if (hasWarnings) 799 | { 800 | _scrollPos = EditorGUILayout.BeginScrollView(_scrollPos, GUILayout.Height(200)); 801 | for (int i = 0; i < _meshFilters.Count; i++) 802 | { 803 | var m = _meshFilters[i].sharedMesh; 804 | 805 | if (!m.HasVertexAttribute(VertexAttribute.TexCoord1)) 806 | { 807 | if (WarningBox($"Mesh {m.name} has no UV2", "Select")) 808 | { 809 | EditorGUIUtility.PingObject(m); 810 | } 811 | } 812 | 813 | if (!m.isReadable) 814 | { 815 | if (WarningBox($"Mesh {m.name} has no Read/Write enabled", "Select")) 816 | { 817 | EditorGUIUtility.PingObject(m); 818 | } 819 | } 820 | 821 | } 822 | EditorGUILayout.EndScrollView(); 823 | } 824 | 825 | 826 | 827 | } 828 | } 829 | 830 | /*public static class JsonHelper 831 | { 832 | public static T[] FromJson(string json) 833 | { 834 | Wrapper wrapper = UnityEngine.JsonUtility.FromJson>(json); 835 | return wrapper.Items; 836 | } 837 | 838 | public static string ToJson(T[] array) 839 | { 840 | Wrapper wrapper = new Wrapper(); 841 | wrapper.Items = array; 842 | return UnityEngine.JsonUtility.ToJson(wrapper); 843 | } 844 | 845 | [Serializable] 846 | private class Wrapper 847 | { 848 | public T[] Items; 849 | } 850 | }*/ 851 | } 852 | #endif --------------------------------------------------------------------------------