Files
ngzz-mc/internal/render/entity_gl.go
NianGao Dev f25ae15077 feat(game): 音频接入、原版掉落、动物攻击、村庄村民、种子/灵敏度配置与设置界面
- 音频(音频.md §2):Session.Sound 事件回调,破坏/放置/攻击/村民环境音接入,
  客户端 audio.Manager 3D 衰减 + 监听器跟随 + 音量可调
- 原版掉落:草方块→泥土、石头→圆石、树叶无掉落(no_drop);掉落物实体
  用物品图标小广告牌渲染(物品纹理并入实体图集)
- 动物攻击(动物体系.md §5):左键优先射线命中实体(半径 0.6 球体判定),
  伤害/击退/死亡移除+肉类掉落;僵尸掉腐肉
- 村庄村民(村民系统.md §2):Generator.VillageAt 定位村庄中心,会话扫描
  已加载区块在村庄生成 3 名村民
- 种子与灵敏度配置:client.yaml world.seed/name、mouse_sensitivity;
  存档与种子联动;鼠标左右反转修复(右移应右转)
- 设置界面(UI与输入.md §6):屏幕状态 4,灵敏度/音量滑条、世界种子显示、
  按键教程多行列表(cjkfont 支持 \\n)、完成按钮;主菜单/暂停菜单入口
- 准星指向方块名:左下角 CJK 显示(lang 本地化,tick 光栅化渲染线程上传)
- 截图统一存项目根目录 jietu/(paths.RootDir;dev 构建识别 bin/ 布局)
2026-08-16 20:18:52 +08:00

122 lines
4.9 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} // 窗口坐标下 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))
// 第二张交叉广告牌:水平轴换为视线方向的水平分量(俯视呈 + 形,
// 侧面不再穿帮成纸片——渲染.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)
}