summaryrefslogtreecommitdiffstats
path: root/Scripts/Impostors.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Scripts/Impostors.cs')
-rw-r--r--Scripts/Impostors.cs432
1 files changed, 204 insertions, 228 deletions
diff --git a/Scripts/Impostors.cs b/Scripts/Impostors.cs
index 92ea581..6d8d67b 100644
--- a/Scripts/Impostors.cs
+++ b/Scripts/Impostors.cs
@@ -5,274 +5,250 @@ using System.IO;
[ExecuteInEditMode]
public class Impostors : MonoBehaviour
{
- [Header("Bounding Sphere")]
- [Tooltip("Sphere radius is controlled by the Transform scale (uses X component)")]
- public float sphere_radius_ = 1f;
+ [Header("Bounding Sphere")]
+ public float sphere_radius_ = 1f;
- [Header("Grid Settings")]
- [Tooltip("Number of lattice points along each axis (e.g., 5 = 5x5 = 25 points)")]
- [Range(2, 20)]
- public int gridResolution = 5;
+ [Header("Grid Settings")]
+ [Range(2, 20)] public int gridResolution = 5;
- [Header("Camera Settings")]
- [Range(1, 4096)]
- public int cameraResolution = 256;
+ [Header("Camera Settings")]
+ [Range(1, 4096)] public int cameraResolution = 256;
+ public float nearClippingDistance = 0.01f;
+ public LayerMask cullingMask = -1;
+ public bool renderSkybox = false;
- [Tooltip("Near clipping distance - cameras are placed this distance outside the sphere")]
- public float nearClippingDistance = 0.01f;
+ [Header("Original Mesh")]
+ public GameObject originalMesh;
- [Tooltip("Layers to render when baking")]
- public LayerMask cullingMask = -1;
+ [HideInInspector] public Camera[] cameras;
- [Tooltip("Render skybox in baked images")]
- public bool renderSkybox = false;
+ private GameObject impostorObject;
+ private Material impostorMaterial;
- [HideInInspector]
- public GameObject[] cameraObjects;
- [HideInInspector]
- public Camera[] cameras;
+ public bool HasImpostor => impostorObject != null;
+ private float Radius => sphere_radius_ * transform.lossyScale.x;
+ private const string OutputFolder = "Assets/yum_food/3ner/Impostor_Generated";
- private float radius() { return sphere_radius_ * transform.lossyScale.x; }
+ void OnEnable() => Camera.onPreRender += UpdateMainCameraPos;
+ void OnDisable() => Camera.onPreRender -= UpdateMainCameraPos;
- void OnDrawGizmos()
- {
- // Use transform scale directly for real-time gizmo updates
- float currentRadius = radius();
+ static void UpdateMainCameraPos(Camera cam)
+ {
+ if (cam.cameraType == CameraType.Game || cam.cameraType == CameraType.SceneView)
+ Shader.SetGlobalVector("_ImpostorMainCameraPos", cam.transform.position);
+ }
- // Draw the bounding sphere
- Gizmos.color = Color.cyan;
- Gizmos.DrawWireSphere(transform.position, currentRadius);
+ void OnDrawGizmos()
+ {
+ Gizmos.color = Color.cyan;
+ Gizmos.DrawWireSphere(transform.position, Radius);
- // Draw the camera positions and directions
- if (Application.isEditor && gridResolution > 0)
+ if (Application.isEditor && gridResolution > 0)
+ {
+ Gizmos.color = Color.yellow;
+ for (int y = 0; y < gridResolution; y++)
+ {
+ for (int x = 0; x < gridResolution; x++)
{
- Gizmos.color = Color.yellow;
-
- for (int y = 0; y < gridResolution; y++)
- {
- for (int x = 0; x < gridResolution; x++)
- {
- Vector3 hemispherePos = PlaneToHemiOctahedron(x, y);
- Vector3 worldPos = transform.position + hemispherePos * (currentRadius + nearClippingDistance);
-
- // Draw camera position
- Gizmos.DrawSphere(worldPos, currentRadius * 0.05f);
-
- // Draw line to sphere center
- Gizmos.DrawLine(worldPos, transform.position);
- }
- }
+ Vector3 worldPos = transform.position + PlaneToHemiOctahedron(x, y) * (Radius + nearClippingDistance);
+ Gizmos.DrawSphere(worldPos, Radius * 0.05f);
+ Gizmos.DrawLine(worldPos, transform.position);
}
+ }
}
+ }
- // Port of plane_to_hemi_octahedron from vertex_deformation.slang
- Vector3 PlaneToHemiOctahedron(int gridX, int gridY)
- {
- // Map grid indices to [-1, 1] plane coordinates
- float u = (gridX / (float)(gridResolution - 1)) * 2f - 1f;
- float v = (gridY / (float)(gridResolution - 1)) * 2f - 1f;
+ Vector3 PlaneToHemiOctahedron(int gridX, int gridY)
+ {
+ float x = (gridX / (float)(gridResolution - 1)) * 2f - 1f;
+ float z = (gridY / (float)(gridResolution - 1)) * 2f - 1f;
- float x = u;
- float z = v;
+ // Rotate 45° to fit square into diamond
+ float x_rot = (x + z) * 0.5f;
+ float z_rot = (z - x) * 0.5f;
- // Rotate 45° and scale to fit square into diamond
- float x_rot = (x + z) * 0.5f;
- float z_rot = (z - x) * 0.5f;
+ // Octahedral decode
+ float y = Mathf.Max(0f, 1f - Mathf.Abs(x_rot) - Mathf.Abs(z_rot));
- // Octahedral decode: y = 1 - |x| - |z|, clamped to hemisphere
- float y = Mathf.Max(0f, 1f - Mathf.Abs(x_rot) - Mathf.Abs(z_rot));
+ // Normalize
+ Vector3 oct_pos = new Vector3(x_rot, y, z_rot).normalized;
- // Normalize to unit sphere
- Vector3 oct_pos = new Vector3(x_rot, y, z_rot);
- oct_pos.Normalize();
+ // Rotate back by -45° around y
+ float rcp_sqrt2 = 0.70710678f;
+ float x_unrot = (oct_pos.x - oct_pos.z) * rcp_sqrt2;
+ float z_unrot = (oct_pos.x + oct_pos.z) * rcp_sqrt2;
- // Rotate back by -45° around y to undo input rotation
- float RCP_SQRT_2 = 0.70710678f;
- float x_unrot = (oct_pos.x - oct_pos.z) * RCP_SQRT_2;
- float z_unrot = (oct_pos.x + oct_pos.z) * RCP_SQRT_2;
- oct_pos = new Vector3(x_unrot, oct_pos.y, z_unrot);
+ return new Vector3(x_unrot, oct_pos.y, z_unrot);
+ }
- return oct_pos;
- }
+ public void CreateCameras()
+ {
+ DestroyExistingCameras();
- public void CreateCameras()
- {
- // Clean up existing cameras
- DestroyExistingCameras();
-
- // Create parent GameObject for all cameras
- GameObject camerasParent = new GameObject("Cameras");
- camerasParent.transform.parent = transform;
- camerasParent.transform.localPosition = Vector3.zero;
- camerasParent.transform.localRotation = Quaternion.identity;
- camerasParent.transform.localScale = Vector3.one;
-
- int totalCameras = gridResolution * gridResolution;
- cameraObjects = new GameObject[totalCameras];
- cameras = new Camera[totalCameras];
-
- int index = 0;
- for (int y = 0; y < gridResolution; y++)
- {
- for (int x = 0; x < gridResolution; x++)
- {
- // Get position on hemisphere
- Vector3 hemisphereDir = PlaneToHemiOctahedron(x, y);
- float currentRadius = radius();
-
- // Place camera offset distance outside bounding sphere surface
- Vector3 worldPos = transform.position + hemisphereDir * (currentRadius + nearClippingDistance);
-
- // Create camera GameObject
- GameObject camObj = new GameObject($"ImpostorCamera_{x}_{y}");
- camObj.transform.parent = camerasParent.transform;
- camObj.transform.position = worldPos;
- camObj.transform.LookAt(transform.position);
-
- // Add and configure camera
- Camera cam = camObj.AddComponent<Camera>();
- cam.orthographic = true;
- cam.orthographicSize = sphere_radius_;
- cam.nearClipPlane = nearClippingDistance;
- cam.farClipPlane = sphere_radius_ * 2f + nearClippingDistance;
- cam.cullingMask = cullingMask;
- if (!renderSkybox)
- {
- cam.clearFlags = CameraClearFlags.SolidColor;
- cam.backgroundColor = Color.clear;
- }
- cam.enabled = false; // Only enable during baking
-
- cameraObjects[index] = camObj;
- cameras[index] = cam;
- index++;
- }
- }
+ GameObject parent = new GameObject("Cameras");
+ parent.transform.SetParent(transform, false);
- Debug.Log($"Created {totalCameras} impostor cameras");
- }
+ cameras = new Camera[gridResolution * gridResolution];
+ int idx = 0;
- public void DestroyExistingCameras()
+ for (int y = 0; y < gridResolution; y++)
{
- // Find and destroy the "Cameras" parent GameObject
- Transform camerasTransform = transform.Find("Cameras");
- if (camerasTransform != null)
- {
- DestroyImmediate(camerasTransform.gameObject);
- }
-
- cameraObjects = null;
- cameras = null;
+ for (int x = 0; x < gridResolution; x++)
+ {
+ Vector3 localDir = PlaneToHemiOctahedron(x, y);
+ Vector3 worldPos = transform.position + (transform.rotation * localDir) * (Radius + nearClippingDistance);
+
+ GameObject camObj = new GameObject($"Camera_{x}_{y}");
+ camObj.transform.SetParent(parent.transform, false);
+ camObj.transform.position = worldPos;
+ camObj.transform.LookAt(transform.position, transform.up);
+
+ Camera cam = camObj.AddComponent<Camera>();
+ cam.orthographic = true;
+ cam.orthographicSize = sphere_radius_;
+ cam.nearClipPlane = nearClippingDistance;
+ cam.farClipPlane = sphere_radius_ * 2f + nearClippingDistance;
+ cam.cullingMask = cullingMask;
+ cam.clearFlags = renderSkybox ? CameraClearFlags.Skybox : CameraClearFlags.SolidColor;
+ cam.backgroundColor = Color.clear;
+ cam.enabled = false;
+
+ cameras[idx++] = cam;
+ }
}
-
- public void BakeTexture()
+ }
+
+ public void DestroyExistingCameras()
+ {
+ Transform camsTransform = transform.Find("Cameras");
+ if (camsTransform != null) DestroyImmediate(camsTransform.gameObject);
+ cameras = null;
+ }
+
+ public void BakeTexture()
+ {
+ SetRenderersEnabled(true);
+ if (cameras == null || cameras.Length != gridResolution * gridResolution || cameras[0] == null) CreateCameras();
+
+ // Render atlas
+ int size = cameraResolution * gridResolution;
+ Texture2D atlas = new Texture2D(size, size, TextureFormat.RGBA32, false);
+ RenderTexture rt = RenderTexture.GetTemporary(cameraResolution, cameraResolution, 24);
+
+ int idx = 0;
+ for (int y = 0; y < gridResolution; y++)
{
- // Create a texture atlas to hold all camera views
- int texWidth = cameraResolution * gridResolution;
- int texHeight = cameraResolution * gridResolution;
- Texture2D atlasTexture = new Texture2D(texWidth, texHeight, TextureFormat.RGBA32, false);
-
- // Create temporary render texture for each camera
- RenderTexture rt = RenderTexture.GetTemporary(cameraResolution, cameraResolution, 24);
+ for (int x = 0; x < gridResolution; x++)
+ {
+ cameras[idx++].targetTexture = rt;
+ cameras[idx - 1].Render();
+ RenderTexture.active = rt;
+ Texture2D temp = new Texture2D(cameraResolution, cameraResolution);
+ temp.ReadPixels(new Rect(0, 0, cameraResolution, cameraResolution), 0, 0);
+ temp.Apply();
+ atlas.SetPixels(x * cameraResolution, y * cameraResolution, cameraResolution, cameraResolution, temp.GetPixels());
+ DestroyImmediate(temp);
+ }
+ }
+ atlas.Apply();
+ RenderTexture.active = null;
+ RenderTexture.ReleaseTemporary(rt);
- int index = 0;
- for (int y = 0; y < gridResolution; y++)
- {
- for (int x = 0; x < gridResolution; x++)
- {
- Camera cam = cameras[index];
-
- // Render camera to RT
- cam.targetTexture = rt;
- cam.Render();
-
- // Read pixels from RT
- RenderTexture.active = rt;
- Texture2D temp = new Texture2D(cameraResolution, cameraResolution, TextureFormat.RGBA32, false);
- temp.ReadPixels(new Rect(0, 0, cameraResolution, cameraResolution), 0, 0);
- temp.Apply();
-
- // Copy to atlas at correct position
- int atlasX = x * cameraResolution;
- int atlasY = y * cameraResolution;
- atlasTexture.SetPixels(atlasX, atlasY, cameraResolution, cameraResolution, temp.GetPixels());
-
- DestroyImmediate(temp);
- index++;
- }
- }
+ // Save
+ if (!AssetDatabase.IsValidFolder(OutputFolder))
+ {
+ if (!AssetDatabase.IsValidFolder("Assets/yum_food/3ner"))
+ AssetDatabase.CreateFolder("Assets/yum_food", "3ner");
+ AssetDatabase.CreateFolder("Assets/yum_food/3ner", "Impostor_Generated");
+ }
- atlasTexture.Apply();
- RenderTexture.active = null;
- RenderTexture.ReleaseTemporary(rt);
+ string name = gameObject.name.Replace(" ", "_");
+ string path = Path.Combine(OutputFolder, $"{name}_atlas.png");
+ File.WriteAllBytes(Path.Combine(Application.dataPath, "..", path), atlas.EncodeToPNG());
+ DestroyImmediate(atlas);
- // Save texture to file
- byte[] bytes = atlasTexture.EncodeToPNG();
+ AssetDatabase.Refresh();
+ TextureImporter importer = AssetImporter.GetAtPath(path) as TextureImporter;
+ if (importer != null)
+ {
+ importer.mipmapEnabled = true;
+ importer.alphaIsTransparency = true;
+ importer.wrapMode = TextureWrapMode.Clamp;
+ importer.filterMode = FilterMode.Trilinear;
+ importer.SaveAndReimport();
+ }
- // Get currently selected folder in Project window
- string folder = "Assets";
- if (Selection.activeObject != null)
- {
- string selectedPath = AssetDatabase.GetAssetPath(Selection.activeObject);
- if (!string.IsNullOrEmpty(selectedPath))
- {
- if (AssetDatabase.IsValidFolder(selectedPath))
- {
- folder = selectedPath;
- }
- else
- {
- folder = Path.GetDirectoryName(selectedPath);
- }
- }
- }
+ // Create impostor
+ Texture2D tex = AssetDatabase.LoadAssetAtPath<Texture2D>(path);
+ if (tex != null)
+ {
+ DestroyExistingImpostor();
- string assetPath = Path.Combine(folder, "ho_bake.png");
- string fullPath = Path.Combine(Application.dataPath, "..", assetPath);
- File.WriteAllBytes(fullPath, bytes);
+ Shader shader = Shader.Find("yum_food/Gimmicks/Impostors");
+ if (shader == null) { Debug.LogError("Shader not found"); return; }
- Debug.Log($"Baked texture saved to: {assetPath}");
- Debug.Log($"Atlas size: {texWidth}x{texHeight} ({gridResolution}x{gridResolution} grid of {cameraResolution}x{cameraResolution} images)");
+ impostorMaterial = new Material(shader);
+ impostorMaterial.SetTexture("_ImpostorAtlas", tex);
+ impostorMaterial.SetInt("_GridResolution", gridResolution);
+ AssetDatabase.CreateAsset(impostorMaterial, Path.Combine(OutputFolder, $"{name}_mat.mat"));
- // Refresh asset database
- AssetDatabase.Refresh();
+ impostorObject = GameObject.CreatePrimitive(PrimitiveType.Quad);
+ impostorObject.name = "Impostor";
+ impostorObject.transform.SetParent(transform, false);
+ impostorObject.transform.localScale = Vector3.one * sphere_radius_ * 2f;
+ DestroyImmediate(impostorObject.GetComponent<Collider>());
+ impostorObject.GetComponent<MeshRenderer>().sharedMaterial = impostorMaterial;
- DestroyImmediate(atlasTexture);
+ SetRenderersEnabled(false);
}
+ }
+
+ public void DestroyExistingImpostor()
+ {
+ if (impostorObject != null) DestroyImmediate(impostorObject);
+ impostorObject = null;
+ impostorMaterial = null;
+ }
+
+ public void ToggleRenderers()
+ {
+ if (originalMesh == null) return;
+ bool showing = originalMesh.GetComponentInChildren<Renderer>()?.enabled ?? false;
+ SetRenderersEnabled(!showing);
+ }
+
+ void SetRenderersEnabled(bool enabled)
+ {
+ if (originalMesh == null) return;
+ foreach (Renderer r in originalMesh.GetComponentsInChildren<Renderer>(true))
+ r.enabled = enabled;
+ if (impostorObject != null) impostorObject.SetActive(!enabled);
+ }
}
[CustomEditor(typeof(Impostors))]
public class ImpostorsEditor : Editor
{
- public override void OnInspectorGUI()
+ public override void OnInspectorGUI()
+ {
+ DrawDefaultInspector();
+ Impostors s = (Impostors)target;
+
+ GUILayout.Space(10);
+ GUILayout.Label("Impostor Management", EditorStyles.boldLabel);
+
+ EditorGUILayout.BeginHorizontal();
+ if (GUILayout.Button("Create Impostor", GUILayout.Height(40))) s.BakeTexture();
+ GUI.enabled = s.HasImpostor;
+ if (GUILayout.Button("Destroy Impostor", GUILayout.Height(40)))
{
- DrawDefaultInspector();
-
- Impostors script = (Impostors)target;
-
- GUILayout.Space(10);
-
- if (GUILayout.Button("Create Cameras", GUILayout.Height(30)))
- {
- script.CreateCameras();
- }
-
- if (GUILayout.Button("Destroy Cameras", GUILayout.Height(30)))
- {
- script.DestroyExistingCameras();
- }
-
- GUILayout.Space(10);
-
- bool hasCameras = script.cameras != null && script.cameras.Length > 0;
-
- GUI.enabled = hasCameras;
- if (GUILayout.Button(new GUIContent("Bake Texture",
- hasCameras ? "" : "Create cameras first"), GUILayout.Height(40)))
- {
- script.BakeTexture();
- }
- GUI.enabled = true;
+ s.DestroyExistingImpostor();
+ s.DestroyExistingCameras();
}
-}
+ GUI.enabled = true;
+ EditorGUILayout.EndHorizontal();
+ if (s.originalMesh != null && GUILayout.Button("Toggle Visibility", GUILayout.Height(30)))
+ s.ToggleRenderers();
+ }
+}