Add Picture Texture field to PictureFrameBuilder

Assigned texture drives the picture material BaseMap via a per-frame
material instance (asset untouched, frames sharing a material stay
independent) and takes priority for aspect resolution. New default
picture material Frame Picture.mat wired in the script defaults.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Fable 5 committed 2026-07-10 07:53:54 +02:00
1 parent d1f7b79013
commit 5dbdc0e6b2
4 files changed
+200 -9

No files matched your search

@@ -11,7 +11,9 @@ namespace Chameleon.Components
// possible rounding, depths of the frame depth), so a frame keeps its proportions when resized via
// Size - resize via Size, NOT via transform scale. Four submeshes with their own
// material slots: 0 picture, 1 inner cavity (recess walls + padding ring), 2 frame (front ring, outer
// sides, edge rounding), 3 back plate. Front faces -Z like Unity's built-in Quad.
// sides, edge rounding), 3 back plate. Front faces -Z like Unity's built-in Quad. An assigned
// PictureTexture becomes the picture material's base map via a per-frame DontSave material
// instance (the shared material asset is never modified) and drives the aspect ratio.
//
// The generated mesh is a per-instance HideFlags.DontSave object (never saved into the scene file);
// it is rebuilt on scene load / domain reload via OnEnable. Duplicated frames get their own mesh
@@ -59,6 +61,8 @@ namespace Chameleon.Components
[SerializeField, Range(0f, 1f)] float _padding = 0.01f;
[Tooltip("Picture plane: -1 = fully inside (on the back plate), 0 = flush with the inner floor, 1 = a full frame depth in front of the frame.")]
[SerializeField, Range(-1f, 1f)] float _pictureDepth = 0.1f;
[Tooltip("Photo for this frame: applied as the picture material's base map via a per-frame material instance (the material asset itself is never modified) and drives the aspect ratio. Empty = the material's own base map is shown.")]
[SerializeField] Texture _pictureTexture;
[Header("Materials")]
[SerializeField] Material _pictureMaterial;
@@ -82,6 +86,7 @@ namespace Chameleon.Components
};
Mesh _mesh;
Material _pictureMaterialInstance;
bool _rebuildQueued;
bool _rebuilding;
@@ -110,6 +115,11 @@ namespace Chameleon.Components
void OnDestroy()
{
if (_pictureMaterialInstance)
{
DestroyOwned(_pictureMaterialInstance);
_pictureMaterialInstance = null;
}
if (!_mesh) return;
if (Application.isPlaying) Destroy(_mesh);
else DestroyImmediate(_mesh);
@@ -301,13 +311,49 @@ namespace Chameleon.Components
void ApplyMaterials()
{
var picture = ResolvePictureMaterial();
var meshRenderer = GetComponent<MeshRenderer>();
var current = meshRenderer.sharedMaterials;
bool dirty = current.Length != 4
|| current[0] != _pictureMaterial || current[1] != _innerMaterial
|| current[0] != picture || current[1] != _innerMaterial
|| current[2] != _frameMaterial || current[3] != _backMaterial;
if (dirty)
meshRenderer.sharedMaterials = new[] { _pictureMaterial, _innerMaterial, _frameMaterial, _backMaterial };
meshRenderer.sharedMaterials = new[] { picture, _innerMaterial, _frameMaterial, _backMaterial };
}
// With a picture texture assigned, the picture slot uses a per-frame DontSave material
// instance whose base map is that texture - many frames can share ONE picture material asset
// with different photos, and the asset is never mutated. Properties are re-copied from the
// source material on every rebuild, so asset edits keep showing up on the instance. Like the
// mesh, the instance is never saved and is recreated in OnEnable after scene load.
Material ResolvePictureMaterial()
{
if (!_pictureTexture || !_pictureMaterial)
{
if (_pictureMaterialInstance)
{
DestroyOwned(_pictureMaterialInstance);
_pictureMaterialInstance = null;
}
return _pictureMaterial;
}
if (!_pictureMaterialInstance)
{
_pictureMaterialInstance = new Material(_pictureMaterial) { hideFlags = HideFlags.DontSave };
}
else
{
if (_pictureMaterialInstance.shader != _pictureMaterial.shader)
_pictureMaterialInstance.shader = _pictureMaterial.shader;
_pictureMaterialInstance.CopyPropertiesFromMaterial(_pictureMaterial);
}
_pictureMaterialInstance.name = _pictureMaterial.name + " (Picture)";
if (_pictureMaterialInstance.HasProperty("_BaseMap"))
_pictureMaterialInstance.SetTexture("_BaseMap", _pictureTexture);
else
_pictureMaterialInstance.mainTexture = _pictureTexture;
return _pictureMaterialInstance;
}
#if UNITY_EDITOR
@@ -318,14 +364,14 @@ namespace Chameleon.Components
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic
| System.Reflection.BindingFlags.Instance);
// Editor-only: derive _aspect (longer side = 1) from the picture material's base map and
// serialize it - the source image file is not available in a build. 1:1 whenever the
// material -> base map -> texture chain breaks.
// Editor-only: derive _aspect (longer side = 1) from the assigned picture texture (falling
// back to the picture material's base map) and serialize it - the source image file is not
// available in a build. 1:1 whenever the whole chain breaks.
void ResolveAspect()
{
var aspect = Vector2.one;
Texture tex = null;
if (_pictureMaterial)
Texture tex = _pictureTexture;
if (!tex && _pictureMaterial)
{
if (_pictureMaterial.HasProperty("_BaseMap")) tex = _pictureMaterial.GetTexture("_BaseMap");
if (!tex) tex = _pictureMaterial.mainTexture;
@@ -4,7 +4,8 @@ MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences:
- _pictureMaterial: {instanceID: 0}
- _pictureTexture: {instanceID: 0}
- _pictureMaterial: {fileID: 2100000, guid: 9c88ebf613f4b1c49af2bb5a621d2dee, type: 2}
- _innerMaterial: {fileID: 2100000, guid: bc0ac45503ad5c44b8b966da0d0a40d3, type: 2}
- _frameMaterial: {fileID: 2100000, guid: a7794896ef878a64399cca929db5c43c, type: 2}
- _backMaterial: {fileID: 2100000, guid: a7794896ef878a64399cca929db5c43c, type: 2}