コード例 #1
0
    private static bool CaptureViews(Transform root, BillboardImposter imposter, Snapshots[] snapshots,
                                     Transform lightingRoot, Shader albedoBake, Shader normalBake, ComputeShader processCompute)
    {
        Vector3 originalScale = root.localScale;

        //reset root local scale
        root.localScale = Vector3.one;

        var prevRt = RenderTexture.active;

        ///////////////// create the atlas for base and pack

        //base target
        var baseAtlas = RenderTexture.GetTemporary(_atlasResolution, _atlasResolution, 0, RenderTextureFormat.ARGB32,
                                                   RenderTextureReadWrite.Linear);

        baseAtlas.enableRandomWrite = true;
        baseAtlas.Create();

        //world normal target
        var packAtlas = RenderTexture.GetTemporary(_atlasResolution, _atlasResolution, 0, RenderTextureFormat.ARGB32,
                                                   RenderTextureReadWrite.Linear);

        packAtlas.enableRandomWrite = true;
        packAtlas.Create();
        //temp
        var tempAtlas = RenderTexture.GetTemporary(baseAtlas.descriptor);

        tempAtlas.Create();

        ////////////// create the single frame RT for base and pack

        var frameReso = _atlasResolution / imposter.Frames;

        //base frame (multiple frames make up target)
        var frame = RenderTexture.GetTemporary(frameReso, frameReso, 32, RenderTextureFormat.ARGB32,
                                               RenderTextureReadWrite.Linear);

        frame.enableRandomWrite = true;
        frame.Create();

        //world normal frame
        var packFrame = RenderTexture.GetTemporary(frameReso, frameReso, 32, RenderTextureFormat.ARGB32,
                                                   RenderTextureReadWrite.Linear);

        packFrame.Create();

        //temp
        var tempFrame = RenderTexture.GetTemporary(frame.descriptor);

        tempFrame.Create();

        //high-res frame, intended for super sampling
        //TODO proper super sampling
        //upscale 4 times
        var frameResUpscale = frameReso * 4;
        var superSizedFrame = RenderTexture.GetTemporary(frameResUpscale, frameResUpscale, 32, RenderTextureFormat.ARGB32,
                                                         RenderTextureReadWrite.Linear);

        superSizedFrame.enableRandomWrite = true;
        superSizedFrame.Create();
        //temp
        var superSizedFrameTemp = RenderTexture.GetTemporary(superSizedFrame.descriptor);

        var superSizedAlphaMask = RenderTexture.GetTemporary(superSizedFrame.descriptor);

        superSizedAlphaMask.Create();

        //////////// create the Texture2D used for writing final image
        imposter.BaseTexture = new Texture2D(baseAtlas.width, baseAtlas.height, TextureFormat.ARGB32, true, true);
        imposter.PackTexture = new Texture2D(baseAtlas.width, baseAtlas.height, TextureFormat.ARGB32, true, true);

        //compute buffer for distance alpha
        ComputeBuffer minDistancesBuffer = new ComputeBuffer(frame.width * frame.height, sizeof(float));
        ComputeBuffer maxDistanceBuffer  = new ComputeBuffer(1, sizeof(float));

        const int layer = 30;

        var clearColor = Color.clear;

        //create camera
        var camera = new GameObject().AddComponent <Camera>();

        camera.gameObject.hideFlags = HideFlags.DontSave;
        camera.cullingMask          = 1 << layer;
        camera.clearFlags           = CameraClearFlags.SolidColor;
        camera.backgroundColor      = clearColor;
        camera.orthographic         = true;
        camera.nearClipPlane        = 0f;
        camera.farClipPlane         = imposter.Radius * 2f;
        camera.orthographicSize     = imposter.Radius;
        camera.allowMSAA            = false;
        camera.enabled = false;

        var frameCount = imposter.Frames * imposter.Frames;

        //set and store original layer to restore afterwards
        var originalLayers = new Dictionary <GameObject, int>();

        StoreLayers(root, layer, ref originalLayers);

        var originalLights = new Dictionary <Light, bool>();
        var customLit      = lightingRoot != null;

        //custom lit renders with lighting into base RGB
        if (customLit)
        {
            //toggle all lights off except for lighting rig
            var lights = FindObjectsOfType <Light>();
            for (var i = 0; i < lights.Length; i++)
            {
                //not part of lighting rig
                if (!lights[i].transform.IsChildOf(lightingRoot))
                {
                    if (originalLights.ContainsKey(lights[i]))
                    {
                        continue;
                    }
                    //store original state
                    originalLights.Add(lights[i], lights[i].enabled);
                    //toggle it off
                    lights[i].enabled = false;
                }
                else
                {
                    //is part of lighting rig
                    lights[i].enabled = true;
                    //store state as off
                    if (!originalLights.ContainsKey(lights[i]))
                    {
                        originalLights.Add(lights[i], false);
                    }
                }
            }
        }

        //first render solid color replacement, checking for filled pixels
        //this decides if the camera can be cropped in closer to maximize atlas usage
        var tempMinMaxRT = RenderTexture.GetTemporary(frame.width, frame.height, 0, RenderTextureFormat.ARGB32);

        tempMinMaxRT.Create();

        Graphics.SetRenderTarget(tempMinMaxRT);
        GL.Clear(true, true, Color.clear);

        camera.clearFlags      = CameraClearFlags.Nothing;
        camera.backgroundColor = clearColor;
        camera.targetTexture   = tempMinMaxRT;

        var min = Vector2.one * frame.width;
        var max = Vector2.zero;

        for (var i = 0; i < frameCount; i++)
        {
            if (i > snapshots.Length - 1)
            {
                Debug.LogError("[IMP] snapshot data length less than frame count! this shouldn't happen!");
                continue;
            }

            //position camera with the current snapshot info
            var snap = snapshots[i];
            camera.transform.position = snap.Position;
            camera.transform.rotation = Quaternion.LookRotation(snap.Ray, Vector3.up);

            //render alpha only
            Shader.SetGlobalFloat("_ImposterRenderAlpha", 1f);
            camera.RenderWithShader(albedoBake, "");
            camera.ResetReplacementShader();

            //render without clearing (accumulating filled pixels)
            camera.Render();

            //supply the root position taken into camera space
            //this is for the min max, in the case root is further from opaque pixels
            var viewPos = camera.WorldToViewportPoint(root.position);
            var texPos  = new Vector2(viewPos.x, viewPos.y) * frame.width;
            texPos.x = Mathf.Clamp(texPos.x, 0f, frame.width);
            texPos.y = Mathf.Clamp(texPos.y, 0f, frame.width);
            min.x    = Mathf.Min(min.x, texPos.x);
            min.y    = Mathf.Min(min.y, texPos.y);
            max.x    = Mathf.Max(max.x, texPos.x);
            max.y    = Mathf.Max(max.y, texPos.y);
        }

        camera.clearFlags      = CameraClearFlags.SolidColor;
        camera.backgroundColor = clearColor;
        camera.targetTexture   = null;

        //now read render texture
        var tempMinMaxTex = new Texture2D(tempMinMaxRT.width, tempMinMaxRT.height, TextureFormat.ARGB32, false);

        RenderTexture.active = tempMinMaxRT;
        tempMinMaxTex.ReadPixels(new Rect(0f, 0f, tempMinMaxRT.width, tempMinMaxRT.height), 0, 0);
        tempMinMaxTex.Apply();

        var tempTexC = tempMinMaxTex.GetPixels32();

        //loop pixels get min max
        for (var c = 0; c < tempTexC.Length; c++)
        {
            if (tempTexC[c].r != 0x00)
            {
                var texPos = Get2DIndex(c, tempMinMaxRT.width);
                min.x = Mathf.Min(min.x, texPos.x);
                min.y = Mathf.Min(min.y, texPos.y);
                max.x = Mathf.Max(max.x, texPos.x);
                max.y = Mathf.Max(max.y, texPos.y);
            }
        }

        DestroyImmediate(tempMinMaxTex, true);
        RenderTexture.ReleaseTemporary(tempMinMaxRT);

        //rescale radius
        var len = new Vector2(max.x - min.x, max.y - min.y);

        ////add 2 pixels to x and y
        //len.x += 2f;
        //len.y += 2f;
        var maxR = Mathf.Max(len.x, len.y);

        var ratio = maxR / frame.width; //assume square

        //adjust ratio (if clipping is too tight)
        //ratio = Mathf.Lerp(1f, ratio, _pixelCrop);

        imposter.Radius = imposter.Radius * ratio;
        //adjust the camera size and far clip
        camera.farClipPlane     = imposter.Radius * 2f;
        camera.orthographicSize = imposter.Radius;

        //use a scale factor to make sure the offset is in the correct location
        //this is related to scaling the asset to 1,1,1 while baking, to ensure imposter matches all types of asset scaling
        Vector3 scaleFactor = new Vector3(root.localScale.x / originalScale.x, root.localScale.y / originalScale.y, root.localScale.z / originalScale.z);

        imposter.Offset = Vector3.Scale(imposter.Offset, scaleFactor);

        //recalculate snapshots
        snapshots = UpdateSnapshots(imposter.Frames, imposter.Radius, root.position + imposter.Offset, imposter.IsHalf);

        ///////////////////// rendering the actual frames
        int kernelCSDilate                = processCompute.FindKernel("CSDilate");
        int kernelCSDistanceAlpha         = processCompute.FindKernel("CSDistanceAlpha");
        int kernelCSDistanceAlphaGetMax   = processCompute.FindKernel("CSDistanceAlphaGetMax");
        int kernelCSDistanceAlphaFinalize = processCompute.FindKernel("CSDistanceAlphaFinalize");

        for (var frameIndex = 0; frameIndex < frameCount; frameIndex++)
        {
            if (frameIndex > snapshots.Length - 1)
            {
                Debug.LogError("[IMP] snapshot data length less than frame count! this shouldn't happen!");
                continue;
            }

            var snap = snapshots[frameIndex];
            camera.transform.position = snap.Position;
            camera.transform.rotation = Quaternion.LookRotation(snap.Ray, Vector3.up);
            clearColor = Color.clear;

            //target and clear base frame
            Graphics.SetRenderTarget(superSizedFrame);
            GL.Clear(true, true, clearColor);

            Graphics.SetRenderTarget(superSizedFrameTemp);
            GL.Clear(true, true, clearColor);

            //render into temp
            camera.targetTexture   = superSizedFrameTemp;
            camera.backgroundColor = clearColor;

            if (!customLit)
            {
                Shader.SetGlobalFloat("_ImposterRenderAlpha", 0f);
                camera.RenderWithShader(albedoBake, "");
                camera.ResetReplacementShader();
            }
            else
            {
                //render without replacement
                camera.Render();
            }

            camera.targetTexture   = superSizedAlphaMask;
            camera.backgroundColor = clearColor;
            camera.Render();

            //solidify alpha (uses step) //TODO probably dont need this anymore
            Graphics.Blit(superSizedAlphaMask, superSizedFrame, _processingMat, 3);
            Graphics.Blit(superSizedFrame, superSizedAlphaMask);

            //combine RGB and ALPHA
            _processingMat.SetTexture("_MainTex", superSizedFrameTemp);
            _processingMat.SetTexture("_MainTex2", superSizedAlphaMask);
            _processingMat.SetFloat("_Step", 1f);

            //result in frameUp
            Graphics.Blit(superSizedFrameTemp, superSizedFrame, _processingMat, 1);

            //target frame and clear, TODO proper sampling
            Graphics.SetRenderTarget(frame);
            GL.Clear(true, true, clearColor);
            Graphics.Blit(superSizedFrame, frame);

            //clear superSized frames for use with normals + depth
            Graphics.SetRenderTarget(superSizedFrameTemp);
            GL.Clear(true, true, clearColor);
            Graphics.SetRenderTarget(superSizedFrame);
            GL.Clear(true, true, clearColor);

            //render normals & depth
            //camera background half gray (helps with height displacement)
            clearColor             = new Color(0.0f, 0.0f, 0.0f, 0.5f);
            camera.targetTexture   = superSizedFrame;
            camera.backgroundColor = clearColor;
            camera.RenderWithShader(normalBake, "");
            camera.ResetReplacementShader();

            //clear the pack frame and write TODO proper sampling
            Graphics.SetRenderTarget(packFrame);
            GL.Clear(true, true, clearColor);
            Graphics.Blit(superSizedFrame, packFrame);


            //////////// perform processing on frames

            //pack frame is done first so alpha of base frame can be used as a mask (before distance alpha process)
            Graphics.SetRenderTarget(tempFrame);
            GL.Clear(true, true, Color.clear);

            //padding / dilate TODO can be improved?
            int threadsX, threadsY, threadsZ;
            CalcWorkSize(packFrame.width * packFrame.height, out threadsX, out threadsY, out threadsZ);
            processCompute.SetTexture(kernelCSDilate, "Source", packFrame);
            processCompute.SetTexture(kernelCSDilate, "SourceMask", frame);
            processCompute.SetTexture(kernelCSDilate, "Result", tempFrame);
            processCompute.SetBool("AllChannels", true);
            processCompute.SetBool("NormalsDepth", true);
            processCompute.Dispatch(kernelCSDilate, threadsX, threadsY, threadsZ);

            Graphics.Blit(tempFrame, packFrame);

            //Perform processing on base atlas, Albedo + alpha (alpha is modified)
            Graphics.SetRenderTarget(tempFrame);
            GL.Clear(true, true, Color.clear);

            //padding / dilate
            CalcWorkSize(frame.width * frame.height, out threadsX, out threadsY, out threadsZ);
            processCompute.SetTexture(kernelCSDilate, "Source", frame);
            processCompute.SetTexture(kernelCSDilate, "SourceMask", frame);
            processCompute.SetTexture(kernelCSDilate, "Result", tempFrame);
            processCompute.SetBool("AllChannels", false);
            processCompute.SetBool("NormalsDepth", false);
            processCompute.Dispatch(kernelCSDilate, threadsX, threadsY, threadsZ);

            Graphics.Blit(tempFrame, frame);

            Graphics.SetRenderTarget(tempFrame);
            GL.Clear(true, true, Color.clear);

            //distance field alpha
            //step 1 store min distance to unfilled alpha
            CalcWorkSize(frame.width * frame.height, out threadsX, out threadsY, out threadsZ);
            processCompute.SetTexture(kernelCSDistanceAlpha, "Source", frame);
            processCompute.SetTexture(kernelCSDistanceAlpha, "SourceMask", frame);
            processCompute.SetBuffer(kernelCSDistanceAlpha, "MinDistances", minDistancesBuffer);
            processCompute.Dispatch(kernelCSDistanceAlpha, threadsX, threadsY, threadsZ);

            //step 2 write maximum of the min distances to MaxDistanceBuffer[0]
            //also reset the min distances to 0 during this kernel
            processCompute.SetInt("MinDistancesLength", minDistancesBuffer.count);
            processCompute.SetBuffer(kernelCSDistanceAlphaGetMax, "MaxOfMinDistances", maxDistanceBuffer);
            processCompute.SetBuffer(kernelCSDistanceAlphaGetMax, "MinDistances", minDistancesBuffer);
            processCompute.Dispatch(kernelCSDistanceAlphaGetMax, 1, 1, 1);

            //step 3 write min distance / max of min to temp frame
            CalcWorkSize(frame.width * frame.height, out threadsX, out threadsY, out threadsZ);
            processCompute.SetTexture(kernelCSDistanceAlphaFinalize, "Source", frame);
            processCompute.SetTexture(kernelCSDistanceAlphaFinalize, "SourceMask", frame);
            processCompute.SetTexture(kernelCSDistanceAlphaFinalize, "Result", tempFrame);
            processCompute.SetBuffer(kernelCSDistanceAlphaFinalize, "MinDistances", minDistancesBuffer);
            processCompute.SetBuffer(kernelCSDistanceAlphaFinalize, "MaxOfMinDistances", maxDistanceBuffer);
            processCompute.Dispatch(kernelCSDistanceAlphaFinalize, threadsX, threadsY, threadsZ);

            Graphics.Blit(tempFrame, frame);

            //convert 1D index to flattened octahedra coordinate
            int x;
            int y;
            //this is 0-(frames-1) ex, 0-(12-1) 0-11 (for 12 x 12 frames)
            XYFromIndex(frameIndex, imposter.Frames, out x, out y);

            //X Y position to write frame into atlas
            //this would be frame index * frame width, ex 2048/12 = 170.6 = 170
            //so 12 * 170 = 2040, loses 8 pixels on the right side of atlas and top of atlas

            x *= frame.width;
            y *= frame.height;

            //copy base frame into base render target
            Graphics.CopyTexture(frame, 0, 0, 0, 0, frame.width, frame.height, baseAtlas, 0, 0, x, y);

            //copy normals frame into normals render target
            Graphics.CopyTexture(packFrame, 0, 0, 0, 0, packFrame.width, packFrame.height, packAtlas, 0, 0, x, y);
        }

        //read render target pixels
        Graphics.SetRenderTarget(packAtlas);
        imposter.PackTexture.ReadPixels(new Rect(0f, 0f, packAtlas.width, packAtlas.height), 0, 0);

        Graphics.SetRenderTarget(baseAtlas);
        imposter.BaseTexture.ReadPixels(new Rect(0f, 0f, baseAtlas.width, baseAtlas.height), 0, 0);

        //restore previous render target
        RenderTexture.active = prevRt;

        baseAtlas.Release();
        frame.Release();
        packAtlas.Release();
        packFrame.Release();

        RenderTexture.ReleaseTemporary(baseAtlas);
        RenderTexture.ReleaseTemporary(packAtlas);
        RenderTexture.ReleaseTemporary(tempAtlas);

        RenderTexture.ReleaseTemporary(frame);
        RenderTexture.ReleaseTemporary(packFrame);
        RenderTexture.ReleaseTemporary(tempFrame);

        RenderTexture.ReleaseTemporary(superSizedFrame);
        RenderTexture.ReleaseTemporary(superSizedAlphaMask);
        RenderTexture.ReleaseTemporary(superSizedFrameTemp);

        minDistancesBuffer.Dispose();
        maxDistanceBuffer.Dispose();

        DestroyImmediate(camera.gameObject, true);

        //restore layers
        RestoreLayers(originalLayers);

        //restore lights
        var enumerator2 = originalLights.Keys.GetEnumerator();

        while (enumerator2.MoveNext())
        {
            var light = enumerator2.Current;
            if (light != null)
            {
                light.enabled = originalLights[light];
            }
        }

        enumerator2.Dispose();
        originalLights.Clear();

        var savePath   = "";
        var file       = "";
        var filePrefab = "";

        if (imposter.AssetReference != null)
        {
            savePath = AssetDatabase.GetAssetPath(imposter.AssetReference);
            var lastSlash = savePath.LastIndexOf("/", StringComparison.Ordinal);
            var folder    = savePath.Substring(0, lastSlash);
            file = savePath.Substring(lastSlash + 1,
                                      savePath.LastIndexOf(".", StringComparison.Ordinal) - lastSlash - 1);

            filePrefab = file;

            savePath = folder + "/" + file + "_Imposter" + ".asset";
        }
        else //no prefab, ask where to save
        {
            file     = root.name;
            savePath = EditorUtility.SaveFilePanelInProject("Save Billboard Imposter", file + "_Imposter", "asset",
                                                            "Select save location");
        }

        imposter.PrefabSuffix = _suffix;

        imposter.name = file;

        AssetDatabase.CreateAsset(imposter, savePath);

        imposter.Save(savePath, file, _createUnityBillboard);

        //spawn
        var spawned = imposter.Spawn(root.position, true, filePrefab);

        spawned.transform.position   = root.position + new Vector3(2f, 0f, 2f);
        spawned.transform.rotation   = root.rotation;
        spawned.transform.localScale = originalScale;

        root.localScale = originalScale;

        return(true);
    }
コード例 #2
0
    private void Draw()
    {
        if (_processingMat == null)
        {
            Shader shader = Shader.Find("Hidden/XRA/IMP/ImposterProcessing");
            if (shader != null)
            {
                _processingMat = new Material(shader);
            }
            else
            {
                Debug.LogError("Imposter Baker Material NULL!");
            }
        }
        var noSelection = Selection.activeTransform == null;

        EditorGUI.BeginChangeCheck(); //check for settings change

        _atlasResolution = EditorGUILayout.IntPopup("Resolution", _atlasResolution, ResNames, ResSizes);

        _frames = EditorGUILayout.IntField(_labelFrames, Mathf.Clamp(_frames, 4, 32));

        if (!IsEven(_frames))
        {
            _frames -= 1;
        }

        //min is 2 x 2
        _frames = Mathf.Max(2, _frames);

        //pixel crop not needed, extra 2 pixels added to X Y, border of frames cleared to black
        //_pixelCrop = EditorGUILayout.Slider("PixelCrop", _pixelCrop, 0f, 1f);
        //_pixelCrop = Mathf.Clamp01(_pixelCrop);

        _isHalf = EditorGUILayout.Toggle(_labelHemisphere, _isHalf);

        EditorGUILayout.LabelField(_labelCustomLighting);
        _lightingRig = (Transform)EditorGUILayout.ObjectField(_lightingRig, typeof(Transform), true);

        var settingsChanged = EditorGUI.EndChangeCheck(); //end check

        EditorGUILayout.LabelField(_labelSuffix);
        _suffix = EditorGUILayout.TextField(_suffix);

        _createUnityBillboard = EditorGUILayout.Toggle(_labelUnityBillboard, _createUnityBillboard);

        //if selection changed, or settings were changed, rig is no longer ready for capture
        if (_root != Selection.activeTransform || settingsChanged)
        {
            Roots.Clear();
        }

        _root = Selection.activeTransform;

        if (noSelection)
        {
            return;
        }

        BillboardImposter imposterAsset = null;

        Snapshots[] snapshots = null;

        if (GUILayout.Button(_labelPreviewSnapshot))
        {
            if (SetupRig(_root, out imposterAsset, out snapshots))
            {
                DebugSnapshots(snapshots, imposterAsset.Radius);
            }
        }

        if (Selection.gameObjects != null && Selection.gameObjects.Length > 1 &&
            Selection.gameObjects.Length != Roots.Count)
        {
            for (var i = 0; i < Selection.gameObjects.Length; i++)
            {
                if (Selection.gameObjects[i].transform.parent == null)
                {
                    Roots.Add(Selection.gameObjects[i].transform);
                }
            }
        }

        if (Roots.Count > 1)
        {
            if (GUILayout.Button("Capture Multiple"))
            {
                for (var i = 0; i < Roots.Count; i++)
                {
                    EditorUtility.DisplayProgressBar("Capturing", "Capturing " + Roots[i].name,
                                                     (i + 1f) / Roots.Count);
                    if (SetupRig(Roots[i], out imposterAsset, out snapshots))
                    {
                        CaptureViews(Roots[i], imposterAsset, snapshots, _lightingRig, _bakeAlbedoShader, _bakeWorldNormalDepthShader, _processingCompute);
                    }
                }

                EditorUtility.ClearProgressBar();
            }
        }
        else if (_root != null)
        {
            if (GUILayout.Button("Capture"))
            {
                EditorUtility.DisplayProgressBar("Capturing", "Capturing " + _root.name, 1f);
                if (SetupRig(_root, out imposterAsset, out snapshots))
                {
                    CaptureViews(_root, imposterAsset, snapshots, _lightingRig, _bakeAlbedoShader, _bakeWorldNormalDepthShader, _processingCompute);
                }
                EditorUtility.ClearProgressBar();
            }
        }
    }
コード例 #3
0
    private static bool SetupRig(Transform root, out BillboardImposter imposterAsset, out Snapshots[] snapshots)
    {
        var mrs = root.GetComponentsInChildren <MeshRenderer>();

        imposterAsset = null;
        snapshots     = null;
        if (mrs == null || mrs.Length == 0)
        {
            return(false);
        }

        imposterAsset = CreateInstance <BillboardImposter>();

        //grow bounds, first centered on root transform
        var bounds = new Bounds(root.position, Vector3.zero);

        for (var i = 0; i < mrs.Length; i++)
        {
            //check if mesh renderer enabled
            if (!mrs[i].enabled)
            {
                continue;
            }
            //instead of encapsulating mesh renderer bounds, encapsulate vertices
            //this is because mesh bounds are sometimes much larger than needed
            var mf = mrs[i].GetComponent <MeshFilter>();
            if (mf == null || mf.sharedMesh == null || mf.sharedMesh.vertices == null)
            {
                continue;
            }
            var verts = mf.sharedMesh.vertices;
            for (var v = 0; v < verts.Length; v++)
            {
                var meshWorldVert   = mf.transform.localToWorldMatrix.MultiplyPoint3x4(verts[v]);
                var meshLocalToRoot = root.worldToLocalMatrix.MultiplyPoint3x4(meshWorldVert);
                var worldVert       = root.localToWorldMatrix.MultiplyPoint3x4(meshLocalToRoot);
                bounds.Encapsulate(worldVert);
            }
        }

        //the bounds will fit within the sphere
        var radius = Vector3.Distance(bounds.min, bounds.max) * 0.5f;

        imposterAsset.Radius          = radius;
        imposterAsset.Frames          = _frames;
        imposterAsset.IsHalf          = _isHalf;
        imposterAsset.AtlasResolution = _atlasResolution;
        imposterAsset.Offset          = bounds.center - root.position;

        Debug.DrawLine(bounds.min, bounds.max, Color.cyan, 1f);
#if UNITY_2018_2_OR_NEWER
        imposterAsset.AssetReference = (GameObject)PrefabUtility.GetCorrespondingObjectFromSource(root.gameObject);
#else
        imposterAsset.AssetReference = (GameObject)PrefabUtility.GetPrefabParent(root.gameObject);
#endif

        snapshots = UpdateSnapshots(_frames, radius, root.position + imposterAsset.Offset, _isHalf);

        DebugSnapshots(snapshots, radius * 0.1f);
        return(true);
    }