Files
ngzz-mc/internal/render/overlay_gl.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

233 lines
8.1 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.
//go:build gl
// 覆盖层渲染:破坏裂纹叠加(destroy_stage_0..9)与第一人称手持方块
// (方块交互与动画.md §4、渲染.md §5.2)。
// 与区块网格共用顶点格式(pos+uv+light)与着色器,通过 uOffset 平移定位。
package render
import (
"image"
"unsafe"
"mc/internal/mesh"
"github.com/go-gl/gl/v3.3-core/gl"
)
// cubeFaces 单位立方体 6 面角顺序(与 mesh.faceDef 一致,
// 窗口空间 CW 绕序为 FrontFace(CW) 的正面)。
var cubeFaces = [6][4][3]float32{
{{0, 1, 0}, {1, 1, 0}, {1, 1, 1}, {0, 1, 1}}, // 顶
{{0, 0, 0}, {1, 0, 0}, {1, 0, 1}, {0, 0, 1}}, // 底(反向)
{{1, 1, 0}, {1, 1, 1}, {1, 0, 1}, {1, 0, 0}}, // 东(+x)(反向)
{{0, 1, 1}, {0, 1, 0}, {0, 0, 0}, {0, 0, 1}}, // 西(-x)(反向)
{{1, 1, 1}, {0, 1, 1}, {0, 0, 1}, {1, 0, 1}}, // 南(+z)(反向)
{{0, 1, 0}, {1, 1, 0}, {1, 0, 0}, {0, 0, 0}}, // 北(-z)(反向)
}
// cubeVerts 按六面 UV 构建单位立方体顶点(mesh.Vertex 格式:pos+uv+light 字节,
// 光照满亮——覆盖层不受世界光照影响)。inflate 沿角外扩(裂纹盒防 z-fighting)。
func cubeVerts(uvs [6][4]float32, inflate float32) []mesh.Vertex {
var verts []mesh.Vertex
for fi, face := range cubeFaces {
u0, v0, u1, v1 := uvs[fi][0], uvs[fi][1], uvs[fi][2], uvs[fi][3]
for _, c := range face {
// 角 UV 权重(同 mesh.cornerUV)
var cu, cv float32
switch fi {
case 0, 1: // 顶/底:x→u,z→v
cu, cv = c[0], c[2]
case 2, 3: // 东/西:z→u,y→v(v 翻转)
cu, cv = c[2], 1-c[1]
default: // 南/北:x→u,y→v(v 翻转)
cu, cv = c[0], 1-c[1]
}
verts = append(verts, mesh.Vertex{
X: (c[0]-0.5)*(1+inflate) + 0.5,
Y: (c[1]-0.5)*(1+inflate) + 0.5,
Z: (c[2]-0.5)*(1+inflate) + 0.5,
U: u0 + (u1-u0)*cu, V: v0 + (v1-v0)*cv,
Sky: 15, Block: 15,
})
}
}
return verts
}
// cubeIndices 24 顶点 → 36 索引(每面 2 三角形)。
func cubeIndices() []uint32 {
idx := make([]uint32, 0, 36)
for base := 0; base < 24; base += 4 {
b := uint32(base)
idx = append(idx, b, b+1, b+2, b+2, b+3, b)
}
return idx
}
// uploadMesh 上传顶点/索引并绑定顶点属性(chunk 顶点布局,渲染.md §5)。
func uploadMesh(verts []mesh.Vertex, idx []uint32) (vao, vbo, ibo uint32) {
gl.GenVertexArrays(1, &vao)
gl.GenBuffers(1, &vbo)
gl.GenBuffers(1, &ibo)
gl.BindVertexArray(vao)
gl.BindBuffer(gl.ARRAY_BUFFER, vbo)
gl.BufferData(gl.ARRAY_BUFFER, len(verts)*int(unsafe.Sizeof(mesh.Vertex{})), gl.Ptr(verts), gl.STATIC_DRAW)
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo)
gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, len(idx)*4, gl.Ptr(idx), gl.STATIC_DRAW)
stride := int32(unsafe.Sizeof(mesh.Vertex{}))
gl.EnableVertexAttribArray(0)
gl.VertexAttribPointer(0, 3, gl.FLOAT, false, stride, gl.PtrOffset(0))
gl.EnableVertexAttribArray(1)
gl.VertexAttribPointer(1, 2, gl.FLOAT, false, stride, gl.PtrOffset(3*4))
gl.EnableVertexAttribArray(2)
gl.VertexAttribPointer(2, 2, gl.UNSIGNED_BYTE, false, stride, gl.PtrOffset(5*4))
gl.BindVertexArray(0)
return
}
// buildCubeVAO 构建立方体 VAO(顶点格式与 chunk 一致:pos+uv+light)。
// uvs 六面 UV(顺序同 cubeFaces);inflate 沿角外扩;立方体锚点为原点,边长为 1。
func buildCubeVAO(uvs [6][4]float32, inflate float32) uint32 {
vao, _, _ := uploadMesh(cubeVerts(uvs, inflate), cubeIndices())
return vao
}
// SetCrackAtlas 上传裂纹图集(10 阶段 destroy_stage)并记录各阶段 UV。
// img 为调用方打包好的图集,rects[i] 为阶段 i 的像素矩形(图集坐标系)。
func (r *Renderer) SetCrackAtlas(img image.Image, rects [10]image.Rectangle) {
if r.crackTex == 0 {
gl.GenTextures(1, &r.crackTex)
}
gl.BindTexture(gl.TEXTURE_2D, r.crackTex)
b := img.Bounds()
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, int32(b.Dx()), int32(b.Dy()), 0, gl.RGBA, gl.UNSIGNED_BYTE, gl.Ptr(imageBytes(img)))
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
W, H := float32(b.Dx()), float32(b.Dy())
const inset = 0.5 // 防渗色(渲染.md §4)
for i, rc := range rects {
r.crackUVs[i] = [4]float32{
(float32(rc.Min.X) + inset) / W, (float32(rc.Min.Y) + inset) / H,
(float32(rc.Max.X) - inset) / W, (float32(rc.Max.Y) - inset) / H,
}
}
}
// DrawCrackOverlay 在目标方块 (x,y,z) 叠加 stage 阶段裂纹盒
// (方块交互与动画.md §4:0–9,六面同图,盒体略外扩防 z-fighting)。
func (r *Renderer) DrawCrackOverlay(x, y, z float32, stage int) {
if r.crackTex == 0 {
return
}
if stage < 0 {
stage = 0
}
if stage > 9 {
stage = 9
}
if r.crackCubes[stage] == 0 {
uv := r.crackUVs[stage]
r.crackCubes[stage] = buildCubeVAO([6][4]float32{uv, uv, uv, uv, uv, uv}, 0.004)
}
gl.UseProgram(r.prog)
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, r.crackTex)
vpLoc := gl.GetUniformLocation(r.prog, gl.Str("uViewProj\x00"))
var vp [16]float32
multiply4(&vp, &r.proj, &r.view)
gl.UniformMatrix4fv(vpLoc, 1, false, &vp[0])
offLoc := gl.GetUniformLocation(r.prog, gl.Str("uOffset\x00"))
gl.Uniform3f(offLoc, x, y, z)
cutLoc := gl.GetUniformLocation(r.prog, gl.Str("uCutout\x00"))
gl.Uniform1f(cutLoc, 0)
billLoc := gl.GetUniformLocation(r.prog, gl.Str("uBill\x00"))
gl.Uniform2f(billLoc, 1, 0) // 非广告牌路径
uvMinLoc := gl.GetUniformLocation(r.prog, gl.Str("uUVMin\x00"))
uvMaxLoc := gl.GetUniformLocation(r.prog, gl.Str("uUVMax\x00"))
gl.Uniform2f(uvMinLoc, 0, 0)
gl.Uniform2f(uvMaxLoc, 1, 1)
gl.Enable(gl.POLYGON_OFFSET_FILL)
gl.PolygonOffset(-1, -1)
gl.BindVertexArray(r.crackCubes[stage])
gl.DrawElements(gl.TRIANGLES, 36, gl.UNSIGNED_INT, gl.PtrOffset(0))
gl.BindVertexArray(0)
gl.Disable(gl.POLYGON_OFFSET_FILL)
gl.Uniform3f(offLoc, 0, 0, 0) // 复位平移(后续绘制依赖 0 偏移)
}
// SetHandCube 上传/更新手持方块立方体(六面 UV 顺序同 cubeFaces)。
// pos/scale 为视图空间下的立方体中心与边长(渲染.md §5.2)。
func (r *Renderer) SetHandCube(uvs [6][4]float32, pos [3]float32, scale float32) {
r.ClearHand()
// 直接按视图空间坐标构建顶点(顶点含 pos 偏移)
var verts []mesh.Vertex
for fi, face := range cubeFaces {
u0, v0, u1, v1 := uvs[fi][0], uvs[fi][1], uvs[fi][2], uvs[fi][3]
for _, c := range face {
var cu, cv float32
switch fi {
case 0, 1:
cu, cv = c[0], c[2]
case 2, 3:
cu, cv = c[2], 1-c[1]
default:
cu, cv = c[0], 1-c[1]
}
verts = append(verts, mesh.Vertex{
X: pos[0] + (c[0]-0.5)*scale,
Y: pos[1] + (c[1]-0.5)*scale,
Z: pos[2] + (c[2]-0.5)*scale,
U: u0 + (u1-u0)*cu, V: v0 + (v1-v0)*cv,
Sky: 15, Block: 15,
})
}
}
r.handVao, r.handVbo, r.handIbo = uploadMesh(verts, cubeIndices())
}
// ClearHand 释放手持方块网格(手持物品为空时调用)。
func (r *Renderer) ClearHand() {
if r.handVao != 0 {
gl.DeleteVertexArrays(1, &r.handVao)
r.handVao = 0
}
if r.handVbo != 0 {
gl.DeleteBuffers(1, &r.handVbo)
r.handVbo = 0
}
if r.handIbo != 0 {
gl.DeleteBuffers(1, &r.handIbo)
r.handIbo = 0
}
}
// DrawHand 绘制手持方块(顶点已是视图空间,仅乘投影矩阵)。
func (r *Renderer) DrawHand() {
if r.handVao == 0 {
return
}
gl.UseProgram(r.prog)
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, r.atlas)
vpLoc := gl.GetUniformLocation(r.prog, gl.Str("uViewProj\x00"))
gl.UniformMatrix4fv(vpLoc, 1, false, &r.proj[0])
offLoc := gl.GetUniformLocation(r.prog, gl.Str("uOffset\x00"))
gl.Uniform3f(offLoc, 0, 0, 0)
cutLoc := gl.GetUniformLocation(r.prog, gl.Str("uCutout\x00"))
gl.Uniform1f(cutLoc, 0)
billLoc := gl.GetUniformLocation(r.prog, gl.Str("uBill\x00"))
gl.Uniform2f(billLoc, 1, 0)
uvMinLoc := gl.GetUniformLocation(r.prog, gl.Str("uUVMin\x00"))
uvMaxLoc := gl.GetUniformLocation(r.prog, gl.Str("uUVMax\x00"))
gl.Uniform2f(uvMinLoc, 0, 0)
gl.Uniform2f(uvMaxLoc, 1, 1)
gl.BindVertexArray(r.handVao)
gl.DrawElements(gl.TRIANGLES, 36, gl.UNSIGNED_INT, gl.PtrOffset(0))
gl.BindVertexArray(0)
}