Derive picture frame size from picture texture aspect ratio

Replace Width/Height with a single Size (longer side); the shorter side
follows the picture material's base map source-image aspect (editor-
resolved, serialized for builds, 1:1 fallback). Add Range attributes on
all frame parameters and default material references (new Frame.mat).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Fable 5 committed 2026-07-09 20:33:18 +02:00
1 parent bac32a87a9
commit 850c92bcce
4 files changed
+231 -16

No files matched your search

+137
View File
@@ -0,0 +1,137 @@
%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
m_Shader: {fileID: 4800000, guid: 933532a4fcc9baf4fa0491de14d08ed7, 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:
- MOTIONVECTORS
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.5
- _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: a7794896ef878a64399cca929db5c43c
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:
@@ -5,8 +5,9 @@ namespace Chameleon.Components
{
// Procedural picture frame: a box-profile frame with a recessed inner floor (FrameInnerDepth) and a
// picture plane offset relative to it (PictureDepth: + sunken, - raised plinth), generated live in the
// editor (and at runtime) from the inspector parameters - resize via Width/Height, NOT via transform
// scale, so the frame profile never stretches (9-slice behaviour). Four submeshes with their own
// editor (and at runtime) from the inspector parameters - resize via Size (width/height follow the
// picture texture's aspect ratio), NOT via transform scale, so the frame profile never stretches
// (9-slice behaviour). 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.
//
@@ -27,28 +28,31 @@ namespace Chameleon.Components
const int SubBack = 3;
[Header("Size")]
[Tooltip("Outer width of the frame in meters.")]
[SerializeField, Min(0.01f)] float _width = 1f;
[Tooltip("Outer height of the frame in meters.")]
[SerializeField, Min(0.01f)] float _height = 1f;
[Tooltip("Length of the frame's longer side in meters; the shorter side follows the picture's aspect ratio.")]
[SerializeField, Min(0.01f)] float _size = 1f;
// Auto-resolved in the editor from the picture material's base map texture (longer side = 1,
// 1:1 when no texture is found) and serialized, because the source image file/importer is not
// available in a build. Manual edits are overwritten on the next rebuild.
[Tooltip("Width : height factors derived from the picture texture (auto-set in the editor, longer side = 1).")]
[SerializeField] Vector2 _aspect = Vector2.one;
[Header("Frame")]
[Tooltip("Border width of the frame profile in the picture plane.")]
[SerializeField, Min(0.001f)] float _frameThickness = 0.08f;
[SerializeField, Range(0.001f, 1.000f)] float _frameThickness = 0.08f;
[Tooltip("Depth of the frame, front face to back face.")]
[SerializeField, Min(0.002f)] float _frameDepth = 0.12f;
[SerializeField, Range(0.002f, 0.500f)] float _frameDepth = 0.12f;
[Tooltip("Rounding radius of the outer frame edges; 0 = sharp box. Clamped to thickness / half depth.")]
[SerializeField, Min(0f)] float _cornerRadius;
[SerializeField, Range(0.000f, 0.500f)] float _cornerRadius;
[Tooltip("Arc segments of the edge rounding.")]
[SerializeField, Range(1, 12)] int _bevelSegments = 4;
[SerializeField, Range(1, 8)] int _bevelSegments = 4;
[Tooltip("How deep behind the front face the inner floor (padding ring) sits.")]
[SerializeField, Min(0f)] float _frameInnerDepth = 0.06f;
[SerializeField, Range(0.000f, 0.498f)] float _frameInnerDepth = 0.06f;
[Header("Picture")]
[Tooltip("Gap between the frame's inner edge and the picture (visible inner-material ring).")]
[SerializeField, Min(0f)] float _padding = 0.02f;
[SerializeField, Range(0.000f, 1.000f)] float _padding = 0.02f;
[Tooltip("Picture plane relative to the inner floor: positive sinks it into the frame, negative raises it out like a plinth, 0 = flush.")]
[SerializeField] float _pictureDepth;
[SerializeField, Range(-0.500f, 0.500f)] float _pictureDepth;
[Header("Materials")]
[SerializeField] Material _pictureMaterial;
@@ -110,8 +114,11 @@ namespace Chameleon.Components
var filter = GetComponent<MeshFilter>();
EnsureMesh(filter);
float w = Mathf.Max(_width, 0.01f);
float h = Mathf.Max(_height, 0.01f);
#if UNITY_EDITOR
ResolveAspect();
#endif
float w = Mathf.Max(_size * _aspect.x, 0.01f);
float h = Mathf.Max(_size * _aspect.y, 0.01f);
float t = Mathf.Clamp(_frameThickness, 0.001f, Mathf.Min(w, h) * 0.5f - 0.001f);
float d = Mathf.Max(_frameDepth, 0.002f);
float r = Mathf.Min(_cornerRadius, Mathf.Min(t, d * 0.5f) - 0.0005f);
@@ -239,6 +246,56 @@ namespace Chameleon.Components
meshRenderer.sharedMaterials = new[] { _pictureMaterial, _innerMaterial, _frameMaterial, _backMaterial };
}
#if UNITY_EDITOR
// Importer API for the source file's pixel size (public in Unity 6, internal in older
// versions - hence reflection; null-fallback is the imported texture size).
static readonly System.Reflection.MethodInfo s_sourceSize = typeof(UnityEditor.TextureImporter)
.GetMethod("GetSourceTextureWidthAndHeight",
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.
void ResolveAspect()
{
var aspect = Vector2.one;
Texture tex = null;
if (_pictureMaterial)
{
if (_pictureMaterial.HasProperty("_BaseMap")) tex = _pictureMaterial.GetTexture("_BaseMap");
if (!tex) tex = _pictureMaterial.mainTexture;
}
if (tex)
{
// Prefer the source file's pixel size: importer NPOT/max-size scaling can distort the
// imported texture's proportions (e.g. 4:3 photo -> power-of-two texture).
int w = tex.width;
int h = tex.height;
string path = UnityEditor.AssetDatabase.GetAssetPath(tex);
if (s_sourceSize != null && !string.IsNullOrEmpty(path)
&& UnityEditor.AssetImporter.GetAtPath(path) is UnityEditor.TextureImporter importer)
{
object[] size = { 0, 0 };
s_sourceSize.Invoke(importer, size);
if ((int)size[0] > 0 && (int)size[1] > 0)
{
w = (int)size[0];
h = (int)size[1];
}
}
if (w > 0 && h > 0)
aspect = w >= h ? new Vector2(1f, (float)h / w) : new Vector2((float)w / h, 1f);
}
if ((aspect - _aspect).sqrMagnitude < 1e-12f) return;
_aspect = aspect;
UnityEditor.EditorUtility.SetDirty(this);
}
#endif
// Flat ring between two concentric rects at depth z, facing the viewer (-Z), mitred corners.
void Ring(int sub, float ox, float oy, float ix, float iy, float z)
{
@@ -1,2 +1,15 @@
fileFormatVersion: 2
guid: 86a75d0253697424fb4b506c394f13bc
guid: 86a75d0253697424fb4b506c394f13bc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences:
- _pictureMaterial: {instanceID: 0}
- _innerMaterial: {fileID: 2100000, guid: a7794896ef878a64399cca929db5c43c, type: 2}
- _frameMaterial: {fileID: 2100000, guid: a7794896ef878a64399cca929db5c43c, type: 2}
- _backMaterial: {fileID: 2100000, guid: a7794896ef878a64399cca929db5c43c, type: 2}
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: