- 崩溃(index out of range):实体所在区块卸载后失去碰撞支撑无限下坠到 负坐标,光查询越界 panic——WorldView 增 ChunkLoaded,未加载区块实体冻结; chunk 光查询加越界防御(双保险) - 立面光照:mesh 每面取**邻格**光照(原版光照模型)——此前用方块自身光照 (sky=0)导致悬崖/矿洞立面全黑(泥土竖向黑色根因) - 掉落可见:生成后 1 秒拾取延迟 + 图标加大到 0.45 - 打击感:攻击命中音(block.hit)+ 实体受击白闪(uTint)+ 手持挥动动画 - 水下效果:相机入水蓝色叠加(渲染.md §8 水体) - 空手显示肤色拳头(白羊毛 × 肤色 tint),持方块正常显示 - HUD 布局:血条左下、饥饿右下(经典布局)
185 lines
6.5 KiB
Go
185 lines
6.5 KiB
Go
// Package mesh 区块网格构建:面剔除 + 顶点格式 + 光照(场景/渲染.md §3)。
|
||
//
|
||
// 网格在 CPU 构建(worker 池),GL 只负责上传——渲染 goroutine 不产生分配压力。
|
||
package mesh
|
||
|
||
import (
|
||
"sync"
|
||
|
||
"mc/internal/block"
|
||
"mc/internal/chunk"
|
||
)
|
||
|
||
// vertexPools 顶点切片池(性能与内存.md §2:热路径零分配——渲染线程不造 mesh 垃圾)。
|
||
var vertexPools = [3]sync.Pool{
|
||
{New: func() any { s := make([]Vertex, 0, 4096); return &s }},
|
||
{New: func() any { s := make([]Vertex, 0, 1024); return &s }},
|
||
{New: func() any { s := make([]Vertex, 0, 1024); return &s }},
|
||
}
|
||
|
||
// Release 归还网格顶点缓冲到池(GPU 上传后调用,性能与内存.md §8 sync.Pool 复位)。
|
||
func Release(m *ChunkMesh) {
|
||
if m == nil {
|
||
return
|
||
}
|
||
for i, s := range [3]*[]Vertex{&m.Opaque, &m.Cutout, &m.Translucent} {
|
||
if cap(*s) > 0 && cap(*s) <= 1<<20 {
|
||
vertexPools[i].Put(s)
|
||
}
|
||
*s = nil
|
||
}
|
||
}
|
||
|
||
// Vertex 顶点格式(渲染.md §5):位置 + UV + 天空/方块光各 4-bit。
|
||
type Vertex struct {
|
||
X, Y, Z float32
|
||
U, V float32
|
||
Sky, Block uint8
|
||
}
|
||
|
||
// ChunkMesh 一个区块的三类网格(渲染.md §6 三 pass)。
|
||
// 每面 4 顶点(四边形),渲染层用索引缓冲扩为 2 三角形(顺序 0-1-2-2-3-0)。
|
||
type ChunkMesh struct {
|
||
Opaque []Vertex // 不透明(主 pass)
|
||
Cutout []Vertex // alpha test(树叶/玻璃)
|
||
Translucent []Vertex // 半透明(水,远→近排序 pass)
|
||
}
|
||
|
||
// Neighbor 邻居方块查询(跨区块面剔除用,区块管理.md §6)。
|
||
type Neighbor func(x, y, z int32) block.State
|
||
|
||
// Light 邻居光照查询(面光照用:每个面取**邻格**的光照等级——
|
||
// 方块自身 sky=0,用自身光照会让所有立面全黑(悬崖/矿洞立面黑色根因)。
|
||
type Light func(x, y, z int32) (sky, blk uint8)
|
||
|
||
// UVFunc 纹理键 → 图集 UV(归一化坐标,含 inset)。
|
||
type UVFunc func(texKey string) (u0, v0, u1, v1 float32, ok bool)
|
||
|
||
// Builder 网格构建器。
|
||
type Builder struct {
|
||
reg *block.Registry
|
||
uv UVFunc
|
||
light Light // 面光照查询(邻格光照;nil 回退自身光照)
|
||
}
|
||
|
||
// NewBuilder 创建构建器。
|
||
func NewBuilder(reg *block.Registry, uv UVFunc) *Builder {
|
||
return &Builder{reg: reg, uv: uv}
|
||
}
|
||
|
||
// SetLight 设置面光照查询(每个面取邻格光照,光照.md 顶点光照)。
|
||
func (b *Builder) SetLight(fn Light) { b.light = fn }
|
||
|
||
// face 一个面的 4 个角偏移与朝向。
|
||
type faceDef struct {
|
||
dir [3]int32
|
||
corners [4][3]int32
|
||
}
|
||
|
||
// faces 六面定义(角顺序与 UV 顶点顺序一致)。
|
||
// 绕序铁律:所有面必须为「窗口空间顺时针」(FrontFace=CW 的正面)——
|
||
// 旧定义只有顶面是 CW,垂直面与底面是 CCW 被背面剔除,
|
||
// 表现为「立面透明的空洞、移动时面闪烁」(方格立面缺失根因)。
|
||
var faces = [6]faceDef{
|
||
{dir: [3]int32{0, 1, 0}, corners: [4][3]int32{{0, 1, 0}, {1, 1, 0}, {1, 1, 1}, {0, 1, 1}}}, // 顶
|
||
{dir: [3]int32{0, -1, 0}, corners: [4][3]int32{{0, 0, 0}, {1, 0, 0}, {1, 0, 1}, {0, 0, 1}}}, // 底(反向)
|
||
{dir: [3]int32{1, 0, 0}, corners: [4][3]int32{{1, 1, 0}, {1, 1, 1}, {1, 0, 1}, {1, 0, 0}}}, // 东(+x)(反向)
|
||
{dir: [3]int32{-1, 0, 0}, corners: [4][3]int32{{0, 1, 1}, {0, 1, 0}, {0, 0, 0}, {0, 0, 1}}}, // 西(-x)(反向)
|
||
{dir: [3]int32{0, 0, 1}, corners: [4][3]int32{{1, 1, 1}, {0, 1, 1}, {0, 0, 1}, {1, 0, 1}}}, // 南(+z)(反向)
|
||
{dir: [3]int32{0, 0, -1}, corners: [4][3]int32{{0, 1, 0}, {1, 1, 0}, {1, 0, 0}, {0, 0, 0}}}, // 北(-z)(反向)
|
||
}
|
||
|
||
// faceTexture 面方向 → 注册表纹理面名(block.Registry.Texture 参数)。
|
||
var faceTexture = [6]string{"up", "down", "east", "west", "south", "north"}
|
||
|
||
// Build 构建区块网格(面剔除:仅当邻格非完全不透明时生成该面,渲染.md §3.1)。
|
||
func (b *Builder) Build(c *chunk.Chunk, nb Neighbor) *ChunkMesh {
|
||
m := &ChunkMesh{}
|
||
// 顶点缓冲从池中取(性能与内存.md §2)
|
||
ob := vertexPools[0].Get().(*[]Vertex)
|
||
cb := vertexPools[1].Get().(*[]Vertex)
|
||
tb := vertexPools[2].Get().(*[]Vertex)
|
||
*ob, *cb, *tb = (*ob)[:0], (*cb)[:0], (*tb)[:0]
|
||
blocks := c.Snapshot()
|
||
baseX, baseZ := float32(c.CX*16), float32(c.CZ*16)
|
||
|
||
for x := 0; x < 16; x++ {
|
||
for y := 0; y < chunk.Height; y++ {
|
||
for z := 0; z < 16; z++ {
|
||
s := blocks[chunk.Index(x, y, z)]
|
||
if s == block.Air {
|
||
continue
|
||
}
|
||
var target *[]Vertex
|
||
switch {
|
||
case b.reg.IsTranslucent(s):
|
||
target = tb
|
||
case b.reg.IsCutout(s):
|
||
target = cb
|
||
default:
|
||
target = ob
|
||
}
|
||
wx := int32(x) + c.CX*16
|
||
wy := int32(y)
|
||
wz := int32(z) + c.CZ*16
|
||
|
||
for fi, f := range faces {
|
||
nx := wx + f.dir[0]
|
||
ny := wy + f.dir[1]
|
||
nz := wz + f.dir[2]
|
||
neighbor := nb(nx, ny, nz)
|
||
if b.reg.IsOpaque(neighbor) {
|
||
continue // 面被不透明邻格遮挡(面剔除)
|
||
}
|
||
// 同类型邻格也剔除(水贴水不渲染内面;树叶相邻保留边缘,简化规则)
|
||
if neighbor != block.Air && !b.reg.IsOpaque(neighbor) &&
|
||
b.reg.IsTranslucent(neighbor) == b.reg.IsTranslucent(s) && b.reg.IsTranslucent(s) {
|
||
continue
|
||
}
|
||
// 面光照:取**邻格**的光照(原版光照模型)——方块自身 sky=0,
|
||
// 用自身光照会让悬崖/矿洞立面全黑
|
||
sky, blk := uint8(0), uint8(0)
|
||
if b.light != nil {
|
||
sky, blk = b.light(nx, ny, nz)
|
||
} else {
|
||
sky, blk = c.SkyLight(x, y, z), c.BlockLight(x, y, z)
|
||
}
|
||
texKey := b.reg.Texture(s, faceTexture[fi])
|
||
u0, v0, u1, v1, ok := b.uv(texKey)
|
||
if !ok {
|
||
u0, v0, u1, v1 = 0, 0, 1, 1
|
||
}
|
||
for _, corner := range f.corners {
|
||
*target = append(*target, Vertex{
|
||
X: baseX + float32(x) + float32(corner[0]),
|
||
Y: float32(y) + float32(corner[1]),
|
||
Z: baseZ + float32(z) + float32(corner[2]),
|
||
U: u0 + (u1-u0)*float32(cornerUV(corner, f)[0]),
|
||
V: v0 + (v1-v0)*float32(cornerUV(corner, f)[1]),
|
||
Sky: sky,
|
||
Block: blk,
|
||
})
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// 挂回结果(注意:切片头来自池,调用方上传后必须 Release)
|
||
m.Opaque, m.Cutout, m.Translucent = *ob, *cb, *tb
|
||
return m
|
||
}
|
||
|
||
// cornerUV 角在面上的 UV 权重(0/1),顺序与 corners 一致。
|
||
func cornerUV(c [3]int32, f faceDef) [2]int32 {
|
||
var u, v int32
|
||
switch f.dir {
|
||
case [3]int32{0, 1, 0}, [3]int32{0, -1, 0}: // 顶/底:x→u,z→v
|
||
u, v = c[0], c[2]
|
||
case [3]int32{1, 0, 0}, [3]int32{-1, 0, 0}: // 东/西:z→u,y→v
|
||
u, v = c[2], 1-c[1]
|
||
default: // 南/北:x→u,y→v
|
||
u, v = c[0], 1-c[1]
|
||
}
|
||
return [2]int32{u, v}
|
||
}
|