//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)) } // 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 } // 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(cx, cz 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]) // pass 1+2:不透明与 cutout(alpha test,不排序) gl.Uniform1f(cutLoc, 0) drawPass(r, n, cx, cz, false, false) gl.Uniform1f(cutLoc, 1) drawPass(r, n, cx, cz, false, true) // pass 3:半透明(关闭深度写入,远→近排序由调用方保证顺序) gl.Uniform1f(cutLoc, 0) gl.DepthMask(false) drawPass(r, n, cx, cz, true, false) gl.DepthMask(true) gl.BindVertexArray(0) } // drawPass 按通道绘制。 func drawPass(r *Renderer, n int, cx, cz func(i int) (int32, int32), translucent, cutout bool) { for i := 0; i < n; i++ { x, z := cx(i), cz(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 } } }