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

+136
View File
@@ -0,0 +1,136 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Frame Picture
m_Shader: {fileID: 4800000, guid: 023c98a32f6cc00478e50479839530c5, type: 3}
m_Parent: {fileID: 0}
m_ModifiedSerializedProperties: 0
m_ValidKeywords: []
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap:
RenderType: Opaque
disabledShaderPasses: []
m_LockedProperties:
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _BaseMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _BumpMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailAlbedoMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailMask:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailNormalMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _EmissionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MetallicGlossMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _OcclusionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _ParallaxMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _SpecGlossMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- unity_Lightmaps:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- unity_LightmapsInd:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- unity_ShadowMasks:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Ints: []
m_Floats:
- _AddPrecomputedVelocity: 0
- _AlphaClip: 0
- _AlphaToMask: 0
- _Blend: 0
- _BlendModePreserveSpecular: 1
- _BumpScale: 1
- _ClearCoatMask: 0
- _ClearCoatSmoothness: 0
- _Cull: 2
- _Cutoff: 0.5
- _DetailAlbedoMapScale: 1
- _DetailNormalMapScale: 1
- _DstBlend: 0
- _DstBlendAlpha: 0
- _EnvironmentReflections: 1
- _GlossMapScale: 0
- _Glossiness: 0
- _GlossyReflections: 0
- _Metallic: 0
- _OcclusionStrength: 1
- _Parallax: 0.005
- _QueueOffset: 0
- _ReceiveShadows: 1
- _Smoothness: 0
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 1
- _SrcBlendAlpha: 1
- _Surface: 0
- _WorkflowMode: 1
- _XRMotionVectorsPass: 1
- _ZWrite: 1
m_Colors:
- _BaseColor: {r: 1, g: 1, b: 1, a: 1}
- _Color: {r: 1, g: 1, b: 1, a: 1}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
- _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1}
m_BuildTextureStacks: []
m_AllowLocking: 1
--- !u!114 &6905797306856702536
MonoBehaviour:
m_ObjectHideFlags: 11
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Editor::UnityEditor.Rendering.Universal.AssetVersion
version: 10
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9c88ebf613f4b1c49af2bb5a621d2dee
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:
@@ -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}