void Bake(GameObject[] selection) { if (Application.isPlaying || selection == null || selection.Length < 1) { return; } Material dataCacher = new Material(Shader.Find("Hidden/BNAO_DataCacher")); Material postProcess = new Material(Shader.Find("Hidden/BNAO_PostProcess")); Material dilate = new Material(Shader.Find("Hidden/BNAO_Dilate")); Material bnao = new Material(Shader.Find("Hidden/BNAO")); Material composite = new Material(Shader.Find("Hidden/BNAO_Composite")); Shader depthShader; if (cullOverrideMode == CullOverrideMode.ForceTwoSided) { depthShader = Shader.Find("Hidden/BNAO_Depth_2Sided"); } else if (cullOverrideMode == CullOverrideMode.ForceOneSided) { depthShader = Shader.Find("Hidden/BNAO_Depth_1Sided"); } else { depthShader = Shader.Find("Hidden/BNAO_Depth"); } // Get objects to be rendered var bnaoObjects = new Dictionary <Material, BNAOObject>(); var meshRenderers = new List <MeshRenderer>(); var skinnedMeshRenderers = new List <SkinnedMeshRenderer>(); foreach (var gameObject in selection) { meshRenderers.AddRange(gameObject.GetComponentsInChildren <MeshRenderer>()); skinnedMeshRenderers.AddRange(gameObject.GetComponentsInChildren <SkinnedMeshRenderer>()); } Material mat = null; foreach (var renderer in meshRenderers) { var meshFilter = renderer.GetComponent <MeshFilter>(); if (meshFilter && meshFilter.sharedMesh) { if (forceSharedTexture) { if (bnaoObjects.Count < 1) { mat = renderer.sharedMaterial; bnaoObjects.Add(mat, new BNAOObject(renderer.name, (int)bakeRes)); } bnaoObjects[mat].renderedMeshes.Add(new RendereredMesh(renderer, meshFilter.sharedMesh)); } else { if (!bnaoObjects.ContainsKey(renderer.sharedMaterial)) { bnaoObjects.Add(renderer.sharedMaterial, new BNAOObject(renderer.name, (int)bakeRes)); } bnaoObjects[renderer.sharedMaterial].renderedMeshes.Add(new RendereredMesh(renderer, meshFilter.sharedMesh)); } } } foreach (var renderer in skinnedMeshRenderers) { if (renderer.sharedMesh) { Mesh mesh = new Mesh(); mesh.name = renderer.sharedMesh.name + "_snapshot"; renderer.BakeMesh(mesh); if (forceSharedTexture) { if (bnaoObjects.Count < 1) { mat = renderer.sharedMaterial; bnaoObjects.Add(mat, new BNAOObject(renderer.name, (int)bakeRes)); } bnaoObjects[mat].renderedMeshes.Add(new RendereredMesh(renderer, mesh)); } else { if (!bnaoObjects.ContainsKey(renderer.sharedMaterial)) { bnaoObjects.Add(renderer.sharedMaterial, new BNAOObject(renderer.name, (int)bakeRes)); } bnaoObjects[renderer.sharedMaterial].renderedMeshes.Add(new RendereredMesh(renderer, mesh)); } } } if (bnaoObjects.Count < 1) { // No renderers found, abort return; } baking = true; // Mode switch switch (bakeMode) { case BakeMode.BentNormal: Shader.EnableKeyword("MODE_BN"); Shader.DisableKeyword("MODE_AO"); break; case BakeMode.AmbientOcclusion: Shader.DisableKeyword("MODE_BN"); Shader.EnableKeyword("MODE_AO"); break; } // Cache data to textures foreach (var bnaoObject in bnaoObjects) { Graphics.SetRenderTarget(new RenderBuffer[2] { bnaoObject.Value.positionCache.colorBuffer, bnaoObject.Value.normalCache.colorBuffer }, bnaoObject.Value.positionCache.depthBuffer); foreach (var renderMesh in bnaoObject.Value.renderedMeshes) { if (useNormalMaps && !forceSharedTexture) { if (renderMesh.renderer.sharedMaterial.HasProperty("_NormalTex")) { dataCacher.SetTexture("_NormalTex", renderMesh.renderer.sharedMaterial.GetTexture("_NormalTex")); dataCacher.SetFloat("_HasNormalTex", 1); } else if (renderMesh.renderer.sharedMaterial.HasProperty("_BumpMap")) { dataCacher.SetTexture("_NormalTex", renderMesh.renderer.sharedMaterial.GetTexture("_BumpMap")); dataCacher.SetFloat("_HasNormalTex", 1); } else { dataCacher.SetFloat("_HasNormalTex", 0); } } dataCacher.SetFloat("_UVChannel", (float)uvChannel); dataCacher.SetPass(0); Graphics.DrawMeshNow(renderMesh.mesh, renderMesh.renderer.transform.localToWorldMatrix); } } // Calculate spherical bounds of all objects to be rendered var bounds = new Bounds(); bool first = true; foreach (var bnaoObject in bnaoObjects) { foreach (var rendermesh in bnaoObject.Value.renderedMeshes) { if (first) { bounds = rendermesh.renderer.bounds; first = false; } else { bounds.Encapsulate(rendermesh.renderer.bounds); } } } Vector3 rendererCenter = bounds.center; float rendererRadius = Mathf.Max(Mathf.Max(bounds.extents.x, bounds.extents.y), bounds.extents.z); // Initialize shadow map var shadowMap = RenderTexture.GetTemporary((int)shadowMapRes, (int)shadowMapRes, 24, RenderTextureFormat.Shadowmap); // Initialize camera var go = new GameObject("BNAO_BakeCamera"); var camera = go.AddComponent <Camera>(); camera.enabled = false; camera.farClipPlane = rendererRadius * 2; camera.nearClipPlane = 0.01f; camera.orthographic = true; camera.orthographicSize = rendererRadius; camera.aspect = 1f; camera.targetTexture = shadowMap; // Disable rest of scene var scene = FindObjectsOfType <Renderer>(); var sceneEnabled = new bool[scene.Length]; for (int i = 0; i < scene.Length; i++) { sceneEnabled[i] = scene[i].enabled; if (!includeScene) { scene[i].enabled = false; } } // Force enable bake objects and force double sided rendering var shadowCastingModes = new List <ShadowCastingMode>(); foreach (var bnaoObject in bnaoObjects) { foreach (var renderMesh in bnaoObject.Value.renderedMeshes) { renderMesh.renderer.enabled = true; shadowCastingModes.Add(renderMesh.renderer.shadowCastingMode); renderMesh.renderer.shadowCastingMode = ShadowCastingMode.TwoSided; } } // Get random vectors (uniformly distributed points on sphere) var directions = PointsOnSphere((int)samples); for (int sample = 0; sample < (int)samples; sample++) { EditorUtility.DisplayProgressBar("Baking " + fullName, "Baking sample " + sample + " / " + (int)(samples) + "...", (float)sample / (float)samples); var dir = directions[sample]; // Position the camera camera.transform.position = rendererCenter + dir * rendererRadius; // Aim the camera camera.transform.rotation = Quaternion.LookRotation(-dir); // Render the shadow map if (useOriginalShaders) { camera.Render(); } else { camera.RenderWithShader(depthShader, "RenderType"); } // Bind shadow map Shader.SetGlobalTexture("_ShadowMap", shadowMap); // Calculate world-to-shadow matrix var proj = camera.projectionMatrix; if (SystemInfo.usesReversedZBuffer) { proj.m20 = -proj.m20; proj.m21 = -proj.m21; proj.m22 = -proj.m22; proj.m23 = -proj.m23; } var view = camera.worldToCameraMatrix; var scaleOffset = Matrix4x4.identity; scaleOffset.m00 = scaleOffset.m11 = scaleOffset.m22 = 0.5f; scaleOffset.m03 = scaleOffset.m13 = scaleOffset.m23 = 0.5f; var worldToShadow = scaleOffset * (proj * view); Shader.SetGlobalMatrix("_WorldToShadow", worldToShadow); Shader.SetGlobalFloat("_Samples", (float)samples); Shader.SetGlobalFloat("_Sample", (float)sample); Shader.SetGlobalVector("_Dir", dir); Shader.SetGlobalFloat("_ShadowBias", shadowBias); Shader.SetGlobalFloat("_ClampToHemisphere", clampToHemisphere ? 1f : 0f); foreach (var bnaoObject in bnaoObjects) { bnao.SetTexture("_PositionCache", bnaoObject.Value.positionCache); bnao.SetTexture("_NormalCache", bnaoObject.Value.normalCache); var tmp = RenderTexture.GetTemporary(bnaoObject.Value.result.descriptor); tmp.filterMode = FilterMode.Point; Graphics.Blit(bnaoObject.Value.result, tmp); bnao.SetTexture("_PrevTex", tmp); Graphics.Blit(bnaoObject.Value.positionCache, bnaoObject.Value.result, bnao, 0); RenderTexture.ReleaseTemporary(tmp); } } EditorUtility.DisplayProgressBar("Baking " + fullName, "Post Processing...", 1); // Post process foreach (var bnaoObject in bnaoObjects) { var tmp = RenderTexture.GetTemporary(bnaoObject.Value.result.descriptor); tmp.filterMode = FilterMode.Point; Graphics.SetRenderTarget(tmp); postProcess.SetTexture("_MainTex", bnaoObject.Value.result); postProcess.SetFloat("_AOBias", aoBias); postProcess.SetFloat("_UVChannel", (float)uvChannel); postProcess.SetFloat("_NormalsSpace", (float)bentNormalsSpace); postProcess.SetPass(0); foreach (var renderMesh in bnaoObject.Value.renderedMeshes) { postProcess.SetMatrix("_WorldToObject", renderMesh.renderer.transform.worldToLocalMatrix); Graphics.DrawMeshNow(renderMesh.mesh, renderMesh.renderer.transform.localToWorldMatrix); } Graphics.Blit(tmp, bnaoObject.Value.result); RenderTexture.ReleaseTemporary(tmp); } // Dilate foreach (var bnaoObject in bnaoObjects) { var tmp = RenderTexture.GetTemporary(bnaoObject.Value.result.descriptor); tmp.filterMode = FilterMode.Point; for (int i = 0; i < dilation; i++) { EditorUtility.DisplayProgressBar("Baking", "Dilating...", (float)i / (float)dilation); dilate.SetTexture("_MainTex", bnaoObject.Value.result); Graphics.Blit(bnaoObject.Value.result, tmp, dilate); i++; if (i < dilation) { dilate.SetTexture("_MainTex", tmp); Graphics.Blit(tmp, bnaoObject.Value.result, dilate); } else { Graphics.Blit(tmp, bnaoObject.Value.result); } } RenderTexture.ReleaseTemporary(tmp); } // Output int u = 0; foreach (var bnaoObject in bnaoObjects) { EditorUtility.DisplayProgressBar("Baking " + fullName, "Saving texture(s)...", (float)u / bnaoObjects.Count); var names = new List <string>(); foreach (var renderedMesh in bnaoObject.Value.renderedMeshes) { names.Add(renderedMesh.renderer.name); } switch (nameMode) { case NameMode.Shortest: names = names.OrderBy(x => x.Length).ToList <string>(); break; case NameMode.Longest: names = names.OrderByDescending(x => x.Length).ToList <string>(); break; case NameMode.Alphabetical: names.Sort(); break; case NameMode.Whatever: break; } RenderTextureToFile(bnaoObject.Value.result, outputPath + "/" + names[0] + "_" + bakeMode.ToString() + ".png", composite); u++; } // Restore shadow casting modes u = 0; foreach (var bnaoObject in bnaoObjects) { foreach (var renderMesh in bnaoObject.Value.renderedMeshes) { renderMesh.renderer.shadowCastingMode = shadowCastingModes[u]; u++; } } // Re-enable rest of scene for (int i = 0; i < scene.Length; i++) { scene[i].enabled = sceneEnabled[i]; } // Clean up RenderTexture.ReleaseTemporary(shadowMap); DestroyImmediate(camera.gameObject); UnityEditor.AssetDatabase.Refresh(); EditorUtility.ClearProgressBar(); baking = false; }