Files
ngzz-mc/internal/mesh/mesh.go
NianGao Dev ea97eaa616 fix(render): 方格垂直面被背面剔除(立面透明空洞/闪烁)+ 鼠标左右反转
- mesh.faceDef 六面绕序铁律:旧定义只有顶面是窗口空间顺时针
  (FrontFace=CW 的正面),底/东/西/南/北五面是逆时针被全部剔除——
  世界只剩顶面、立面透明、移动时面闪烁(数值投影验证 5 面 area>0 被剔)
- 覆盖层 cubeFaces(裂纹盒/手持方块)同步反转
- 鼠标右移应右转:yaw=0 朝 -Z,右转朝 +X 需 yaw 减小,dyaw 取反
  (原实现 dyaw += dx 左右颠倒)
2026-08-16 20:01:13 +08:00

170 lines
5.8 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 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
// UVFunc 纹理键 → 图集 UV(归一化坐标,含 inset)。
type UVFunc func(texKey string) (u0, v0, u1, v1 float32, ok bool)
// Builder 网格构建器。
type Builder struct {
reg *block.Registry
uv UVFunc
}
// NewBuilder 创建构建器。
func NewBuilder(reg *block.Registry, uv UVFunc) *Builder {
return &Builder{reg: reg, uv: uv}
}
// 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
sky, blk := c.SkyLight(x, y, z), c.BlockLight(x, y, z)
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
}
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}
}