Files
ngzz-mc/internal/render/entity_gl.go
NianGao Dev 9e2f5a6186 fix(game): 崩溃修复(卸载区块实体冻结)+ 立面光照、掉落可见、打击感、水下效果、空手拳头、血条左/饥饿右
- 崩溃(index out of range):实体所在区块卸载后失去碰撞支撑无限下坠到
  负坐标,光查询越界 panic——WorldView 增 ChunkLoaded,未加载区块实体冻结;
  chunk 光查询加越界防御(双保险)
- 立面光照:mesh 每面取**邻格**光照(原版光照模型)——此前用方块自身光照
  (sky=0)导致悬崖/矿洞立面全黑(泥土竖向黑色根因)
- 掉落可见:生成后 1 秒拾取延迟 + 图标加大到 0.45
- 打击感:攻击命中音(block.hit)+ 实体受击白闪(uTint)+ 手持挥动动画
- 水下效果:相机入水蓝色叠加(渲染.md §8 水体)
- 空手显示肤色拳头(白羊毛 × 肤色 tint),持方块正常显示
- HUD 布局:血条左下、饥饿右下(经典布局)
2026-08-16 20:59:28 +08:00

129 lines
5.2 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
// 实体广告牌渲染(实体系统.md §5、渲染.md §5.3
// 以相机朝向的四边形绘制实体贴图zombie/villager/动物)。
// 复用 chunk 着色器aPos 为单位四边形局部坐标x∈[-0.5,0.5]y∈[0,1]
// y=0 为脚底),顶点着色器按 uCamRight/uCamUp 展开成世界坐标;
// UV 经 uUVMin/uUVMax 重映射到图集内该实体的格子。
package render
import (
"image"
"math"
"unsafe"
"mc/internal/mesh"
"github.com/go-gl/gl/v3.3-core/gl"
)
// SetEntityAtlas 上传实体贴图图集并记录 UV键 → 归一化 UV
func (r *Renderer) SetEntityAtlas(img image.Image, uv map[string][4]float32) {
if r.entityTex == 0 {
gl.GenTextures(1, &r.entityTex)
}
gl.BindTexture(gl.TEXTURE_2D, r.entityTex)
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)
r.entityUV = uv
}
// EntityUV 返回实体贴图 UV不存在返回 false
func (r *Renderer) EntityUV(key string) ([4]float32, bool) {
uv, ok := r.entityUV[key]
return uv, ok
}
// ensureBillQuad 惰性构建单位广告牌四边形pos+uv+light 顶点格式CW 绕序正面朝相机)。
// aUV.y=0 对应纹理底部脚底1 对应顶部(头)——顶点序按此翻转 v。
func (r *Renderer) ensureBillQuad() {
if r.billVao != 0 {
return
}
verts := []mesh.Vertex{
{X: -0.5, Y: 0, Z: 0, U: 0, V: 1, Sky: 15, Block: 15}, // 脚底左uv v=1
{X: -0.5, Y: 1, Z: 0, U: 0, V: 0, Sky: 15, Block: 15}, // 头左uv v=0
{X: 0.5, Y: 1, Z: 0, U: 1, V: 0, Sky: 15, Block: 15}, // 头右
{X: 0.5, Y: 0, Z: 0, U: 1, V: 1, Sky: 15, Block: 15}, // 脚底右
}
idx := []uint32{0, 3, 1, 3, 2, 1} // 窗口坐标下 CWFrontFace=CW 时为正面向相机)
gl.GenVertexArrays(1, &r.billVao)
gl.GenBuffers(1, &r.billVbo)
gl.GenBuffers(1, &r.billIbo)
gl.BindVertexArray(r.billVao)
gl.BindBuffer(gl.ARRAY_BUFFER, r.billVbo)
gl.BufferData(gl.ARRAY_BUFFER, len(verts)*int(unsafe.Sizeof(mesh.Vertex{})), gl.Ptr(verts), gl.STATIC_DRAW)
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, r.billIbo)
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)
}
// DrawBillboard 在 (x,y,z)(脚底)绘制 h 高 × w 宽的实体广告牌,贴图 uv。
// flash=true 时整体白闪(受击反馈,打击感)。
func (r *Renderer) DrawBillboard(x, y, z float32, h, w float32, uv [4]float32, flash bool) {
if r.entityTex == 0 {
return
}
r.ensureBillQuad()
gl.UseProgram(r.prog)
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, r.entityTex)
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)
tintLoc := gl.GetUniformLocation(r.prog, gl.Str("uTint\x00"))
if flash {
gl.Uniform4f(tintLoc, 2.5, 2.5, 2.5, 1) // 受击白闪
} else {
gl.Uniform4f(tintLoc, 1, 1, 1, 1)
}
// 相机基向量(视图矩阵列 0/1 即世界系下的 right/up
rightLoc := gl.GetUniformLocation(r.prog, gl.Str("uCamRight\x00"))
upLoc := gl.GetUniformLocation(r.prog, gl.Str("uCamUp\x00"))
gl.Uniform3f(rightLoc, r.view[0], r.view[1], r.view[2])
gl.Uniform3f(upLoc, r.view[4], r.view[5], r.view[6])
billLoc := gl.GetUniformLocation(r.prog, gl.Str("uBill\x00"))
gl.Uniform2f(billLoc, w, h)
uvMinLoc := gl.GetUniformLocation(r.prog, gl.Str("uUVMin\x00"))
uvMaxLoc := gl.GetUniformLocation(r.prog, gl.Str("uUVMax\x00"))
gl.Uniform2f(uvMinLoc, uv[0], uv[1])
gl.Uniform2f(uvMaxLoc, uv[2], uv[3])
gl.Disable(gl.CULL_FACE) // 广告牌永远朝向相机,无需剔除
gl.BindVertexArray(r.billVao)
gl.DrawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, gl.PtrOffset(0))
// 第二张交叉广告牌:水平轴换为视线方向的水平分量(俯视呈 + 形,
// 侧面不再穿帮成纸片——渲染.md §5.3
fx, fz := -r.view[8], -r.view[10]
if fl := fx*fx + fz*fz; fl > 1e-6 {
inv := 1.0 / float32(math.Sqrt(float64(fl)))
gl.Uniform3f(rightLoc, fx*inv, 0, fz*inv)
gl.DrawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, gl.PtrOffset(0))
}
gl.BindVertexArray(0)
gl.Enable(gl.CULL_FACE)
// 复位广告牌状态(避免影响后续裂纹/手持/UI
gl.Uniform2f(billLoc, 1, 0) // uBill.y=0 → 顶点着色器走普通路径
gl.Uniform2f(uvMinLoc, 0, 0)
gl.Uniform2f(uvMaxLoc, 1, 1)
}