- 音频(音频.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/ 布局)
122 lines
4.9 KiB
Go
122 lines
4.9 KiB
Go
//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)
|
||
}
|