//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) }