diff options
| author | Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> | 2026-10-04 00:21:06 +0900 |
|---|---|---|
| committer | Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> | 2026-10-04 00:21:06 +0900 |
| commit | d871b2e093a70d5ee4e7c6c91f54f05c7422707d (patch) | |
| tree | f617fa9dfedd990ae86d3e4bfab49eaecd965e5b | |
| parent | c81104129a9ba6e26aa33a00770f76a6aa97b2a9 (diff) | |
bluebey-studio: 髪の板の背中の筋(円筒UVの継ぎ目)を修正
円筒UVの継ぎ目をまたぐ三角形がテクスチャ全体を引き伸ばし、背中に縦の筋が出ていた。髪の板を三角形ごとに独立した頂点へ展開してから、継ぎ目をまたぐ三角形のUVを1周ぶん補正し、筋を消した。トリム後のモデルも取り込み。
| -rw-r--r-- | public/bluebey-studio/assets/bluebey.glb | bin | 3253400 -> 3109288 bytes | |||
| -rw-r--r-- | public/bluebey-studio/src/model.js | 33 |
2 files changed, 33 insertions, 0 deletions
diff --git a/public/bluebey-studio/assets/bluebey.glb b/public/bluebey-studio/assets/bluebey.glb Binary files differindex 13131f5..0072864 100644 --- a/public/bluebey-studio/assets/bluebey.glb +++ b/public/bluebey-studio/assets/bluebey.glb diff --git a/public/bluebey-studio/src/model.js b/public/bluebey-studio/src/model.js index 68f1255..3925e85 100644 --- a/public/bluebey-studio/src/model.js +++ b/public/bluebey-studio/src/model.js @@ -134,6 +134,10 @@ export async function loadModel(source, { onProgress } = {}) { const mouth = mouthPlate ?? mouthOriginal; // The wrapping hair shell, if the model carries one (`hair-plate`). const hairMesh = hairPlate; + // Its cylindrical UV has a seam at the back of the head. Triangles that cross + // it get one UV near 0 and the next near 1, so the whole texture is stretched + // across them - a visible streak down the back. Close the seam per triangle. + if (hairMesh) healCylinderSeam(hairMesh); if (!eyeMesh) throw new Error('eye mesh not found in the GLB'); if (!mouth) throw new Error('mouth mesh not found in the GLB'); @@ -248,3 +252,32 @@ function inflateOverlay(mesh, centre, distance) { geometry.computeBoundingBox(); geometry.computeBoundingSphere(); } + +/** + * Make every triangle of a cylindrical UV island sit on one side of the seam. + * + * A cylinder unwrap cuts the surface open along one line (here the back of the + * head). A triangle that spans the cut has its corners at u ~ 1 and u ~ 0, so + * interpolating them drags the whole texture across the triangle - the streak + * seen down the back. Wrapping the low corners up by one turn keeps each + * triangle local; the texture's two edges are identical (the covering is drawn + * right across), so the join is invisible. + */ +function healCylinderSeam(mesh) { + const source = mesh?.geometry; + if (!source?.attributes?.uv) return; + // Give every triangle its own corners first. The seam's low and high sides + // share vertices, so editing a shared one would drag its neighbours too; a + // non-indexed copy isolates each triangle and the fix cannot leak. + const geometry = source.index ? source.toNonIndexed() : source; + const uv = geometry.attributes.uv; + for (let i = 0; i < uv.count; i += 3) { + const top = Math.max(uv.getX(i), uv.getX(i + 1), uv.getX(i + 2)); + if (top - Math.min(uv.getX(i), uv.getX(i + 1), uv.getX(i + 2)) <= 0.5) continue; + for (let k = 0; k < 3; k += 1) { + if (uv.getX(i + k) < top - 0.5) uv.setX(i + k, uv.getX(i + k) + 1); + } + } + uv.needsUpdate = true; + mesh.geometry = geometry; +} |
