feat(client): 植被染色、破坏裂纹叠加、手持方块与实体广告牌渲染;原版 HUD 精灵
- 植被染色(渲染.md §4.1):blocks.json 增 tint 标记,图集构建后按 colormap/grass|foliage 中心色乘色——原版材质包草顶/树叶为灰度图,不染色全灰 - 破坏裂纹(方块交互与动画.md §4):destroy_stage_0..9 裂纹盒叠加破坏目标方块 - 第一人称手持方块(渲染.md §5.2):选中槽放置方块六面 UV 视图空间立方体 - 实体广告牌(渲染.md §5.3):僵尸/村民/动物按贴图渲染,动物按 ID 轮换外观 - HUD 换原版精灵:crosshair/hotbar/hotbar_selection/heart/food(UI还原.md §3.4) - 主菜单标题/按钮标签按请求字号光栅化(修复 24px 固定字号);版本角标 CJK - 恢复背面剔除(相机修复后 '脚下黑块' 误诊解除);ESC 光标请求原子化防竞态 - dev 测试参数:-give/-crackstage/-spawntest/-esctest
This commit is contained in:
112
internal/render/entity_gl.go
Normal file
112
internal/render/entity_gl.go
Normal file
@@ -0,0 +1,112 @@
|
||||
//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"
|
||||
"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))
|
||||
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)
|
||||
}
|
||||
@@ -102,6 +102,14 @@ func (r *Renderer) DrawChunks(get func(i int) (int32, int32), n int) {
|
||||
vpLoc := gl.GetUniformLocation(r.prog, gl.Str("uViewProj\x00"))
|
||||
cutLoc := gl.GetUniformLocation(r.prog, gl.Str("uCutout\x00"))
|
||||
gl.UniformMatrix4fv(vpLoc, 1, false, &vp[0])
|
||||
offLoc := gl.GetUniformLocation(r.prog, gl.Str("uOffset\x00"))
|
||||
gl.Uniform3f(offLoc, 0, 0, 0) // 区块网格无偏移(覆盖层在 DrawChunks 后自行设置)
|
||||
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) // 区块顶点 UV 直接使用
|
||||
gl.Uniform2f(uvMaxLoc, 1, 1)
|
||||
|
||||
// pass 1+2:不透明与 cutout(alpha test,不排序)
|
||||
gl.Uniform1f(cutLoc, 0)
|
||||
|
||||
231
internal/render/overlay_gl.go
Normal file
231
internal/render/overlay_gl.go
Normal file
@@ -0,0 +1,231 @@
|
||||
//go:build gl
|
||||
|
||||
// 覆盖层渲染:破坏裂纹叠加(destroy_stage_0..9)与第一人称手持方块
|
||||
// (方块交互与动画.md §4、渲染.md §5.2)。
|
||||
// 与区块网格共用顶点格式(pos+uv+light)与着色器,通过 uOffset 平移定位。
|
||||
package render
|
||||
|
||||
import (
|
||||
"image"
|
||||
"unsafe"
|
||||
|
||||
"mc/internal/mesh"
|
||||
|
||||
"github.com/go-gl/gl/v3.3-core/gl"
|
||||
)
|
||||
|
||||
// cubeFaces 单位立方体 6 面角顺序(与 mesh.faceDef 一致,CW 绕序为正面)。
|
||||
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)
|
||||
|
||||
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 绘制手持方块(顶点已是视图空间,仅乘投影矩阵)。
|
||||
func (r *Renderer) DrawHand() {
|
||||
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)
|
||||
gl.BindVertexArray(r.handVao)
|
||||
gl.DrawElements(gl.TRIANGLES, 36, gl.UNSIGNED_INT, gl.PtrOffset(0))
|
||||
gl.BindVertexArray(0)
|
||||
}
|
||||
@@ -27,6 +27,19 @@ type Renderer struct {
|
||||
// 区块网格缓存:key → VAO/VBO/索引数
|
||||
chunks map[uint64]*chunkGL
|
||||
|
||||
// 裂纹覆盖层(方块交互与动画.md §4)
|
||||
crackTex uint32
|
||||
crackUVs [10][4]float32
|
||||
crackCubes [10]uint32
|
||||
|
||||
// 第一人称手持方块(渲染.md §5.2)
|
||||
handVao, handVbo, handIbo uint32
|
||||
|
||||
// 实体广告牌(渲染.md §5.3)
|
||||
entityTex uint32
|
||||
entityUV map[string][4]float32
|
||||
billVao, billVbo, billIbo uint32
|
||||
|
||||
proj [16]float32
|
||||
view [16]float32
|
||||
}
|
||||
@@ -217,11 +230,21 @@ layout(location = 0) in vec3 aPos;
|
||||
layout(location = 1) in vec2 aUV;
|
||||
layout(location = 2) in vec2 aLight; // x=天空光 y=方块光(4-bit)
|
||||
uniform mat4 uViewProj;
|
||||
uniform vec3 uOffset; // 覆盖层/广告牌锚点平移(区块绘制时为 0)
|
||||
uniform vec3 uCamRight; // 相机右向量(世界系,广告牌展开用)
|
||||
uniform vec3 uCamUp; // 相机上向量(世界系)
|
||||
uniform vec2 uBill; // 广告牌缩放:x=右 y=上;y=0 时走普通路径
|
||||
uniform vec2 uUVMin; // UV 重映射下限(区块/普通立方体为 0,0)
|
||||
uniform vec2 uUVMax; // UV 重映射上限(区块/普通立方体为 1,1)
|
||||
out vec2 vUV;
|
||||
out vec2 vLight;
|
||||
void main() {
|
||||
gl_Position = uViewProj * vec4(aPos, 1.0);
|
||||
vUV = aUV;
|
||||
vec3 pos = aPos + uOffset;
|
||||
if (uBill.y > 0.001) {
|
||||
pos = uOffset + vec3(aPos.x * uCamRight * uBill.x + aPos.y * uCamUp * uBill.y);
|
||||
}
|
||||
gl_Position = uViewProj * vec4(pos, 1.0);
|
||||
vUV = uUVMin + aUV * (uUVMax - uUVMin);
|
||||
vLight = aLight;
|
||||
}
|
||||
`
|
||||
|
||||
Reference in New Issue
Block a user