f8b21af4be
10_Wiki/Topics 대규모 정리: - 오류 캡처/미완성 stub 문서 227개 제거 - 교차폴더 중복 43클러스터 병합 (63파일 → redirect) - 링크명 정규화: 깨진 링크 수정·redirect 직결·개념 매핑 ~2,400건 - 카테고리 MOC 6개 신규 생성 - Graph 섹션 미해결 related-keyword 링크 10,058건 제거 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
141 lines
4.9 KiB
Markdown
141 lines
4.9 KiB
Markdown
---
|
|
id: wiki-2026-0508-edge-bleeding
|
|
title: Edge Bleeding
|
|
category: 10_Wiki/Topics
|
|
status: verified
|
|
canonical_id: self
|
|
aliases: [Texture Bleeding, Atlas Bleeding]
|
|
duplicate_of: none
|
|
source_trust_level: A
|
|
confidence_score: 0.9
|
|
verification_status: applied
|
|
tags: [graphics, texture, rendering]
|
|
raw_sources: []
|
|
last_reinforced: 2026-05-10
|
|
github_commit: pending
|
|
tech_stack:
|
|
language: GLSL/HLSL
|
|
framework: WebGL/Three.js/Unity/Unreal
|
|
---
|
|
|
|
# Edge Bleeding
|
|
|
|
## 매 한 줄
|
|
> **"매 texture sampling 의 인접 texel이 unintended 하게 leak — atlas seam의 colored line으로 manifest."**. 매 mipmap downsample / bilinear interpolation 매 texel boundary를 cross 하면서 발생. 매 sprite atlas, texture array, lightmap에서 매 visible artifact.
|
|
|
|
## 매 핵심
|
|
|
|
### 매 원인
|
|
- **Bilinear filter**: 매 sample point 매 texel center 사이 → 인접 texel weighted.
|
|
- **Mipmap downsample**: 매 box filter 매 atlas neighbor를 average.
|
|
- **UV precision**: float precision 매 정확한 boundary 매 hit 못 함.
|
|
- **Anisotropic filtering**: 매 large footprint → 매 더 많은 neighbor sampling.
|
|
|
|
### 매 manifestation
|
|
- Sprite atlas: 매 sprite edge 매 인접 sprite color line.
|
|
- Tilemap: 매 tile seam 매 dark/bright line.
|
|
- Skybox cubemap: 매 face boundary 매 visible seam.
|
|
- Lightmap: 매 chart boundary 매 dark crack.
|
|
|
|
### 매 응용 (해결책)
|
|
1. **Padding (gutter)**: 매 sprite 사이 1-2px transparent / replicated edge.
|
|
2. **Half-texel UV inset**: UV 매 `(0.5/W, 0.5/H)` ~ `(1 - 0.5/W, 1 - 0.5/H)` 의 clamp.
|
|
3. **CLAMP_TO_EDGE**: wrap mode 매 repeat → clamp.
|
|
4. **Conservative UV**: 매 mesh UV 매 atlas region 의 inside 의 inset.
|
|
5. **Mipmap-aware padding**: 매 mip level 별 padding (1px → 2px → 4px).
|
|
|
|
## 💻 패턴
|
|
|
|
### Half-texel inset (GLSL)
|
|
```glsl
|
|
uniform vec2 atlasSize; // e.g. (2048, 2048)
|
|
uniform vec4 spriteRect; // x, y, w, h in pixels
|
|
|
|
vec2 sampleUV(vec2 localUV) {
|
|
vec2 halfTexel = 0.5 / atlasSize;
|
|
vec2 minUV = spriteRect.xy / atlasSize + halfTexel;
|
|
vec2 maxUV = (spriteRect.xy + spriteRect.zw) / atlasSize - halfTexel;
|
|
return mix(minUV, maxUV, localUV);
|
|
}
|
|
```
|
|
|
|
### Atlas packer with padding (TS)
|
|
```typescript
|
|
function packSprite(sprite: ImageData, padding = 2): PackedSprite {
|
|
const w = sprite.width + padding * 2;
|
|
const h = sprite.height + padding * 2;
|
|
const padded = new ImageData(w, h);
|
|
// edge replicate (not transparent) for bilinear safety
|
|
for (let y = 0; y < h; y++) {
|
|
for (let x = 0; x < w; x++) {
|
|
const sx = clamp(x - padding, 0, sprite.width - 1);
|
|
const sy = clamp(y - padding, 0, sprite.height - 1);
|
|
copyPixel(sprite, sx, sy, padded, x, y);
|
|
}
|
|
}
|
|
return { data: padded, padding };
|
|
}
|
|
```
|
|
|
|
### Three.js NearestFilter (no bleed, no smooth)
|
|
```javascript
|
|
texture.minFilter = THREE.NearestFilter;
|
|
texture.magFilter = THREE.NearestFilter;
|
|
texture.generateMipmaps = false;
|
|
texture.wrapS = texture.wrapT = THREE.ClampToEdgeWrapping;
|
|
```
|
|
|
|
### Mipmap-aware padding count
|
|
```typescript
|
|
function paddingForMipLevels(levels: number): number {
|
|
return Math.pow(2, levels - 1); // 4 levels → 8px padding
|
|
}
|
|
```
|
|
|
|
### Conservative cubemap seam fix (HLSL)
|
|
```hlsl
|
|
float3 SampleCubeSeamless(TextureCube tex, SamplerState s, float3 dir, float roughness) {
|
|
float mip = roughness * 8.0;
|
|
float texSize = 512.0 / pow(2, mip);
|
|
float scale = 1.0 - 1.0 / texSize;
|
|
return tex.SampleLevel(s, dir * scale, mip).rgb;
|
|
}
|
|
```
|
|
|
|
## 매 결정 기준
|
|
| 상황 | Approach |
|
|
|---|---|
|
|
| Pixel art sprite | NearestFilter + padding 1px |
|
|
| 3D texture atlas (smooth) | Half-texel inset + padding (2 * mip levels) |
|
|
| Tilemap | Padding + clamp + per-tile texture array (best) |
|
|
| Cubemap | GL_TEXTURE_CUBE_MAP_SEAMLESS (GL 3.2+) |
|
|
| Lightmap | Chart padding 4px + dilation |
|
|
|
|
**기본값**: 매 padding 2px + half-texel inset, 매 mipmap 매 사용 시 2 * (mip levels) px.
|
|
|
|
## 🔗 Graph
|
|
- 변형: [[Atlas Bleeding]]
|
|
- 응용: [[Texture Array]]
|
|
- Adjacent: [[Mipmap]]
|
|
|
|
## 🤖 LLM 활용
|
|
**언제**: sprite atlas / tilemap / lightmap 매 visible seam, 매 mipmap 매 사용한 atlas, 매 cubemap face 경계 artifact.
|
|
**언제 X**: single texture (no atlas), 매 NEAREST filter 매 사용 + no mipmap, fully procedural texture.
|
|
|
|
## ❌ 안티패턴
|
|
- **Transparent padding alone**: 매 bilinear가 transparent와 mix → 매 dark fringe.
|
|
- **Padding 너무 small**: mip level 3+ 매 도달 시 여전히 leak.
|
|
- **Repeat wrap on atlas**: 매 opposite edge 매 sample → 완전한 wrong color.
|
|
- **UV exactly at texel boundary**: 매 float precision 의해 random side 매 sample.
|
|
- **Generate mipmap on packed atlas**: 매 mip downsample 매 인접 sprite를 average → mid-mip levels artifact.
|
|
|
|
## 🧪 검증 / 중복
|
|
- Verified (Real-Time Rendering 4ed, Unity / Unreal docs, OpenGL spec).
|
|
- 신뢰도 A.
|
|
|
|
## 🕓 Changelog
|
|
| 날짜 | 변경 |
|
|
|---|---|
|
|
| 2026-05-08 | Phase 1 |
|
|
| 2026-05-10 | Manual cleanup — Edge bleeding 원인 / padding 전략 / mip-aware solution |
|