├── _config.yml ├── .gitattributes ├── bakedReflectionExample.JPG ├── SpecularLightmaps ├── Tests 1 │ ├── Tests 1.asmdef │ ├── Tests 1.asmdef.meta │ ├── ArrayIndexConv.cs.meta │ └── ArrayIndexConv.cs ├── Tests 1.meta ├── glossy test.mat.meta ├── SpecularLightmapClassic.shader.meta ├── CubemapToTexture2D.shader.meta ├── ExtractPoisition.shader.meta ├── BakedReflectionsCommon.cginc.meta ├── displayBakedReflection.shader.meta ├── SpecularLightmapTag.cs ├── SpecularLightmapTag.cs.meta ├── SpecularLightmapSettings.cs.meta ├── SpecularLightmapSettings.cs ├── SpecularLightmapping.cs.meta ├── CubemapToTexture2D.shader ├── SpecularLightmapClassic.shader ├── glossy test.mat ├── BakedReflectionsCommon.cginc ├── displayBakedReflection.shader ├── ExtractPoisition.shader └── SpecularLightmapping.cs ├── .gitignore ├── README.md └── LICENSE /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /bakedReflectionExample.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/julhe/BakedReflectionsUnity/HEAD/bakedReflectionExample.JPG -------------------------------------------------------------------------------- /SpecularLightmaps/Tests 1/Tests 1.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Tests1", 3 | "optionalUnityReferences": [ 4 | "TestAssemblies" 5 | ], 6 | "includePlatforms": [ 7 | "Editor" 8 | ] 9 | } -------------------------------------------------------------------------------- /SpecularLightmaps/Tests 1.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d14d59b74744ec240a901583ab747042 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /SpecularLightmaps/Tests 1/Tests 1.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ceeec172c2556264594157cbbadcbe27 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /SpecularLightmaps/glossy test.mat.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 13416cc08026de34db1b7292ee9324b0 3 | timeCreated: 1514589463 4 | licenseType: Free 5 | NativeFormatImporter: 6 | mainObjectFileID: 2100000 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /SpecularLightmaps/SpecularLightmapClassic.shader.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 19bec75c5831a204f9c8a14ec42217f2 3 | ShaderImporter: 4 | externalObjects: {} 5 | defaultTextures: [] 6 | nonModifiableTextures: [] 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /SpecularLightmaps/CubemapToTexture2D.shader.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bf48cafb27726a041bf025acebd6ac42 3 | timeCreated: 1514238829 4 | licenseType: Free 5 | ShaderImporter: 6 | externalObjects: {} 7 | defaultTextures: [] 8 | userData: 9 | assetBundleName: 10 | assetBundleVariant: 11 | -------------------------------------------------------------------------------- /SpecularLightmaps/ExtractPoisition.shader.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 85a109257ac9ea04181bdcbdd49e25c1 3 | timeCreated: 1514219395 4 | licenseType: Free 5 | ShaderImporter: 6 | externalObjects: {} 7 | defaultTextures: [] 8 | userData: 9 | assetBundleName: 10 | assetBundleVariant: 11 | -------------------------------------------------------------------------------- /SpecularLightmaps/BakedReflectionsCommon.cginc.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0155ba81a9f41ef41bc1d4c5a13d752d 3 | timeCreated: 1514463670 4 | licenseType: Free 5 | ShaderImporter: 6 | externalObjects: {} 7 | defaultTextures: [] 8 | userData: 9 | assetBundleName: 10 | assetBundleVariant: 11 | -------------------------------------------------------------------------------- /SpecularLightmaps/displayBakedReflection.shader.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 42011d045c037054985437079c4dc381 3 | timeCreated: 1514220008 4 | licenseType: Free 5 | ShaderImporter: 6 | externalObjects: {} 7 | defaultTextures: [] 8 | userData: 9 | assetBundleName: 10 | assetBundleVariant: 11 | -------------------------------------------------------------------------------- /SpecularLightmaps/SpecularLightmapTag.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | namespace SpecularLightmapping 6 | { 7 | public class BakedReflectionTag : MonoBehaviour 8 | { 9 | public SpecularLightmapSettings overrideSettings; 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /SpecularLightmaps/SpecularLightmapTag.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6b5d397cd43ec7e4d802f3370d2a79fd 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /SpecularLightmaps/Tests 1/ArrayIndexConv.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7652ca88a1a374a48a04cbb70afc7be0 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /SpecularLightmaps/SpecularLightmapSettings.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 79f2ef981eaa4984c8d41db5180b402a 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /SpecularLightmaps/SpecularLightmapSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | namespace SpecularLightmapping 6 | { 7 | [CreateAssetMenu(fileName = "SpecularLightmapSettings.asset")] 8 | public class SpecularLightmapSettings : ScriptableObject 9 | { 10 | [Range(1f, 10f)] 11 | public int sliceCountLevel = 2; 12 | [Range(1f, 10f)] 13 | public int resolutionLevel = 2; 14 | 15 | 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /[Ll]ibrary/ 2 | /[Tt]emp/ 3 | /[Oo]bj/ 4 | /[Bb]uild/ 5 | /[Bb]uilds/ 6 | /Assets/AssetStoreTools* 7 | 8 | # Visual Studio 2015 cache directory 9 | /.vs/ 10 | 11 | # Autogenerated VS/MD/Consulo solution and project files 12 | ExportedObj/ 13 | .consulo/ 14 | *.csproj 15 | *.unityproj 16 | *.sln 17 | *.suo 18 | *.tmp 19 | *.user 20 | *.userprefs 21 | *.pidb 22 | *.booproj 23 | *.svd 24 | *.pdb 25 | 26 | # Unity3D generated meta files 27 | *.pidb.meta 28 | 29 | # Unity3D Generated File On Crash Reports 30 | sysinfo.txt 31 | 32 | # Builds 33 | *.apk 34 | *.unitypackage 35 | -------------------------------------------------------------------------------- /SpecularLightmaps/SpecularLightmapping.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: aca3994e555bbd4458efd8833be502d5 3 | timeCreated: 1515241534 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: 8 | - reflectionAtlas: {instanceID: 0} 9 | - TargetMaterial: {instanceID: 0} 10 | - ExtractShader: {fileID: 4800000, guid: 85a109257ac9ea04181bdcbdd49e25c1, type: 3} 11 | - CubeMapTo2DTextureShader: {fileID: 4800000, guid: bf48cafb27726a041bf025acebd6ac42, 12 | type: 3} 13 | - GammaSpaceConversionShader: {fileID: 4800000, guid: d70cd8790841c9c4388d6ab33dc4788a, 14 | type: 3} 15 | - tmpCubemap: {instanceID: 0} 16 | - tmpTexture: {instanceID: 0} 17 | executionOrder: 0 18 | icon: {instanceID: 0} 19 | userData: 20 | assetBundleName: 21 | assetBundleVariant: 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Baked Reflections for Unity 3 | 4 | Fast and spatial aware reflections for any unwraped object. 5 | 6 | ![exampleImage](https://github.com/julhe/BakedReflectionsUnity/blob/master/bakedReflectionExample.JPG "exampleImage") 7 | Baked Reflections / Reflection Probe Atlas implementation for Unity by Julian Heinken (@schneckerstein) v1 8 | 9 | 10 | USAGE: 11 | 1. Place this script on the object you like to have reflections for. 12 | NOTE: The implementation relies on the second uv channel (UV2). Therefore, it will only work if you activated "Generate Lightmap UVs" in the import settings of your mesh. 13 | 2. Change the shader of the material to "Unlit/displayBakedReflections" (or modify your own shader) 14 | 3. Modify "Slice Count Level" and "Resolution Level" to your preferences, click on "Start" to start baking the reflection atlas. 15 | Its not recommended to let "Total Axis Size" exceed 8192, since this is the highest texture resolution unity is able to import later. 16 | 4. Click on "Export to Exr" to export the reflection atlas. (Default location is "Assets/Baked SurfaceReflections") 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Julian Heinken 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. -------------------------------------------------------------------------------- /SpecularLightmaps/CubemapToTexture2D.shader: -------------------------------------------------------------------------------- 1 | Shader "Hidden/CubemapToTexture2D" 2 | { 3 | Properties 4 | { 5 | _MainTex("InCube", CUBE) = "white" {} 6 | } 7 | SubShader 8 | { 9 | // No culling or depth 10 | Cull Off ZWrite Off ZTest Always 11 | 12 | Pass 13 | { 14 | CGPROGRAM 15 | #pragma vertex vert 16 | #pragma fragment frag 17 | 18 | #include "UnityCG.cginc" 19 | #include "BakedReflectionsCommon.cginc" 20 | 21 | struct appdata 22 | { 23 | float4 vertex : POSITION; 24 | float2 uv : TEXCOORD0; 25 | }; 26 | 27 | struct v2f 28 | { 29 | float2 uv : TEXCOORD0; 30 | float4 vertex : SV_POSITION; 31 | }; 32 | 33 | float4 _vertexTransform; 34 | v2f vert (appdata v) 35 | { 36 | v2f o; 37 | o.vertex = UnityObjectToClipPos(v.vertex); 38 | 39 | o.vertex.xy = o.vertex.xy * 0.5 + 0.5; 40 | 41 | o.vertex.xy = _vertexTransform.xy * o.vertex.xy + _vertexTransform.zw; 42 | 43 | o.vertex.xy = o.vertex.xy * 2.0 - 1.0; 44 | o.uv = v.uv; 45 | return o; 46 | } 47 | 48 | 49 | 50 | samplerCUBE _MainTex; 51 | float4x4 normalToHemisphere; 52 | fixed4 frag (v2f i) : SV_Target 53 | { 54 | 55 | #ifdef BAKED_REFLECTIONS_HEMISPHERE_MODE 56 | float3 normal = mul((float3x3)normalToHemisphere, hemioct_to_float32x3(i.uv * 2.0 - 1.0)); 57 | #else 58 | float3 normal = oct_to_float32x3(i.uv * 2.0 - 1.0); 59 | #endif 60 | 61 | float4 color = texCUBE(_MainTex, normal); 62 | color.a = 1; 63 | return color; 64 | } 65 | ENDCG 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /SpecularLightmaps/Tests 1/ArrayIndexConv.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using NUnit.Framework; 4 | using UnityEngine; 5 | using UnityEngine.TestTools; 6 | 7 | namespace Tests 8 | { 9 | public class ArrayIndexConv 10 | { 11 | const int ReflectionAtlas_AxisSize = 32; 12 | private Vector2Int ToCoordinate(int index) 13 | { 14 | return new Vector2Int(index % ReflectionAtlas_AxisSize, index / ReflectionAtlas_AxisSize); 15 | } 16 | 17 | private int ToIndex(Vector2Int coordinate) 18 | { 19 | return coordinate.y * ReflectionAtlas_AxisSize + coordinate.x; 20 | } 21 | 22 | 23 | // A Test behaves as an ordinary method 24 | [Test] 25 | public void ArrayIndexConvSimplePasses() 26 | { 27 | for (int x = 0; x < ReflectionAtlas_AxisSize; x++) 28 | { 29 | for (int y = 0; y < ReflectionAtlas_AxisSize; y++) 30 | { 31 | Vector2Int coord = new Vector2Int(x,y); 32 | int indexFromCoord = ToIndex(coord); 33 | Vector2Int coordFromIndex = ToCoordinate(indexFromCoord); 34 | 35 | Assert.AreEqual(coord, coordFromIndex); 36 | } 37 | } 38 | // Use the Assert class to test conditions 39 | } 40 | 41 | 42 | 43 | // A UnityTest behaves like a coroutine in Play Mode. In Edit Mode you can use 44 | // `yield return null;` to skip a frame. 45 | [UnityTest] 46 | public IEnumerator ArrayIndexConvWithEnumeratorPasses() 47 | { 48 | // Use the Assert class to test conditions. 49 | // Use yield to skip a frame. 50 | yield return null; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /SpecularLightmaps/SpecularLightmapClassic.shader: -------------------------------------------------------------------------------- 1 | Shader "Custom/SpecularLightmap" 2 | { 3 | Properties 4 | { 5 | _Color ("Color", Color) = (1,1,1,1) 6 | _MainTex ("Albedo (RGB)", 2D) = "white" {} 7 | _Glossiness ("Smoothness", Range(0,1)) = 0.5 8 | _Metallic ("Metallic", Range(0,1)) = 0.0 9 | } 10 | SubShader 11 | { 12 | Tags { "RenderType"="Opaque" } 13 | LOD 200 14 | 15 | CGPROGRAM 16 | // Physically based Standard lighting model, and enable shadows on all light types 17 | #pragma surface surf Standard fullforwardshadows 18 | 19 | // Use shader model 3.0 target, to get nicer looking lighting 20 | #pragma target 3.0 21 | 22 | sampler2D _MainTex; 23 | 24 | struct Input 25 | { 26 | float2 uv_MainTex; 27 | }; 28 | 29 | half _Glossiness; 30 | half _Metallic; 31 | fixed4 _Color; 32 | 33 | // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader. 34 | // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing. 35 | // #pragma instancing_options assumeuniformscaling 36 | UNITY_INSTANCING_BUFFER_START(Props) 37 | // put more per-instance properties here 38 | UNITY_INSTANCING_BUFFER_END(Props) 39 | 40 | void surf (Input IN, inout SurfaceOutputStandard o) 41 | { 42 | // Albedo comes from a texture tinted by color 43 | fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; 44 | o.Albedo = c.rgb; 45 | // Metallic and smoothness come from slider variables 46 | o.Metallic = _Metallic; 47 | o.Smoothness = _Glossiness; 48 | o.Alpha = c.a; 49 | } 50 | ENDCG 51 | } 52 | FallBack "Diffuse" 53 | } 54 | -------------------------------------------------------------------------------- /SpecularLightmaps/glossy test.mat: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!21 &2100000 4 | Material: 5 | serializedVersion: 6 6 | m_ObjectHideFlags: 0 7 | m_CorrespondingSourceObject: {fileID: 0} 8 | m_PrefabInstance: {fileID: 0} 9 | m_PrefabAsset: {fileID: 0} 10 | m_Name: glossy test 11 | m_Shader: {fileID: 4800000, guid: 42011d045c037054985437079c4dc381, type: 3} 12 | m_ShaderKeywords: 13 | m_LightmapFlags: 4 14 | m_EnableInstancingVariants: 0 15 | m_DoubleSidedGI: 0 16 | m_CustomRenderQueue: -1 17 | stringTagMap: {} 18 | disabledShaderPasses: [] 19 | m_SavedProperties: 20 | serializedVersion: 3 21 | m_TexEnvs: 22 | - _BumpMap: 23 | m_Texture: {fileID: 0} 24 | m_Scale: {x: 1, y: 1} 25 | m_Offset: {x: 0, y: 0} 26 | - _DetailAlbedoMap: 27 | m_Texture: {fileID: 0} 28 | m_Scale: {x: 1, y: 1} 29 | m_Offset: {x: 0, y: 0} 30 | - _DetailMask: 31 | m_Texture: {fileID: 0} 32 | m_Scale: {x: 1, y: 1} 33 | m_Offset: {x: 0, y: 0} 34 | - _DetailNormalMap: 35 | m_Texture: {fileID: 0} 36 | m_Scale: {x: 1, y: 1} 37 | m_Offset: {x: 0, y: 0} 38 | - _Displacement: 39 | m_Texture: {fileID: 0} 40 | m_Scale: {x: 1, y: 1} 41 | m_Offset: {x: 0, y: 0} 42 | - _EmissionMap: 43 | m_Texture: {fileID: 0} 44 | m_Scale: {x: 1, y: 1} 45 | m_Offset: {x: 0, y: 0} 46 | - _Gradient: 47 | m_Texture: {fileID: 2800000, guid: 77c4a668928030740a55e132993b5ea7, type: 3} 48 | m_Scale: {x: 3.46, y: 1.97} 49 | m_Offset: {x: 0, y: 0} 50 | - _MainTex: 51 | m_Texture: {fileID: 2800000, guid: e7ff8dd79aad628469c5ac0fc3986fc0, type: 3} 52 | m_Scale: {x: 1, y: 1} 53 | m_Offset: {x: 0, y: 0} 54 | - _MetallicGlossMap: 55 | m_Texture: {fileID: 0} 56 | m_Scale: {x: 1, y: 1} 57 | m_Offset: {x: 0, y: 0} 58 | - _OcclusionMap: 59 | m_Texture: {fileID: 0} 60 | m_Scale: {x: 1, y: 1} 61 | m_Offset: {x: 0, y: 0} 62 | - _ParallaxMap: 63 | m_Texture: {fileID: 0} 64 | m_Scale: {x: 1, y: 1} 65 | m_Offset: {x: 0, y: 0} 66 | - _ReflectionArray: 67 | m_Texture: {fileID: 0} 68 | m_Scale: {x: 5.24, y: 2.54} 69 | m_Offset: {x: 0, y: 0} 70 | m_Floats: 71 | - _BumpScale: 1 72 | - _Cutoff: 0.5 73 | - _DetailNormalMapScale: 1 74 | - _DstBlend: 0 75 | - _EdgeLength: 50 76 | - _GlossMapScale: 1 77 | - _Glossiness: 0.5 78 | - _GlossyReflections: 1 79 | - _HeightScale: 1 80 | - _Metallic: 0 81 | - _Mode: 0 82 | - _OcclusionStrength: 1 83 | - _Parallax: 0.02 84 | - _RimPower: 3 85 | - _Smoothness: 1 86 | - _SmoothnessTextureChannel: 0 87 | - _SpecularHighlights: 1 88 | - _SrcBlend: 1 89 | - _UVAspectRatio: 1 90 | - _UVSec: 0 91 | - _ZWrite: 1 92 | - _gradMax: 1 93 | - _gradMin: 0 94 | - _sliceSize: 257.75 95 | - _slicesPerAxis: 267.6 96 | - g_HeightFieldSize: 1 97 | m_Colors: 98 | - _BakedReflectionParams: {r: 32, g: 0.03125, b: 0, a: 0} 99 | - _BaseColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} 100 | - _Color: {r: 1, g: 1, b: 1, a: 1} 101 | - _Emission: {r: 1, g: 1, b: 1, a: 1} 102 | - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} 103 | - _GradColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} 104 | - _RimColor: {r: 0.25999996, g: 0.18999997, b: 0.15999997, a: 0} 105 | - g_WaterScatterColor: {r: 0, g: 0, b: 0, a: 1} 106 | -------------------------------------------------------------------------------- /SpecularLightmaps/BakedReflectionsCommon.cginc: -------------------------------------------------------------------------------- 1 | #ifndef BAKED_REFLECTIONS_COMMON 2 | #define BAKED_REFLECTIONS_COMMON 3 | 4 | // There are two storage modes for baked reflections. Sphere and Hemisphere 5 | //Hemisphere | Sphere 6 | //-------------------------------------------------------------- 7 | //+fewer Storage requierements | -More Storage 8 | //+no interpolation issues | -interpolation Issues 9 | // | 10 | //-Require Tangents | +no Requieremetn for Tangents 11 | //-artefacts on uv seams | +no artefacts on uv seams 12 | 13 | #define BAKED_REFLECTIONS_HEMISPHERE_MODE 14 | 15 | // === Octahedral Packing === 16 | // TODO: I'm not sure if this implementation is 100% optimized. 17 | 18 | // Returns �1 19 | float2 signNotZero(float2 v) { 20 | return float2((v.x >= 0.0) ? +1.0 : -1.0, (v.y >= 0.0) ? +1.0 : -1.0); 21 | } 22 | 23 | // Assume normalized input. Output is on [-1, 1] for each component. 24 | float2 float32x3_to_oct(in float3 v) { 25 | // Project the sphere onto the octahedron, and then onto the xy plane 26 | float2 p = v.xy * (1.0 / (abs(v.x) + abs(v.y) + abs(v.z))); 27 | // Reflect the folds of the lower hemisphere over the diagonals 28 | return (v.z <= 0.0) ? ((1.0 - abs(p.yx)) * signNotZero(p)) : p; 29 | } 30 | 31 | float3 oct_to_float32x3(float2 e) { 32 | float3 v = float3(e.xy, 1.0 - abs(e.x) - abs(e.y)); 33 | if (v.z < 0) v.xy = (1.0 - abs(v.yx)) * signNotZero(v.xy); 34 | return normalize(v); 35 | } 36 | 37 | float2 float32x3_to_hemioct(in float3 v) { 38 | // Project the hemisphere onto the hemi-octahedron, 39 | // and then into the xy plane 40 | float2 p = v.xy * (1.0 / (abs(v.x) + abs(v.y) + v.z)); 41 | // Rotate and scale the center diamond to the unit square 42 | return float2(p.x + p.y, p.x - p.y); 43 | } 44 | float3 hemioct_to_float32x3(float2 e) { 45 | // Rotate and scale the unit square back to the center diamond 46 | float2 temp = float2(e.x + e.y, e.x - e.y) * 0.5; 47 | float3 v = float3(temp, 1.0 - abs(temp.x) - abs(temp.y)); 48 | return normalize(v); 49 | } 50 | 51 | // ========================== 52 | 53 | //### actuall functions for shader use### 54 | 55 | float2 _BakedReflectionParams; 56 | //x: slices per axis, 57 | //z: inverse of "slices per axis" for speedups 58 | 59 | 60 | float3 SampleBakedReflectionEx(sampler2D reflectionAtlas, float3 reflectionVector, float2 uv, float perceptualRoughness) { 61 | float slicesPerAxis = _BakedReflectionParams.x; 62 | float slicesPerAxis_rcp = _BakedReflectionParams.y; 63 | // convert reflection vector to uv of the hemisphere slice 64 | #ifdef BAKED_REFLECTIONS_HEMISPHERE_MODE 65 | float2 sliceUV = (float32x3_to_hemioct(reflectionVector) * 0.5 + 0.5); 66 | #else 67 | float2 sliceUV = (float32x3_to_oct(reflectionVector) * 0.5 + 0.5); 68 | #endif 69 | sliceUV = saturate(sliceUV); 70 | sliceUV *= slicesPerAxis_rcp; 71 | 72 | float2 uvPixelSpace = uv * slicesPerAxis; 73 | float2 uvFloor = floor(uvPixelSpace) * slicesPerAxis_rcp; 74 | float2 uvCeil = ceil(uvPixelSpace) * slicesPerAxis_rcp; 75 | 76 | // perform bilinear interpolation 77 | // TODO: change to tex2Dlod 78 | float lodLevel = perceptualRoughness * 6.0; 79 | float3 smp0 = tex2Dlod(reflectionAtlas, float4(uvFloor + sliceUV, 0, lodLevel)); 80 | float3 smp1 = tex2Dlod(reflectionAtlas, float4(float2(uvCeil.x, uvFloor.y) + sliceUV, 0, lodLevel)); 81 | 82 | float2 uvFrac = frac(uvPixelSpace); 83 | float3 x = lerp(smp0, smp1, uvFrac.x); 84 | 85 | float3 smp2 = tex2Dlod(reflectionAtlas, float4(float2(uvFloor.x, uvCeil.y) + sliceUV, 0, lodLevel)); 86 | float3 smp3 = tex2Dlod(reflectionAtlas, float4(uvCeil + sliceUV, 0, lodLevel)); 87 | float3 y = lerp(smp2, smp3, uvFrac.x); 88 | 89 | return lerp(x, y, uvFrac.y); 90 | } 91 | 92 | float3 SampleBakedReflection(sampler2D reflectionAtlas, float3 reflectionVector, float2 uv, float smoothness) { 93 | return SampleBakedReflectionEx(reflectionAtlas, reflectionVector, uv, (1.0 - smoothness)); 94 | } 95 | #endif -------------------------------------------------------------------------------- /SpecularLightmaps/displayBakedReflection.shader: -------------------------------------------------------------------------------- 1 | // Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld' 2 | 3 | Shader "Unlit/displayBakedReflection" 4 | { 5 | Properties 6 | { 7 | _ReflectionArray ("Texture", 2D) = "white" {} 8 | _Smoothness("_Smoothness", Range(0,1)) = 1 9 | _sliceSize("_sliceSize", float) = 0 10 | _slicesPerAxis("_slicesPerAxis", float) = 0 11 | _MainTex("MainTex", 2D) = "white" { } 12 | _Gradient("_BumpMap", 2D) = "bump" { } 13 | _BakedReflectionParams("slice Size", Vector) = (0, 0, 0) 14 | } 15 | SubShader 16 | { 17 | Tags { "RenderType"="Opaque" } 18 | LOD 100 19 | 20 | Pass 21 | { 22 | CGPROGRAM 23 | #pragma vertex vert 24 | #pragma fragment frag 25 | // make fog work 26 | #pragma multi_compile_fog 27 | #pragma target 5.0 28 | #include "UnityCG.cginc" 29 | #include "BakedReflectionsCommon.cginc" 30 | 31 | struct appdata 32 | { 33 | float4 vertex : POSITION; 34 | float3 normal : NORMAL; 35 | float2 uv : TEXCOORD1; 36 | }; 37 | 38 | struct v2f 39 | { 40 | float2 uv : TEXCOORD0; 41 | float3 normal : NORMAL; 42 | half3 tspace0 : TEXCOORD1; // tangent.x, bitangent.x, normal.x 43 | half3 tspace1 : TEXCOORD2; // tangent.y, bitangent.y, normal.y 44 | half3 tspace2 : TEXCOORD3; // tangent.z, bitangent.z, normal.z 45 | float3 worldPos : TEXCOORD4; 46 | UNITY_FOG_COORDS(5) 47 | float4 vertex : SV_POSITION; 48 | }; 49 | 50 | sampler2D _MainTex; 51 | float4 _MainTex_ST; 52 | 53 | v2f vert (appdata v, float3 normal : NORMAL, float4 tangent : TANGENT) 54 | { 55 | v2f o; 56 | o.vertex = UnityObjectToClipPos(v.vertex); 57 | o.uv = v.uv; 58 | o.worldPos = mul(unity_ObjectToWorld, v.vertex); 59 | 60 | half3 wNormal = UnityObjectToWorldNormal(normal); 61 | half3 wTangent = UnityObjectToWorldDir(tangent.xyz); 62 | 63 | // compute bitangent from cross product of normal and tangent 64 | half tangentSign = tangent.w * unity_WorldTransformParams.w; 65 | half3 wBitangent = cross(wNormal, wTangent) * tangentSign; 66 | 67 | // output the tangent space matrix 68 | o.tspace0 = half3(wTangent.x, wBitangent.x, wNormal.x); 69 | o.tspace1 = half3(wTangent.y, wBitangent.y, wNormal.y); 70 | o.tspace2 = half3(wTangent.z, wBitangent.z, wNormal.z); 71 | 72 | UNITY_TRANSFER_FOG(o,o.vertex); 73 | return o; 74 | } 75 | 76 | sampler2D _ReflectionArray; 77 | sampler2D _Gradient; 78 | float _Smoothness; 79 | fixed4 frag (v2f i) : SV_Target 80 | { 81 | // sample the normal map, and decode from the Unity encoding 82 | half3 tnormal = UnpackNormal(tex2D(_Gradient, i.uv * 10)); 83 | 84 | // transform normal from tangent to world space 85 | half3 worldNormal; 86 | float3x3 tangentToWorld = float3x3(i.tspace0, i.tspace1, i.tspace2); 87 | worldNormal = mul(tangentToWorld, tnormal); 88 | 89 | float3x3 worldToTangent = transpose(tangentToWorld); 90 | 91 | float3 viewDir = -normalize(UnityWorldSpaceViewDir(i.worldPos)); 92 | float3 reflectionVector = reflect(viewDir, worldNormal); 93 | float3 tangentView = mul(worldToTangent, reflectionVector); 94 | 95 | float smoothness = _Smoothness; 96 | //return normalToLatLong(reflectionVector).xyxy; 97 | float3 reflection; 98 | #ifdef BAKED_REFLECTIONS_HEMISPHERE_MODE 99 | reflection = SampleBakedReflection(_ReflectionArray, tangentView, i.uv, smoothness).rgb; 100 | #else 101 | reflection = SampleBakedReflection(_ReflectionArray, reflectionVector, i.uv, smoothness).rgb; 102 | #endif 103 | 104 | float4 outColor = float4(reflection, 1.0); 105 | // return float4(reflectionVector.xy * 0.5 + 0.5, 0, 1); 106 | // outColor *= 0.67; 107 | float fresnel = saturate(0.67 + pow(1.0 - saturate(dot(viewDir, worldNormal)), 5)); 108 | //UNITY_APPLY_FOG(i.fogCoord, outColor); 109 | 110 | return outColor; 111 | } 112 | ENDCG 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /SpecularLightmaps/ExtractPoisition.shader: -------------------------------------------------------------------------------- 1 | // Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld' 2 | 3 | Shader "Unlit/ExtractPoisition" 4 | { 5 | Properties 6 | { 7 | _MainTex ("Texture", 2D) = "white" {} 8 | } 9 | SubShader 10 | { 11 | Tags { "RenderType"="Opaque" } 12 | LOD 100 13 | Cull Off 14 | Pass 15 | { 16 | CGPROGRAM 17 | // Upgrade NOTE: excluded shader from OpenGL ES 2.0 because it uses non-square matrices 18 | #pragma exclude_renderers gles 19 | #pragma vertex vert 20 | //#pragma geometry geom 21 | #pragma fragment frag 22 | 23 | #include "UnityCG.cginc" 24 | 25 | struct appdata 26 | { 27 | float4 vertex : POSITION; 28 | float3 normal : NORMAL; 29 | float4 tangent : TANGENT; 30 | float2 uv : TEXCOORD1; 31 | }; 32 | 33 | struct v2f 34 | { 35 | float3 worldPosition : COLOR; 36 | float3 worldNormal : COLOR1; 37 | float4 worldTangent: COLOR2; 38 | float4 pos : SV_POSITION; 39 | }; 40 | 41 | 42 | v2f vert (appdata v) 43 | { 44 | v2f o; 45 | float2 fullscreenUV = v.uv; 46 | fullscreenUV = fullscreenUV * 2.0 - 1.0; 47 | 48 | o.pos = float4(fullscreenUV, 0, 1); 49 | 50 | o.worldPosition = mul(unity_ObjectToWorld, v.vertex).rgb; 51 | o.worldNormal = UnityObjectToWorldNormal(v.normal).rgb; 52 | 53 | o.worldTangent.w = v.tangent.w * unity_WorldTransformParams.w; 54 | o.worldTangent.xyz = UnityObjectToWorldDir(v.tangent.xyz) ; 55 | return o; 56 | } 57 | 58 | [maxvertexcount(3)] 59 | void geom(triangle v2f p[3], inout TriangleStream triStream) 60 | { 61 | v2f p0 = p[0]; 62 | v2f p1 = p[1]; 63 | v2f p2 = p[2]; 64 | // "do" conservative raserization 65 | // source: https://github.com/otaku690/SparseVoxelOctree/blob/master/WIN/SVO/shader/voxelize.geom.glsl 66 | //Next we enlarge the triangle to enable conservative rasterization 67 | float4 AABB; 68 | float2 hPixel = float2(1.0 / _ScreenParams.x, 1.0 / _ScreenParams.y); 69 | float pl = 1.4142135637309 / (max(_ScreenParams.x, _ScreenParams.y) / 2.0); 70 | 71 | //calculate AABB of this triangle 72 | AABB.xy = p0.pos.xy; 73 | AABB.zw = p0.pos.xy; 74 | 75 | AABB.xy = min(p1.pos.xy, AABB.xy); 76 | AABB.zw = max(p1.pos.xy, AABB.zw); 77 | 78 | AABB.xy = min(p2.pos.xy, AABB.xy); 79 | AABB.zw = max(p2.pos.xy, AABB.zw); 80 | 81 | //Enlarge half-pixel 82 | AABB.xy -= hPixel; 83 | AABB.zw += hPixel; 84 | 85 | //find 3 triangle edge plane 86 | float3 e0 = float3(p1.pos.xy - p0.pos.xy, 0); 87 | float3 e1 = float3(p2.pos.xy - p1.pos.xy, 0); 88 | float3 e2 = float3(p0.pos.xy - p2.pos.xy, 0); 89 | float3 n0 = cross(e0, float3(0, 0, 1)); 90 | float3 n1 = cross(e1, float3(0, 0, 1)); 91 | float3 n2 = cross(e2, float3(0, 0, 1)); 92 | 93 | //dilate the triangle 94 | // julian: I can't figure out why the dilate-offset sometimes produces insane distorted triangels 95 | // so I normalize the offset, which works pretty well so far 96 | p0.pos.xy += pl*normalize((e2.xy / dot(e2.xy, n0.xy)) + (e0.xy / dot(e0.xy, n2.xy))); 97 | p1.pos.xy += pl*normalize((e0.xy / dot(e0.xy, n1.xy)) + (e1.xy / dot(e1.xy, n0.xy))); 98 | p2.pos.xy += pl*normalize((e1.xy / dot(e1.xy, n2.xy)) + (e2.xy / dot(e2.xy, n1.xy))); 99 | 100 | triStream.Append(p0); 101 | triStream.Append(p1); 102 | triStream.Append(p2); 103 | 104 | triStream.RestartStrip(); 105 | } 106 | 107 | struct RenderTargets 108 | { 109 | float4 worldPos : SV_Target0; 110 | float4 worldNormal : SV_Target1; 111 | float4 worldTangent : SV_Target2; 112 | }; 113 | 114 | 115 | RenderTargets frag (v2f i) 116 | { 117 | float3 worldPos = i.worldPosition; 118 | 119 | // calculate derivate to get the position that is exacly on uv 120 | float2x3 ddWorldspace = float2x3(ddx(worldPos), ddy(worldPos)); 121 | float3 offset = ddWorldspace[0] + ddWorldspace[1]; 122 | offset /= 2.0; 123 | 124 | //worldPos -= offset; 125 | RenderTargets o; 126 | o.worldPos = float4(worldPos /*+ i.worldNormal * 1.05*/, 1) ; 127 | o.worldNormal = float4(i.worldNormal, 1); 128 | o.worldTangent = i.worldTangent; 129 | return o; 130 | } 131 | ENDCG 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /SpecularLightmaps/SpecularLightmapping.cs: -------------------------------------------------------------------------------- 1 | // 2 | // BakedReflections implementation for Unity by Julian Heinken (@schneckerstein) v1 3 | // 4 | // USAGE: 5 | // 1. Place this script on the object you like to have reflections for. 6 | // NOTE: The implementation relies on the second uv channel (UV2). Therefore, it will only work if you activated "Generate Lightmap UVs" in the import settings of your mesh. 7 | // 2. Change the shader of the material to "Unlit/displayBakedReflections" (or modify your own shader) 8 | // 3. Modify "Slice Count Level" and "Resolution Level" to your preferences, click on "Start" to start baking the reflection atlas. 9 | // Its not recommended to let "Total Axis Size" exceed 8192, since this is the highest texture resolution unity is able to import later. 10 | // 4. Click on "Export to Exr" to export the reflection atlas. (Default location is "Assets/Baked SurfaceReflections") 11 | 12 | //MIT-License 13 | //Copyright(c) 2018 Julian Heinken 14 | //Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, 15 | //including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 16 | //The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 17 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | //IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 19 | //ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | 21 | #if UNITY_EDITOR //TODO: find smater way 22 | using System; 23 | using System.Collections.Generic; 24 | using System.Collections; 25 | using UnityEditor; 26 | using UnityEngine; 27 | using System.Linq; //TODO: remove for release 28 | using UnityEngine.Rendering; 29 | namespace SpecularLightmapping 30 | { 31 | [ExecuteInEditMode] 32 | public class SpecularLightmapping : MonoBehaviour 33 | { 34 | private const string EXPORT_DIRECTORY = "Assets/Baked SurfaceReflections"; 35 | 36 | public Material TargetMaterial; 37 | [Header("Options"), Space] 38 | public bool Dilation; 39 | 40 | [Range(1f, 10f)] 41 | public int sliceCountLevel = 2; 42 | [Range(1f, 10f)] 43 | public int resolutionLevel = 2; 44 | [Space] 45 | public int sliceCount = 16384; 46 | public int cubeMapResolution = 16; 47 | public int totalAtlasAxisSize; 48 | [Space] 49 | public bool start; 50 | [Space] 51 | public RenderTexture reflectionAtlas; 52 | 53 | const Texture2D.EXRFlags ExportExrFlags = Texture2D.EXRFlags.CompressZIP; 54 | public bool exportToExr; 55 | 56 | private GameObject renderCamera_GameObject; 57 | private Camera RenderCamera; 58 | 59 | [HideInInspector] 60 | public Shader ExtractShader, CubeMapTo2DTextureShader; 61 | 62 | 63 | struct Line 64 | { 65 | public Vector3 start, end; 66 | 67 | public Line(Vector3 start, Vector3 end) 68 | { 69 | this.start = start; 70 | this.end = end; 71 | } 72 | } 73 | 74 | private int slicesPerAxis = 0; 75 | void OnValidate() 76 | { 77 | sliceCount = Mathf.RoundToInt(Mathf.Pow(2f, sliceCountLevel * 2f)); // can only use even powers of two. from odd ones, there's no square root that is a power of two it self. 78 | cubeMapResolution = Mathf.RoundToInt(Mathf.Pow(2f, resolutionLevel)); 79 | totalAtlasAxisSize = Mathf.RoundToInt(Mathf.Sqrt(sliceCount) * cubeMapResolution); 80 | slicesPerAxis = (Mathf.RoundToInt(Mathf.Sqrt(sliceCount))); 81 | Debug.Assert(Mathf.IsPowerOfTwo(slicesPerAxis)); 82 | } 83 | void Awake() 84 | { 85 | var meshRenderer = GetComponent(); 86 | if (!meshRenderer) 87 | return; 88 | 89 | TargetMaterial = meshRenderer.sharedMaterial; 90 | } 91 | 92 | void Update () { 93 | if (start) 94 | { 95 | start = false; 96 | debugPoints.Clear(); 97 | PrepareReflectionBuild(); 98 | CurrentState = WorkerState.Baking; 99 | EditorApplication.update += EditorUpdate; 100 | } 101 | if (exportToExr) 102 | { 103 | exportToExr = false; 104 | ExportToFile(true); 105 | } 106 | } 107 | 108 | void GetReflectionProbePositions(out Vector3[] positions, out Vector3[] normals, out Vector4[] tangents, Shader ExtractShader, Matrix4x4 localToWorld, Mesh mesh, int sliceCount) 109 | { 110 | // setup extraction shader 111 | Material extractMaterial = new Material(ExtractShader); 112 | extractMaterial.SetPass(0); 113 | 114 | // create and setup renderbuffers 115 | RenderTexture worldPositionBuffer = new RenderTexture(slicesPerAxis, slicesPerAxis, 0, RenderTextureFormat.ARGBFloat); 116 | worldPositionBuffer.Create(); 117 | RenderTexture worldNormalBuffer = new RenderTexture(slicesPerAxis, slicesPerAxis, 0, RenderTextureFormat.ARGBFloat); 118 | worldNormalBuffer.Create(); 119 | RenderTexture worldTangentBuffer = new RenderTexture(slicesPerAxis, slicesPerAxis, 0, RenderTextureFormat.ARGBFloat); 120 | worldTangentBuffer.Create(); 121 | 122 | RenderBuffer[] renderBuffers = new RenderBuffer[3]; 123 | renderBuffers[0] = worldPositionBuffer.colorBuffer; 124 | renderBuffers[1] = worldNormalBuffer.colorBuffer; 125 | renderBuffers[2] = worldTangentBuffer.colorBuffer; 126 | 127 | RenderTexture depth = new RenderTexture(slicesPerAxis, slicesPerAxis, 32, RenderTextureFormat.Depth); 128 | RenderTargetSetup renderTargetSetup = new RenderTargetSetup(renderBuffers, depth.depthBuffer); 129 | RenderTexture prevRenderTarget = RenderTexture.active; 130 | Graphics.SetRenderTarget(renderTargetSetup); 131 | 132 | GL.Clear(true, true, Color.clear); 133 | 134 | // render the mesh 135 | Graphics.DrawMeshNow(mesh, localToWorld); 136 | 137 | // copy the result back from the GPU 138 | Texture2D samplePositionsCPU = new Texture2D(slicesPerAxis, slicesPerAxis, TextureFormat.RGBAFloat, false, true); 139 | RenderTexture.active = worldPositionBuffer; 140 | samplePositionsCPU.ReadPixels(new Rect(0, 0, samplePositionsCPU.width, samplePositionsCPU.height), 0, 0); 141 | samplePositionsCPU.Apply(); 142 | 143 | Texture2D sampleNormalsCPU = new Texture2D(slicesPerAxis, slicesPerAxis, TextureFormat.RGBAFloat, false, true); 144 | RenderTexture.active = worldNormalBuffer; 145 | sampleNormalsCPU.ReadPixels(new Rect(0, 0, sampleNormalsCPU.width, sampleNormalsCPU.height), 0, 0); 146 | sampleNormalsCPU.Apply(); 147 | 148 | Texture2D sampleTangentsCPU = new Texture2D(slicesPerAxis, slicesPerAxis, TextureFormat.RGBAFloat, false, true); 149 | RenderTexture.active = worldTangentBuffer; 150 | sampleTangentsCPU.ReadPixels(new Rect(0, 0, sampleTangentsCPU.width, sampleTangentsCPU.height), 0, 0); 151 | sampleTangentsCPU.Apply(); 152 | 153 | RenderTexture.active = prevRenderTarget; 154 | positions = new Vector3[sliceCount]; 155 | Color[] samplesPositionsColors = samplePositionsCPU.GetPixels(); 156 | 157 | normals = new Vector3[sliceCount]; 158 | Color[] samplesNormalColors = sampleNormalsCPU.GetPixels(); 159 | 160 | tangents = new Vector4[sliceCount]; 161 | Color[] samplesTangentColors = sampleTangentsCPU.GetPixels(); 162 | 163 | // copy the results into usable arrays 164 | for (int i = 0; i < sliceCount; i++) 165 | { 166 | Color c = samplesPositionsColors[i]; 167 | positions[i] = new Vector3(c.r, c.g, c.b); 168 | 169 | Color n = samplesNormalColors[i]; 170 | normals[i] = new Vector3(n.r, n.g, n.b); 171 | 172 | Color t = samplesTangentColors[i]; 173 | tangents[i] = new Vector4(t.r, t.g, t.b, t.a); 174 | } 175 | 176 | // cleanup 177 | worldPositionBuffer.Release(); 178 | worldNormalBuffer.Release(); 179 | worldTangentBuffer.Release(); 180 | 181 | depth.Release(); 182 | DestroyImmediate(samplePositionsCPU); 183 | DestroyImmediate(sampleNormalsCPU); 184 | DestroyImmediate(sampleTangentsCPU); 185 | } 186 | 187 | private const RenderTextureFormat DefaultRenderTextureFormat = RenderTextureFormat.ARGBHalf; 188 | private RenderTexture tmpCubemap; 189 | private Vector3[] samplesPositions, sampleNormals; 190 | private Vector4[] samplesTangent; 191 | private Material cubemapToTexture; 192 | private const string cubemapToTexture_HDR_keyword = "RENDERTARGET_HDR"; 193 | void PrepareReflectionBuild() 194 | { 195 | isCovered = new bool[sliceCount]; 196 | debugVectors.Clear(); 197 | debugLines.Clear(); 198 | ReflectionAtlas_AxisSize = Mathf.RoundToInt(Mathf.Sqrt(sliceCount) ); 199 | 200 | GetReflectionProbePositions( 201 | out samplesPositions, 202 | out sampleNormals, 203 | out samplesTangent, 204 | ExtractShader, 205 | transform.localToWorldMatrix, 206 | GetComponent().sharedMesh, 207 | sliceCount); 208 | 209 | const int SuperSamplingAA = 8; 210 | tmpCubemap = new RenderTexture(cubeMapResolution * SuperSamplingAA, cubeMapResolution * SuperSamplingAA, 32, DefaultRenderTextureFormat) 211 | { 212 | useMipMap = true, 213 | autoGenerateMips = true, 214 | dimension = TextureDimension.Cube 215 | }; 216 | tmpCubemap.Create(); 217 | 218 | 219 | 220 | if (reflectionAtlas != null) 221 | { 222 | reflectionAtlas.Release(); 223 | } 224 | reflectionAtlas = new RenderTexture(ReflectionAtlas_AxisSize * cubeMapResolution, ReflectionAtlas_AxisSize * cubeMapResolution, 0, DefaultRenderTextureFormat) 225 | { 226 | anisoLevel = 0, 227 | useMipMap = false, 228 | autoGenerateMips = false 229 | }; 230 | reflectionAtlas.Create(); 231 | cubemapToTexture = new Material(CubeMapTo2DTextureShader); //TODO: release??? 232 | 233 | workIndex = 0; 234 | needWork = true; 235 | 236 | if (!renderCamera_GameObject) 237 | { 238 | renderCamera_GameObject = new GameObject(); 239 | renderCamera_GameObject.hideFlags = HideFlags.HideAndDontSave; 240 | RenderCamera = renderCamera_GameObject.AddComponent(); 241 | RenderCamera.clearFlags = CameraClearFlags.Skybox; 242 | RenderCamera.allowHDR = true; 243 | } 244 | 245 | if (TargetMaterial != null) 246 | { 247 | TargetMaterial.SetTexture("_ReflectionArray", reflectionAtlas); 248 | TargetMaterial.SetVector("_BakedReflectionParams", new Vector4(ReflectionAtlas_AxisSize, 1f / ReflectionAtlas_AxisSize)); 249 | } 250 | } 251 | 252 | private bool needWork = false; 253 | private int workIndex, ReflectionAtlas_AxisSize; 254 | private WorkerState CurrentState = WorkerState.Idle; 255 | void EditorUpdate() 256 | { 257 | switch (CurrentState) 258 | { 259 | case WorkerState.Idle: 260 | break; 261 | case WorkerState.Baking: 262 | needWork = Work(); 263 | bool cancelByUser = EditorUtility.DisplayCancelableProgressBar( 264 | "Baking Cubemap for " + this.name, 265 | workIndex + " / " + sliceCount, 266 | workIndex / (float) sliceCount); 267 | if (!needWork || cancelByUser) 268 | CurrentState = WorkerState.Finish; 269 | break; 270 | case WorkerState.Finish: 271 | bool anySliceCovered = isCovered.Cast().Any(x => x); 272 | 273 | if (Dilation && anySliceCovered) //also take care over empty atlases to prevent infinity loops 274 | { 275 | Vector2 axisSizeRCP = new Vector2(1f / slicesPerAxis, 1f / slicesPerAxis); 276 | Texture2D debugSlice = new Texture2D(cubeMapResolution, cubeMapResolution, TextureFormat.RGBAHalf, false, false); 277 | for (int i = 0; i < sliceCount; i++) 278 | { 279 | continue; 280 | if(isCovered[i]) 281 | continue; 282 | Vector2Int slice = IndexToCoordinate(i); 283 | Vector2 uvCoord = axisSizeRCP * slice; 284 | Color uvColor = new Color(uvCoord.x, uvCoord.y, 0, 0); 285 | debugSlice.SetPixels(Enumerable.Repeat(uvColor, cubeMapResolution * cubeMapResolution).ToArray()); 286 | debugSlice.Apply(); 287 | 288 | Graphics.CopyTexture( 289 | debugSlice, 0, 0, 290 | 0, //src X 291 | 0, //src Y 292 | cubeMapResolution, //src width 293 | cubeMapResolution, //src height 294 | 295 | reflectionAtlas, 0, 0, 296 | slice.x * cubeMapResolution, //dst X 297 | slice.y * cubeMapResolution //dst Y 298 | ); 299 | } 300 | 301 | bool didAnyDialation = true; 302 | while(didAnyDialation) { 303 | didAnyDialation = false; 304 | for (int i = 0; i < sliceCount; i++) { 305 | if(isCovered[i]) 306 | continue; 307 | 308 | if(Dilate(i)) { 309 | isCovered[i] = true; 310 | didAnyDialation = true; 311 | } 312 | } 313 | } 314 | 315 | print("debugPoints=" + debugPoints.Count); 316 | } 317 | 318 | // debugPoints = samplesPositions; 319 | RenderTexture.active = null; 320 | CleanUp(); 321 | CurrentState = WorkerState.Idle; 322 | EditorApplication.update -= EditorUpdate; 323 | 324 | break; 325 | default: 326 | throw new ArgumentOutOfRangeException(); 327 | } 328 | } 329 | 330 | // src: https://stackoverflow.com/questions/620605/how-to-make-a-valid-windows-filename-from-an-arbitrary-string#620619 331 | static string ValidateFileName(string fileName) 332 | { 333 | foreach (char c in System.IO.Path.GetInvalidFileNameChars()) 334 | { 335 | fileName = fileName.Replace(c, '_'); 336 | } 337 | 338 | return fileName; 339 | } 340 | void ExportToFile(bool toEXR) 341 | { 342 | if (reflectionAtlas != null) 343 | { 344 | RenderTexture.active = reflectionAtlas; 345 | 346 | Texture2D reflectionAtlas_tex = new Texture2D( 347 | reflectionAtlas.width, 348 | reflectionAtlas.height, 349 | TextureFormat.RGBAHalf, 350 | false, 351 | toEXR); 352 | reflectionAtlas_tex.ReadPixels(new Rect(0, 0, reflectionAtlas_tex.width, reflectionAtlas_tex.height), 0, 0); 353 | reflectionAtlas_tex.Apply(); 354 | 355 | if (!System.IO.Directory.Exists(EXPORT_DIRECTORY)) 356 | System.IO.Directory.CreateDirectory(EXPORT_DIRECTORY); 357 | 358 | string path = EXPORT_DIRECTORY + "/" + ValidateFileName(this.name) + (toEXR ? ".exr" : ".png"); 359 | System.IO.File.WriteAllBytes(path, toEXR ? reflectionAtlas_tex.EncodeToEXR(ExportExrFlags) : reflectionAtlas_tex.EncodeToPNG()); 360 | print("exported reflection atlas to: " + path); 361 | 362 | UnityEditor.AssetDatabase.Refresh(); 363 | RenderTexture.active = null; 364 | 365 | DestroyImmediate(reflectionAtlas_tex); 366 | } 367 | } 368 | void CleanUp() 369 | { 370 | EditorUtility.ClearProgressBar(); 371 | if (tmpCubemap) 372 | tmpCubemap.Release(); 373 | 374 | DestroyImmediate(renderCamera_GameObject); 375 | } 376 | private enum WorkerState 377 | { 378 | Idle, 379 | Baking, 380 | Finish, 381 | } 382 | 383 | bool[] isCovered; 384 | 385 | private Vector2Int IndexToCoordinate(int index) { 386 | return new Vector2Int(index % ReflectionAtlas_AxisSize, index / ReflectionAtlas_AxisSize); 387 | } 388 | 389 | private int CoordinateToIndex(Vector2Int coordinate) { 390 | return coordinate.y * ReflectionAtlas_AxisSize + coordinate.x; 391 | } 392 | List debugLines = new List(), debugVectors = new List(); 393 | private bool Work() 394 | { 395 | var start = DateTime.Now; 396 | const int MAX_TIME_PER_ITERATION = 30; 397 | while ((DateTime.Now - start).Milliseconds < MAX_TIME_PER_ITERATION && workIndex < sliceCount) 398 | { 399 | Vector3 normal = sampleNormals[workIndex]; 400 | 401 | if (normal != Vector3.zero) 402 | { 403 | RenderCamera.transform.position = samplesPositions[workIndex]; 404 | RenderCamera.RenderToCubemap(tmpCubemap); 405 | 406 | Vector3 tangent = samplesTangent[workIndex]; 407 | Vector3 biTangent = Vector3.Cross(normal, tangent) * samplesTangent[workIndex].w; 408 | 409 | //construct world to hemisphere matrix 410 | Matrix4x4 toHemisphere = new Matrix4x4(tangent, biTangent, normal, Vector4.zero); 411 | cubemapToTexture.SetMatrix("normalToHemisphere", toHemisphere); 412 | Vector2Int destination = IndexToCoordinate(workIndex); 413 | 414 | float destinationX_01 = (destination.x * cubeMapResolution) / (float)reflectionAtlas.width; 415 | float destinationY_01 = (destination.y * cubeMapResolution) / (float)reflectionAtlas.width; 416 | float sliceSize_01 = cubeMapResolution / (float) reflectionAtlas.width; 417 | cubemapToTexture.SetVector("_vertexTransform", new Vector4(sliceSize_01, sliceSize_01, destinationX_01, destinationY_01)); 418 | Graphics.Blit(tmpCubemap, reflectionAtlas, cubemapToTexture); 419 | 420 | isCovered[workIndex] = true; 421 | } 422 | workIndex++; 423 | } 424 | 425 | return workIndex < sliceCount; 426 | } 427 | 428 | /// 429 | /// Fills the slice with a convered neighbour slice 430 | /// 431 | /// 432 | /// True if the dialation was succsessfull 433 | private bool Dilate(int index) 434 | { 435 | Texture2D debugSlice = new Texture2D(cubeMapResolution, cubeMapResolution, TextureFormat.RGBAHalf, false, false); 436 | debugSlice.SetPixels(Enumerable.Repeat(Color.red, cubeMapResolution * cubeMapResolution).ToArray()); 437 | debugSlice.Apply(); 438 | Vector2Int slice = IndexToCoordinate(index); 439 | for (int x = -1; x < 2; x++) { 440 | for (int y = -1; y < 2; y++) { 441 | if(x == 0 && y == 0) 442 | continue; 443 | 444 | Vector2Int offset = new Vector2Int(x, y); 445 | Vector2Int referenceSlicePosition = slice + offset; 446 | // referenceSlicePosition.y = (slicesPerAxis - 1) - referenceSlicePosition.y; 447 | bool isCoordinateInBounds = 448 | 0 <= referenceSlicePosition.x && referenceSlicePosition.x < slicesPerAxis && 449 | 0 <= referenceSlicePosition.y && referenceSlicePosition.y < slicesPerAxis; 450 | 451 | int referenceSliceIndex = CoordinateToIndex(referenceSlicePosition); 452 | if (isCoordinateInBounds && isCovered[referenceSliceIndex]) 453 | { 454 | 455 | debugPoints.Add(samplesPositions[referenceSliceIndex]); 456 | 457 | Vector2 axisSizeRCP = new Vector2(1f / slicesPerAxis, 1f / slicesPerAxis); 458 | Vector2 uvCoord = axisSizeRCP * referenceSlicePosition; 459 | Color uvColor = new Color(uvCoord.x, uvCoord.y, 0); 460 | debugSlice.SetPixels(Enumerable.Repeat(uvColor, cubeMapResolution * cubeMapResolution).ToArray()); 461 | debugSlice.Apply(); 462 | RenderTexture tmpTexture = RenderTexture.GetTemporary(cubeMapResolution, cubeMapResolution, 0, DefaultRenderTextureFormat); 463 | // can't have source- and destination texture to be the same object, so we first copy the slice into a temporal texture 464 | Graphics.CopyTexture( 465 | reflectionAtlas, 466 | 0, //src element 467 | 0, //src mip 468 | referenceSlicePosition.x * cubeMapResolution, //src X 469 | referenceSlicePosition.y * cubeMapResolution, //src Y 470 | cubeMapResolution, //src width 471 | cubeMapResolution, //src height 472 | tmpTexture, 473 | 0, // dst element 474 | 0, // dst mip 475 | 0, // dst x 476 | 0 // dst y 477 | ); 478 | 479 | //Graphics.Blit(debugSlice, tmpTexture); 480 | 481 | Graphics.CopyTexture( 482 | tmpTexture, 0, 0, 483 | 0, //src X 484 | 0, //src Y 485 | cubeMapResolution, //src width 486 | cubeMapResolution, //src height 487 | 488 | reflectionAtlas, 0, 0, 489 | slice.x * cubeMapResolution, //dst X 490 | slice.y * cubeMapResolution //dst Y 491 | ); 492 | 493 | RenderTexture.ReleaseTemporary(tmpTexture); 494 | DestroyImmediate(debugSlice); 495 | return true; 496 | } else { 497 | offset = offset; 498 | } 499 | } 500 | } 501 | DestroyImmediate(debugSlice); 502 | return false; 503 | } 504 | 505 | private List debugPoints = new List(); 506 | [Header("Debug")] 507 | public bool showDebugProbes; 508 | public float debugProbeScale = 1f; 509 | 510 | private void OnDrawGizmosSelected() 511 | { 512 | if (!showDebugProbes || debugPoints == null) 513 | return; 514 | 515 | Gizmos.color = Color.black; 516 | 517 | for (int i = 0; i < debugPoints.Count; i++) 518 | { 519 | if (i == debugPoints.Count - 1) 520 | { 521 | Gizmos.color = Color.white; 522 | } 523 | Gizmos.DrawSphere(debugPoints[i], debugProbeScale); 524 | Gizmos.color = Color.gray; 525 | } 526 | for (int i = 0; i < debugLines.Count; i++) 527 | { 528 | Gizmos.DrawLine(debugLines[i].start, debugLines[i].end); 529 | } 530 | Gizmos.color = Color.red; 531 | for (int i = 0; i < debugVectors.Count; i++) 532 | { 533 | Gizmos.DrawLine(debugVectors[i].start, debugVectors[i].end); 534 | } 535 | } 536 | private void OnDestroy() 537 | { 538 | if (EditorApplication.update != null) 539 | { 540 | EditorApplication.update -= EditorUpdate; 541 | } 542 | CleanUp(); 543 | } 544 | } 545 | #endif 546 | } --------------------------------------------------------------------------------