Files
ngzz-mc/internal/render/entity_gl.go
NianGao Dev b0b632df05 feat(client): 手持物品渲染 + 动物方块模型 + 实体贴图 alpha test
- 手持非放置物品(工具/食物):实体图集物品图标小四边形(原版倾斜角,
  双面、alpha test),随挥动动画平移;可放置方块仍为六面立方体
- 动物(牛/猪/羊/鸡)改为方块组合模型(身体+头+腿),按原版皮肤布局
  取子矩形贴图,水平面向相机;修复动物 VAO 未捕获 EBO 导致的驱动崩溃
- 僵尸/村民/掉落物广告牌开启 alpha test(透明区域不再显黑块)
2026-08-16 23:23:30 +08:00

129 lines
5.2 KiB
Go
Raw Permalink 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} // 窗口坐标下 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。
// 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, 1) // alpha test:剔除实体贴图透明区域(否则透明像素显黑块)
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)
}