Files
ngzz-mc/internal/render/entity_gl.go
NianGao Dev d52bf1126f feat(client): 植被染色、破坏裂纹叠加、手持方块与实体广告牌渲染;原版 HUD 精灵
- 植被染色(渲染.md §4.1):blocks.json 增 tint 标记,图集构建后按
  colormap/grass|foliage 中心色乘色——原版材质包草顶/树叶为灰度图,不染色全灰
- 破坏裂纹(方块交互与动画.md §4):destroy_stage_0..9 裂纹盒叠加破坏目标方块
- 第一人称手持方块(渲染.md §5.2):选中槽放置方块六面 UV 视图空间立方体
- 实体广告牌(渲染.md §5.3):僵尸/村民/动物按贴图渲染,动物按 ID 轮换外观
- HUD 换原版精灵:crosshair/hotbar/hotbar_selection/heart/food(UI还原.md §3.4)
- 主菜单标题/按钮标签按请求字号光栅化(修复 24px 固定字号);版本角标 CJK
- 恢复背面剔除(相机修复后 '脚下黑块' 误诊解除);ESC 光标请求原子化防竞态
- dev 测试参数:-give/-crackstage/-spawntest/-esctest
2026-08-16 10:08:40 +08:00

113 lines
4.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.
//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"
"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} // 窗口坐标下 CW(FrontFace=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。
func (r *Renderer) DrawBillboard(x, y, z float32, h, w float32, uv [4]float32) {
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)
// 相机基向量(视图矩阵列 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))
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)
}