Merge branch 'wl/data/WL-814u-pixel-char-study'
This commit is contained in:
commit
e425f7aedb
|
|
@ -0,0 +1,17 @@
|
|||
포즈: 씬 기본 yaw 200° → 카메라 정면 yaw 240° 로 돌려서 측정·캡처 (카메라 yaw 60°)
|
||||
프리뷰 적용: Face ← face02_Pixel · Hair05 ← Hair05_Pixel (임시 Material 인스턴스 · 에셋 저장 0)
|
||||
saved Screenshots_WL/WL814u/u_1_ingame_compare.png (2176×1920)
|
||||
saved Screenshots_WL/WL814u/u_2_char_4x_compare.png (652×480)
|
||||
saved Screenshots_WL/WL814u/u_3_head_12x_compare.png (1548×672)
|
||||
|
||||
== 읽힘 실측 (얼굴 데칼 · 화면 1080×1920 · ortho 10 · 카메라 정면) ==
|
||||
현재 face02: 50 px (22×7) · 고유색 49 (98 % 가 서로 다른 색) · 밝기 71~184(폭 112) 평균 146
|
||||
도트 face02_Pixel: 64 px (22×7) · 고유색 42 (66 % 가 서로 다른 색) · 밝기 9~164(폭 155) 평균 79
|
||||
현재 얼굴 데칼 22×7 px → 눈 1개 6.85×2.08 px · 눈썹 4.08×1.21 · 입 1.66×0.16
|
||||
도트 얼굴 데칼 22×7 px → 눈 1개 6.85×2.08 px · 눈썹 4.08×1.21 · 입 1.66×0.16
|
||||
|
||||
== 부위 색 수 (캐릭터 전체 / 머리칼) ==
|
||||
현재 전체: 2996 px (51×100) · 고유색 2635 (88 % 가 서로 다른 색) · 밝기 30~237(폭 207) 평균 143
|
||||
도트 전체: 2996 px (51×100) · 고유색 2058 (69 % 가 서로 다른 색) · 밝기 9~243(폭 234) 평균 116
|
||||
현재 머리칼: 1154 px (41×38) · 고유색 1067 (92 % 가 서로 다른 색) · 밝기 104~237(폭 132) 평균 180
|
||||
도트 머리칼: 1154 px (41×38) · 고유색 509 (44 % 가 서로 다른 색) · 밝기 42~243(폭 201) 평균 114
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
== ⓓ-1 해상도별 회전 떨림 (캐릭터 yaw 0.25°/프레임 × 32회 · 이동분 제거 후 비교) ==
|
||||
| 설정 | 캐릭터 키 | 모양 변화 %/프레임 | 색 변화 %/프레임 | 1° 환산 모양 % |
|
||||
| 도트A 240줄 (ortho 3.4) | 37 px | **0.31 %** | **2.8 %** | 1.2 % |
|
||||
| 360줄 (ortho 3.4) | 55 px | **0.29 %** | **2.8 %** | 1.2 % |
|
||||
| 480줄 (ortho 3.4) | 73 px | **0.28 %** | **2.5 %** | 1.1 % |
|
||||
| 현재 1920줄 (ortho 10) | 100 px | **0.77 %** | **5.1 %** | 3.1 % |
|
||||
|
||||
== ⓓ-2 회전 계단화 (yaw 를 N방향으로 반올림 · 도트A 240줄) ==
|
||||
| 방향 수 | 각도 간격 | 모양 변화 평균 %/프레임 | 그중 최대(계단 순간) % | 계단이 생긴 프레임 수/32 |
|
||||
| 양자화 없음(연속) | - | **0.23 %** | 0.73 % | 1 |
|
||||
| 32 | 11.3° | **0.57 %** | 18.38 % | 1 |
|
||||
| 16 | 22.5° | **0.00 %** | 0.00 % | 0 |
|
||||
| 8 | 45.0° | **1.04 %** | 33.42 % | 1 |
|
||||
|
||||
== ⓓ-3 이동 시 떨림 · 픽셀 격자 스냅(VoxelGridAdjuster 방식) 유/무 (도트A 240줄) ==
|
||||
픽셀 1개 = 0.02833 m · 프레임당 이동 0.37 px (일부러 격자에 안 맞는 값)
|
||||
| 스냅 | 모양 변화 평균 %/프레임 | 최대 % |
|
||||
| 끔 | **11.09 %** | 15.84 % |
|
||||
| 켬(격자 스냅) | **0.00 %** | 0.00 % |
|
||||
|
||||
== ⓓ-4 외곽선 두께 vs 떨림 (회전 중 · 도트A 240줄 · 외곽선 = 실루엣을 N px 부풀린 띠) ==
|
||||
| 외곽선 두께 | 띠 면적(px) | 띠가 프레임마다 바뀌는 비율 % | 캐릭터 대비 띠 비중 % |
|
||||
| 1 px | 109 | **2.1 %** | 27 % |
|
||||
| 2 px | 211 | **1.3 %** | 52 % |
|
||||
| 3 px | 310 | **1.0 %** | 76 % |
|
||||
|
||||
== 캡처 ==
|
||||
u_6_crawl_rotation.png — 회전 8프레임: 위=양자화 없음 / 아래=16방향 계단화
|
||||
u_7_crawl_move_snap.png — 이동 8프레임: 위=스냅 없음 / 아래=픽셀 격자 스냅
|
||||
|
||||
== ⓓ-2(재측정) 회전 계단화 · 90° 회전 (1°/프레임 × 90) · 도트A 240줄 ==
|
||||
| 방향 수 | 각도 간격 | 떨림 0 인 프레임 | 모양 변화 평균 %/프레임 | 계단 순간 최대 % | 계단 횟수/90° |
|
||||
| 양자화 없음 | - | **0/90** | **2.89 %** | 22.6 % | 81 |
|
||||
| 32 | 11.3° | **82/90** | **1.55 %** | 28.1 % | 8 |
|
||||
| 16 | 22.5° | **86/90** | **1.31 %** | 47.2 % | 4 |
|
||||
| 8 | 45.0° | **88/90** | **1.04 %** | 59.3 % | 2 |
|
||||
|
|
@ -0,0 +1,242 @@
|
|||
// WL814u_Capture.cs — 시제품 텍스처 임포트 설정 + 현재/도트 비교 캡처 + 읽힘 실측 (에디터 전용)
|
||||
// 🔴 씬 저장 0 · 프리팹/머티리얼 에셋 수정 0 (씬 인스턴스에 임시 머티리얼만 꽂았다가 버린다) · ProjectSettings 0
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
|
||||
public static class WL814u_Capture
|
||||
{
|
||||
const string SCENE = "Assets/WL/Look/Arena/Scenes/WL_ArenaProto.unity";
|
||||
const string TEX = "Assets/WL/Look/Character/Textures/";
|
||||
const string SHOT = "Screenshots_WL/WL814u/";
|
||||
const string OUT = "AgentScripts/WL814u_CAPTURE.txt";
|
||||
const int LAYER = 31;
|
||||
const int W = 1080, H = 1920;
|
||||
static readonly StringBuilder sb = new StringBuilder();
|
||||
static void L(string s) { sb.AppendLine(s); Console.WriteLine("[814uC] " + s); }
|
||||
|
||||
// ── 1) 임포트 설정 ───────────────────────────────────────────────
|
||||
public static void Import()
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var f in new[] { "face02_Pixel.png", "Hair05_Pixel.png" })
|
||||
{
|
||||
var p = TEX + f;
|
||||
AssetDatabase.ImportAsset(p, ImportAssetOptions.ForceUpdate);
|
||||
var ti = (TextureImporter)AssetImporter.GetAtPath(p);
|
||||
if (ti == null) { L("!! importer null " + p); continue; }
|
||||
ti.textureType = TextureImporterType.Default;
|
||||
ti.alphaSource = TextureImporterAlphaSource.FromInput;
|
||||
ti.alphaIsTransparency = true;
|
||||
ti.mipmapEnabled = true; // 1/59 축소 → 밉이 있어야 한다(끄면 심하게 반짝인다)
|
||||
ti.filterMode = FilterMode.Point; // 도트: 보간 금지
|
||||
ti.wrapMode = TextureWrapMode.Clamp;
|
||||
ti.sRGBTexture = true;
|
||||
ti.maxTextureSize = 512; // 814s 채택값과 동일 조건에서 비교
|
||||
ti.textureCompression = TextureImporterCompression.Uncompressed; // 하드 엣지 보존(블록압축은 계단을 뭉갠다)
|
||||
ti.SaveAndReimport();
|
||||
var t = AssetDatabase.LoadAssetAtPath<Texture2D>(p);
|
||||
L(string.Format("import {0} → {1}×{2} · {3} · mip {4} · {5}", f, t.width, t.height, t.format, t.mipmapCount, t.filterMode));
|
||||
}
|
||||
}
|
||||
catch (Exception e) { L("EXCEPTION " + e); }
|
||||
finally { File.WriteAllText(OUT, sb.ToString(), new UTF8Encoding(true)); }
|
||||
}
|
||||
|
||||
// ── 2) 캡처 ─────────────────────────────────────────────────────
|
||||
class Shot { public Color32[] px; public int w, h; }
|
||||
|
||||
static Shot Render(Camera cam, int w, int h)
|
||||
{
|
||||
var rt = new RenderTexture(w, h, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB);
|
||||
rt.antiAliasing = 1; rt.filterMode = FilterMode.Point;
|
||||
var pT = cam.targetTexture; var pA = RenderTexture.active;
|
||||
cam.targetTexture = rt; cam.Render(); RenderTexture.active = rt;
|
||||
var t = new Texture2D(w, h, TextureFormat.RGBA32, false); t.ReadPixels(new Rect(0, 0, w, h), 0, 0); t.Apply();
|
||||
cam.targetTexture = pT; RenderTexture.active = pA;
|
||||
var s = new Shot { px = t.GetPixels32(), w = w, h = h };
|
||||
UnityEngine.Object.DestroyImmediate(t); rt.Release(); UnityEngine.Object.DestroyImmediate(rt);
|
||||
return s;
|
||||
}
|
||||
|
||||
static void Save(Shot s, string file)
|
||||
{
|
||||
var t = new Texture2D(s.w, s.h, TextureFormat.RGBA32, false);
|
||||
t.SetPixels32(s.px); t.Apply();
|
||||
Directory.CreateDirectory(SHOT);
|
||||
File.WriteAllBytes(SHOT + file, t.EncodeToPNG());
|
||||
UnityEngine.Object.DestroyImmediate(t);
|
||||
L(" saved " + SHOT + file + " (" + s.w + "×" + s.h + ")");
|
||||
}
|
||||
|
||||
// 여러 Shot 을 가로로 붙이고 zoom 배 점 확대 (crop = null 이면 전체)
|
||||
static Shot Compose(Shot[] shots, int zoom, RectInt? crop, int gap)
|
||||
{
|
||||
int cw = crop.HasValue ? crop.Value.width : shots[0].w;
|
||||
int ch = crop.HasValue ? crop.Value.height : shots[0].h;
|
||||
int ow = (cw * zoom) * shots.Length + gap * (shots.Length - 1);
|
||||
int oh = ch * zoom;
|
||||
var o = new Shot { w = ow, h = oh, px = new Color32[ow * oh] };
|
||||
for (int i = 0; i < o.px.Length; i++) o.px[i] = new Color32(24, 24, 28, 255);
|
||||
for (int k = 0; k < shots.Length; k++)
|
||||
{
|
||||
int xoff = k * (cw * zoom + gap);
|
||||
for (int y = 0; y < oh; y++)
|
||||
for (int x = 0; x < cw * zoom; x++)
|
||||
{
|
||||
int sx = (crop.HasValue ? crop.Value.x : 0) + x / zoom;
|
||||
int sy = (crop.HasValue ? crop.Value.y : 0) + y / zoom;
|
||||
if (sx < 0 || sy < 0 || sx >= shots[k].w || sy >= shots[k].h) continue;
|
||||
o.px[y * ow + xoff + x] = shots[k].px[sy * shots[k].w + sx];
|
||||
}
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
public static void Capture()
|
||||
{
|
||||
try { CapBody(); }
|
||||
catch (Exception e) { L("EXCEPTION " + e); }
|
||||
finally { File.WriteAllText(OUT, sb.ToString(), new UTF8Encoding(true)); }
|
||||
}
|
||||
|
||||
static void CapBody()
|
||||
{
|
||||
EditorSceneManager.OpenScene(SCENE, OpenSceneMode.Single);
|
||||
GameObject root = null;
|
||||
foreach (var go in UnityEngine.SceneManagement.SceneManager.GetActiveScene().GetRootGameObjects())
|
||||
if (go.name.Contains("LH_M05")) { root = go; break; }
|
||||
var smrs = root.GetComponentsInChildren<SkinnedMeshRenderer>(true).Where(r => r.enabled).ToArray();
|
||||
var faceSMR = smrs.First(r => r.name == "Face");
|
||||
var hairSMR = smrs.First(r => r.name == "Hair05");
|
||||
var scam = Camera.main ?? UnityEngine.Object.FindObjectsByType<Camera>(FindObjectsInactive.Exclude, FindObjectsSortMode.None).First(c => c.orthographic);
|
||||
|
||||
// 🔴 씬의 기본 포즈(yaw 200)에서는 캐릭터가 카메라를 등지고 있어 얼굴이 거의 안 보인다.
|
||||
// 「얼굴이 몇 px 인가」는 카메라를 정면으로 볼 때가 최대치이므로 그 자세로 잰다(메모리에서만 · 씬 저장 0).
|
||||
float camYaw = scam.transform.eulerAngles.y;
|
||||
float poseYaw = root.transform.eulerAngles.y;
|
||||
root.transform.rotation = Quaternion.Euler(0, camYaw + 180f, 0);
|
||||
L(string.Format("포즈: 씬 기본 yaw {0:F0}° → 카메라 정면 yaw {1:F0}° 로 돌려서 측정·캡처 (카메라 yaw {2:F0}°)", poseYaw, camYaw + 180f, camYaw));
|
||||
|
||||
// 캐릭터 중심 프레이밍 (씬 카메라는 건드리지 않고 복사본만 쓴다)
|
||||
var b = new Bounds(root.transform.position, Vector3.zero); foreach (var r in smrs) b.Encapsulate(r.bounds);
|
||||
var go2 = new GameObject("WL814u_Cap");
|
||||
var cam = go2.AddComponent<Camera>(); cam.CopyFrom(scam);
|
||||
cam.orthographic = true; cam.orthographicSize = 10f; cam.aspect = (float)W / H; cam.targetTexture = null;
|
||||
cam.transform.SetPositionAndRotation(b.center - scam.transform.forward * 50f, scam.transform.rotation);
|
||||
var ucd = cam.GetUniversalAdditionalCameraData(); if (ucd != null) { ucd.renderPostProcessing = false; ucd.SetRenderer(0); }
|
||||
|
||||
// 🔴 배치 모드 첫 렌더는 텍스처가 아직 안 올라와 캐릭터가 하얗게 나온다(1차 캡처에서 실측) → 워밍업 3장 버린다
|
||||
for (int i = 0; i < 3; i++) Render(cam, W, H);
|
||||
var before = Render(cam, W, H);
|
||||
|
||||
// ── 도트 텍스처를 「임시 머티리얼 인스턴스」에만 꽂는다 (에셋 무변경) ──
|
||||
var faceOrig = faceSMR.sharedMaterial; var hairOrig = hairSMR.sharedMaterial;
|
||||
var facePx = AssetDatabase.LoadAssetAtPath<Texture2D>(TEX + "face02_Pixel.png");
|
||||
var hairPx = AssetDatabase.LoadAssetAtPath<Texture2D>(TEX + "Hair05_Pixel.png");
|
||||
if (facePx == null || hairPx == null) { L("!! 시제품 텍스처를 못 찾음"); return; }
|
||||
var fm = new Material(faceOrig) { name = "TEMP_face_px" };
|
||||
if (fm.HasProperty("_MainTex")) fm.SetTexture("_MainTex", facePx);
|
||||
if (fm.HasProperty("_BaseMap")) fm.SetTexture("_BaseMap", facePx);
|
||||
var hm = new Material(hairOrig) { name = "TEMP_hair_px" };
|
||||
if (hm.HasProperty("_BaseMap")) hm.SetTexture("_BaseMap", hairPx);
|
||||
if (hm.HasProperty("_ShadowBaseMap")) hm.SetTexture("_ShadowBaseMap", hairPx);
|
||||
if (hm.HasProperty("_MainTex")) hm.SetTexture("_MainTex", hairPx);
|
||||
faceSMR.sharedMaterial = fm; hairSMR.sharedMaterial = hm;
|
||||
L("프리뷰 적용: Face ← face02_Pixel · Hair05 ← Hair05_Pixel (임시 Material 인스턴스 · 에셋 저장 0)");
|
||||
|
||||
var after = Render(cam, W, H);
|
||||
|
||||
// 원복 (씬은 어차피 저장 안 하지만 확실히)
|
||||
faceSMR.sharedMaterial = faceOrig; hairSMR.sharedMaterial = hairOrig;
|
||||
|
||||
// ── 캐릭터 bbox 를 찾아 머리 크롭 ──
|
||||
var vpMin = cam.WorldToViewportPoint(b.min); var vpMax = cam.WorldToViewportPoint(b.max);
|
||||
int cx = Mathf.RoundToInt(W * 0.5f), cy = Mathf.RoundToInt(H * 0.5f);
|
||||
// §1 실측: 키 98 px · y[912..1009] (같은 프레이밍) → 머리는 위쪽 40 px
|
||||
var headCrop = new RectInt(cx - 32, cy + 4, 64, 56);
|
||||
var bodyCrop = new RectInt(cx - 40, cy - 56, 80, 120);
|
||||
|
||||
Save(Compose(new[] { before, after }, 1, null, 16), "u_1_ingame_compare.png");
|
||||
Save(Compose(new[] { before, after }, 4, bodyCrop, 12), "u_2_char_4x_compare.png");
|
||||
Save(Compose(new[] { before, after }, 12, headCrop, 12), "u_3_head_12x_compare.png");
|
||||
|
||||
// ── 읽힘 실측: 얼굴 데칼 영역의 색·대비 ──
|
||||
// 캐릭터만 남기는 레이어 마스크 카메라로 face 만 렌더 → 그 마스크 안에서 before/after 비교
|
||||
var all = root.GetComponentsInChildren<Transform>(true);
|
||||
var orig = new Dictionary<Transform, int>(); foreach (var t in all) { orig[t] = t.gameObject.layer; t.gameObject.layer = LAYER; }
|
||||
var mcamGO = new GameObject("WL814u_Mask"); var mcam = mcamGO.AddComponent<Camera>();
|
||||
mcam.CopyFrom(cam); mcam.cullingMask = 1 << LAYER; mcam.clearFlags = CameraClearFlags.SolidColor; mcam.aspect = (float)W / H;
|
||||
var md = mcam.GetUniversalAdditionalCameraData(); if (md != null) { md.renderPostProcessing = false; md.SetRenderer(0); }
|
||||
|
||||
Func<SkinnedMeshRenderer[], bool[]> mask = sel =>
|
||||
{
|
||||
foreach (var r in smrs) r.enabled = sel.Contains(r);
|
||||
mcam.backgroundColor = Color.black; var k1 = Render(mcam, W, H);
|
||||
mcam.backgroundColor = Color.white; var k2 = Render(mcam, W, H);
|
||||
var m = new bool[W * H];
|
||||
for (int i = 0; i < m.Length; i++)
|
||||
{
|
||||
int d = Mathf.Max(Mathf.Abs(k1.px[i].r - k2.px[i].r), Mathf.Max(Mathf.Abs(k1.px[i].g - k2.px[i].g), Mathf.Abs(k1.px[i].b - k2.px[i].b)));
|
||||
m[i] = d < 128;
|
||||
}
|
||||
foreach (var r in smrs) r.enabled = true;
|
||||
return m;
|
||||
};
|
||||
|
||||
var mFaceBefore = mask(new[] { faceSMR });
|
||||
faceSMR.sharedMaterial = fm; hairSMR.sharedMaterial = hm;
|
||||
var mFaceAfter = mask(new[] { faceSMR });
|
||||
faceSMR.sharedMaterial = faceOrig; hairSMR.sharedMaterial = hairOrig;
|
||||
var mAll = mask(smrs);
|
||||
var mHair = mask(new[] { hairSMR });
|
||||
|
||||
L("");
|
||||
L("== 읽힘 실측 (얼굴 데칼 · 화면 1080×1920 · ortho 10 · 카메라 정면) ==");
|
||||
Report("현재 face02", before, mFaceBefore);
|
||||
Report("도트 face02_Pixel", after, mFaceAfter);
|
||||
// 얼굴 데칼 bbox → 눈 1개 환산 (텍스처 그려진 영역 889×617 텍셀 기준)
|
||||
foreach (var kv in new[] { new KeyValuePair<string, bool[]>("현재", mFaceBefore), new KeyValuePair<string, bool[]>("도트", mFaceAfter) })
|
||||
{
|
||||
int fx0 = W, fx1 = -1, fy0 = H, fy1 = -1;
|
||||
for (int i = 0; i < kv.Value.Length; i++) if (kv.Value[i]) { int x = i % W, y = i / W; if (x < fx0) fx0 = x; if (x > fx1) fx1 = x; if (y < fy0) fy0 = y; if (y > fy1) fy1 = y; }
|
||||
if (fx1 < 0) continue;
|
||||
float sx = (fx1 - fx0 + 1) / 889f, sy = (fy1 - fy0 + 1) / 617f;
|
||||
L(string.Format(" {0} 얼굴 데칼 {1}×{2} px → 눈 1개 {3:F2}×{4:F2} px · 눈썹 {5:F2}×{6:F2} · 입 {7:F2}×{8:F2}",
|
||||
kv.Key, fx1 - fx0 + 1, fy1 - fy0 + 1, 277 * sx, 183 * sy, 165 * sx, 107 * sy, 67 * sx, 14 * sy));
|
||||
}
|
||||
L("");
|
||||
L("== 부위 색 수 (캐릭터 전체 / 머리칼) ==");
|
||||
Report("현재 전체", before, mAll); Report("도트 전체", after, mAll);
|
||||
Report("현재 머리칼", before, mHair); Report("도트 머리칼", after, mHair);
|
||||
|
||||
foreach (var kv in orig) if (kv.Key != null) kv.Key.gameObject.layer = kv.Value;
|
||||
UnityEngine.Object.DestroyImmediate(mcamGO); UnityEngine.Object.DestroyImmediate(go2);
|
||||
UnityEngine.Object.DestroyImmediate(fm); UnityEngine.Object.DestroyImmediate(hm);
|
||||
}
|
||||
|
||||
static void Report(string tag, Shot s, bool[] m)
|
||||
{
|
||||
var set = new HashSet<int>(); int n = 0; float lmin = 999, lmax = -1; double lsum = 0;
|
||||
int x0 = W, x1 = -1, y0 = H, y1 = -1;
|
||||
for (int i = 0; i < m.Length; i++)
|
||||
{
|
||||
if (!m[i]) continue;
|
||||
n++; var c = s.px[i];
|
||||
set.Add((c.r << 16) | (c.g << 8) | c.b);
|
||||
float l = 0.299f * c.r + 0.587f * c.g + 0.114f * c.b;
|
||||
lsum += l; if (l < lmin) lmin = l; if (l > lmax) lmax = l;
|
||||
int x = i % W, y = i / W; if (x < x0) x0 = x; if (x > x1) x1 = x; if (y < y0) y0 = y; if (y > y1) y1 = y;
|
||||
}
|
||||
if (n == 0) { L(" " + tag + ": 0 px"); return; }
|
||||
L(string.Format(" {0}: {1} px ({2}×{3}) · 고유색 {4} ({5:F0} % 가 서로 다른 색) · 밝기 {6:F0}~{7:F0}(폭 {8:F0}) 평균 {9:F0}",
|
||||
tag, n, x1 - x0 + 1, y1 - y0 + 1, set.Count, 100f * set.Count / n, lmin, lmax, lmax - lmin, lsum / n));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,403 @@
|
|||
// WL814u_Crawl.cs — 「3D 를 유지한 채 픽셀 떨림을 줄이는 대책」 실측 (에디터 전용 · 발주서 WL-814u 범위변경 ⓓ)
|
||||
// 🔴 씬 저장 0 · 에셋 수정 0 · ProjectSettings 0.
|
||||
//
|
||||
// 떨림 수치 정의 (근거를 남긴다)
|
||||
// 두 연속 프레임의 캐릭터 실루엣을 각자의 bbox 왼쪽아래로 맞춘 뒤(= 이동분을 뺀 뒤) 비교한다.
|
||||
// shapeFlip % = XOR(실루엣A, 실루엣B) / 두 면적 평균 ← 「모양이 몇 % 바뀌었나」
|
||||
// colorΔ % = 둘 다 덮인 픽셀 중 색 차이>24 인 비율 ← 「색이 몇 % 흔들렸나」
|
||||
// 이동분을 빼기 때문에 「캐릭터가 지나간다」는 정상 움직임은 0 이 되고, 떨림만 남는다.
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
|
||||
public static class WL814u_Crawl
|
||||
{
|
||||
const string SCENE = "Assets/WL/Look/Arena/Scenes/WL_ArenaProto.unity";
|
||||
const string SHOT = "Screenshots_WL/WL814u/";
|
||||
const string OUT = "AgentScripts/WL814u_CRAWL.txt";
|
||||
const int LAYER = 31;
|
||||
static readonly StringBuilder sb = new StringBuilder();
|
||||
static void L(string s) { sb.AppendLine(s); Console.WriteLine("[814uX] " + s); }
|
||||
|
||||
class Shot { public Color32[] px; public int w, h; }
|
||||
class Frame { public bool[] m; public Shot c; public int x0, y0, x1, y1, area; }
|
||||
|
||||
static GameObject root; static SkinnedMeshRenderer[] smrs; static Camera scam;
|
||||
static Camera cam, mcam;
|
||||
|
||||
static Shot Render(Camera c, int w, int h)
|
||||
{
|
||||
var rt = new RenderTexture(w, h, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB);
|
||||
rt.antiAliasing = 1; rt.filterMode = FilterMode.Point;
|
||||
var pA = RenderTexture.active; c.targetTexture = rt; c.Render(); RenderTexture.active = rt;
|
||||
var t = new Texture2D(w, h, TextureFormat.RGBA32, false); t.ReadPixels(new Rect(0, 0, w, h), 0, 0); t.Apply();
|
||||
c.targetTexture = null; RenderTexture.active = pA;
|
||||
var s = new Shot { px = t.GetPixels32(), w = w, h = h };
|
||||
UnityEngine.Object.DestroyImmediate(t); rt.Release(); UnityEngine.Object.DestroyImmediate(rt);
|
||||
return s;
|
||||
}
|
||||
|
||||
static Frame Grab(int w, int h)
|
||||
{
|
||||
mcam.backgroundColor = Color.black; var a = Render(mcam, w, h);
|
||||
mcam.backgroundColor = Color.white; var b = Render(mcam, w, h);
|
||||
var f = new Frame { m = new bool[w * h], c = a, x0 = w, y0 = h, x1 = -1, y1 = -1 };
|
||||
for (int i = 0; i < f.m.Length; i++)
|
||||
{
|
||||
int d = Mathf.Max(Mathf.Abs(a.px[i].r - b.px[i].r), Mathf.Max(Mathf.Abs(a.px[i].g - b.px[i].g), Mathf.Abs(a.px[i].b - b.px[i].b)));
|
||||
if (d < 128) { f.m[i] = true; f.area++; int x = i % w, y = i / w; if (x < f.x0) f.x0 = x; if (x > f.x1) f.x1 = x; if (y < f.y0) f.y0 = y; if (y > f.y1) f.y1 = y; }
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
// bbox 왼쪽아래 기준으로 맞춘 뒤 비교
|
||||
static void Compare(Frame A, Frame B, int w, int h, out float shapeFlip, out float colorD)
|
||||
{
|
||||
int dx = B.x0 - A.x0, dy = B.y0 - A.y0;
|
||||
int xor = 0, both = 0, chg = 0;
|
||||
for (int y = A.y0; y <= A.y1; y++)
|
||||
for (int x = A.x0; x <= A.x1; x++)
|
||||
{
|
||||
int bx = x + dx, by = y + dy;
|
||||
bool ma = A.m[y * w + x];
|
||||
bool mb = (bx >= 0 && by >= 0 && bx < w && by < h) && B.m[by * w + bx];
|
||||
if (ma != mb) xor++;
|
||||
else if (ma)
|
||||
{
|
||||
both++;
|
||||
var ca = A.c.px[y * w + x]; var cb = B.c.px[by * w + bx];
|
||||
if (Mathf.Abs(ca.r - cb.r) + Mathf.Abs(ca.g - cb.g) + Mathf.Abs(ca.b - cb.b) > 24) chg++;
|
||||
}
|
||||
}
|
||||
shapeFlip = 200f * xor / Mathf.Max(1, A.area + B.area);
|
||||
colorD = 100f * chg / Mathf.Max(1, both);
|
||||
}
|
||||
|
||||
static bool[] Dilate(bool[] m, int w, int h, int n)
|
||||
{
|
||||
var cur = (bool[])m.Clone();
|
||||
for (int k = 0; k < n; k++)
|
||||
{
|
||||
var nx = (bool[])cur.Clone();
|
||||
for (int y = 0; y < h; y++)
|
||||
for (int x = 0; x < w; x++)
|
||||
if (cur[y * w + x])
|
||||
{
|
||||
if (x > 0) nx[y * w + x - 1] = true; if (x < w - 1) nx[y * w + x + 1] = true;
|
||||
if (y > 0) nx[(y - 1) * w + x] = true; if (y < h - 1) nx[(y + 1) * w + x] = true;
|
||||
}
|
||||
cur = nx;
|
||||
}
|
||||
return cur;
|
||||
}
|
||||
|
||||
static void Setup(float ortho, int w, int h)
|
||||
{
|
||||
if (cam != null) UnityEngine.Object.DestroyImmediate(cam.gameObject);
|
||||
if (mcam != null) UnityEngine.Object.DestroyImmediate(mcam.gameObject);
|
||||
var b = new Bounds(root.transform.position, Vector3.zero); foreach (var r in smrs) b.Encapsulate(r.bounds);
|
||||
cam = new GameObject("WL814u_X").AddComponent<Camera>(); cam.CopyFrom(scam);
|
||||
cam.orthographic = true; cam.orthographicSize = ortho; cam.aspect = (float)w / h; cam.targetTexture = null;
|
||||
cam.transform.SetPositionAndRotation(b.center - scam.transform.forward * 50f, scam.transform.rotation);
|
||||
var d1 = cam.GetUniversalAdditionalCameraData(); if (d1 != null) { d1.renderPostProcessing = false; d1.SetRenderer(0); }
|
||||
mcam = new GameObject("WL814u_XM").AddComponent<Camera>(); mcam.CopyFrom(cam);
|
||||
mcam.cullingMask = 1 << LAYER; mcam.clearFlags = CameraClearFlags.SolidColor; mcam.aspect = (float)w / h;
|
||||
var d2 = mcam.GetUniversalAdditionalCameraData(); if (d2 != null) { d2.renderPostProcessing = false; d2.SetRenderer(0); }
|
||||
for (int i = 0; i < 3; i++) Render(mcam, w, h); // 워밍업(첫 렌더는 텍스처 미로드)
|
||||
}
|
||||
|
||||
// PixelCameraManager.PositionToGrid 와 같은 식: 카메라 축 기준 픽셀 격자에 반올림
|
||||
static Vector3 ToGrid(Vector3 p, float pixelWorld)
|
||||
{
|
||||
var t = cam.transform;
|
||||
float rx = Vector3.Dot(p, t.right), ry = Vector3.Dot(p, t.up), rz = Vector3.Dot(p, t.forward);
|
||||
rx = Mathf.Round(rx / pixelWorld) * pixelWorld; ry = Mathf.Round(ry / pixelWorld) * pixelWorld; rz = Mathf.Round(rz / pixelWorld) * pixelWorld;
|
||||
return rx * t.right + ry * t.up + rz * t.forward;
|
||||
}
|
||||
|
||||
public static void Run()
|
||||
{
|
||||
try { Body(); }
|
||||
catch (Exception e) { L("EXCEPTION " + e); }
|
||||
finally { Directory.CreateDirectory(Path.GetDirectoryName(OUT)); File.WriteAllText(OUT, sb.ToString(), new UTF8Encoding(true)); }
|
||||
}
|
||||
|
||||
// ⓓ-2 재측정 — 1차는 8° 만 돌려서 16방향이 경계를 한 번도 안 넘었다(0.00 % 는 측정 구간 탓).
|
||||
// 이번엔 90° 를 돌려 모든 양자화가 경계를 여러 번 넘게 한다.
|
||||
public static void Run2()
|
||||
{
|
||||
try
|
||||
{
|
||||
EditorSceneManager.OpenScene(SCENE, OpenSceneMode.Single);
|
||||
foreach (var go in UnityEngine.SceneManagement.SceneManager.GetActiveScene().GetRootGameObjects())
|
||||
if (go.name.Contains("LH_M05")) root = go;
|
||||
smrs = root.GetComponentsInChildren<SkinnedMeshRenderer>(true).Where(r => r.enabled).ToArray();
|
||||
scam = Camera.main ?? UnityEngine.Object.FindObjectsByType<Camera>(FindObjectsInactive.Exclude, FindObjectsSortMode.None).First(c => c.orthographic);
|
||||
var allT = root.GetComponentsInChildren<Transform>(true);
|
||||
var orig = new Dictionary<Transform, int>(); foreach (var t in allT) { orig[t] = t.gameObject.layer; t.gameObject.layer = LAYER; }
|
||||
var baseRot = root.transform.rotation;
|
||||
float yaw0 = scam.transform.eulerAngles.y + 180f;
|
||||
Setup(3.4f, 135, 240);
|
||||
const int N = 91; const float STEP = 1.0f; // 1°/프레임 × 90 = 90° 회전
|
||||
L("");
|
||||
L("== ⓓ-2(재측정) 회전 계단화 · 90° 회전 (1°/프레임 × 90) · 도트A 240줄 ==");
|
||||
L(" | 방향 수 | 각도 간격 | 떨림 0 인 프레임 | 모양 변화 평균 %/프레임 | 계단 순간 최대 % | 계단 횟수/90° |");
|
||||
foreach (int dirs in new[] { 0, 32, 16, 8 })
|
||||
{
|
||||
float q = dirs == 0 ? 0f : 360f / dirs;
|
||||
Frame prev = null; var sf = new List<float>(); int zero = 0, steps = 0; float mx = 0;
|
||||
for (int i = 0; i < N; i++)
|
||||
{
|
||||
float y = yaw0 + i * STEP; if (q > 0f) y = Mathf.Round(y / q) * q;
|
||||
root.transform.rotation = Quaternion.Euler(0, y, 0);
|
||||
var f = Grab(135, 240);
|
||||
if (prev != null)
|
||||
{
|
||||
Compare(prev, f, 135, 240, out float s, out float c);
|
||||
sf.Add(s); if (s < 0.01f) zero++; if (s > 0.5f) steps++; if (s > mx) mx = s;
|
||||
}
|
||||
prev = f;
|
||||
}
|
||||
L(string.Format(" | {0} | {1} | **{2}/90** | **{3:F2} %** | {4:F1} % | {5} |",
|
||||
dirs == 0 ? "양자화 없음" : dirs.ToString(), dirs == 0 ? "-" : q.ToString("F1") + "°",
|
||||
zero, sf.Average(), mx, steps));
|
||||
}
|
||||
root.transform.rotation = baseRot;
|
||||
foreach (var kv in orig) if (kv.Key != null) kv.Key.gameObject.layer = kv.Value;
|
||||
if (cam != null) UnityEngine.Object.DestroyImmediate(cam.gameObject);
|
||||
if (mcam != null) UnityEngine.Object.DestroyImmediate(mcam.gameObject);
|
||||
}
|
||||
catch (Exception e) { L("EXCEPTION " + e); }
|
||||
finally { File.AppendAllText(OUT, sb.ToString(), new UTF8Encoding(false)); }
|
||||
}
|
||||
|
||||
static void Body()
|
||||
{
|
||||
EditorSceneManager.OpenScene(SCENE, OpenSceneMode.Single);
|
||||
foreach (var go in UnityEngine.SceneManagement.SceneManager.GetActiveScene().GetRootGameObjects())
|
||||
if (go.name.Contains("LH_M05")) root = go;
|
||||
smrs = root.GetComponentsInChildren<SkinnedMeshRenderer>(true).Where(r => r.enabled).ToArray();
|
||||
scam = Camera.main ?? UnityEngine.Object.FindObjectsByType<Camera>(FindObjectsInactive.Exclude, FindObjectsSortMode.None).First(c => c.orthographic);
|
||||
var all = root.GetComponentsInChildren<Transform>(true);
|
||||
var orig = new Dictionary<Transform, int>(); foreach (var t in all) { orig[t] = t.gameObject.layer; t.gameObject.layer = LAYER; }
|
||||
float camYaw = scam.transform.eulerAngles.y;
|
||||
var basePos = root.transform.position;
|
||||
var baseRot = root.transform.rotation;
|
||||
root.transform.rotation = Quaternion.Euler(0, camYaw + 180f, 0);
|
||||
float yaw0 = camYaw + 180f;
|
||||
|
||||
const int N = 33; // 33 프레임 = 32 비교
|
||||
const float STEP = 0.25f; // 프레임당 0.25° 회전 (걷다가 방향 트는 속도 수준)
|
||||
|
||||
try
|
||||
{
|
||||
// ── 1) 해상도별 회전 떨림 ────────────────────────────────────
|
||||
L("== ⓓ-1 해상도별 회전 떨림 (캐릭터 yaw 0.25°/프레임 × 32회 · 이동분 제거 후 비교) ==");
|
||||
L(" | 설정 | 캐릭터 키 | 모양 변화 %/프레임 | 색 변화 %/프레임 | 1° 환산 모양 % |");
|
||||
var res = new (string name, float ortho, int w, int h)[] {
|
||||
("도트A 240줄 (ortho 3.4)", 3.4f, 135, 240),
|
||||
("360줄 (ortho 3.4)", 3.4f, 203, 360),
|
||||
("480줄 (ortho 3.4)", 3.4f, 270, 480),
|
||||
("현재 1920줄 (ortho 10)", 10f, 1080, 1920),
|
||||
};
|
||||
foreach (var r in res)
|
||||
{
|
||||
Setup(r.ortho, r.w, r.h);
|
||||
var (sf, cd, hgt) = RotSweep(r.w, r.h, yaw0, STEP, N, 0f);
|
||||
L(string.Format(" | {0} | {1} px | **{2:F2} %** | **{3:F1} %** | {4:F1} % |", r.name, hgt, sf, cd, sf / STEP));
|
||||
}
|
||||
|
||||
// ── 2) 회전 양자화 ──────────────────────────────────────────
|
||||
L("");
|
||||
L("== ⓓ-2 회전 계단화 (yaw 를 N방향으로 반올림 · 도트A 240줄) ==");
|
||||
L(" | 방향 수 | 각도 간격 | 모양 변화 평균 %/프레임 | 그중 최대(계단 순간) % | 계단이 생긴 프레임 수/32 |");
|
||||
Setup(3.4f, 135, 240);
|
||||
foreach (int dirs in new[] { 0, 32, 16, 8 })
|
||||
{
|
||||
float q = dirs == 0 ? 0f : 360f / dirs;
|
||||
var (sf, cd, _) = RotSweep(135, 240, yaw0, STEP, N, q, out float mx, out int steps);
|
||||
L(string.Format(" | {0} | {1} | **{2:F2} %** | {3:F2} % | {4} |",
|
||||
dirs == 0 ? "양자화 없음(연속)" : dirs.ToString(), dirs == 0 ? "-" : (q.ToString("F1") + "°"), sf, mx, steps));
|
||||
}
|
||||
|
||||
// ── 3) 이동 + 픽셀 격자 스냅 ────────────────────────────────
|
||||
L("");
|
||||
L("== ⓓ-3 이동 시 떨림 · 픽셀 격자 스냅(VoxelGridAdjuster 방식) 유/무 (도트A 240줄) ==");
|
||||
Setup(3.4f, 135, 240);
|
||||
float pixelWorld = 2f * 3.4f / 240f;
|
||||
L(string.Format(" 픽셀 1개 = {0:F5} m · 프레임당 이동 0.37 px (일부러 격자에 안 맞는 값)", pixelWorld));
|
||||
L(" | 스냅 | 모양 변화 평균 %/프레임 | 최대 % |");
|
||||
foreach (bool snap in new[] { false, true })
|
||||
{
|
||||
root.transform.rotation = Quaternion.Euler(0, yaw0, 0);
|
||||
Frame prev = null; var sfs = new List<float>();
|
||||
for (int i = 0; i < N; i++)
|
||||
{
|
||||
var p = basePos + cam.transform.right * (0.37f * pixelWorld * i);
|
||||
root.transform.position = snap ? ToGrid(p, pixelWorld) : p;
|
||||
var f = Grab(135, 240);
|
||||
if (prev != null) { Compare(prev, f, 135, 240, out float s, out float c); sfs.Add(s); }
|
||||
prev = f;
|
||||
}
|
||||
root.transform.position = basePos;
|
||||
L(string.Format(" | {0} | **{1:F2} %** | {2:F2} % |", snap ? "켬(격자 스냅)" : "끔", sfs.Average(), sfs.Max()));
|
||||
}
|
||||
|
||||
// ── 4) 외곽선 두께와 떨림 ───────────────────────────────────
|
||||
L("");
|
||||
L("== ⓓ-4 외곽선 두께 vs 떨림 (회전 중 · 도트A 240줄 · 외곽선 = 실루엣을 N px 부풀린 띠) ==");
|
||||
Setup(3.4f, 135, 240);
|
||||
L(" | 외곽선 두께 | 띠 면적(px) | 띠가 프레임마다 바뀌는 비율 % | 캐릭터 대비 띠 비중 % |");
|
||||
var frames = new List<Frame>();
|
||||
for (int i = 0; i < 17; i++) { root.transform.rotation = Quaternion.Euler(0, yaw0 + i * STEP, 0); frames.Add(Grab(135, 240)); }
|
||||
foreach (int th in new[] { 1, 2, 3 })
|
||||
{
|
||||
var bandFlip = new List<float>(); var bandArea = new List<float>(); var ratio = new List<float>();
|
||||
for (int i = 1; i < frames.Count; i++)
|
||||
{
|
||||
var A = frames[i - 1]; var B = frames[i];
|
||||
var dA = Dilate(A.m, 135, 240, th); var dB = Dilate(B.m, 135, 240, th);
|
||||
int dx = B.x0 - A.x0, dy = B.y0 - A.y0;
|
||||
int xor = 0, ar = 0;
|
||||
for (int y = 1; y < 239; y++)
|
||||
for (int x = 1; x < 134; x++)
|
||||
{
|
||||
bool ba = dA[y * 135 + x] && !A.m[y * 135 + x];
|
||||
int bxx = x + dx, byy = y + dy;
|
||||
bool bb = (bxx > 0 && byy > 0 && bxx < 134 && byy < 239) && dB[byy * 135 + bxx] && !B.m[byy * 135 + bxx];
|
||||
if (ba) ar++;
|
||||
if (ba != bb) xor++;
|
||||
}
|
||||
bandFlip.Add(100f * xor / Mathf.Max(1, ar)); bandArea.Add(ar); ratio.Add(100f * ar / A.area);
|
||||
}
|
||||
L(string.Format(" | {0} px | {1:F0} | **{2:F1} %** | {3:F0} % |", th, bandArea.Average(), bandFlip.Average(), ratio.Average()));
|
||||
}
|
||||
|
||||
// ── 5) 연속 프레임 비교 이미지 ──────────────────────────────
|
||||
L("");
|
||||
L("== 캡처 ==");
|
||||
Strip("u_6_crawl_rotation.png", 3.4f, 135, 240, yaw0, STEP, 0f, 16f, "회전 8프레임: 위=양자화 없음 / 아래=16방향 계단화");
|
||||
StripMove("u_7_crawl_move_snap.png", 3.4f, 135, 240, yaw0, basePos);
|
||||
}
|
||||
finally
|
||||
{
|
||||
root.transform.rotation = baseRot; root.transform.position = basePos;
|
||||
foreach (var kv in orig) if (kv.Key != null) kv.Key.gameObject.layer = kv.Value;
|
||||
if (cam != null) UnityEngine.Object.DestroyImmediate(cam.gameObject);
|
||||
if (mcam != null) UnityEngine.Object.DestroyImmediate(mcam.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
static (float, float, int) RotSweep(int w, int h, float yaw0, float step, int n, float q)
|
||||
{ return RotSweep(w, h, yaw0, step, n, q, out _, out _); }
|
||||
|
||||
static (float, float, int) RotSweep(int w, int h, float yaw0, float step, int n, float q, out float maxFlip, out int stepCount)
|
||||
{
|
||||
Frame prev = null; var sf = new List<float>(); var cd = new List<float>(); int hgt = 0;
|
||||
maxFlip = 0; stepCount = 0;
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
float y = yaw0 + i * step;
|
||||
if (q > 0f) y = Mathf.Round(y / q) * q;
|
||||
root.transform.rotation = Quaternion.Euler(0, y, 0);
|
||||
var f = Grab(w, h);
|
||||
hgt = f.y1 - f.y0 + 1;
|
||||
if (prev != null)
|
||||
{
|
||||
Compare(prev, f, w, h, out float s, out float c);
|
||||
sf.Add(s); cd.Add(c);
|
||||
if (s > maxFlip) maxFlip = s;
|
||||
if (s > 0.5f) stepCount++;
|
||||
}
|
||||
prev = f;
|
||||
}
|
||||
return (sf.Average(), cd.Average(), hgt);
|
||||
}
|
||||
|
||||
// 회전 8프레임 × 2행(양자화 없음 / 양자화)
|
||||
static void Strip(string file, float ortho, int w, int h, float yaw0, float step, float q1, float dirs2, string note)
|
||||
{
|
||||
Setup(ortho, w, h);
|
||||
int zoom = 6, crop = 48;
|
||||
var rows = new List<Shot[]>();
|
||||
foreach (float dirs in new[] { 0f, dirs2 })
|
||||
{
|
||||
float q = dirs == 0f ? 0f : 360f / dirs;
|
||||
var row = new List<Shot>();
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
float y = yaw0 + i * step * 4f; // 8프레임에 8° 회전
|
||||
if (q > 0f) y = Mathf.Round(y / q) * q;
|
||||
root.transform.rotation = Quaternion.Euler(0, y, 0);
|
||||
row.Add(Render(cam, w, h));
|
||||
}
|
||||
rows.Add(row.ToArray());
|
||||
}
|
||||
// 합성
|
||||
int cw = crop, chh = crop, gap = 4;
|
||||
int ow = (cw * zoom + gap) * 8 - gap, oh = (chh * zoom + gap) * rows.Count - gap;
|
||||
var o = new Shot { w = ow, h = oh, px = new Color32[ow * oh] };
|
||||
for (int i = 0; i < o.px.Length; i++) o.px[i] = new Color32(24, 24, 28, 255);
|
||||
int cx = w / 2 - crop / 2, cy = h / 2 - crop / 2;
|
||||
for (int r = 0; r < rows.Count; r++)
|
||||
for (int k = 0; k < 8; k++)
|
||||
for (int y = 0; y < chh * zoom; y++)
|
||||
for (int x = 0; x < cw * zoom; x++)
|
||||
{
|
||||
int sx = cx + x / zoom, sy = cy + y / zoom;
|
||||
if (sx < 0 || sy < 0 || sx >= w || sy >= h) continue;
|
||||
int dxp = k * (cw * zoom + gap) + x;
|
||||
int dyp = (rows.Count - 1 - r) * (chh * zoom + gap) + y;
|
||||
o.px[dyp * ow + dxp] = rows[r][k].px[sy * w + sx];
|
||||
}
|
||||
SaveShot(o, file); L(" " + file + " — " + note);
|
||||
}
|
||||
|
||||
static void StripMove(string file, float ortho, int w, int h, float yaw0, Vector3 basePos)
|
||||
{
|
||||
Setup(ortho, w, h);
|
||||
root.transform.rotation = Quaternion.Euler(0, yaw0, 0);
|
||||
float pixelWorld = 2f * ortho / h;
|
||||
int zoom = 6, crop = 48, gap = 4;
|
||||
var rows = new List<Shot[]>();
|
||||
foreach (bool snap in new[] { false, true })
|
||||
{
|
||||
var row = new List<Shot>();
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
var p = basePos + cam.transform.right * (0.37f * pixelWorld * i);
|
||||
root.transform.position = snap ? ToGrid(p, pixelWorld) : p;
|
||||
row.Add(Render(cam, w, h));
|
||||
}
|
||||
rows.Add(row.ToArray());
|
||||
}
|
||||
root.transform.position = basePos;
|
||||
int ow = (crop * zoom + gap) * 8 - gap, oh = (crop * zoom + gap) * 2 - gap;
|
||||
var o = new Shot { w = ow, h = oh, px = new Color32[ow * oh] };
|
||||
for (int i = 0; i < o.px.Length; i++) o.px[i] = new Color32(24, 24, 28, 255);
|
||||
int cx = w / 2 - crop / 2, cy = h / 2 - crop / 2;
|
||||
for (int r = 0; r < 2; r++)
|
||||
for (int k = 0; k < 8; k++)
|
||||
for (int y = 0; y < crop * zoom; y++)
|
||||
for (int x = 0; x < crop * zoom; x++)
|
||||
{
|
||||
int sx = cx + x / zoom, sy = cy + y / zoom;
|
||||
if (sx < 0 || sy < 0 || sx >= w || sy >= h) continue;
|
||||
o.px[((1 - r) * (crop * zoom + gap) + y) * ow + k * (crop * zoom + gap) + x] = rows[r][k].px[sy * w + sx];
|
||||
}
|
||||
SaveShot(o, file); L(" " + file + " — 이동 8프레임: 위=스냅 없음 / 아래=픽셀 격자 스냅");
|
||||
}
|
||||
|
||||
static void SaveShot(Shot s, string file)
|
||||
{
|
||||
var t = new Texture2D(s.w, s.h, TextureFormat.RGBA32, false); t.SetPixels32(s.px); t.Apply();
|
||||
Directory.CreateDirectory(SHOT); File.WriteAllBytes(SHOT + file, t.EncodeToPNG());
|
||||
UnityEngine.Object.DestroyImmediate(t);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
# 🔴 1차 probe — 카메라 aspect 를 안 고정해 「머리 px」과 「면적/색 수」가 틀렸다. 확정값은 WL814u_PROBE2.txt / WL814u_CAPTURE.txt 를 보라. 키 98 px·얼굴 데칼 15×10 px(뒷모습) 은 동일.
|
||||
|
||||
scene = Assets/WL/Look/Arena/Scenes/WL_ArenaProto.unity
|
||||
root = PC_LH_M05 pos=(0.0000, 0.0000, 0.0000) rot=(0.00, 200.00, 0.00) scale=(1.0527, 1.0527, 1.0527)
|
||||
|
||||
== SMR 전수 ==
|
||||
Arm_m05 | sub=1 | mat=M05_ToonS | shader=Shader Graphs/Toon | tex=M05_WL (512x512)
|
||||
Body_m05 | sub=1 | mat=M05_ToonS | shader=Shader Graphs/Toon | tex=M05_WL (512x512)
|
||||
Face | sub=1 | mat=face02_ToonS | shader=Unlit/Transparent | tex=face02_WL (512x512)
|
||||
Hair05 | sub=1 | mat=Hair05_ToonS | shader=Shader Graphs/Toon | tex=Hair05_WL (512x512)
|
||||
Head | sub=1 | mat=Skin_ToonS | shader=Shader Graphs/Toon | tex=Skin_WL (512x512)
|
||||
Leg_M05 | sub=1 | mat=M05_ToonS | shader=Shader Graphs/Toon | tex=M05_WL (512x512)
|
||||
분류: face=1 hair=1 skin=1 body=3
|
||||
|
||||
== 본(머리 관련) ==
|
||||
Neck_M world=(0.0137, 0.6592, 0.0017)
|
||||
Head_M world=(0.0105, 0.6834, -0.0053)
|
||||
HeadEnd_M world=(0.0124, 0.7437, -0.0081)
|
||||
|
||||
== UV 점유 (텍스처 2048 기준) ==
|
||||
Arm_m05 sub0: u[0.3949..0.6612] v[0.6496..0.9741] → 546 × 664 텍셀 (tri 752)
|
||||
|
||||
== 씬 카메라 == Main Camera ortho=True size=10 pos=(-22.5000, 15.6000, -12.9904) euler=(30.000, 60.000, 0.000)
|
||||
|
||||
════════ A. 현재 카메라 (ortho 10 · 1080×1920) ════════
|
||||
화면 1080×1920 · ortho 10 · 1 m = 96.000 px · 1 px = 0.01042 m
|
||||
🔴 캐릭터 전체 실루엣: 키 98 px · 폭 46 px · 면적 2937 px (bbox x[518..563] y[912..1009])
|
||||
화면 세로의 5.1 % · 고유 색 2309
|
||||
🔴 머리(Neck_M 242 px ~ 정수리 1009 px): 높이 768 px · 폭 46 px · 면적 2937 px · 머리:키 = 1:0.13
|
||||
face02(얼굴 데칼): 15 × 10 px · 면적 32 px · 고유 색 31 (bbox x[543..557] y[968..977])
|
||||
🔴 얼굴 실효 해상도: 텍셀 1개 = 화면 0.0169 × 0.0162 px (= 텍스처 889×617 텍셀이 화면 15×10 px 로 찍힌다)
|
||||
🔴 눈 1개 = 화면 4.67 × 2.97 px · 눈썹 1개 = 2.78 × 1.73 px · 입 = 1.13 × 0.23 px
|
||||
텍스처 2048 기준 축소율 = 1/59 (가로) · 1/62 (세로)
|
||||
Hair05(머리칼): 42 × 41 px · 면적 1268 px · 고유 색 1135 (bbox x[522..563] y[969..1009])
|
||||
skin(피부): 33 × 38 px · 면적 1005 px · 고유 색 907 (bbox x[526..558] y[964..1001])
|
||||
M05(몸/갑옷): 44 × 61 px · 면적 1597 px · 고유 색 1189 (bbox x[518..561] y[912..972])
|
||||
|
||||
════════ B. 도트A (ortho 3.4 · 135×240) ════════
|
||||
화면 135×240 · ortho 3.4 · 1 m = 35.294 px · 1 px = 0.02833 m
|
||||
🔴 캐릭터 전체 실루엣: 키 36 px · 폭 16 px · 면적 392 px (bbox x[60..75] y[102..137])
|
||||
화면 세로의 15.0 % · 고유 색 379
|
||||
🔴 머리(Neck_M 247 px ~ 정수리 137 px): 높이 -109 px · 폭 -135 px · 면적 0 px · 머리:키 = 1:36.00
|
||||
face02(얼굴 데칼): 2 × 2 px · 면적 4 px · 고유 색 4 (bbox x[69..70] y[123..124])
|
||||
🔴 얼굴 실효 해상도: 텍셀 1개 = 화면 0.0022 × 0.0032 px (= 텍스처 889×617 텍셀이 화면 2×2 px 로 찍힌다)
|
||||
🔴 눈 1개 = 화면 0.62 × 0.59 px · 눈썹 1개 = 0.37 × 0.35 px · 입 = 0.15 × 0.05 px
|
||||
텍스처 2048 기준 축소율 = 1/445 (가로) · 1/309 (세로)
|
||||
Hair05(머리칼): 15 × 14 px · 면적 165 px · 고유 색 160 (bbox x[61..75] y[124..137])
|
||||
skin(피부): 13 × 14 px · 면적 135 px · 고유 색 132 (bbox x[62..74] y[121..134])
|
||||
M05(몸/갑옷): 15 × 22 px · 면적 214 px · 고유 색 207 (bbox x[60..74] y[102..123])
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
== UV 점유 (렌더러별 · 텍스처 2048 기준) ==
|
||||
Arm_m05: u[0.3949..0.6612] v[0.6496..0.9741] → 546×664 텍셀 · 64격자 점유 84/1024칸 · verts 468 tri 752
|
||||
Body_m05: u[0.0020..0.9980] v[0.0020..0.9187] → 2040×1878 텍셀 · 64격자 점유 315/1024칸 · verts 604 tri 984
|
||||
Face: u[0.0303..0.9698] v[0.2727..0.7664] → 1924×1011 텍셀 · 64격자 점유 118/1024칸 · verts 121 tri 212
|
||||
Hair05: u[0.0020..0.9980] v[0.0020..0.9951] → 2040×2034 텍셀 · 64격자 점유 464/1024칸 · verts 611 tri 914
|
||||
Head: u[0.6434..0.9980] v[0.0568..0.6033] → 726×1119 텍셀 · 64격자 점유 158/1024칸 · verts 339 tri 582
|
||||
Leg_M05: u[0.0022..0.5137] v[0.4002..0.8157] → 1048×851 텍셀 · 64격자 점유 125/1024칸 · verts 498 tri 844
|
||||
|
||||
════════ A. 현재 (ortho 10 · 1080×1920) ════════
|
||||
전체: 키 98 px · 폭 46 px · 면적 2291 px (y[912..1009])
|
||||
🔴 머리(Neck_M y=970 → 정수리 y=1009): 높이 40 px · 폭 42 px · 면적 1337 px · 머리:키 = 1:2.45
|
||||
🔴 얼굴 데칼(이목구비 전부): 15 × 10 px · 면적 32 px
|
||||
🔴 눈~턱(얼굴 데칼 위 977 → 턱=Neck_M 970): 8 px
|
||||
눈 1개 = 4.67 × 2.97 px · 눈썹 2.78 × 1.73 px · 입 1.13 × 0.23 px · 텍셀 1개 = 0.0169 px
|
||||
── 픽셀 떨림(pixel crawl) 실험 · 캐릭터 yaw 를 0.5° 씩 16회 ──
|
||||
0.5° 회전마다 실루엣 픽셀의 0.6 % 가 켜지거나 꺼진다 (최소 0.1 · 최대 0.8)
|
||||
0.5° 회전마다 남아 있는 픽셀의 4.9 % 가 색이 바뀐다(Δ>24) (최소 4.3 · 최대 5.5)
|
||||
→ 1° 당 실루엣 1.1 % · 색 9.8 %
|
||||
|
||||
════════ B. 도트A (ortho 3.4 · 135×240) ════════
|
||||
전체: 키 36 px · 폭 16 px · 면적 392 px (y[102..137])
|
||||
🔴 머리(Neck_M y=124 → 정수리 y=137): 높이 14 px · 폭 15 px · 면적 175 px · 머리:키 = 1:2.57
|
||||
🔴 얼굴 데칼(이목구비 전부): 2 × 2 px · 면적 4 px
|
||||
🔴 눈~턱(얼굴 데칼 위 124 → 턱=Neck_M 124): 1 px
|
||||
눈 1개 = 0.62 × 0.59 px · 눈썹 0.37 × 0.35 px · 입 0.15 × 0.05 px · 텍셀 1개 = 0.0022 px
|
||||
── 픽셀 떨림(pixel crawl) 실험 · 캐릭터 yaw 를 0.5° 씩 16회 ──
|
||||
0.5° 회전마다 실루엣 픽셀의 0.6 % 가 켜지거나 꺼진다 (최소 0.0 · 최대 1.8)
|
||||
0.5° 회전마다 남아 있는 픽셀의 4.9 % 가 색이 바뀐다(Δ>24) (최소 3.3 · 최대 7.6)
|
||||
→ 1° 당 실루엣 1.3 % · 색 9.8 %
|
||||
|
|
@ -0,0 +1,297 @@
|
|||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// WL814u_Probe.cs — 「도트 일러스트급 캐릭터」 한계 실측 (에디터 전용 · 발주서 WL-814u §1)
|
||||
//
|
||||
// 🔴 씬 저장 0 · 프리팹/머티리얼/에셋 수정 0 · ProjectSettings 0.
|
||||
// 씬은 읽기 전용으로 열고 메모리에서만 만진다(레이어 임시 변경 → 원복 · 저장 안 함).
|
||||
//
|
||||
// 측정
|
||||
// ① 화면에서 캐릭터가 차지하는 픽셀 (현재 카메라 ortho 10 · 1080×1920, 그리고 도트A ortho 3.4 · 135×240)
|
||||
// — 전체 키 / 머리(Neck_M 위) / 얼굴(face02 데칼의 실제 그려진 영역) / 눈 1개
|
||||
// ② 부위별 마스크 안에 실제로 찍히는 고유 색 수
|
||||
// ③ face02 · Hair05 의 UV 점유 영역 → 화면 실효 해상도(텍셀당 화면 픽셀)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
|
||||
public static class WL814u_Probe
|
||||
{
|
||||
const string SCENE = "Assets/WL/Look/Arena/Scenes/WL_ArenaProto.unity";
|
||||
const string OUT = "AgentScripts/WL814u_PROBE.txt";
|
||||
const int LAYER = 31;
|
||||
|
||||
// face02.png(2048²) 에서 알파>32 덩어리 5개 (파이썬 실측 · png 좌표 · row0 = 위)
|
||||
// eyeL x[578..854] y[948..1130] / eyeR x[1190..1466] / browL x[663..827] y[817..923] / browR x[1200..1364] / mouth x[991..1057] y[1420..1433]
|
||||
const int FACE_TEX = 2048;
|
||||
const int DRAWN_X0 = 578, DRAWN_X1 = 1466, DRAWN_Y0 = 817, DRAWN_Y1 = 1433; // 889 × 617
|
||||
const int EYE_W = 277, EYE_H = 183, BROW_W = 165, BROW_H = 107, MOUTH_W = 67, MOUTH_H = 14;
|
||||
|
||||
static readonly StringBuilder sb = new StringBuilder();
|
||||
static void L(string s) { sb.AppendLine(s); Console.WriteLine("[814u] " + s); }
|
||||
|
||||
public static void Run()
|
||||
{
|
||||
try { Body(); }
|
||||
catch (Exception e) { L("EXCEPTION " + e); }
|
||||
finally
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(OUT));
|
||||
File.WriteAllText(OUT, sb.ToString(), new UTF8Encoding(true));
|
||||
Console.WriteLine("[814u] wrote " + OUT);
|
||||
}
|
||||
}
|
||||
|
||||
// ── 유틸 ────────────────────────────────────────────────────────────────
|
||||
static GameObject FindRoot()
|
||||
{
|
||||
foreach (var go in UnityEngine.SceneManagement.SceneManager.GetActiveScene().GetRootGameObjects())
|
||||
if (go.name.Contains("LH_M05") || go.name.StartsWith("PC_")) return go;
|
||||
return null;
|
||||
}
|
||||
|
||||
class Shot
|
||||
{
|
||||
public Color32[] px; public int w, h;
|
||||
public Color32 At(int x, int y) { return px[y * w + x]; }
|
||||
}
|
||||
|
||||
static Shot Render(Camera cam, int w, int h, Color bg)
|
||||
{
|
||||
var rt = new RenderTexture(w, h, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB);
|
||||
rt.antiAliasing = 1; rt.filterMode = FilterMode.Point;
|
||||
var prevT = cam.targetTexture; var prevActive = RenderTexture.active;
|
||||
var prevCF = cam.clearFlags; var prevBG = cam.backgroundColor;
|
||||
cam.clearFlags = CameraClearFlags.SolidColor; cam.backgroundColor = bg;
|
||||
cam.targetTexture = rt; cam.Render();
|
||||
RenderTexture.active = rt;
|
||||
var t = new Texture2D(w, h, TextureFormat.RGBA32, false);
|
||||
t.ReadPixels(new Rect(0, 0, w, h), 0, 0); t.Apply();
|
||||
cam.targetTexture = prevT; RenderTexture.active = prevActive;
|
||||
cam.clearFlags = prevCF; cam.backgroundColor = prevBG;
|
||||
var s = new Shot { px = t.GetPixels32(), w = w, h = h };
|
||||
UnityEngine.Object.DestroyImmediate(t); rt.Release(); UnityEngine.Object.DestroyImmediate(rt);
|
||||
return s;
|
||||
}
|
||||
|
||||
// 배경 검정/흰색 두 번 찍어 「배경이 비치지 않는 픽셀」 = 덮인 픽셀. 알파·포스트 영향 없이 정확.
|
||||
static bool[] Mask(Camera cam, int w, int h, out int x0, out int y0, out int x1, out int y1, out int count)
|
||||
{
|
||||
var b = Render(cam, w, h, Color.black);
|
||||
var wht = Render(cam, w, h, Color.white);
|
||||
var m = new bool[w * h];
|
||||
x0 = w; y0 = h; x1 = -1; y1 = -1; count = 0;
|
||||
for (int y = 0; y < h; y++)
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
int i = y * w + x;
|
||||
int d = Mathf.Max(Mathf.Abs(b.px[i].r - wht.px[i].r), Mathf.Max(Mathf.Abs(b.px[i].g - wht.px[i].g), Mathf.Abs(b.px[i].b - wht.px[i].b)));
|
||||
if (d < 128) { m[i] = true; count++; if (x < x0) x0 = x; if (x > x1) x1 = x; if (y < y0) y0 = y; if (y > y1) y1 = y; }
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
static int UniqueColors(Shot s, bool[] m)
|
||||
{
|
||||
var set = new HashSet<int>();
|
||||
for (int i = 0; i < m.Length; i++) if (m[i]) set.Add((s.px[i].r << 16) | (s.px[i].g << 8) | s.px[i].b);
|
||||
return set.Count;
|
||||
}
|
||||
|
||||
// ── 본체 ────────────────────────────────────────────────────────────────
|
||||
static void Body()
|
||||
{
|
||||
EditorSceneManager.OpenScene(SCENE, OpenSceneMode.Single);
|
||||
L("scene = " + SCENE);
|
||||
var root = FindRoot();
|
||||
if (root == null) { L("!! PC root not found"); return; }
|
||||
L("root = " + root.name + " pos=" + root.transform.position.ToString("F4") + " rot=" + root.transform.eulerAngles.ToString("F2") + " scale=" + root.transform.localScale.ToString("F4"));
|
||||
|
||||
var smrs = root.GetComponentsInChildren<SkinnedMeshRenderer>(true).Where(r => r.enabled && r.gameObject.activeInHierarchy).ToArray();
|
||||
L("");
|
||||
L("== SMR 전수 ==");
|
||||
foreach (var r in smrs)
|
||||
{
|
||||
var mat = r.sharedMaterial;
|
||||
var tex = mat != null && mat.HasProperty("_BaseMap") ? mat.GetTexture("_BaseMap") : (mat != null ? mat.mainTexture : null);
|
||||
L(string.Format(" {0} | sub={1} | mat={2} | shader={3} | tex={4} {5}", r.name, r.sharedMesh != null ? r.sharedMesh.subMeshCount : -1,
|
||||
mat != null ? mat.name : "null", mat != null ? mat.shader.name : "null",
|
||||
tex != null ? tex.name : "null", tex != null ? ("(" + tex.width + "x" + tex.height + ")") : ""));
|
||||
}
|
||||
|
||||
// 서브메시 인덱스 = SMR 컴포넌트가 mesh 의 어느 서브메시를 그리는가 (Suriyun 리그는 SMR 1개 = 서브메시 1개)
|
||||
// face / hair / body / skin 구분
|
||||
Func<SkinnedMeshRenderer, string> texName = r =>
|
||||
{
|
||||
var m = r.sharedMaterial; if (m == null) return "";
|
||||
var t = m.HasProperty("_BaseMap") ? m.GetTexture("_BaseMap") : m.mainTexture;
|
||||
return t != null ? t.name.ToLowerInvariant() : m.name.ToLowerInvariant();
|
||||
};
|
||||
var faceR = smrs.Where(r => texName(r).Contains("face")).ToArray();
|
||||
var hairR = smrs.Where(r => texName(r).Contains("hair")).ToArray();
|
||||
var skinR = smrs.Where(r => texName(r).Contains("skin")).ToArray();
|
||||
var bodyR = smrs.Where(r => texName(r).Contains("m05")).ToArray();
|
||||
L(string.Format(" 분류: face={0} hair={1} skin={2} body={3}", faceR.Length, hairR.Length, skinR.Length, bodyR.Length));
|
||||
|
||||
// 본
|
||||
L("");
|
||||
L("== 본(머리 관련) ==");
|
||||
var bones = root.GetComponentsInChildren<Transform>(true);
|
||||
Transform neck = null, head = null;
|
||||
foreach (var b in bones)
|
||||
{
|
||||
if (b.name == "Neck_M" || b.name == "Head_M" || b.name == "HeadEnd_M" || b.name.Contains("Jaw") || b.name.StartsWith("Eye"))
|
||||
{
|
||||
L(" " + b.name + " world=" + b.position.ToString("F4"));
|
||||
if (b.name == "Neck_M") neck = b;
|
||||
if (b.name == "Head_M") head = b;
|
||||
}
|
||||
}
|
||||
|
||||
// UV 점유 (변형 없는 sharedMesh 의 UV — UV 는 스키닝으로 안 변한다)
|
||||
L("");
|
||||
L("== UV 점유 (텍스처 2048 기준) ==");
|
||||
foreach (var r in smrs)
|
||||
{
|
||||
var mesh = r.sharedMesh; if (mesh == null) continue;
|
||||
var uv = mesh.uv; if (uv == null || uv.Length == 0) continue;
|
||||
// 이 SMR 이 그리는 서브메시만
|
||||
var used = new HashSet<int>();
|
||||
for (int s = 0; s < mesh.subMeshCount; s++) { }
|
||||
var tris = mesh.triangles; // 전체 (SMR 이 서브메시 1개만 그려도 mesh 는 공유 · 아래에서 서브메시별로 다시 계산)
|
||||
for (int s = 0; s < mesh.subMeshCount; s++)
|
||||
{
|
||||
var t2 = mesh.GetTriangles(s);
|
||||
float u0 = 9, u1 = -9, v0 = 9, v1 = -9;
|
||||
foreach (var i in t2) { var q = uv[i]; if (q.x < u0) u0 = q.x; if (q.x > u1) u1 = q.x; if (q.y < v0) v0 = q.y; if (q.y > v1) v1 = q.y; }
|
||||
L(string.Format(" {0} sub{1}: u[{2:F4}..{3:F4}] v[{4:F4}..{5:F4}] → {6:F0} × {7:F0} 텍셀 (tri {8})",
|
||||
r.name, s, u0, u1, v0, v1, (u1 - u0) * 2048, (v1 - v0) * 2048, t2.Length / 3));
|
||||
}
|
||||
break; // 메시는 6 SMR 이 공유 — 한 번만 출력
|
||||
}
|
||||
|
||||
// 카메라
|
||||
Camera scam = Camera.main;
|
||||
if (scam == null) scam = UnityEngine.Object.FindObjectsByType<Camera>(FindObjectsInactive.Exclude, FindObjectsSortMode.None).FirstOrDefault(c => c.orthographic);
|
||||
if (scam == null) { L("!! camera not found"); return; }
|
||||
L("");
|
||||
L("== 씬 카메라 == " + scam.name + " ortho=" + scam.orthographic + " size=" + scam.orthographicSize +
|
||||
" pos=" + scam.transform.position.ToString("F4") + " euler=" + scam.transform.eulerAngles.ToString("F3"));
|
||||
|
||||
// 레이어 임시 변경 (씬 저장 안 함)
|
||||
var origLayer = new Dictionary<Transform, int>();
|
||||
foreach (var t in bones) { origLayer[t] = t.gameObject.layer; t.gameObject.layer = LAYER; }
|
||||
|
||||
try
|
||||
{
|
||||
Measure("A. 현재 카메라 (ortho 10 · 1080×1920)", scam, 10f, 1080, 1920, root, smrs, faceR, hairR, skinR, bodyR, neck);
|
||||
Measure("B. 도트A (ortho 3.4 · 135×240)", scam, 3.4f, 135, 240, root, smrs, faceR, hairR, skinR, bodyR, neck);
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (var kv in origLayer) if (kv.Key != null) kv.Key.gameObject.layer = kv.Value;
|
||||
}
|
||||
}
|
||||
|
||||
static void Measure(string title, Camera scam, float ortho, int W, int H, GameObject root,
|
||||
SkinnedMeshRenderer[] all, SkinnedMeshRenderer[] faceR, SkinnedMeshRenderer[] hairR,
|
||||
SkinnedMeshRenderer[] skinR, SkinnedMeshRenderer[] bodyR, Transform neck)
|
||||
{
|
||||
L("");
|
||||
L("════════ " + title + " ════════");
|
||||
|
||||
// 캐릭터만 찍는 임시 카메라
|
||||
var go = new GameObject("WL814u_Cam");
|
||||
var cam = go.AddComponent<Camera>();
|
||||
cam.CopyFrom(scam);
|
||||
cam.orthographic = true; cam.orthographicSize = ortho;
|
||||
cam.cullingMask = 1 << LAYER;
|
||||
cam.targetTexture = null;
|
||||
cam.transform.SetPositionAndRotation(scam.transform.position, scam.transform.rotation);
|
||||
var ucd = cam.GetUniversalAdditionalCameraData();
|
||||
if (ucd != null) { ucd.renderPostProcessing = false; ucd.renderShadows = true; ucd.SetRenderer(0); }
|
||||
|
||||
// 캐릭터를 화면 중앙에 (직교라 시선방향 이동은 화면을 안 바꾼다 → 좌우/상하만 맞춘다)
|
||||
var b = new Bounds(root.transform.position, Vector3.zero);
|
||||
foreach (var r in all) b.Encapsulate(r.bounds);
|
||||
var center = b.center;
|
||||
cam.transform.position = center - cam.transform.forward * 50f;
|
||||
|
||||
// 전체 씬(배경 포함) 컬러 — 부위 색 수 세기용
|
||||
var fullCam = new GameObject("WL814u_FullCam").AddComponent<Camera>();
|
||||
fullCam.CopyFrom(scam);
|
||||
fullCam.orthographic = true; fullCam.orthographicSize = ortho;
|
||||
fullCam.transform.SetPositionAndRotation(cam.transform.position, cam.transform.rotation);
|
||||
fullCam.targetTexture = null;
|
||||
var fd = fullCam.GetUniversalAdditionalCameraData(); if (fd != null) { fd.renderPostProcessing = false; fd.SetRenderer(0); }
|
||||
var full = Render(fullCam, W, H, Color.black);
|
||||
|
||||
float pxPerM = H / (2f * ortho);
|
||||
L(string.Format(" 화면 {0}×{1} · ortho {2} · 1 m = {3:F3} px · 1 px = {4:F5} m", W, H, ortho, pxPerM, 1f / pxPerM));
|
||||
|
||||
// 전체 실루엣
|
||||
int x0, y0, x1, y1, cnt;
|
||||
SetOnly(all, all);
|
||||
var mAll = Mask(cam, W, H, out x0, out y0, out x1, out y1, out cnt);
|
||||
int hAll = y1 - y0 + 1, wAll = x1 - x0 + 1;
|
||||
L(string.Format(" 🔴 캐릭터 전체 실루엣: 키 {0} px · 폭 {1} px · 면적 {2} px (bbox x[{3}..{4}] y[{5}..{6}])", hAll, wAll, cnt, x0, x1, y0, y1));
|
||||
L(string.Format(" 화면 세로의 {0:F1} % · 고유 색 {1}", 100f * hAll / H, UniqueColors(full, mAll)));
|
||||
|
||||
// 머리 = Neck_M 스크린 y 위쪽
|
||||
if (neck != null)
|
||||
{
|
||||
var sp = cam.WorldToScreenPoint(neck.position);
|
||||
int neckY = Mathf.RoundToInt(sp.y);
|
||||
int headPx = y1 - neckY + 1;
|
||||
// 머리 폭 = 목 위쪽 행들의 최대 폭
|
||||
int hx0 = W, hx1 = -1, hcnt = 0;
|
||||
for (int y = Mathf.Max(0, neckY); y <= y1; y++)
|
||||
for (int x = 0; x < W; x++) if (mAll[y * W + x]) { hcnt++; if (x < hx0) hx0 = x; if (x > hx1) hx1 = x; }
|
||||
L(string.Format(" 🔴 머리(Neck_M {0} px ~ 정수리 {1} px): 높이 {2} px · 폭 {3} px · 면적 {4} px · 머리:키 = 1:{5:F2}",
|
||||
neckY, y1, headPx, hx1 - hx0 + 1, hcnt, (float)hAll / Mathf.Max(1, headPx)));
|
||||
}
|
||||
|
||||
// 부위별
|
||||
foreach (var pair in new[] {
|
||||
new KeyValuePair<string, SkinnedMeshRenderer[]>("face02(얼굴 데칼)", faceR),
|
||||
new KeyValuePair<string, SkinnedMeshRenderer[]>("Hair05(머리칼)", hairR),
|
||||
new KeyValuePair<string, SkinnedMeshRenderer[]>("skin(피부)", skinR),
|
||||
new KeyValuePair<string, SkinnedMeshRenderer[]>("M05(몸/갑옷)", bodyR) })
|
||||
{
|
||||
if (pair.Value.Length == 0) continue;
|
||||
SetOnly(all, pair.Value);
|
||||
int fx0, fy0, fx1, fy1, fc;
|
||||
var m = Mask(cam, W, H, out fx0, out fy0, out fx1, out fy1, out fc);
|
||||
if (fc == 0) { L(" " + pair.Key + ": 화면에 0 px (가려짐)"); continue; }
|
||||
L(string.Format(" {0}: {1} × {2} px · 면적 {3} px · 고유 색 {4} (bbox x[{5}..{6}] y[{7}..{8}])",
|
||||
pair.Key, fx1 - fx0 + 1, fy1 - fy0 + 1, fc, UniqueColors(full, m), fx0, fx1, fy0, fy1));
|
||||
|
||||
if (pair.Value == faceR)
|
||||
{
|
||||
float sx = (float)(fx1 - fx0 + 1) / (DRAWN_X1 - DRAWN_X0 + 1);
|
||||
float sy = (float)(fy1 - fy0 + 1) / (DRAWN_Y1 - DRAWN_Y0 + 1);
|
||||
L(string.Format(" 🔴 얼굴 실효 해상도: 텍셀 1개 = 화면 {0:F4} × {1:F4} px (= 텍스처 {2}×{3} 텍셀이 화면 {4}×{5} px 로 찍힌다)",
|
||||
sx, sy, DRAWN_X1 - DRAWN_X0 + 1, DRAWN_Y1 - DRAWN_Y0 + 1, fx1 - fx0 + 1, fy1 - fy0 + 1));
|
||||
L(string.Format(" 🔴 눈 1개 = 화면 {0:F2} × {1:F2} px · 눈썹 1개 = {2:F2} × {3:F2} px · 입 = {4:F2} × {5:F2} px",
|
||||
EYE_W * sx, EYE_H * sy, BROW_W * sx, BROW_H * sy, MOUTH_W * sx, MOUTH_H * sy));
|
||||
L(string.Format(" 텍스처 2048 기준 축소율 = 1/{0:F0} (가로) · 1/{1:F0} (세로)", 1f / sx, 1f / sy));
|
||||
}
|
||||
}
|
||||
SetOnly(all, all);
|
||||
|
||||
UnityEngine.Object.DestroyImmediate(go);
|
||||
UnityEngine.Object.DestroyImmediate(fullCam.gameObject);
|
||||
}
|
||||
|
||||
static void SetOnly(SkinnedMeshRenderer[] all, SkinnedMeshRenderer[] on)
|
||||
{
|
||||
var set = new HashSet<SkinnedMeshRenderer>(on);
|
||||
foreach (var r in all) r.enabled = set.Contains(r);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
// WL814u_Probe2.cs — §1 보정 실측 + UV 점유 + 픽셀 떨림(pixel crawl) 실험 (에디터 전용)
|
||||
// 🔴 씬 저장 0 · 에셋 수정 0.
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
|
||||
public static class WL814u_Probe2
|
||||
{
|
||||
const string SCENE = "Assets/WL/Look/Arena/Scenes/WL_ArenaProto.unity";
|
||||
const string OUT = "AgentScripts/WL814u_PROBE2.txt";
|
||||
const int LAYER = 31;
|
||||
static readonly StringBuilder sb = new StringBuilder();
|
||||
static void L(string s) { sb.AppendLine(s); Console.WriteLine("[814u2] " + s); }
|
||||
|
||||
public static void Run()
|
||||
{
|
||||
try { Body(); }
|
||||
catch (Exception e) { L("EXCEPTION " + e); }
|
||||
finally { Directory.CreateDirectory(Path.GetDirectoryName(OUT)); File.WriteAllText(OUT, sb.ToString(), new UTF8Encoding(true)); }
|
||||
}
|
||||
|
||||
class Shot { public Color32[] px; public int w, h; }
|
||||
|
||||
static Shot Render(Camera cam, int w, int h, Color bg)
|
||||
{
|
||||
var rt = new RenderTexture(w, h, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB);
|
||||
rt.antiAliasing = 1; rt.filterMode = FilterMode.Point;
|
||||
var pT = cam.targetTexture; var pA = RenderTexture.active; var pC = cam.clearFlags; var pB = cam.backgroundColor;
|
||||
cam.clearFlags = CameraClearFlags.SolidColor; cam.backgroundColor = bg; cam.targetTexture = rt; cam.Render();
|
||||
RenderTexture.active = rt;
|
||||
var t = new Texture2D(w, h, TextureFormat.RGBA32, false); t.ReadPixels(new Rect(0, 0, w, h), 0, 0); t.Apply();
|
||||
cam.targetTexture = pT; RenderTexture.active = pA; cam.clearFlags = pC; cam.backgroundColor = pB;
|
||||
var s = new Shot { px = t.GetPixels32(), w = w, h = h };
|
||||
UnityEngine.Object.DestroyImmediate(t); rt.Release(); UnityEngine.Object.DestroyImmediate(rt);
|
||||
return s;
|
||||
}
|
||||
|
||||
static bool[] Mask(Camera cam, int w, int h, out int x0, out int y0, out int x1, out int y1, out int cnt, out Shot color)
|
||||
{
|
||||
var b = Render(cam, w, h, Color.black); var wt = Render(cam, w, h, Color.white);
|
||||
color = b;
|
||||
var m = new bool[w * h]; x0 = w; y0 = h; x1 = -1; y1 = -1; cnt = 0;
|
||||
for (int i = 0; i < m.Length; i++)
|
||||
{
|
||||
int d = Mathf.Max(Mathf.Abs(b.px[i].r - wt.px[i].r), Mathf.Max(Mathf.Abs(b.px[i].g - wt.px[i].g), Mathf.Abs(b.px[i].b - wt.px[i].b)));
|
||||
if (d < 128) { m[i] = true; cnt++; int x = i % w, y = i / w; if (x < x0) x0 = x; if (x > x1) x1 = x; if (y < y0) y0 = y; if (y > y1) y1 = y; }
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
static void Body()
|
||||
{
|
||||
EditorSceneManager.OpenScene(SCENE, OpenSceneMode.Single);
|
||||
GameObject root = null;
|
||||
foreach (var go in UnityEngine.SceneManagement.SceneManager.GetActiveScene().GetRootGameObjects())
|
||||
if (go.name.Contains("LH_M05")) { root = go; break; }
|
||||
if (root == null) { L("!! root"); return; }
|
||||
|
||||
var smrs = root.GetComponentsInChildren<SkinnedMeshRenderer>(true).Where(r => r.enabled && r.gameObject.activeInHierarchy).ToArray();
|
||||
|
||||
// ── UV 점유 (렌더러별 · 자기 메시) ─────────────────────────────
|
||||
L("== UV 점유 (렌더러별 · 텍스처 2048 기준) ==");
|
||||
foreach (var r in smrs)
|
||||
{
|
||||
var mesh = r.sharedMesh; if (mesh == null) continue;
|
||||
var uv = mesh.uv; var tri = mesh.triangles;
|
||||
float u0 = 9, u1 = -9, v0 = 9, v1 = -9;
|
||||
var cells = new HashSet<int>(); // 64텍셀 격자 점유 칸 수
|
||||
foreach (var i in tri)
|
||||
{
|
||||
var q = uv[i];
|
||||
if (q.x < u0) u0 = q.x; if (q.x > u1) u1 = q.x; if (q.y < v0) v0 = q.y; if (q.y > v1) v1 = q.y;
|
||||
cells.Add(Mathf.Clamp((int)(q.x * 32), 0, 31) * 32 + Mathf.Clamp((int)(q.y * 32), 0, 31));
|
||||
}
|
||||
L(string.Format(" {0}: u[{1:F4}..{2:F4}] v[{3:F4}..{4:F4}] → {5:F0}×{6:F0} 텍셀 · 64격자 점유 {7}/1024칸 · verts {8} tri {9}",
|
||||
r.name, u0, u1, v0, v1, (u1 - u0) * 2048, (v1 - v0) * 2048, cells.Count, mesh.vertexCount, tri.Length / 3));
|
||||
}
|
||||
|
||||
Transform neck = root.GetComponentsInChildren<Transform>(true).FirstOrDefault(t => t.name == "Neck_M");
|
||||
Camera scam = Camera.main ?? UnityEngine.Object.FindObjectsByType<Camera>(FindObjectsInactive.Exclude, FindObjectsSortMode.None).FirstOrDefault(c => c.orthographic);
|
||||
|
||||
var all = root.GetComponentsInChildren<Transform>(true);
|
||||
var orig = new Dictionary<Transform, int>();
|
||||
foreach (var t in all) { orig[t] = t.gameObject.layer; t.gameObject.layer = LAYER; }
|
||||
var faceR = smrs.Where(r => r.name == "Face").ToArray();
|
||||
|
||||
try
|
||||
{
|
||||
Do("A. 현재 (ortho 10 · 1080×1920)", scam, 10f, 1080, 1920, root, smrs, faceR, neck);
|
||||
Do("B. 도트A (ortho 3.4 · 135×240)", scam, 3.4f, 135, 240, root, smrs, faceR, neck);
|
||||
}
|
||||
finally { foreach (var kv in orig) if (kv.Key != null) kv.Key.gameObject.layer = kv.Value; }
|
||||
}
|
||||
|
||||
static void Do(string title, Camera scam, float ortho, int W, int H, GameObject root, SkinnedMeshRenderer[] smrs, SkinnedMeshRenderer[] faceR, Transform neck)
|
||||
{
|
||||
L(""); L("════════ " + title + " ════════");
|
||||
var go = new GameObject("WL814u_Cam2");
|
||||
var cam = go.AddComponent<Camera>();
|
||||
cam.CopyFrom(scam); cam.orthographic = true; cam.orthographicSize = ortho;
|
||||
cam.cullingMask = 1 << LAYER; cam.targetTexture = null;
|
||||
cam.aspect = (float)W / H; // 🔴 해상도 고정 (1차 probe 의 머리 측정 버그 원인)
|
||||
cam.transform.SetPositionAndRotation(scam.transform.position, scam.transform.rotation);
|
||||
var ucd = cam.GetUniversalAdditionalCameraData(); if (ucd != null) { ucd.renderPostProcessing = false; ucd.SetRenderer(0); }
|
||||
var b = new Bounds(root.transform.position, Vector3.zero); foreach (var r in smrs) b.Encapsulate(r.bounds);
|
||||
cam.transform.position = b.center - cam.transform.forward * 50f;
|
||||
|
||||
int x0, y0, x1, y1, cnt; Shot col;
|
||||
foreach (var r in smrs) r.enabled = true;
|
||||
var mAll = Mask(cam, W, H, out x0, out y0, out x1, out y1, out cnt, out col);
|
||||
L(string.Format(" 전체: 키 {0} px · 폭 {1} px · 면적 {2} px (y[{3}..{4}])", y1 - y0 + 1, x1 - x0 + 1, cnt, y0, y1));
|
||||
|
||||
// 머리 = Neck_M 위 (뷰포트 기준 · 해상도 무관)
|
||||
var vp = cam.WorldToViewportPoint(neck.position);
|
||||
int neckY = Mathf.RoundToInt(vp.y * H);
|
||||
int hx0 = W, hx1 = -1, hcnt = 0;
|
||||
for (int y = Mathf.Max(0, neckY); y <= y1; y++) for (int x = 0; x < W; x++) if (mAll[y * W + x]) { hcnt++; if (x < hx0) hx0 = x; if (x > hx1) hx1 = x; }
|
||||
int headPx = y1 - neckY + 1;
|
||||
L(string.Format(" 🔴 머리(Neck_M y={0} → 정수리 y={1}): 높이 {2} px · 폭 {3} px · 면적 {4} px · 머리:키 = 1:{5:F2}",
|
||||
neckY, y1, headPx, hx1 - hx0 + 1, hcnt, (float)(y1 - y0 + 1) / Mathf.Max(1, headPx)));
|
||||
|
||||
// 얼굴 데칼
|
||||
foreach (var r in smrs) r.enabled = faceR.Contains(r);
|
||||
int fx0, fy0, fx1, fy1, fc; Shot fcol;
|
||||
var mF = Mask(cam, W, H, out fx0, out fy0, out fx1, out fy1, out fc, out fcol);
|
||||
if (fc > 0)
|
||||
{
|
||||
L(string.Format(" 🔴 얼굴 데칼(이목구비 전부): {0} × {1} px · 면적 {2} px", fx1 - fx0 + 1, fy1 - fy0 + 1, fc));
|
||||
L(string.Format(" 🔴 눈~턱(얼굴 데칼 위 {0} → 턱=Neck_M {1}): {2} px", fy1, neckY, fy1 - neckY + 1));
|
||||
float sx = (fx1 - fx0 + 1) / 889f, sy = (fy1 - fy0 + 1) / 617f;
|
||||
L(string.Format(" 눈 1개 = {0:F2} × {1:F2} px · 눈썹 {2:F2} × {3:F2} px · 입 {4:F2} × {5:F2} px · 텍셀 1개 = {6:F4} px",
|
||||
277 * sx, 183 * sy, 165 * sx, 107 * sy, 67 * sx, 14 * sy, sx));
|
||||
}
|
||||
else L(" 얼굴 데칼: 0 px");
|
||||
foreach (var r in smrs) r.enabled = true;
|
||||
|
||||
// ── 픽셀 떨림 실험: 캐릭터를 0.5° 씩 돌리며 실루엣/색이 몇 % 바뀌나 ──
|
||||
L(" ── 픽셀 떨림(pixel crawl) 실험 · 캐릭터 yaw 를 0.5° 씩 16회 ──");
|
||||
var y0rot = root.transform.eulerAngles.y;
|
||||
bool[] prevM = null; Shot prevC = null; var flips = new List<float>(); var colch = new List<float>();
|
||||
for (int i = 0; i <= 16; i++)
|
||||
{
|
||||
root.transform.rotation = Quaternion.Euler(0, y0rot + i * 0.5f, 0);
|
||||
int a0, a1, a2, a3, ac; Shot c;
|
||||
var m = Mask(cam, W, H, out a0, out a2, out a1, out a3, out ac, out c);
|
||||
if (prevM != null)
|
||||
{
|
||||
int flip = 0, chg = 0, both = 0;
|
||||
for (int k = 0; k < m.Length; k++)
|
||||
{
|
||||
if (m[k] != prevM[k]) flip++;
|
||||
if (m[k] && prevM[k]) { both++; if (Mathf.Abs(c.px[k].r - prevC.px[k].r) + Mathf.Abs(c.px[k].g - prevC.px[k].g) + Mathf.Abs(c.px[k].b - prevC.px[k].b) > 24) chg++; }
|
||||
}
|
||||
flips.Add(100f * flip / Mathf.Max(1, ac));
|
||||
colch.Add(100f * chg / Mathf.Max(1, both));
|
||||
}
|
||||
prevM = m; prevC = c;
|
||||
}
|
||||
root.transform.rotation = Quaternion.Euler(0, y0rot, 0);
|
||||
L(string.Format(" 0.5° 회전마다 실루엣 픽셀의 {0:F1} % 가 켜지거나 꺼진다 (최소 {1:F1} · 최대 {2:F1})", flips.Average(), flips.Min(), flips.Max()));
|
||||
L(string.Format(" 0.5° 회전마다 남아 있는 픽셀의 {0:F1} % 가 색이 바뀐다(Δ>24) (최소 {1:F1} · 최대 {2:F1})", colch.Average(), colch.Min(), colch.Max()));
|
||||
L(" → 1° 당 실루엣 " + (flips.Average() * 2).ToString("F1") + " % · 색 " + (colch.Average() * 2).ToString("F1") + " %");
|
||||
|
||||
UnityEngine.Object.DestroyImmediate(go);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
[face] 원본 2048^2 -> 격자 32x32 (1칸 = 64 텍셀 = 화면 약 1 px)
|
||||
[face] 알파 이진화(>=12 %): 덮인 칸 33/1024 · 통째로 사라질 뻔한 얇은 부위 1개를 되살렸다
|
||||
[face] 원본 반투명 텍셀 5538개 -> 결과 반투명 0개(점 필터 전제)
|
||||
[face] 부위별 색 단계: 눈R 13칸/4단(원본 L 14~133) · 눈L 13칸/4단(원본 L 14~130) · 눈썹R 3칸/2단(원본 L 19~38) · 눈썹L 2칸/2단(원본 L 29~41) · 입 2칸/2단(원본 L 27~27)
|
||||
[face] 색 연동 외곽선(인접 색 x 0.55, 최암단 제외) 적용 칸 10 - 균일 검정이 아니다
|
||||
[face] 결과 고유색 13 (원본 불투명부 6,368) · 반투명 텍셀 0
|
||||
[face] 저장 -> face02_Pixel.png
|
||||
[hair] 원본 2048^2 -> 격자 32x32 (Hair05 는 전부 불투명)
|
||||
[hair] 팔레트 4색 = [(76, 49, 33), (149, 111, 77), (212, 177, 123), (255, 245, 170)]
|
||||
[hair] 명암 4단 (원본 L 140~223 -> 56~245) · 단별 칸수 [115, 514, 272, 123]
|
||||
[hair] 덩어리 경계 어둡게(x 0.55) 칸 425/1024
|
||||
[hair] 결과 고유색 7 (원본 7,516)
|
||||
[hair] 저장 -> Hair05_Pixel.png
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
|
|
@ -0,0 +1,181 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 82f211711ffeb8946ad87fbb0c05b1f0
|
||||
TextureImporter:
|
||||
internalIDToNameTable:
|
||||
- first:
|
||||
213: 5096586660263067579
|
||||
second: Hair05_Pixel_0
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 1
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 1
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 0
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 512
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Server
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: iOS
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites:
|
||||
- serializedVersion: 2
|
||||
name: Hair05_Pixel_0
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 2048
|
||||
height: 2048
|
||||
alignment: 0
|
||||
pivot: {x: 0, y: 0}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: -1
|
||||
bones: []
|
||||
spriteID: bbf79fb8096bab640800000000000000
|
||||
internalID: 5096586660263067579
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
|
|
@ -0,0 +1,181 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 41cf20ede2594db478b8ea1c66ddd89d
|
||||
TextureImporter:
|
||||
internalIDToNameTable:
|
||||
- first:
|
||||
213: -8515626147460811103
|
||||
second: face02_Pixel_0
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 1
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 1
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 0
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 512
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Server
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: iOS
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites:
|
||||
- serializedVersion: 2
|
||||
name: face02_Pixel_0
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 2048
|
||||
height: 2048
|
||||
alignment: 0
|
||||
pivot: {x: 0, y: 0}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: -1
|
||||
bones: []
|
||||
spriteID: 1a6f198f22b62d980800000000000000
|
||||
internalID: -8515626147460811103
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Loading…
Reference in New Issue