From 968c6ff3adb3fffe4a4e3b6e060fed72e9768863 Mon Sep 17 00:00:00 2001 From: Evert Prants Date: Thu, 9 Apr 2020 19:37:35 +0300 Subject: [PATCH] textured voxel terrain --- assets/shaders/terrain.fs | 2 +- assets/textures/voxel.png | Bin 0 -> 1434 bytes src/engine/camera.js | 2 + src/engine/components/planet/index.js | 16 ++--- src/engine/environment.js | 4 +- src/engine/mesh/material.js | 1 + src/engine/voxel/index.js | 94 ++++++++++++++------------ src/engine/voxel/voxeldata.js | 18 +++-- src/engine/voxel/voxeltexture.js | 29 +++++++- 9 files changed, 105 insertions(+), 61 deletions(-) create mode 100644 assets/textures/voxel.png diff --git a/assets/shaders/terrain.fs b/assets/shaders/terrain.fs index da14641..6d0ea04 100644 --- a/assets/shaders/terrain.fs +++ b/assets/shaders/terrain.fs @@ -35,7 +35,7 @@ void main() { } totalDiffuse = max(totalDiffuse,0.2); - vec4 textureColor = texture2D(texture0, uv * 40.0); + vec4 textureColor = texture2D(texture0, uv); if(textureColor.a<0.5){ discard; } diff --git a/assets/textures/voxel.png b/assets/textures/voxel.png new file mode 100644 index 0000000000000000000000000000000000000000..f193486f02bcb01740ca0ae3d7f5cdd8fe6913c7 GIT binary patch literal 1434 zcmZuxX;2af6h(X zPdnl?0RZ59NPu4yacri^uht4}6qpc@5?yE1$ZWFR9zSNol{ra`2!gspglix>x6$b4 z`BcNe&fWguT(Y_AaO@RR9?u*(i5@xkbzs7DSGx0`H4KPu4?|1sSCTl|M;ysI`hhoX`gbLv?@zXQ5fI1>s9vmpv^0c z;_Fi6>-_6R^@UF^IwLE0L%?zgF7egQXsxSl0K|xMb+0}5$ZA)F9a*BN$E+^Ok7t0y3M;@oS8J+bX5GZQ2tm{k?nFrP=E{i2jKmPqOPvv}{~p(%Sg zgR-^WGJ)S}yM)XF`f7Ey;^3%O>S19z*~Jgldw_52j-+ArAclUk*6`Zv^ec9H6&f<7 zhpE_7>1M1Qu)jSNb-wxjAqo2}yOo&rpuDLnx#3J+b=dTQV?x>VDh?l9wYKCt9WiYm(%)`WEgq*Zb9e(ks z#oH6fSTiZ9qvyq1NykcUbMB+qZr*YcrvW`X8pq1)7uB#6P-Sq z`Cj2ZHiJlPUN5L?PcitcaYD zcjylccUbow`yb+|%pf0I_3l)%aa|vbHzyj?tLP73xOaAQ?3gfrqth;)4`wedlGsaY zgPsh9$;3+>)^)}oGd(#%Tzcz6GiUX&!@&#b74?9E#!(qL`>gnNp$ON(=chhdWqhTS zXgV4FoN(6jFh2dw%LoNqAVd(O%hDwhGfqM5Y_+$Z)SgQI230+W`DSF4M`7l4|0)Gg;UthL0>Qf5er1B@O8!OgSQ$F3wpmEGg`7+g@DwqE*}{I~GG>VJ}31Jj%s%B{w~0$H$c OUoj*wBA^SESoSx{BbxpI literal 0 HcmV?d00001 diff --git a/src/engine/camera.js b/src/engine/camera.js index 698f20a..609a156 100644 --- a/src/engine/camera.js +++ b/src/engine/camera.js @@ -143,6 +143,8 @@ class Camera extends Node { vec3.multiply(vec, this.right, velocity) vec3.add(this.pos, this.pos, vec) } + + this.frustum.construct(mat4.multiply([], this.projection, this.view)) } processMouseMove (offset, constrain = true) { diff --git a/src/engine/components/planet/index.js b/src/engine/components/planet/index.js index 2019399..346244e 100644 --- a/src/engine/components/planet/index.js +++ b/src/engine/components/planet/index.js @@ -17,8 +17,7 @@ class PlanetGenerator { return this.noise.getNoise3D(detail, normal[0], normal[1], normal[2]) } - getBiome (detail, normal) { - const heightAtPoint = this.getHeight(detail, normal) + getBiome (height, normal) { // const moistureAtPoint = this.moisture.getNoise3D(5, normal) // 0 - hot, 1 - cold @@ -27,12 +26,10 @@ class PlanetGenerator { const poleTemp = 0.4 // TODO: pole settings for planet const distanceFromPoles = Math.abs(vec3.dot(normal, [0.0, 1.0, 0.0])) - - let e = heightAtPoint // Calculate temperature for the poles - e = (e * e + poleTemp + (equatorTemp - poleTemp) * distanceFromPoles) + const e = (height * height + poleTemp + (equatorTemp - poleTemp) * distanceFromPoles) - if (heightAtPoint < 0.1) return [1 / 255, 56 / 255, 104 / 255] // Water + if (height < 0.1) return [1 / 255, 56 / 255, 104 / 255] // Water else if (e <= 0.8) return [44 / 255, 50 / 255, 29 / 255] // Grass else if (e < 0.9) return [50 / 255, 50 / 255, 50 / 255] // Stone else return [1, 1, 1] // Snow @@ -176,7 +173,7 @@ class CubeFace { const pointHeight = this.generator.getHeight(this.level + 1, normal) const pos = mulv3(normal, pointHeight * 10 + radius) - const pointBiome = this.generator.getBiome(this.level + 1, normal) + const pointBiome = this.generator.getBiome(pointHeight, normal) vertices[vertexPointer * 3] = pos[0] vertices[vertexPointer * 3 + 1] = pos[1] @@ -255,6 +252,7 @@ class CubeFace { } draw (gl, shader) { + if (!this.visible) return if (!this.mesh) { for (const i in this.children) { this.children[i].draw(gl, shader) @@ -262,16 +260,16 @@ class CubeFace { return } - if (!this.visible) return - CubeFace.determineIndexBuffer(this) this.mesh.prepare(gl, shader) + if (this.mesh.cbuffer && shader.hasAttribute(gl, 'aColor')) { gl.bindBuffer(gl.ARRAY_BUFFER, this.mesh.cbuffer) shader.setAttribute(gl, 'aColor', 3, false, 3 * Float32Array.BYTES_PER_ELEMENT, 0) this.mesh._bufferCount++ } + this.mesh.draw(gl, shader) } diff --git a/src/engine/environment.js b/src/engine/environment.js index 0619e28..e83dd07 100644 --- a/src/engine/environment.js +++ b/src/engine/environment.js @@ -35,14 +35,14 @@ class DirectionalLight extends SpotLight { } class Environment { - constructor (ambient) { + constructor (ambient, sun) { this.ambient = ambient this.fogStart = 0 this.fogEnd = 0 this.fogColor = [0.8, 0.8, 0.8] - this.sun = new Light([0.0, 10000.0, -20000.0], [1.0, 1.0, 1.0]) + this.sun = sun this.lights = [this.sun] this.maxEnvironmentLights = ENV_MAX_LIGHTS diff --git a/src/engine/mesh/material.js b/src/engine/mesh/material.js index 8971b94..3315da8 100644 --- a/src/engine/mesh/material.js +++ b/src/engine/mesh/material.js @@ -84,6 +84,7 @@ class Material { const tex = this.textures[i] if (tex && tex instanceof Texture) { gl.activeTexture(gl.TEXTURE0 + parseInt(i)) + gl.bindTexture(tex.type, tex.id) } } diff --git a/src/engine/voxel/index.js b/src/engine/voxel/index.js index f970199..085597c 100644 --- a/src/engine/voxel/index.js +++ b/src/engine/voxel/index.js @@ -1,9 +1,9 @@ import { Mesh } from '../mesh' -import { Node, MeshInstance } from '../components' +import { MeshInstance } from '../components' import { vec3 } from 'gl-matrix' -import { addv3, subv3, mulv3, dim3to1, dim1to3 } from '../utility' -import { BoundingBox } from '../mesh/aabb' +import { addv3, subv3, dim3to1 } from '../utility' import VoxelData from './voxeldata' +import VoxelTexture from './voxeltexture' const FACE_VERTEX = [ // Bottom @@ -24,34 +24,34 @@ const FACE_VERTEX = [ ], // Left [ - [0.0, 0.0, 1.0], - [0.0, 1.0, 1.0], [0.0, 1.0, 0.0], [0.0, 0.0, 0.0], + [0.0, 0.0, 1.0], + [0.0, 1.0, 1.0], [-1.0, 0.0, 0.0] ], // Right [ - [1.0, 0.0, 0.0], - [1.0, 1.0, 0.0], [1.0, 1.0, 1.0], [1.0, 0.0, 1.0], + [1.0, 0.0, 0.0], + [1.0, 1.0, 0.0], [1.0, 0.0, 0.0] ], // Front [ - [1.0, 0.0, 1.0], - [1.0, 1.0, 1.0], [0.0, 1.0, 1.0], [0.0, 0.0, 1.0], + [1.0, 0.0, 1.0], + [1.0, 1.0, 1.0], [0.0, 0.0, -1.0] ], // Back [ - [0.0, 0.0, 0.0], - [0.0, 1.0, 0.0], [1.0, 1.0, 0.0], [1.0, 0.0, 0.0], + [0.0, 0.0, 0.0], + [0.0, 1.0, 0.0], [0.0, 0.0, 1.0] ] ] @@ -63,8 +63,10 @@ const FACE_RIGHT = 3 const FACE_FRONT = 4 const FACE_BACK = 5 -const AIR_VOXEL = VoxelData.register('air', { solid: false }) -const GROUND_VOXEL = VoxelData.register('ground', { solid: true }) +const AIR = VoxelData.register('air', { solid: false }) +const GRASS = VoxelData.register('grass', { solid: true, tiles: [0, 2, 1, 1, 1, 1] }) +const DIRT = VoxelData.register('dirt', { solid: true, tiles: [0] }) +const STONE = VoxelData.register('stone', { solid: true, tiles: [3] }) class VoxelChunk extends MeshInstance { constructor (world, pos, size = 16) { @@ -85,6 +87,11 @@ class VoxelChunk extends MeshInstance { // If the chunk is outside of the view, make it inactive this.active = true + + this.bounds = [ + vec3.transformMat4([], [0.0, 0.0, 0.0], this.transform), + vec3.transformMat4([], [this.size, this.size, this.size], this.transform) + ] } getVoxel (x, y, z) { @@ -95,41 +102,41 @@ class VoxelChunk extends MeshInstance { if (neighbor) { return neighbor.getVoxel(this.size + x, y, z) } - return AIR_VOXEL + return AIR } else if (x >= this.size) { neighbor = this.world.getChunk(this.relativePos[0] + 1, this.relativePos[1], this.relativePos[2]) if (neighbor) { return neighbor.getVoxel(x - this.size, y, z) } - return AIR_VOXEL + return AIR } else if (y < 0) { neighbor = this.world.getChunk(this.relativePos[0], this.relativePos[1] - 1, this.relativePos[2]) if (neighbor) { return neighbor.getVoxel(x, this.size + y, z) } - return AIR_VOXEL + return AIR } else if (y >= this.size) { neighbor = this.world.getChunk(this.relativePos[0], this.relativePos[1] + 1, this.relativePos[2]) if (neighbor) { return neighbor.getVoxel(x, y - this.size, z) } - return AIR_VOXEL + return AIR } else if (z < 0) { neighbor = this.world.getChunk(this.relativePos[0], this.relativePos[1], this.relativePos[2] - 1) if (neighbor) { return neighbor.getVoxel(x, y, this.size + z) } - return AIR_VOXEL + return AIR } else if (z >= this.size) { neighbor = this.world.getChunk(this.relativePos[0], this.relativePos[1], this.relativePos[2] + 1) if (neighbor) { return neighbor.getVoxel(x, y, z - this.size) } - return AIR_VOXEL + return AIR } } - return this.data[dim3to1(x, y, z, this.size)] || AIR_VOXEL + return this.data[dim3to1(x, y, z, this.size)] || AIR } getVoxelv (v) { @@ -137,13 +144,18 @@ class VoxelChunk extends MeshInstance { } generate (generator) { - this.material = generator.material + this.material = VoxelTexture.material for (let x = 0; x < this.size; x++) { for (let z = 0; z < this.size; z++) { - const columnHeight = generator.getHeight(x + this.pos[0], z + this.pos[2]) + const columnHeight = Math.floor(generator.getHeight(x + this.pos[0], z + this.pos[2])) for (let y = 0; y < this.size; y++) { - const solid = this.pos[1] + y < columnHeight - this.data[dim3to1(x, y, z, this.size)] = solid ? GROUND_VOXEL : AIR_VOXEL + let voxel = AIR + + if (this.pos[1] + y === columnHeight) voxel = GRASS + else if (this.pos[1] + y < columnHeight - 3) voxel = STONE + else if (this.pos[1] + y < columnHeight) voxel = DIRT + + this.data[dim3to1(x, y, z, this.size)] = voxel } } } @@ -154,7 +166,7 @@ class VoxelChunk extends MeshInstance { // Programmatically generate a voxel face // Returns the position, normal and texture coordinates for each vertex in this face - createFace (indices, points, pos, face) { + createFace (indices, points, pos, vId, face) { // Add the corresponding offsets for this face to the position const corners = [ addv3(pos, FACE_VERTEX[face][0]), addv3(pos, FACE_VERTEX[face][1]), @@ -163,14 +175,15 @@ class VoxelChunk extends MeshInstance { // Select the normal for this face const normal = FACE_VERTEX[face][4] + const uvs = VoxelData.textureIndex(vId, face) // Create the 4 vertices that make up this face // They're named points because this function returns not only vertices, // but corresponding texture coordinates and normals at the same time for convenience - points.push([corners[0], normal, [0.0, 1.0]]) - points.push([corners[1], normal, [0.0, 0.0]]) - points.push([corners[2], normal, [1.0, 0.0]]) - points.push([corners[3], normal, [1.0, 1.0]]) + points.push([corners[0], normal, uvs[1]]) + points.push([corners[1], normal, uvs[0]]) + points.push([corners[2], normal, uvs[2]]) + points.push([corners[3], normal, uvs[3]]) // Create the face const inx = points.length - 4 @@ -203,30 +216,31 @@ class VoxelChunk extends MeshInstance { for (let y = 0; y < this.size; y++) { for (let z = 0; z < this.size; z++) { const cellPos = [x, y, z] - if (!VoxelData.isSolid(this.getVoxel(x, y, z))) continue + const vid = this.getVoxel(x, y, z) + if (!VoxelData.isSolid(vid)) continue if (!VoxelData.isSolid(this.getVoxel(x, y - 1, z))) { - this.createFace(indices, points, cellPos, FACE_BOTTOM) + this.createFace(indices, points, cellPos, vid, FACE_BOTTOM) } if (!VoxelData.isSolid(this.getVoxel(x, y + 1, z))) { - this.createFace(indices, points, cellPos, FACE_TOP) + this.createFace(indices, points, cellPos, vid, FACE_TOP) } if (!VoxelData.isSolid(this.getVoxel(x - 1, y, z))) { - this.createFace(indices, points, cellPos, FACE_LEFT) + this.createFace(indices, points, cellPos, vid, FACE_LEFT) } if (!VoxelData.isSolid(this.getVoxel(x + 1, y, z))) { - this.createFace(indices, points, cellPos, FACE_RIGHT) + this.createFace(indices, points, cellPos, vid, FACE_RIGHT) } if (!VoxelData.isSolid(this.getVoxel(x, y, z + 1))) { - this.createFace(indices, points, cellPos, FACE_FRONT) + this.createFace(indices, points, cellPos, vid, FACE_FRONT) } if (!VoxelData.isSolid(this.getVoxel(x, y, z - 1))) { - this.createFace(indices, points, cellPos, FACE_BACK) + this.createFace(indices, points, cellPos, vid, FACE_BACK) } } } @@ -262,10 +276,7 @@ class VoxelChunk extends MeshInstance { } update (gl, dt, camera) { - this.active = camera.frustum.containsBox( - vec3.transformMat4([], [0.0, 0.0, 0.0], this.transform), - vec3.transformMat4([], [this.size, this.size, this.size], this.transform) - ) + if (this.bounds.length) this.active = camera.frustum.containsBox(this.bounds[0], this.bounds[1]) if (!this.active) return false if (!this.generated) return this.generate(this.world.generator) if (this.dirty) return this.createMesh(gl) @@ -352,8 +363,7 @@ class VoxelWorld { } class VoxelGenerator { - constructor (noise, material, groundHeight = 64) { - this.material = material + constructor (noise, groundHeight = 64) { this.noise = noise this.groundHeight = groundHeight } diff --git a/src/engine/voxel/voxeldata.js b/src/engine/voxel/voxeldata.js index 66be7e7..cb4fd55 100644 --- a/src/engine/voxel/voxeldata.js +++ b/src/engine/voxel/voxeldata.js @@ -1,3 +1,6 @@ +import VoxelTexture from './voxeltexture' + +const MISSING_UV = [[0.0, 1.0], [0.0, 0.0], [1.0, 0.0], [1.0, 1.0]] const VoxelData = { index: 0, @@ -12,15 +15,22 @@ const VoxelData = { }, get: (name) => { return VoxelData.registeredVoxels[name] - }, - afterRegistration: () => { - }, isSolid: (id) => { return VoxelData.indexCache[id] ? VoxelData.indexCache[id].solid : false }, textureIndex: (id, face) => { - + const icache = VoxelData.indexCache[id] + if (!icache.tiles) return MISSING_UV + if (icache.tiles.length === 1) return VoxelTexture.faceUVs(icache.tiles[0]) + if (icache.tiles.length >= 2 && icache.tiles.length < 6) { + // top face + if (face === 1) { + return VoxelTexture.faceUVs(icache.tiles[0]) + } + return VoxelTexture.faceUVs(icache.tiles[1]) + } + return VoxelTexture.faceUVs(icache.tiles[face]) || MISSING_UV } } diff --git a/src/engine/voxel/voxeltexture.js b/src/engine/voxel/voxeltexture.js index 38d4d5a..87c9244 100644 --- a/src/engine/voxel/voxeltexture.js +++ b/src/engine/voxel/voxeltexture.js @@ -1,9 +1,32 @@ import Screen from '../screen' +import { Texture, Material } from '../mesh/material' -function generateTextureMap (registeredVoxels) { - // body... -} +const clip = 1 / 100 const VoxelTexture = { + size: 0, + voxelSize: 0, + texture: null, + material: null, + loadFromFile: async (fileName, size) => { + VoxelTexture.texture = await Texture.fromFile(Screen.gl, fileName, true, Screen.gl.NEAREST, Screen.gl.CLAMP_TO_EDGE) + VoxelTexture.size = size + VoxelTexture.voxelSize = 1 / size + VoxelTexture.material = new Material([VoxelTexture.texture]) + }, + faceUVs: (textureIndex) => { + let x = textureIndex / VoxelTexture.size + let y = textureIndex - (x * VoxelTexture.size) + y = 1 - y - VoxelTexture.voxelSize + + return [ + [x + clip, y + clip], + [x + clip, y + VoxelTexture.voxelSize - clip], + [x - clip + VoxelTexture.voxelSize, y + clip], + [x - clip + VoxelTexture.voxelSize, y - clip + VoxelTexture.voxelSize] + ] + } } + +export default VoxelTexture