textured voxel terrain

This commit is contained in:
Evert Prants 2020-04-09 19:37:35 +03:00
parent ab35890f1b
commit 968c6ff3ad
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
9 changed files with 105 additions and 61 deletions

View File

@ -35,7 +35,7 @@ void main() {
} }
totalDiffuse = max(totalDiffuse,0.2); totalDiffuse = max(totalDiffuse,0.2);
vec4 textureColor = texture2D(texture0, uv * 40.0); vec4 textureColor = texture2D(texture0, uv);
if(textureColor.a<0.5){ if(textureColor.a<0.5){
discard; discard;
} }

BIN
assets/textures/voxel.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -143,6 +143,8 @@ class Camera extends Node {
vec3.multiply(vec, this.right, velocity) vec3.multiply(vec, this.right, velocity)
vec3.add(this.pos, this.pos, vec) vec3.add(this.pos, this.pos, vec)
} }
this.frustum.construct(mat4.multiply([], this.projection, this.view))
} }
processMouseMove (offset, constrain = true) { processMouseMove (offset, constrain = true) {

View File

@ -17,8 +17,7 @@ class PlanetGenerator {
return this.noise.getNoise3D(detail, normal[0], normal[1], normal[2]) return this.noise.getNoise3D(detail, normal[0], normal[1], normal[2])
} }
getBiome (detail, normal) { getBiome (height, normal) {
const heightAtPoint = this.getHeight(detail, normal)
// const moistureAtPoint = this.moisture.getNoise3D(5, normal) // const moistureAtPoint = this.moisture.getNoise3D(5, normal)
// 0 - hot, 1 - cold // 0 - hot, 1 - cold
@ -27,12 +26,10 @@ class PlanetGenerator {
const poleTemp = 0.4 const poleTemp = 0.4
// TODO: pole settings for planet // TODO: pole settings for planet
const distanceFromPoles = Math.abs(vec3.dot(normal, [0.0, 1.0, 0.0])) const distanceFromPoles = Math.abs(vec3.dot(normal, [0.0, 1.0, 0.0]))
let e = heightAtPoint
// Calculate temperature for the poles // 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.8) return [44 / 255, 50 / 255, 29 / 255] // Grass
else if (e < 0.9) return [50 / 255, 50 / 255, 50 / 255] // Stone else if (e < 0.9) return [50 / 255, 50 / 255, 50 / 255] // Stone
else return [1, 1, 1] // Snow else return [1, 1, 1] // Snow
@ -176,7 +173,7 @@ class CubeFace {
const pointHeight = this.generator.getHeight(this.level + 1, normal) const pointHeight = this.generator.getHeight(this.level + 1, normal)
const pos = mulv3(normal, pointHeight * 10 + radius) 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] = pos[0]
vertices[vertexPointer * 3 + 1] = pos[1] vertices[vertexPointer * 3 + 1] = pos[1]
@ -255,6 +252,7 @@ class CubeFace {
} }
draw (gl, shader) { draw (gl, shader) {
if (!this.visible) return
if (!this.mesh) { if (!this.mesh) {
for (const i in this.children) { for (const i in this.children) {
this.children[i].draw(gl, shader) this.children[i].draw(gl, shader)
@ -262,16 +260,16 @@ class CubeFace {
return return
} }
if (!this.visible) return
CubeFace.determineIndexBuffer(this) CubeFace.determineIndexBuffer(this)
this.mesh.prepare(gl, shader) this.mesh.prepare(gl, shader)
if (this.mesh.cbuffer && shader.hasAttribute(gl, 'aColor')) { if (this.mesh.cbuffer && shader.hasAttribute(gl, 'aColor')) {
gl.bindBuffer(gl.ARRAY_BUFFER, this.mesh.cbuffer) gl.bindBuffer(gl.ARRAY_BUFFER, this.mesh.cbuffer)
shader.setAttribute(gl, 'aColor', 3, false, 3 * Float32Array.BYTES_PER_ELEMENT, 0) shader.setAttribute(gl, 'aColor', 3, false, 3 * Float32Array.BYTES_PER_ELEMENT, 0)
this.mesh._bufferCount++ this.mesh._bufferCount++
} }
this.mesh.draw(gl, shader) this.mesh.draw(gl, shader)
} }

View File

@ -35,14 +35,14 @@ class DirectionalLight extends SpotLight {
} }
class Environment { class Environment {
constructor (ambient) { constructor (ambient, sun) {
this.ambient = ambient this.ambient = ambient
this.fogStart = 0 this.fogStart = 0
this.fogEnd = 0 this.fogEnd = 0
this.fogColor = [0.8, 0.8, 0.8] 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.lights = [this.sun]
this.maxEnvironmentLights = ENV_MAX_LIGHTS this.maxEnvironmentLights = ENV_MAX_LIGHTS

View File

@ -84,6 +84,7 @@ class Material {
const tex = this.textures[i] const tex = this.textures[i]
if (tex && tex instanceof Texture) { if (tex && tex instanceof Texture) {
gl.activeTexture(gl.TEXTURE0 + parseInt(i)) gl.activeTexture(gl.TEXTURE0 + parseInt(i))
gl.bindTexture(tex.type, tex.id) gl.bindTexture(tex.type, tex.id)
} }
} }

View File

@ -1,9 +1,9 @@
import { Mesh } from '../mesh' import { Mesh } from '../mesh'
import { Node, MeshInstance } from '../components' import { MeshInstance } from '../components'
import { vec3 } from 'gl-matrix' import { vec3 } from 'gl-matrix'
import { addv3, subv3, mulv3, dim3to1, dim1to3 } from '../utility' import { addv3, subv3, dim3to1 } from '../utility'
import { BoundingBox } from '../mesh/aabb'
import VoxelData from './voxeldata' import VoxelData from './voxeldata'
import VoxelTexture from './voxeltexture'
const FACE_VERTEX = [ const FACE_VERTEX = [
// Bottom // Bottom
@ -24,34 +24,34 @@ const FACE_VERTEX = [
], ],
// Left // Left
[ [
[0.0, 0.0, 1.0],
[0.0, 1.0, 1.0],
[0.0, 1.0, 0.0], [0.0, 1.0, 0.0],
[0.0, 0.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] [-1.0, 0.0, 0.0]
], ],
// Right // Right
[ [
[1.0, 0.0, 0.0],
[1.0, 1.0, 0.0],
[1.0, 1.0, 1.0], [1.0, 1.0, 1.0],
[1.0, 0.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] [1.0, 0.0, 0.0]
], ],
// Front // Front
[ [
[1.0, 0.0, 1.0],
[1.0, 1.0, 1.0],
[0.0, 1.0, 1.0], [0.0, 1.0, 1.0],
[0.0, 0.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] [0.0, 0.0, -1.0]
], ],
// Back // Back
[ [
[0.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[1.0, 1.0, 0.0], [1.0, 1.0, 0.0],
[1.0, 0.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] [0.0, 0.0, 1.0]
] ]
] ]
@ -63,8 +63,10 @@ const FACE_RIGHT = 3
const FACE_FRONT = 4 const FACE_FRONT = 4
const FACE_BACK = 5 const FACE_BACK = 5
const AIR_VOXEL = VoxelData.register('air', { solid: false }) const AIR = VoxelData.register('air', { solid: false })
const GROUND_VOXEL = VoxelData.register('ground', { solid: true }) 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 { class VoxelChunk extends MeshInstance {
constructor (world, pos, size = 16) { constructor (world, pos, size = 16) {
@ -85,6 +87,11 @@ class VoxelChunk extends MeshInstance {
// If the chunk is outside of the view, make it inactive // If the chunk is outside of the view, make it inactive
this.active = true 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) { getVoxel (x, y, z) {
@ -95,41 +102,41 @@ class VoxelChunk extends MeshInstance {
if (neighbor) { if (neighbor) {
return neighbor.getVoxel(this.size + x, y, z) return neighbor.getVoxel(this.size + x, y, z)
} }
return AIR_VOXEL return AIR
} else if (x >= this.size) { } else if (x >= this.size) {
neighbor = this.world.getChunk(this.relativePos[0] + 1, this.relativePos[1], this.relativePos[2]) neighbor = this.world.getChunk(this.relativePos[0] + 1, this.relativePos[1], this.relativePos[2])
if (neighbor) { if (neighbor) {
return neighbor.getVoxel(x - this.size, y, z) return neighbor.getVoxel(x - this.size, y, z)
} }
return AIR_VOXEL return AIR
} else if (y < 0) { } else if (y < 0) {
neighbor = this.world.getChunk(this.relativePos[0], this.relativePos[1] - 1, this.relativePos[2]) neighbor = this.world.getChunk(this.relativePos[0], this.relativePos[1] - 1, this.relativePos[2])
if (neighbor) { if (neighbor) {
return neighbor.getVoxel(x, this.size + y, z) return neighbor.getVoxel(x, this.size + y, z)
} }
return AIR_VOXEL return AIR
} else if (y >= this.size) { } else if (y >= this.size) {
neighbor = this.world.getChunk(this.relativePos[0], this.relativePos[1] + 1, this.relativePos[2]) neighbor = this.world.getChunk(this.relativePos[0], this.relativePos[1] + 1, this.relativePos[2])
if (neighbor) { if (neighbor) {
return neighbor.getVoxel(x, y - this.size, z) return neighbor.getVoxel(x, y - this.size, z)
} }
return AIR_VOXEL return AIR
} else if (z < 0) { } else if (z < 0) {
neighbor = this.world.getChunk(this.relativePos[0], this.relativePos[1], this.relativePos[2] - 1) neighbor = this.world.getChunk(this.relativePos[0], this.relativePos[1], this.relativePos[2] - 1)
if (neighbor) { if (neighbor) {
return neighbor.getVoxel(x, y, this.size + z) return neighbor.getVoxel(x, y, this.size + z)
} }
return AIR_VOXEL return AIR
} else if (z >= this.size) { } else if (z >= this.size) {
neighbor = this.world.getChunk(this.relativePos[0], this.relativePos[1], this.relativePos[2] + 1) neighbor = this.world.getChunk(this.relativePos[0], this.relativePos[1], this.relativePos[2] + 1)
if (neighbor) { if (neighbor) {
return neighbor.getVoxel(x, y, z - this.size) 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) { getVoxelv (v) {
@ -137,13 +144,18 @@ class VoxelChunk extends MeshInstance {
} }
generate (generator) { generate (generator) {
this.material = generator.material this.material = VoxelTexture.material
for (let x = 0; x < this.size; x++) { for (let x = 0; x < this.size; x++) {
for (let z = 0; z < this.size; z++) { 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++) { for (let y = 0; y < this.size; y++) {
const solid = this.pos[1] + y < columnHeight let voxel = AIR
this.data[dim3to1(x, y, z, this.size)] = solid ? GROUND_VOXEL : AIR_VOXEL
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 // Programmatically generate a voxel face
// Returns the position, normal and texture coordinates for each vertex in this 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 // Add the corresponding offsets for this face to the position
const corners = [ const corners = [
addv3(pos, FACE_VERTEX[face][0]), addv3(pos, FACE_VERTEX[face][1]), 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 // Select the normal for this face
const normal = FACE_VERTEX[face][4] const normal = FACE_VERTEX[face][4]
const uvs = VoxelData.textureIndex(vId, face)
// Create the 4 vertices that make up this face // Create the 4 vertices that make up this face
// They're named points because this function returns not only vertices, // They're named points because this function returns not only vertices,
// but corresponding texture coordinates and normals at the same time for convenience // but corresponding texture coordinates and normals at the same time for convenience
points.push([corners[0], normal, [0.0, 1.0]]) points.push([corners[0], normal, uvs[1]])
points.push([corners[1], normal, [0.0, 0.0]]) points.push([corners[1], normal, uvs[0]])
points.push([corners[2], normal, [1.0, 0.0]]) points.push([corners[2], normal, uvs[2]])
points.push([corners[3], normal, [1.0, 1.0]]) points.push([corners[3], normal, uvs[3]])
// Create the face // Create the face
const inx = points.length - 4 const inx = points.length - 4
@ -203,30 +216,31 @@ class VoxelChunk extends MeshInstance {
for (let y = 0; y < this.size; y++) { for (let y = 0; y < this.size; y++) {
for (let z = 0; z < this.size; z++) { for (let z = 0; z < this.size; z++) {
const cellPos = [x, y, 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))) { 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))) { 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))) { 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))) { 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))) { 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))) { 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) { update (gl, dt, camera) {
this.active = camera.frustum.containsBox( if (this.bounds.length) this.active = camera.frustum.containsBox(this.bounds[0], this.bounds[1])
vec3.transformMat4([], [0.0, 0.0, 0.0], this.transform),
vec3.transformMat4([], [this.size, this.size, this.size], this.transform)
)
if (!this.active) return false if (!this.active) return false
if (!this.generated) return this.generate(this.world.generator) if (!this.generated) return this.generate(this.world.generator)
if (this.dirty) return this.createMesh(gl) if (this.dirty) return this.createMesh(gl)
@ -352,8 +363,7 @@ class VoxelWorld {
} }
class VoxelGenerator { class VoxelGenerator {
constructor (noise, material, groundHeight = 64) { constructor (noise, groundHeight = 64) {
this.material = material
this.noise = noise this.noise = noise
this.groundHeight = groundHeight this.groundHeight = groundHeight
} }

View File

@ -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 = { const VoxelData = {
index: 0, index: 0,
@ -12,15 +15,22 @@ const VoxelData = {
}, },
get: (name) => { get: (name) => {
return VoxelData.registeredVoxels[name] return VoxelData.registeredVoxels[name]
},
afterRegistration: () => {
}, },
isSolid: (id) => { isSolid: (id) => {
return VoxelData.indexCache[id] ? VoxelData.indexCache[id].solid : false return VoxelData.indexCache[id] ? VoxelData.indexCache[id].solid : false
}, },
textureIndex: (id, face) => { 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
} }
} }

View File

@ -1,9 +1,32 @@
import Screen from '../screen' import Screen from '../screen'
import { Texture, Material } from '../mesh/material'
function generateTextureMap (registeredVoxels) { const clip = 1 / 100
// body...
}
const VoxelTexture = { 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