Old hobby attempt at a 3D voxel engine in Go/OpenGL.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

render.go 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. package main
  2. import (
  3. "fmt"
  4. "image"
  5. "image/draw"
  6. "image/png"
  7. _ "image/png"
  8. "io/ioutil"
  9. // "math"
  10. "os"
  11. // "runtime"
  12. "github.com/go-gl/gl/v2.1/gl"
  13. )
  14. const Z_OFFSET = 1 / 64
  15. const VIEW_DISTANCE = 10
  16. const EYE_HEIGHT = 1.7
  17. type Render struct {
  18. textures map[string]Texture
  19. blockSheet uint32
  20. buffers map[Position]*VertexBuffer
  21. toRefresh chan VertexRefreshRequest
  22. }
  23. type VertexRefreshRequest struct {
  24. pos Position
  25. vbo *VertexBuffer
  26. }
  27. type VertexBuffer struct {
  28. world World
  29. data []float32
  30. count int32
  31. vbo uint32
  32. vboCount int32
  33. vboInit bool
  34. refreshReady bool
  35. }
  36. var playerLocal *Player
  37. func (v *VertexBuffer) Append(q Quad) {
  38. for i := 0; i < 4; i++ {
  39. v.data = append(v.data, q.v[i].coord[0], q.v[i].coord[1], q.v[i].coord[2],
  40. q.normal[0], q.normal[1], q.normal[2],
  41. q.v[i].color[0], q.v[i].color[1], q.v[i].color[2],
  42. q.v[i].texcoord[0], q.v[i].texcoord[1])
  43. }
  44. v.count += 4
  45. }
  46. func (v *VertexBuffer) AppendFancy(q *Quad, coordOffset Vec3, texCoordOffset Vec2, lightLevel float32) {
  47. for i := 0; i < 4; i++ {
  48. v.data = append(v.data, q.v[i].coord[0] + coordOffset[0], q.v[i].coord[1] + coordOffset[1], q.v[i].coord[2] + coordOffset[2],
  49. q.normal[0], q.normal[1], q.normal[2],
  50. q.v[i].color[0] * lightLevel, q.v[i].color[1] * lightLevel, q.v[i].color[2] * lightLevel,
  51. q.v[i].texcoord[0] + texCoordOffset[0], q.v[i].texcoord[1] + texCoordOffset[1])
  52. }
  53. v.count += 4
  54. }
  55. func (v *VertexBuffer) Deinit() {
  56. gl.DeleteBuffers(1, &v.vbo)
  57. v.vboInit = false
  58. v.data = nil
  59. }
  60. func (v *VertexBuffer) Reset() {
  61. v.data = nil
  62. v.count = 0
  63. }
  64. func (v *VertexBuffer) Refresh() bool {
  65. if !v.vboInit {
  66. gl.GenBuffers(1, &v.vbo)
  67. v.vboInit = true
  68. }
  69. if v.count > 0 && v.refreshReady {
  70. gl.BindBuffer(gl.ARRAY_BUFFER, v.vbo)
  71. gl.BufferData(gl.ARRAY_BUFFER, 4*len(v.data), gl.Ptr(v.data), gl.STATIC_DRAW)
  72. v.vboCount = v.count
  73. v.data = nil
  74. v.refreshReady = false
  75. return true
  76. }
  77. return false
  78. }
  79. func (v *VertexBuffer) Draw() {
  80. if v.vboCount > 0 && v.vboInit {
  81. gl.BindBuffer(gl.ARRAY_BUFFER, v.vbo)
  82. gl.VertexPointer(3, gl.FLOAT, 44, gl.PtrOffset(0))
  83. gl.TexCoordPointer(2, gl.FLOAT, 44, gl.PtrOffset(9*4))
  84. gl.NormalPointer(gl.FLOAT, 44, gl.PtrOffset(3*4))
  85. gl.ColorPointer(3, gl.FLOAT, 44, gl.PtrOffset(6*4))
  86. gl.DrawArrays(gl.QUADS, 0, v.vboCount)
  87. }
  88. }
  89. func nearestPow2(n int) int {
  90. t := uint32(n - 1)
  91. t |= t >> 1
  92. t |= t >> 2
  93. t |= t >> 4
  94. t |= t >> 8
  95. t |= t >> 16
  96. return int(t + 1)
  97. }
  98. func chunkRefreshLoop(r *Render) {
  99. a := 0
  100. for a == 0 {
  101. vbo := <- r.toRefresh
  102. vbo.vbo.Reset()
  103. renderChunk(r, vbo.pos, vbo.vbo)
  104. vbo.vbo.refreshReady = true
  105. }
  106. }
  107. func (r *Render) Init(width int32, height int32, debugtextures bool) {
  108. if err := gl.Init(); err != nil {
  109. panic(err)
  110. }
  111. r.toRefresh = make(chan VertexRefreshRequest, 64)
  112. go chunkRefreshLoop(r)
  113. go chunkRefreshLoop(r)
  114. r.buffers = make(map[Position]*VertexBuffer, 1000)
  115. r.textures = make(map[string]Texture)
  116. r.initTextures(debugtextures)
  117. setupScene()
  118. r.Resize(width, height)
  119. }
  120. func (r *Render) initTextures(debugtextures bool) {
  121. // TODO: not assume that all textures are going to be 16x16
  122. files, _ := ioutil.ReadDir("./textures/")
  123. textures := make(map[string]image.Image, 256)
  124. for _, texfn := range files {
  125. if texfn.IsDir() {
  126. continue
  127. }
  128. imgFile, err := os.Open("./textures/" + texfn.Name())
  129. if err == nil {
  130. img, _, err := image.Decode(imgFile)
  131. if err == nil {
  132. textures[texfn.Name()] = img
  133. }
  134. imgFile.Close()
  135. }
  136. }
  137. countSide := 1
  138. for countSide*countSide < len(textures) {
  139. countSide *= 2
  140. }
  141. rgba := image.NewRGBA(image.Rectangle{image.ZP, image.Pt(countSide << 4, countSide << 4)})
  142. pos := 0
  143. fmt.Printf("Initialized texture of size %d x %d\n", countSide << 4, countSide << 4)
  144. gl.Enable(gl.TEXTURE_2D)
  145. gl.GenTextures(1, &r.blockSheet)
  146. for name, img := range textures {
  147. pX := (pos % countSide)
  148. pY := (pos / countSide)
  149. dp := image.Pt(pX << 4, pY << 4)
  150. pos++
  151. draw.Draw(rgba, image.Rectangle{dp, dp.Add(image.Pt(16, 16))}, img, image.ZP, draw.Src)
  152. fmt.Printf("Loaded texture %s @ %d, %d\n", name, pX, pY)
  153. r.textures[name] = Texture{
  154. binding: r.blockSheet,
  155. minU: float32(pX) / float32(countSide),
  156. maxU: float32(pX + 1) / float32(countSide),
  157. minV: float32(pY) / float32(countSide),
  158. maxV: float32(pY + 1) / float32(countSide),
  159. }
  160. }
  161. if debugtextures {
  162. tmpFile, _ := os.Create("./blockSheet.png")
  163. png.Encode(tmpFile, rgba)
  164. tmpFile.Close()
  165. }
  166. gl.BindTexture(gl.TEXTURE_2D, r.blockSheet)
  167. gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
  168. gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
  169. gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
  170. gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
  171. gl.TexImage2D(
  172. gl.TEXTURE_2D,
  173. 0,
  174. gl.RGBA,
  175. int32(rgba.Rect.Size().X),
  176. int32(rgba.Rect.Size().Y),
  177. 0,
  178. gl.RGBA,
  179. gl.UNSIGNED_BYTE,
  180. gl.Ptr(rgba.Pix))
  181. }
  182. func (r *Render) markForUpdate(p Position) {
  183. buf, exists := r.buffers[p]
  184. if exists {
  185. r.toRefresh <- VertexRefreshRequest{pos: p, vbo: buf}
  186. }
  187. }
  188. func (r *Render) OnRenderUpdate(x int, y int, z int) {
  189. cx := x >> 4
  190. cy := y >> 4
  191. cz := z >> 4
  192. r.markForUpdate(Position{cx, cy, cz})
  193. if x & 15 == 0 {
  194. r.markForUpdate(Position{cx - 1, cy, cz})
  195. } else if x & 15 == 15 {
  196. r.markForUpdate(Position{cx + 1, cy, cz})
  197. }
  198. if y & 15 == 0 {
  199. r.markForUpdate(Position{cx, cy - 1, cz})
  200. } else if y & 15 == 15 {
  201. r.markForUpdate(Position{cx, cy + 1, cz})
  202. }
  203. if z & 15 == 0 {
  204. r.markForUpdate(Position{cx, cy, cz - 1})
  205. } else if z & 15 == 15 {
  206. r.markForUpdate(Position{cx, cy, cz + 1})
  207. }
  208. }
  209. func (r *Render) Deinit() {
  210. gl.DeleteTextures(1, &r.blockSheet)
  211. }
  212. func intMax(a int, b int) int {
  213. if a > b { return a } else { return b }
  214. }
  215. func isUsefulChunk(player *Player, p Position) bool {
  216. dist := intMax(intMax(p.x * 16 - int(player.pos[0]), p.y * 16 - int(player.pos[1])), p.z * 16 - int(player.pos[2]))
  217. return dist <= VIEW_DISTANCE*16
  218. }
  219. func dynamicChunkRender(r *Render, w World, p Position, vbo *VertexBuffer) {
  220. }
  221. func (r *Render) drawBlockVBOs(player *Player, w World) {
  222. gl.Enable(gl.TEXTURE_2D)
  223. gl.BindTexture(gl.TEXTURE_2D, r.blockSheet)
  224. pcx := int(player.pos[0]) >> 4
  225. pcy := int(player.pos[1]) >> 4
  226. pcz := int(player.pos[2]) >> 4
  227. // remove unused VBOs
  228. for pos, buf := range r.buffers {
  229. if !isUsefulChunk(player, pos) {
  230. buf.Deinit()
  231. delete(r.buffers, pos)
  232. }
  233. }
  234. gl.EnableClientState(gl.VERTEX_ARRAY)
  235. gl.EnableClientState(gl.TEXTURE_COORD_ARRAY)
  236. gl.EnableClientState(gl.NORMAL_ARRAY)
  237. gl.EnableClientState(gl.COLOR_ARRAY)
  238. maxRefresh := 8
  239. // add necessary VBOs and render
  240. for y := -VIEW_DISTANCE; y <= VIEW_DISTANCE; y++ {
  241. for z := -VIEW_DISTANCE; z <= VIEW_DISTANCE; z++ {
  242. for x := -VIEW_DISTANCE; x <= VIEW_DISTANCE; x++ {
  243. pos := Position{x: pcx + x, y: pcy + y, z: pcz + z}
  244. if !w.IsLoaded(pos.x << 4, pos.y << 4, pos.z << 4) {
  245. continue
  246. }
  247. if _, exists := r.buffers[pos]; !exists {
  248. v := VertexBuffer{world: w}
  249. r.buffers[pos] = &v
  250. r.toRefresh <- VertexRefreshRequest{pos: pos, vbo: r.buffers[pos]}
  251. } else {
  252. if maxRefresh > 0 && r.buffers[pos].Refresh() {
  253. maxRefresh--
  254. }
  255. r.buffers[pos].Draw()
  256. }
  257. }
  258. }
  259. }
  260. gl.DisableClientState(gl.VERTEX_ARRAY)
  261. gl.DisableClientState(gl.TEXTURE_COORD_ARRAY)
  262. gl.DisableClientState(gl.NORMAL_ARRAY)
  263. gl.DisableClientState(gl.COLOR_ARRAY)
  264. }
  265. func (r *Render) drawBlockHighlight(pos Position) {
  266. xMin := float32(pos.x) - Z_OFFSET
  267. xMax := float32(pos.x + 1) + Z_OFFSET
  268. yMin := float32(pos.y) - Z_OFFSET
  269. yMax := float32(pos.y + 1) + Z_OFFSET
  270. zMin := float32(pos.z) - Z_OFFSET
  271. zMax := float32(pos.z + 1) + Z_OFFSET
  272. gl.Disable(gl.TEXTURE_2D)
  273. gl.Enable(gl.LINE_SMOOTH)
  274. gl.PolygonMode(gl.FRONT_AND_BACK, gl.LINE)
  275. gl.LineWidth(2)
  276. gl.Begin(gl.QUADS)
  277. gl.Vertex3f(xMin, yMin, zMin)
  278. gl.Vertex3f(xMax, yMin, zMin)
  279. gl.Vertex3f(xMax, yMin, zMax)
  280. gl.Vertex3f(xMin, yMin, zMax)
  281. gl.Vertex3f(xMin, yMax, zMin)
  282. gl.Vertex3f(xMax, yMax, zMin)
  283. gl.Vertex3f(xMax, yMax, zMax)
  284. gl.Vertex3f(xMin, yMax, zMax)
  285. gl.Vertex3f(xMin, yMin, zMin)
  286. gl.Vertex3f(xMin, yMax, zMin)
  287. gl.Vertex3f(xMin, yMax, zMax)
  288. gl.Vertex3f(xMin, yMin, zMax)
  289. gl.Vertex3f(xMax, yMin, zMin)
  290. gl.Vertex3f(xMax, yMax, zMin)
  291. gl.Vertex3f(xMax, yMax, zMax)
  292. gl.Vertex3f(xMax, yMin, zMax)
  293. gl.Vertex3f(xMin, yMin, zMin)
  294. gl.Vertex3f(xMin, yMax, zMin)
  295. gl.Vertex3f(xMax, yMax, zMin)
  296. gl.Vertex3f(xMax, yMin, zMin)
  297. gl.Vertex3f(xMin, yMin, zMax)
  298. gl.Vertex3f(xMin, yMax, zMax)
  299. gl.Vertex3f(xMax, yMax, zMax)
  300. gl.Vertex3f(xMax, yMin, zMax)
  301. gl.End()
  302. gl.PolygonMode(gl.FRONT_AND_BACK, gl.FILL)
  303. }
  304. func (r *Render) Render(player *Player, w World) {
  305. // --- INIT ---
  306. playerLocal = player
  307. gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
  308. gl.MatrixMode(gl.MODELVIEW)
  309. gl.LoadIdentity()
  310. gl.Color4f(1, 1, 1, 1)
  311. // --- BLOCK AREA ---
  312. gl.PushMatrix()
  313. gl.Rotatef(player.pitch/DEG_RAD, 1, 0, 0)
  314. gl.Rotatef(player.yaw/DEG_RAD, 0, 1, 0)
  315. gl.Translatef(-player.pos[0], -player.pos[1]-EYE_HEIGHT, -player.pos[2])
  316. r.drawBlockVBOs(player, w)
  317. // draw block wireframe
  318. if pos, exists := player.GetHoverCoords(w); exists {
  319. r.drawBlockHighlight(pos)
  320. }
  321. gl.PopMatrix()
  322. // --- CLEANUP ---
  323. }
  324. func (r *Render) Resize(width int32, height int32) {
  325. ratio := float64(height) / float64(width) * 0.01
  326. gl.Viewport(0, 0, width, height)
  327. gl.MatrixMode(gl.PROJECTION)
  328. gl.LoadIdentity()
  329. if ratio > 1.0 {
  330. gl.Frustum(-(0.01 / ratio), (0.01 / ratio), -1, 1, 0.01, 512)
  331. } else {
  332. gl.Frustum(-0.01, 0.01, -ratio, ratio, 0.01, 512)
  333. }
  334. gl.MatrixMode(gl.MODELVIEW)
  335. gl.LoadIdentity()
  336. }
  337. func setupScene() {
  338. gl.Enable(gl.ALPHA_TEST)
  339. gl.Enable(gl.DEPTH_TEST)
  340. gl.Disable(gl.LIGHTING)
  341. gl.ClearColor(0.4, 0.6, 0.8, 0)
  342. gl.ClearDepth(1)
  343. gl.DepthFunc(gl.LEQUAL)
  344. gl.Fogi(gl.FOG_MODE, gl.LINEAR)
  345. gl.Fogf(gl.FOG_START, (VIEW_DISTANCE * 16) - 32)
  346. gl.Fogf(gl.FOG_END, (VIEW_DISTANCE * 16) - 8)
  347. fogcol := []float32{0.4, 0.6, 0.8, 1}
  348. gl.Fogfv(gl.FOG_COLOR, &fogcol[0])
  349. gl.Enable(gl.FOG)
  350. ambient := []float32{1, 1, 1, 1}
  351. gl.LightModelfv(gl.LIGHT_MODEL_AMBIENT, &ambient[0])
  352. }
  353. func isSolidSide(w World, x int, y int, z int, side Direction) bool {
  354. b := w.GetBlock(x, y, z)
  355. if b != nil {
  356. return b.IsSideSolid(side)
  357. } else {
  358. return false
  359. }
  360. }
  361. func renderQuad(vbo *VertexBuffer, x int, y int, z int, q *Quad, d Direction) {
  362. lightLevelScaler := []float32{0.55, 1.0, 0.85, 0.85, 0.7, 0.7}
  363. vbo.AppendFancy(q, Vec3{float32(x), float32(y), float32(z)}, Vec2{}, lightLevelScaler[int(d)])
  364. }
  365. func renderModel(vbo *VertexBuffer, x int, y int, z int, m Model) {
  366. for i := 0; i < 6; i++ {
  367. xOff := 0
  368. yOff := 0
  369. zOff := 0
  370. if i == 0 { yOff = -1 }
  371. if i == 1 { yOff = 1 }
  372. if i == 2 { xOff = -1 }
  373. if i == 3 { xOff = 1 }
  374. if i == 4 { zOff = -1 }
  375. if i == 5 { zOff = 1 }
  376. if !isSolidSide(vbo.world, x+xOff, y+yOff, z+zOff, Direction(i ^ 1)) {
  377. for _, quad := range m.faceQuads[i] {
  378. renderQuad(vbo, x, y, z, &quad, Direction(i))
  379. }
  380. }
  381. }
  382. for _, quad := range m.quads {
  383. renderQuad(vbo, x, y, z, &quad, UNKNOWN)
  384. }
  385. }
  386. func renderChunk(r *Render, p Position, vbo *VertexBuffer) {
  387. for y := 0; y < 16; y++ {
  388. py := p.y << 4 + y
  389. for z := 0; z < 16; z++ {
  390. pz := p.z << 4 + z
  391. for x := 0; x < 16; x++ {
  392. px := p.x << 4 + x
  393. block := vbo.world.GetBlock(px, py, pz)
  394. if block != nil {
  395. renderModel(vbo, px, py, pz, block.GetModel(r))
  396. }
  397. }
  398. }
  399. }
  400. }