175 lines
5.2 KiB
Go
175 lines
5.2 KiB
Go
//go:build gl
|
||
|
||
package render
|
||
|
||
import (
|
||
"unsafe"
|
||
|
||
"mc/internal/mesh"
|
||
|
||
"github.com/go-gl/gl/v3.3-core/gl"
|
||
)
|
||
|
||
// chunkKey 区块坐标 → key(与 world 包一致)。
|
||
func chunkKey(cx, cz int32) uint64 { return uint64(uint32(cx))<<32 | uint64(uint32(cz)) }
|
||
|
||
// HasChunk 区块网格是否已上传(dev 截图测试等待视野就绪用)。
|
||
func (r *Renderer) HasChunk(cx, cz int32) bool {
|
||
_, ok := r.chunks[chunkKey(cx, cz)]
|
||
return ok
|
||
}
|
||
|
||
// UploadChunk 上传区块网格到 GPU(dirty 网格重建后调用,渲染.md §3.3)。
|
||
// 三类网格合并为单 VBO + 单 IBO:不透明 → cutout → 半透明 顺序连续存放。
|
||
func (r *Renderer) UploadChunk(cx, cz int32, m *mesh.ChunkMesh) {
|
||
k := chunkKey(cx, cz)
|
||
if old, ok := r.chunks[k]; ok {
|
||
old.destroy()
|
||
}
|
||
cg := &chunkGL{}
|
||
gl.GenVertexArrays(1, &cg.vao)
|
||
gl.GenBuffers(1, &cg.vbo)
|
||
gl.GenBuffers(1, &cg.ibo)
|
||
gl.BindVertexArray(cg.vao)
|
||
|
||
// 合并顶点
|
||
all := make([]mesh.Vertex, 0, len(m.Opaque)+len(m.Cutout)+len(m.Translucent))
|
||
all = append(all, m.Opaque...)
|
||
all = append(all, m.Cutout...)
|
||
all = append(all, m.Translucent...)
|
||
cg.translucent = len(m.Translucent) > 0
|
||
|
||
gl.BindBuffer(gl.ARRAY_BUFFER, cg.vbo)
|
||
gl.BufferData(gl.ARRAY_BUFFER, len(all)*int(unsafe.Sizeof(mesh.Vertex{})), gl.Ptr(all), gl.STATIC_DRAW)
|
||
|
||
// 索引:每面 4 顶点四边形 → 2 三角形(0-1-2-2-3-0)
|
||
idx := make([]uint32, 0, len(all)/4*6)
|
||
for base := 0; base+3 < len(all); base += 4 {
|
||
b := uint32(base)
|
||
idx = append(idx, b, b+1, b+2, b+2, b+3, b)
|
||
}
|
||
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, cg.ibo)
|
||
gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, len(idx)*4, gl.Ptr(idx), gl.STATIC_DRAW)
|
||
|
||
// 顶点属性布局(渲染.md §5 顶点格式)
|
||
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)
|
||
cg.idxCount = int32(len(m.Opaque) / 4 * 6)
|
||
cg.idxCut = int32(len(m.Cutout) / 4 * 6)
|
||
cg.idxTrans = int32(len(m.Translucent) / 4 * 6)
|
||
r.chunks[k] = cg
|
||
}
|
||
|
||
// ClearAllChunks 切换存档时释放全部 GPU 网格。
|
||
func (r *Renderer) ClearAllChunks() {
|
||
for k, cg := range r.chunks {
|
||
cg.destroy()
|
||
delete(r.chunks, k)
|
||
}
|
||
}
|
||
|
||
// RemoveChunk 移除区块网格(区块卸载时调用)。
|
||
func (r *Renderer) RemoveChunk(cx, cz int32) {
|
||
k := chunkKey(cx, cz)
|
||
if cg, ok := r.chunks[k]; ok {
|
||
cg.destroy()
|
||
delete(r.chunks, k)
|
||
}
|
||
}
|
||
|
||
// destroy 释放 GPU 资源。
|
||
func (cg *chunkGL) destroy() {
|
||
if cg.vao != 0 {
|
||
gl.DeleteVertexArrays(1, &cg.vao)
|
||
}
|
||
if cg.vbo != 0 {
|
||
gl.DeleteBuffers(1, &cg.vbo)
|
||
}
|
||
if cg.ibo != 0 {
|
||
gl.DeleteBuffers(1, &cg.ibo)
|
||
}
|
||
cg.vao, cg.vbo, cg.ibo = 0, 0, 0
|
||
}
|
||
|
||
// DrawChunks 绘制全部区块网格(三 pass,渲染.md §6)。
|
||
// chunks:本帧要绘制的区块(已由视锥剔除过滤)。
|
||
func (r *Renderer) DrawChunks(get func(i int) (int32, int32), n int) {
|
||
gl.UseProgram(r.prog)
|
||
gl.ActiveTexture(gl.TEXTURE0)
|
||
gl.BindTexture(gl.TEXTURE_2D, r.atlas)
|
||
|
||
var vp [16]float32
|
||
multiply4(&vp, &r.proj, &r.view)
|
||
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)
|
||
tintLoc := gl.GetUniformLocation(r.prog, gl.Str("uTint\x00"))
|
||
gl.Uniform4f(tintLoc, 1, 1, 1, 1) // 区块无颜色乘数
|
||
|
||
// pass 1+2:不透明与 cutout(alpha test,不排序)
|
||
gl.Uniform1f(cutLoc, 0)
|
||
drawPass(r, get, n, false, false)
|
||
gl.Uniform1f(cutLoc, 1)
|
||
drawPass(r, get, n, false, true)
|
||
// pass 3:半透明(关闭深度写入,远→近排序由调用方保证顺序)
|
||
gl.Uniform1f(cutLoc, 0)
|
||
gl.DepthMask(false)
|
||
drawPass(r, get, n, true, false)
|
||
gl.DepthMask(true)
|
||
gl.BindVertexArray(0)
|
||
}
|
||
|
||
// drawPass 按通道绘制。
|
||
func drawPass(r *Renderer, get func(i int) (int32, int32), n int, translucent, cutout bool) {
|
||
for i := 0; i < n; i++ {
|
||
x, z := get(i)
|
||
cg, ok := r.chunks[chunkKey(x, z)]
|
||
if !ok {
|
||
continue
|
||
}
|
||
base := int32(0)
|
||
count := cg.idxCount
|
||
if cutout {
|
||
base = cg.idxCount
|
||
count = cg.idxCut
|
||
}
|
||
if translucent {
|
||
base = cg.idxCount + cg.idxCut
|
||
count = cg.idxTrans
|
||
}
|
||
if count == 0 {
|
||
continue
|
||
}
|
||
gl.BindVertexArray(cg.vao)
|
||
gl.DrawElements(gl.TRIANGLES, count, gl.UNSIGNED_INT, gl.PtrOffset(int(base)*4))
|
||
}
|
||
}
|
||
|
||
// multiply4 列主序 4×4 矩阵乘法:out = a × b。
|
||
func multiply4(out, a, b *[16]float32) {
|
||
for col := 0; col < 4; col++ {
|
||
for row := 0; row < 4; row++ {
|
||
var v float32
|
||
for k := 0; k < 4; k++ {
|
||
v += a[k*4+row] * b[col*4+k]
|
||
}
|
||
out[col*4+row] = v
|
||
}
|
||
}
|
||
}
|