feat(client): 手持物品渲染 + 动物方块模型 + 实体贴图 alpha test
- 手持非放置物品(工具/食物):实体图集物品图标小四边形(原版倾斜角, 双面、alpha test),随挥动动画平移;可放置方块仍为六面立方体 - 动物(牛/猪/羊/鸡)改为方块组合模型(身体+头+腿),按原版皮肤布局 取子矩形贴图,水平面向相机;修复动物 VAO 未捕获 EBO 导致的驱动崩溃 - 僵尸/村民/掉落物广告牌开启 alpha test(透明区域不再显黑块)
This commit is contained in:
125
internal/render/animals_gl.go
Normal file
125
internal/render/animals_gl.go
Normal file
@@ -0,0 +1,125 @@
|
||||
//go:build gl
|
||||
|
||||
// 动物方块模型渲染(渲染.md §5.3):身体/头/腿用方块组合近似原版动物外观,
|
||||
// 贴图取自实体图集内该动物的纹理格子(子矩形映射到各面)。
|
||||
// 与区块共用着色器:alpha test 剔除透明、满亮不受光照影响。
|
||||
package render
|
||||
|
||||
import (
|
||||
"math"
|
||||
"unsafe"
|
||||
|
||||
"mc/internal/mesh"
|
||||
|
||||
"github.com/go-gl/gl/v3.3-core/gl"
|
||||
)
|
||||
|
||||
// AnimalPart 动物模型部件(相对脚底原点,模型前向 +z)。
|
||||
type AnimalPart struct {
|
||||
CX, CY, CZ float32 // 部件中心
|
||||
W, H, D float32 // 部件尺寸
|
||||
// UV 六面在图集 tile 内归一化子矩形(u0,v0,u1,v1),顺序同 cubeFaces:
|
||||
// 顶/底/东/西/南(前)/北(后)
|
||||
UV [6][4]float32
|
||||
}
|
||||
|
||||
// AnimalModel 动物模型:部件列表。
|
||||
type AnimalModel struct {
|
||||
Parts []AnimalPart
|
||||
}
|
||||
|
||||
// DrawAnimalModel 在 (x,y,z)(脚底)以 yaw(0=朝 +z)绘制方块动物模型。
|
||||
// tile 为实体图集内动物纹理的归一化矩形;部件 UV 相对 tile 定义。
|
||||
// flash=true 受击白闪(uTint 提亮)。
|
||||
func (r *Renderer) DrawAnimalModel(x, y, z, yaw float32, model AnimalModel, tile [4]float32, flash bool) {
|
||||
if r.entityTex == 0 || len(model.Parts) == 0 {
|
||||
return
|
||||
}
|
||||
cosY := float32(math.Cos(float64(yaw)))
|
||||
sinY := float32(math.Sin(float64(yaw)))
|
||||
tw, th := tile[2]-tile[0], tile[3]-tile[1]
|
||||
|
||||
verts := make([]mesh.Vertex, 0, len(model.Parts)*24)
|
||||
for _, p := range model.Parts {
|
||||
for fi := 0; fi < 6; fi++ {
|
||||
uv := p.UV[fi]
|
||||
u0 := tile[0] + uv[0]*tw
|
||||
v0 := tile[1] + uv[1]*th
|
||||
u1 := tile[0] + uv[2]*tw
|
||||
v1 := tile[1] + uv[3]*th
|
||||
for _, c := range cubeFaces[fi] {
|
||||
lx := (c[0]-0.5)*p.W + p.CX
|
||||
ly := (c[1]-0.5)*p.H + p.CY
|
||||
lz := (c[2]-0.5)*p.D + p.CZ
|
||||
// yaw 绕 Y 旋转:模型 +z → (sinY, cosY)
|
||||
wx := lx*cosY + lz*sinY
|
||||
wz := -lx*sinY + lz*cosY
|
||||
var cu, cv float32
|
||||
switch fi {
|
||||
case 0, 1: // 顶/底:x→u,z→v
|
||||
cu, cv = c[0], c[2]
|
||||
case 2, 3: // 东/西:z→u,y→v
|
||||
cu, cv = c[2], 1-c[1]
|
||||
default: // 南/北:x→u,y→v
|
||||
cu, cv = c[0], 1-c[1]
|
||||
}
|
||||
verts = append(verts, mesh.Vertex{
|
||||
X: x + wx, Y: y + ly, Z: z + wz,
|
||||
U: u0 + (u1-u0)*cu, V: v0 + (v1-v0)*cv,
|
||||
Sky: 15, Block: 15,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if r.animalVao == 0 {
|
||||
gl.GenVertexArrays(1, &r.animalVao)
|
||||
gl.GenBuffers(1, &r.animalVbo)
|
||||
gl.GenBuffers(1, &r.animalIbo)
|
||||
gl.BindVertexArray(r.animalVao)
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, r.animalVbo)
|
||||
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))
|
||||
// 索引缓冲绑定必须捕获进 VAO(ELEMENT_ARRAY_BUFFER 属 VAO 状态)
|
||||
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, r.animalIbo)
|
||||
gl.BindVertexArray(0)
|
||||
}
|
||||
|
||||
idx := make([]uint32, 0, len(verts)/4*6)
|
||||
for base := 0; base+3 < len(verts); base += 4 {
|
||||
b := uint32(base)
|
||||
idx = append(idx, b, b+1, b+2, b+2, b+3, b)
|
||||
}
|
||||
// 数据上传须保持 VAO 绑定(避免污染其他 VAO 的 EBO 状态)
|
||||
gl.BindVertexArray(r.animalVao)
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, r.animalVbo)
|
||||
gl.BufferData(gl.ARRAY_BUFFER, len(verts)*int(unsafe.Sizeof(mesh.Vertex{})), gl.Ptr(verts), gl.DYNAMIC_DRAW)
|
||||
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, r.animalIbo)
|
||||
gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, len(idx)*4, gl.Ptr(idx), gl.DYNAMIC_DRAW)
|
||||
|
||||
gl.UseProgram(r.prog)
|
||||
gl.ActiveTexture(gl.TEXTURE0)
|
||||
gl.BindTexture(gl.TEXTURE_2D, r.entityTex)
|
||||
var vp [16]float32
|
||||
multiply4(&vp, &r.proj, &r.view)
|
||||
gl.UniformMatrix4fv(gl.GetUniformLocation(r.prog, gl.Str("uViewProj\x00")), 1, false, &vp[0])
|
||||
gl.Uniform3f(gl.GetUniformLocation(r.prog, gl.Str("uOffset\x00")), 0, 0, 0) // 顶点已烘焙为世界坐标
|
||||
gl.Uniform1f(gl.GetUniformLocation(r.prog, gl.Str("uCutout\x00")), 1) // alpha test:剔除纹理透明区域
|
||||
if flash {
|
||||
gl.Uniform4f(gl.GetUniformLocation(r.prog, gl.Str("uTint\x00")), 2.5, 2.5, 2.5, 1)
|
||||
} else {
|
||||
gl.Uniform4f(gl.GetUniformLocation(r.prog, gl.Str("uTint\x00")), 1, 1, 1, 1)
|
||||
}
|
||||
gl.Uniform2f(gl.GetUniformLocation(r.prog, gl.Str("uBill\x00")), 1, 0) // 非广告牌路径
|
||||
gl.Uniform2f(gl.GetUniformLocation(r.prog, gl.Str("uUVMin\x00")), 0, 0)
|
||||
gl.Uniform2f(gl.GetUniformLocation(r.prog, gl.Str("uUVMax\x00")), 1, 1)
|
||||
|
||||
gl.BindVertexArray(r.animalVao)
|
||||
gl.DrawElements(gl.TRIANGLES, int32(len(idx)), gl.UNSIGNED_INT, gl.PtrOffset(0))
|
||||
gl.BindVertexArray(0)
|
||||
}
|
||||
@@ -88,7 +88,7 @@ func (r *Renderer) DrawBillboard(x, y, z float32, h, w float32, uv [4]float32, f
|
||||
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)
|
||||
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) // 受击白闪
|
||||
|
||||
@@ -7,6 +7,7 @@ package render
|
||||
|
||||
import (
|
||||
"image"
|
||||
"math"
|
||||
"unsafe"
|
||||
|
||||
"mc/internal/mesh"
|
||||
@@ -234,3 +235,76 @@ func (r *Renderer) DrawHand(tint [3]float32) {
|
||||
gl.DrawElements(gl.TRIANGLES, 36, gl.UNSIGNED_INT, gl.PtrOffset(0))
|
||||
gl.BindVertexArray(0)
|
||||
}
|
||||
|
||||
// ensureHandItemQuad 惰性构建手持物品四边形 VAO(顶点每帧重传:位置随挥动变化)。
|
||||
func (r *Renderer) ensureHandItemQuad() {
|
||||
if r.handItemVao != 0 {
|
||||
return
|
||||
}
|
||||
gl.GenVertexArrays(1, &r.handItemVao)
|
||||
gl.GenBuffers(1, &r.handItemVbo)
|
||||
gl.GenBuffers(1, &r.handItemIbo)
|
||||
gl.BindVertexArray(r.handItemVao)
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, r.handItemVbo)
|
||||
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))
|
||||
idx := []uint32{0, 1, 2, 0, 2, 3}
|
||||
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, r.handItemIbo)
|
||||
gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, len(idx)*4, gl.Ptr(idx), gl.STATIC_DRAW)
|
||||
gl.BindVertexArray(0)
|
||||
}
|
||||
|
||||
// DrawHandItem 绘制手持物品图标(非放置物品,如工具/食物):视图空间小四边形,
|
||||
// 轻微倾斜(原版手持角度),双面、alpha test,贴实体图集内物品贴图(渲染.md §5.2)。
|
||||
// pos 为视图空间中心,scale 为边长。
|
||||
func (r *Renderer) DrawHandItem(uv [4]float32, pos [3]float32, scale float32) {
|
||||
if r.entityTex == 0 {
|
||||
return
|
||||
}
|
||||
r.ensureHandItemQuad()
|
||||
|
||||
// 四角局部坐标(-0.5..0.5),绕 Y 转 yaw、绕 X 抬 pitch(烘焙进视图空间顶点)
|
||||
const yaw, pitch = -0.6, 0.25
|
||||
cy, sy := float32(math.Cos(yaw)), float32(math.Sin(yaw))
|
||||
cp, sp := float32(math.Cos(pitch)), float32(math.Sin(pitch))
|
||||
corners := [4][2]float32{{-0.5, -0.5}, {0.5, -0.5}, {0.5, 0.5}, {-0.5, 0.5}}
|
||||
verts := make([]mesh.Vertex, 0, 4)
|
||||
for _, c := range corners {
|
||||
lx, ly := c[0]*scale, c[1]*scale
|
||||
rx := lx * cy
|
||||
rz := -lx * sy
|
||||
ry := ly*cp - rz*sp
|
||||
rz2 := ly*sp + rz*cp
|
||||
verts = append(verts, mesh.Vertex{
|
||||
X: pos[0] + rx, Y: pos[1] + ry, Z: pos[2] + rz2,
|
||||
U: c[0] + 0.5, V: 0.5 - c[1], // 图集 tile:v=0 为图像顶,图标保持正立
|
||||
Sky: 15, Block: 15,
|
||||
})
|
||||
}
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, r.handItemVbo)
|
||||
gl.BufferData(gl.ARRAY_BUFFER, len(verts)*int(unsafe.Sizeof(mesh.Vertex{})), gl.Ptr(verts), gl.DYNAMIC_DRAW)
|
||||
|
||||
gl.UseProgram(r.prog)
|
||||
gl.ActiveTexture(gl.TEXTURE0)
|
||||
gl.BindTexture(gl.TEXTURE_2D, r.entityTex)
|
||||
gl.UniformMatrix4fv(gl.GetUniformLocation(r.prog, gl.Str("uViewProj\x00")), 1, false, &r.proj[0])
|
||||
gl.Uniform3f(gl.GetUniformLocation(r.prog, gl.Str("uOffset\x00")), 0, 0, 0)
|
||||
gl.Uniform1f(gl.GetUniformLocation(r.prog, gl.Str("uCutout\x00")), 1) // alpha test 剔透明
|
||||
gl.Uniform4f(gl.GetUniformLocation(r.prog, gl.Str("uTint\x00")), 1, 1, 1, 1)
|
||||
gl.Uniform2f(gl.GetUniformLocation(r.prog, gl.Str("uBill\x00")), 1, 0)
|
||||
gl.Uniform2f(gl.GetUniformLocation(r.prog, gl.Str("uUVMin\x00")), uv[0], uv[1])
|
||||
gl.Uniform2f(gl.GetUniformLocation(r.prog, gl.Str("uUVMax\x00")), uv[2], uv[3])
|
||||
|
||||
gl.Disable(gl.CULL_FACE) // 双面(背面为图标反相)
|
||||
gl.BindVertexArray(r.handItemVao)
|
||||
gl.DrawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, gl.PtrOffset(0))
|
||||
gl.BindVertexArray(0)
|
||||
gl.Enable(gl.CULL_FACE)
|
||||
gl.Uniform2f(gl.GetUniformLocation(r.prog, gl.Str("uUVMin\x00")), 0, 0)
|
||||
gl.Uniform2f(gl.GetUniformLocation(r.prog, gl.Str("uUVMax\x00")), 1, 1)
|
||||
}
|
||||
|
||||
@@ -35,11 +35,17 @@ type Renderer struct {
|
||||
// 第一人称手持方块(渲染.md §5.2)
|
||||
handVao, handVbo, handIbo uint32
|
||||
|
||||
// 手持物品图标(非放置物品:实体图集内物品贴图的小四边形)
|
||||
handItemVao, handItemVbo, handItemIbo uint32
|
||||
|
||||
// 实体广告牌(渲染.md §5.3)
|
||||
entityTex uint32
|
||||
entityUV map[string][4]float32
|
||||
billVao, billVbo, billIbo uint32
|
||||
|
||||
// 动物方块模型(渲染.md §5.3:身体/头/腿方块组合,动态 VBO)
|
||||
animalVao, animalVbo, animalIbo uint32
|
||||
|
||||
proj [16]float32
|
||||
view [16]float32
|
||||
}
|
||||
@@ -174,6 +180,14 @@ func compileShader(kind uint32, src string) (uint32, error) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// SetWindowIcon 设置窗口图标(素材包 pack.png 原版 logo,构建与发布.md §8)。
|
||||
func (r *Renderer) SetWindowIcon(img image.Image) {
|
||||
if img == nil || r.window == nil {
|
||||
return
|
||||
}
|
||||
r.window.SetIcon([]image.Image{img})
|
||||
}
|
||||
|
||||
// SetAtlas 上传纹理图集(最近邻采样 + mipmap,渲染.md §4)。
|
||||
func (r *Renderer) SetAtlas(img image.Image) {
|
||||
if r.atlas == 0 {
|
||||
@@ -213,6 +227,22 @@ func (r *Renderer) Shutdown() {
|
||||
for _, c := range r.chunks {
|
||||
c.destroy()
|
||||
}
|
||||
for _, v := range [][3]uint32{
|
||||
{r.handVao, r.handVbo, r.handIbo},
|
||||
{r.handItemVao, r.handItemVbo, r.handItemIbo},
|
||||
{r.billVao, r.billVbo, r.billIbo},
|
||||
{r.animalVao, r.animalVbo, r.animalIbo},
|
||||
} {
|
||||
if v[0] != 0 {
|
||||
gl.DeleteVertexArrays(1, &v[0])
|
||||
}
|
||||
if v[1] != 0 {
|
||||
gl.DeleteBuffers(1, &v[1])
|
||||
}
|
||||
if v[2] != 0 {
|
||||
gl.DeleteBuffers(1, &v[2])
|
||||
}
|
||||
}
|
||||
if r.atlas != 0 {
|
||||
gl.DeleteTextures(1, &r.atlas)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user