Files
ngzz-mc/internal/atlas/atlas.go
NianGao Dev fc151e7c07 fix(render): 修复白屏/黑屏——视图矩阵、UI着色器、网格构建性能与安装目录
- 相机视图矩阵按 R_cam^T·T(-pos) 正确推导(旧布局非正交,非零视角下场景塌缩为灰屏)
- UI 片元着色器改为纹理×颜色(旧实现丢弃纹理 RGB,彩色纹理全白)
- UI 纹理与图集 mipmap:图集补齐 2 次幂、UI 上传不生成 mipmap(NPOT 不完整纹理采样纯白)
- 网格构建持一次读锁(旧实现每格邻居查询各加锁约 40 万次/区块,出生区块迟迟无网格,
  玩家透过脚下的洞看到地下横截面——进游戏黑屏主因);脏区块按距离排序优先构建
- 开启背面剔除(CW 正面):区块边界面向未加载邻居的内侧面不再以暗色出现在视野里
- 出生逻辑:等区块加载后定位地表出生,相机略微俯视
- dev 截图测试:-shot 等待玩家所在 5×5 区块网格就绪再拍(48s 兜底),先测试后打包
- 安装器改为按用户安装到 LOCALAPPDATA(Program Files 无写权限导致启动即崩)
- dev 截图工具独立成 devshot_gl.go
2026-08-16 08:33:08 +08:00

95 lines
2.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package atlas 纹理图集构建:把纹理键列表打包成单张图(渲染.md §4)。
//
// 所有纹理统一按 tileSize 采样(最近邻缩放,保持像素风),
// 返回图集图像与「纹理键 → 像素矩形」映射,供网格构建换算 UV。
package atlas
import (
"fmt"
"image"
"image/draw"
"image/png"
"os"
)
// Build 打包纹理键列表为图集。
// resolve:纹理键 → PNG 文件路径;缺失纹理跳过(键不进入 uv 表,运行时用占位纹理)。
// 尺寸取 2 的次幂(mipmap 兼容:非 2 次幂图集在部分驱动上 GenerateMipmap 失败会致纹理不完整)。
func Build(resolve func(key string) (string, bool), keys []string, tileSize int) (image.Image, map[string]image.Rectangle, error) {
if len(keys) == 0 {
return nil, nil, fmt.Errorf("atlas.Build: 纹理列表为空")
}
if tileSize <= 0 {
tileSize = 16
}
cols := 1
for cols*cols < len(keys) {
cols++
}
rows := (len(keys) + cols - 1) / cols
// 补齐到 2 的次幂(mipmap 要求)
cols = nextPow2(cols)
rows = nextPow2(rows)
img := image.NewRGBA(image.Rect(0, 0, cols*tileSize, rows*tileSize))
uv := make(map[string]image.Rectangle, len(keys))
for i, key := range keys {
path, ok := resolve(key)
if !ok {
continue // 缺失纹理:跳过(占位)
}
tile, err := loadPNG(path)
if err != nil {
return nil, nil, fmt.Errorf("atlas.Build %s: %w", key, err)
}
x := (i % cols) * tileSize
y := (i / cols) * tileSize
r := image.Rect(x, y, x+tileSize, y+tileSize)
// 最近邻缩放(非标准尺寸纹理保持像素风)
scaled := scaleNearest(tile, tileSize)
draw.Draw(img, r, scaled, image.Point{}, draw.Src)
uv[key] = r
}
return img, uv, nil
}
// nextPow2 返回不小于 n 的最小 2 次幂。
func nextPow2(n int) int {
p := 1
for p < n {
p <<= 1
}
return p
}
// loadPNG 解码 PNG。
func loadPNG(path string) (image.Image, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
img, err := png.Decode(f)
if err != nil {
return nil, err
}
return img, nil
}
// scaleNearest 最近邻缩放到 tileSize×tileSize。
func scaleNearest(src image.Image, size int) image.Image {
sb := src.Bounds()
if sb.Dx() == size && sb.Dy() == size {
return src
}
dst := image.NewRGBA(image.Rect(0, 0, size, size))
for y := 0; y < size; y++ {
for x := 0; x < size; x++ {
sx := sb.Min.X + x*sb.Dx()/size
sy := sb.Min.Y + y*sb.Dy()/size
dst.Set(x, y, src.At(sx, sy))
}
}
return dst
}