//go:build gl // 覆盖层渲染:破坏裂纹叠加(destroy_stage_0..9)与第一人称手持方块 // (方块交互与动画.md §4、渲染.md §5.2)。 // 与区块网格共用顶点格式(pos+uv+light)与着色器,通过 uOffset 平移定位。 package render import ( "image" "math" "unsafe" "mc/internal/mesh" "github.com/go-gl/gl/v3.3-core/gl" ) // cubeFaces 单位立方体 6 面角顺序(与 mesh.faceDef 一致, // 窗口空间 CW 绕序为 FrontFace(CW) 的正面;窗口坐标 y 向上)。 var cubeFaces = [6][4][3]float32{ {{0, 1, 0}, {1, 1, 0}, {1, 1, 1}, {0, 1, 1}}, // 顶 {{0, 0, 1}, {1, 0, 1}, {1, 0, 0}, {0, 0, 0}}, // 底 {{1, 0, 0}, {1, 0, 1}, {1, 1, 1}, {1, 1, 0}}, // 东(+x) {{0, 0, 1}, {0, 0, 0}, {0, 1, 0}, {0, 1, 1}}, // 西(-x) {{1, 0, 1}, {0, 0, 1}, {0, 1, 1}, {1, 1, 1}}, // 南(+z) {{0, 0, 0}, {1, 0, 0}, {1, 1, 0}, {0, 1, 0}}, // 北(-z) } // cubeVerts 按六面 UV 构建单位立方体顶点(mesh.Vertex 格式:pos+uv+light 字节, // 光照满亮——覆盖层不受世界光照影响)。inflate 沿角外扩(裂纹盒防 z-fighting)。 func cubeVerts(uvs [6][4]float32, inflate float32) []mesh.Vertex { var verts []mesh.Vertex for fi, face := range cubeFaces { u0, v0, u1, v1 := uvs[fi][0], uvs[fi][1], uvs[fi][2], uvs[fi][3] for _, c := range face { // 角 UV 权重(同 mesh.cornerUV) 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(v 翻转) cu, cv = c[2], 1-c[1] default: // 南/北:x→u,y→v(v 翻转) cu, cv = c[0], 1-c[1] } verts = append(verts, mesh.Vertex{ X: (c[0]-0.5)*(1+inflate) + 0.5, Y: (c[1]-0.5)*(1+inflate) + 0.5, Z: (c[2]-0.5)*(1+inflate) + 0.5, U: u0 + (u1-u0)*cu, V: v0 + (v1-v0)*cv, Sky: 15, Block: 15, }) } } return verts } // cubeIndices 24 顶点 → 36 索引(每面 2 三角形)。 func cubeIndices() []uint32 { idx := make([]uint32, 0, 36) for base := 0; base < 24; base += 4 { b := uint32(base) idx = append(idx, b, b+1, b+2, b+2, b+3, b) } return idx } // uploadMesh 上传顶点/索引并绑定顶点属性(chunk 顶点布局,渲染.md §5)。 func uploadMesh(verts []mesh.Vertex, idx []uint32) (vao, vbo, ibo uint32) { gl.GenVertexArrays(1, &vao) gl.GenBuffers(1, &vbo) gl.GenBuffers(1, &ibo) gl.BindVertexArray(vao) gl.BindBuffer(gl.ARRAY_BUFFER, vbo) gl.BufferData(gl.ARRAY_BUFFER, len(verts)*int(unsafe.Sizeof(mesh.Vertex{})), gl.Ptr(verts), gl.STATIC_DRAW) gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo) 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) return } // buildCubeVAO 构建立方体 VAO(顶点格式与 chunk 一致:pos+uv+light)。 // uvs 六面 UV(顺序同 cubeFaces);inflate 沿角外扩;立方体锚点为原点,边长为 1。 func buildCubeVAO(uvs [6][4]float32, inflate float32) uint32 { vao, _, _ := uploadMesh(cubeVerts(uvs, inflate), cubeIndices()) return vao } // SetCrackAtlas 上传裂纹图集(10 阶段 destroy_stage)并记录各阶段 UV。 // img 为调用方打包好的图集,rects[i] 为阶段 i 的像素矩形(图集坐标系)。 func (r *Renderer) SetCrackAtlas(img image.Image, rects [10]image.Rectangle) { if r.crackTex == 0 { gl.GenTextures(1, &r.crackTex) } gl.BindTexture(gl.TEXTURE_2D, r.crackTex) 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) W, H := float32(b.Dx()), float32(b.Dy()) const inset = 0.5 // 防渗色(渲染.md §4) for i, rc := range rects { r.crackUVs[i] = [4]float32{ (float32(rc.Min.X) + inset) / W, (float32(rc.Min.Y) + inset) / H, (float32(rc.Max.X) - inset) / W, (float32(rc.Max.Y) - inset) / H, } } } // DrawCrackOverlay 在目标方块 (x,y,z) 叠加 stage 阶段裂纹盒 // (方块交互与动画.md §4:0–9,六面同图,盒体略外扩防 z-fighting)。 func (r *Renderer) DrawCrackOverlay(x, y, z float32, stage int) { if r.crackTex == 0 { return } if stage < 0 { stage = 0 } if stage > 9 { stage = 9 } if r.crackCubes[stage] == 0 { uv := r.crackUVs[stage] r.crackCubes[stage] = buildCubeVAO([6][4]float32{uv, uv, uv, uv, uv, uv}, 0.004) } gl.UseProgram(r.prog) gl.ActiveTexture(gl.TEXTURE0) gl.BindTexture(gl.TEXTURE_2D, r.crackTex) 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) billLoc := gl.GetUniformLocation(r.prog, gl.Str("uBill\x00")) gl.Uniform2f(billLoc, 1, 0) // 非广告牌路径 uvMinLoc := gl.GetUniformLocation(r.prog, gl.Str("uUVMin\x00")) uvMaxLoc := gl.GetUniformLocation(r.prog, gl.Str("uUVMax\x00")) gl.Uniform2f(uvMinLoc, 0, 0) gl.Uniform2f(uvMaxLoc, 1, 1) tintLoc := gl.GetUniformLocation(r.prog, gl.Str("uTint\x00")) gl.Uniform4f(tintLoc, 1, 1, 1, 1) gl.Enable(gl.POLYGON_OFFSET_FILL) gl.PolygonOffset(-1, -1) gl.BindVertexArray(r.crackCubes[stage]) gl.DrawElements(gl.TRIANGLES, 36, gl.UNSIGNED_INT, gl.PtrOffset(0)) gl.BindVertexArray(0) gl.Disable(gl.POLYGON_OFFSET_FILL) gl.Uniform3f(offLoc, 0, 0, 0) // 复位平移(后续绘制依赖 0 偏移) } // SetHandCube 上传/更新手持方块立方体(六面 UV 顺序同 cubeFaces)。 // pos/scale 为视图空间下的立方体中心与边长(渲染.md §5.2)。 func (r *Renderer) SetHandCube(uvs [6][4]float32, pos [3]float32, scale float32) { r.ClearHand() // 直接按视图空间坐标构建顶点(顶点含 pos 偏移) var verts []mesh.Vertex for fi, face := range cubeFaces { u0, v0, u1, v1 := uvs[fi][0], uvs[fi][1], uvs[fi][2], uvs[fi][3] for _, c := range face { var cu, cv float32 switch fi { case 0, 1: cu, cv = c[0], c[2] case 2, 3: cu, cv = c[2], 1-c[1] default: cu, cv = c[0], 1-c[1] } verts = append(verts, mesh.Vertex{ X: pos[0] + (c[0]-0.5)*scale, Y: pos[1] + (c[1]-0.5)*scale, Z: pos[2] + (c[2]-0.5)*scale, U: u0 + (u1-u0)*cu, V: v0 + (v1-v0)*cv, Sky: 15, Block: 15, }) } } r.handVao, r.handVbo, r.handIbo = uploadMesh(verts, cubeIndices()) } // ClearHand 释放手持方块网格(手持物品为空时调用)。 func (r *Renderer) ClearHand() { if r.handVao != 0 { gl.DeleteVertexArrays(1, &r.handVao) r.handVao = 0 } if r.handVbo != 0 { gl.DeleteBuffers(1, &r.handVbo) r.handVbo = 0 } if r.handIbo != 0 { gl.DeleteBuffers(1, &r.handIbo) r.handIbo = 0 } } // DrawHand 绘制手持方块(顶点已是视图空间,仅乘投影矩阵;tint 肤色乘数)。 func (r *Renderer) DrawHand(tint [3]float32) { if r.handVao == 0 { return } gl.UseProgram(r.prog) gl.ActiveTexture(gl.TEXTURE0) gl.BindTexture(gl.TEXTURE_2D, r.atlas) vpLoc := gl.GetUniformLocation(r.prog, gl.Str("uViewProj\x00")) gl.UniformMatrix4fv(vpLoc, 1, false, &r.proj[0]) offLoc := gl.GetUniformLocation(r.prog, gl.Str("uOffset\x00")) gl.Uniform3f(offLoc, 0, 0, 0) cutLoc := gl.GetUniformLocation(r.prog, gl.Str("uCutout\x00")) gl.Uniform1f(cutLoc, 0) billLoc := gl.GetUniformLocation(r.prog, gl.Str("uBill\x00")) gl.Uniform2f(billLoc, 1, 0) uvMinLoc := gl.GetUniformLocation(r.prog, gl.Str("uUVMin\x00")) uvMaxLoc := gl.GetUniformLocation(r.prog, gl.Str("uUVMax\x00")) gl.Uniform2f(uvMinLoc, 0, 0) gl.Uniform2f(uvMaxLoc, 1, 1) tintLoc := gl.GetUniformLocation(r.prog, gl.Str("uTint\x00")) gl.Uniform4f(tintLoc, tint[0], tint[1], tint[2], 1) gl.BindVertexArray(r.handVao) 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) }