mediocre frustum culling, very simple biomes
This commit is contained in:
parent
26c822366d
commit
f0a2eacd41
@ -3,13 +3,11 @@ precision mediump float;
|
||||
uniform float g;
|
||||
uniform float g2;
|
||||
|
||||
uniform sampler2D texture0;
|
||||
|
||||
varying vec3 c0;
|
||||
varying vec3 c1;
|
||||
varying vec2 vUV;
|
||||
varying vec3 vColor;
|
||||
|
||||
void main (void) {
|
||||
vec3 diffuse = texture2D(texture0, vUV).xyz;
|
||||
gl_FragColor = vec4(c1, 1.0) + vec4(diffuse * c0, 1.0);
|
||||
gl_FragColor = vec4(c1, 1.0) + vec4(vColor * c0, 1.0);
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ precision mediump float;
|
||||
|
||||
attribute vec3 aVertexPosition;
|
||||
attribute vec3 aNormal;
|
||||
attribute vec2 aTexCoords;
|
||||
attribute vec3 aColor;
|
||||
|
||||
uniform vec3 v3CameraPosition; // The camera position
|
||||
uniform vec3 v3LightPosition; // The direction vector to the light source
|
||||
@ -27,7 +27,7 @@ const float fSamples = 3.0;
|
||||
varying vec3 c0;
|
||||
varying vec3 c1;
|
||||
varying vec3 vNormal;
|
||||
varying vec2 vUV;
|
||||
varying vec3 vColor;
|
||||
|
||||
uniform mat4 uModelMatrix;
|
||||
uniform mat4 uViewMatrix;
|
||||
@ -86,6 +86,6 @@ void main(void) {
|
||||
c1 = v3FrontColor * (v3InvWavelength * fKrESun + fKmESun);
|
||||
|
||||
gl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix * vec4(aVertexPosition,1);
|
||||
vUV = aTexCoords;
|
||||
vColor = aColor;
|
||||
vNormal = aNormal;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import Screen from './screen'
|
||||
import { Node } from './components'
|
||||
import { glMatrix, mat4, vec2, vec3 } from 'gl-matrix'
|
||||
import { glMatrix, mat4, vec2, vec3, vec4 } from 'gl-matrix'
|
||||
|
||||
const SPEED = 100.0
|
||||
const SENSITIVTY = 100.0
|
||||
@ -8,6 +8,92 @@ const FOV = 45.0
|
||||
const ZNEAR = 0.1
|
||||
const ZFAR = 10000.0
|
||||
|
||||
class Frustum {
|
||||
constructor () {
|
||||
this.planes = []
|
||||
}
|
||||
|
||||
construct (projectionMatrix) {
|
||||
const me = projectionMatrix
|
||||
|
||||
this.planes = [
|
||||
vec4.normalize([], [me[3] - me[0], me[7] - me[4], me[11] - me[8], me[15] - me[12]]),
|
||||
vec4.normalize([], [me[3] + me[0], me[7] + me[4], me[11] + me[8], me[15] + me[12]]),
|
||||
vec4.normalize([], [me[3] + me[1], me[7] + me[5], me[11] + me[9], me[15] + me[13]]),
|
||||
vec4.normalize([], [me[3] - me[1], me[7] - me[5], me[11] - me[9], me[15] - me[13]]),
|
||||
vec4.normalize([], [me[3] - me[2], me[7] - me[6], me[11] - me[10], me[15] - me[14]]),
|
||||
vec4.normalize([], [me[3] + me[2], me[7] + me[6], me[11] + me[10], me[15] + me[14]])
|
||||
]
|
||||
}
|
||||
|
||||
// Calculates the closest distance from a given point to a given clipping plane
|
||||
distanceToPlane (plane, point) {
|
||||
return vec3.dot(this.planes[plane], point) + this.planes[plane][3]
|
||||
}
|
||||
|
||||
containsPoint (point) {
|
||||
// For each plane; return outside if the point is behind the plane
|
||||
for (let i = 0; i < 6; i++) {
|
||||
if (this.distanceToPlane(i, point) <= 0.0) return false
|
||||
}
|
||||
|
||||
// Return inside
|
||||
return true
|
||||
}
|
||||
|
||||
containsSphere (position, radius) {
|
||||
// Plane counter
|
||||
let planeCount = 0
|
||||
|
||||
// Use the point-to-plane distance to calculate the number of planes the sphere is in front of
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const distance = this.distanceToPlane(i, position)
|
||||
if (distance <= -radius) {
|
||||
return 0
|
||||
} else if (distance > radius) {
|
||||
planeCount++
|
||||
}
|
||||
}
|
||||
|
||||
// Return inside if in front of all planes; otherwise intersecting
|
||||
return planeCount === 6 ? 1 : 2
|
||||
}
|
||||
|
||||
containsBox (min, max) {
|
||||
// Build a vector holding all box corners
|
||||
const points = []
|
||||
for (let i = 0; i < 8; i++) {
|
||||
points.push([i < 4 ? max[0] : min[0], i % 4 < 2 ? max[1] : min[1], i % 2 ? max[2] : min[2]])
|
||||
}
|
||||
|
||||
// Test the box as a polygon
|
||||
return this.containsPolygon(points)
|
||||
}
|
||||
|
||||
containsPolygon (points) {
|
||||
// Plane counter
|
||||
let planeCount = 0
|
||||
|
||||
// Use the point-to-plane distance to calculate the number of planes the polygon is in front of
|
||||
for (let i = 0; i < 6; i++) {
|
||||
let pointCount = 0
|
||||
for (let j = 0; j < points.length; j++) {
|
||||
if (this.distanceToPlane(i, points[j]) > 0.0) {
|
||||
pointCount++
|
||||
}
|
||||
}
|
||||
if (pointCount === 0) {
|
||||
return 0
|
||||
} else if (pointCount === points.length) {
|
||||
planeCount++
|
||||
}
|
||||
}
|
||||
|
||||
// Return inside if in front of all planes; otherwise intersecting
|
||||
return planeCount === 6 ? 1 : 2
|
||||
}
|
||||
}
|
||||
|
||||
class Camera extends Node {
|
||||
constructor (pos, rotation) {
|
||||
super(pos, null, rotation)
|
||||
@ -27,6 +113,9 @@ class Camera extends Node {
|
||||
this.nearPlane = ZNEAR
|
||||
this.farPlane = ZFAR
|
||||
|
||||
// Frustum planes
|
||||
this.frustum = new Frustum()
|
||||
|
||||
this.updateTransform()
|
||||
}
|
||||
|
||||
@ -99,10 +188,13 @@ class Camera extends Node {
|
||||
|
||||
vec3.cross(upCross, this.right, this.front)
|
||||
vec3.normalize(this.up, upCross)
|
||||
|
||||
this.frustum.construct(mat4.multiply([], this.projection, this.view))
|
||||
}
|
||||
|
||||
updateProjection (gl) {
|
||||
mat4.perspective(this.projection, this.fov, Screen.aspectRatio, this.nearPlane, this.farPlane)
|
||||
this.updateTransform()
|
||||
}
|
||||
|
||||
// Calculate the view matrix on-the-go
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { MeshInstance } from '../'
|
||||
import Sphere from '../../mesh/geometry/sphere'
|
||||
import { vec3 } from 'gl-matrix'
|
||||
import { subv3, normalv3 } from '../../utility'
|
||||
|
||||
class Atmosphere extends MeshInstance {
|
||||
constructor (pos, innerRadius, outerRadius, wavelength = [0.650, 0.570, 0.475]) {
|
||||
super(Sphere.new(outerRadius, 200, 200), pos)
|
||||
class Atmosphere {
|
||||
constructor (planet, outerRadius, wavelength = [0.650, 0.570, 0.475]) {
|
||||
this.mesh = Sphere.new(outerRadius, 200, 200)
|
||||
this.planet = planet
|
||||
this.outerRadius = outerRadius
|
||||
this.innerRadius = innerRadius
|
||||
this.innerRadius = planet.generator.radius
|
||||
this.wavelength = wavelength
|
||||
|
||||
this.Kr = 0.0025
|
||||
@ -19,7 +19,7 @@ class Atmosphere extends MeshInstance {
|
||||
}
|
||||
|
||||
setUniforms (gl, shader, camera, sun) {
|
||||
const camHeight = vec3.length(subv3(camera.pos, this.pos))
|
||||
const camHeight = vec3.length(subv3(camera.pos, this.planet.origin))
|
||||
const invWavelength = [1 / Math.pow(this.wavelength[0], 4), 1 / Math.pow(this.wavelength[1], 4), 1 / Math.pow(this.wavelength[2], 4)]
|
||||
gl.uniform3fv(shader.getUniformLocation(gl, 'v3CameraPosition'), camera.pos)
|
||||
gl.uniform3fv(shader.getUniformLocation(gl, 'v3LightPosition'), normalv3(sun.pos))
|
||||
@ -43,7 +43,7 @@ class Atmosphere extends MeshInstance {
|
||||
|
||||
draw (gl, shader, camera, sun) {
|
||||
// Set model transform matrix uniform
|
||||
gl.uniformMatrix4fv(shader.getUniformLocation(gl, 'uModelMatrix'), false, this.transform)
|
||||
gl.uniformMatrix4fv(shader.getUniformLocation(gl, 'uModelMatrix'), false, this.planet.transform)
|
||||
this.setUniforms(gl, shader, camera, sun)
|
||||
|
||||
// Draw the mesh
|
||||
|
@ -4,15 +4,38 @@ import { mat4, vec3 } from 'gl-matrix'
|
||||
import { subv3, mulv3, addv3, divv3, normalv3, crossv3 } from '../../utility'
|
||||
import Screen from '../../screen'
|
||||
|
||||
const lodMax = 11
|
||||
const lodMax = 8
|
||||
|
||||
class PlanetGenerator {
|
||||
constructor (resolution, radius, noise) {
|
||||
this.resolution = resolution
|
||||
constructor (radius, noise, waterHeight = 0.3) {
|
||||
this.radius = radius
|
||||
this.noise = noise
|
||||
this.moisture = Object.assign({ seed: noise.seed / 2 }, noise)
|
||||
}
|
||||
|
||||
PlanetIndexBuffers.generate(resolution)
|
||||
getHeight (detail, normal) {
|
||||
return this.noise.getNoise3D(detail, normal[0], normal[1], normal[2])
|
||||
}
|
||||
|
||||
getBiome (detail, normal) {
|
||||
const heightAtPoint = this.getHeight(detail, normal)
|
||||
// const moistureAtPoint = this.moisture.getNoise3D(5, normal)
|
||||
|
||||
// 0 - hot, 1 - cold
|
||||
const equatorTemp = 1
|
||||
// 0 - hot, 1 - cold
|
||||
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)
|
||||
|
||||
if (heightAtPoint < 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
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,7 +44,7 @@ const PlanetIndexBuffers = {
|
||||
if (sideResolution <= 1 || sideResolution % 2 === 0) {
|
||||
throw new Error('Resolution must be higher than 1 and an odd number.')
|
||||
}
|
||||
|
||||
PlanetIndexBuffers.resolution = sideResolution
|
||||
PlanetIndexBuffers.base = PlanetIndexBuffers.generateBuffer(sideResolution)
|
||||
PlanetIndexBuffers.fixT = PlanetIndexBuffers.generateBuffer(sideResolution, true, false, false, false)
|
||||
PlanetIndexBuffers.fixTR = PlanetIndexBuffers.generateBuffer(sideResolution, true, true, false, false)
|
||||
@ -104,6 +127,7 @@ class CubeFace {
|
||||
this.level = level
|
||||
|
||||
this.generated = false
|
||||
this.visible = true
|
||||
this.planet = planet
|
||||
this.generator = planet.generator
|
||||
|
||||
@ -125,9 +149,10 @@ class CubeFace {
|
||||
generate () {
|
||||
if (this.generated) return
|
||||
|
||||
const sideResolution = this.generator.resolution
|
||||
const sideResolution = PlanetIndexBuffers.resolution
|
||||
const vertices = []
|
||||
const normals = []
|
||||
const colors = []
|
||||
const textureCoords = []
|
||||
|
||||
const radius = this.generator.radius
|
||||
@ -148,8 +173,10 @@ class CubeFace {
|
||||
|
||||
// Normalize and multiply by radius to create a spherical mesh
|
||||
const normal = normalv3(vertex)
|
||||
const pointHeight = this.generator.noise.getNoise3D(this.level + 1, normal[0], normal[1], normal[2])
|
||||
const pos = mulv3(normal, pointHeight * 2 + radius)
|
||||
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)
|
||||
|
||||
vertices[vertexPointer * 3] = pos[0]
|
||||
vertices[vertexPointer * 3 + 1] = pos[1]
|
||||
@ -157,6 +184,9 @@ class CubeFace {
|
||||
normals[vertexPointer * 3] = normal[0]
|
||||
normals[vertexPointer * 3 + 1] = normal[1]
|
||||
normals[vertexPointer * 3 + 2] = normal[2]
|
||||
colors[vertexPointer * 3] = pointBiome[0]
|
||||
colors[vertexPointer * 3 + 1] = pointBiome[1]
|
||||
colors[vertexPointer * 3 + 2] = pointBiome[2]
|
||||
textureCoords[vertexPointer * 2] = i * (1 / sideResolution)
|
||||
textureCoords[vertexPointer * 2 + 1] = j * (1 / sideResolution)
|
||||
|
||||
@ -170,6 +200,7 @@ class CubeFace {
|
||||
Mesh.generateNormals(normals, PlanetIndexBuffers.base.indices, vertices)
|
||||
|
||||
this.mesh = Mesh.construct(Screen.gl, vertices, null, textureCoords, normals)
|
||||
this.mesh.cbuffer = Mesh.loadToBuffer(Screen.gl, Screen.gl.ARRAY_BUFFER, new Float32Array(colors))
|
||||
this.bounds = BoundingBox.fromMesh(this.mesh)
|
||||
this.generated = true
|
||||
}
|
||||
@ -231,22 +262,31 @@ 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)
|
||||
}
|
||||
|
||||
update (camera, dt) {
|
||||
if (!this.center) return
|
||||
this.visible = camera.frustum.containsBox(this.bounds.min, this.bounds.max)
|
||||
|
||||
const camToOrigin = vec3.distance(camera.pos, this.center)
|
||||
const divisionLevel = Math.pow(2, this.level)
|
||||
const splitDistance = this.generator.radius / divisionLevel
|
||||
|
||||
if (camToOrigin < splitDistance * 2 && this.children.length === 0) {
|
||||
if (camToOrigin < splitDistance * 5 && this.children.length === 0) {
|
||||
this.subdivide()
|
||||
return
|
||||
} else if (camToOrigin > splitDistance * 2.5 && this.children.length > 0) {
|
||||
} else if (camToOrigin > splitDistance * 5.5 && this.children.length > 0) {
|
||||
this.merge()
|
||||
return
|
||||
}
|
||||
@ -367,7 +407,6 @@ class CubeFace {
|
||||
|
||||
static determineIndexBuffer (face) {
|
||||
let buffer = PlanetIndexBuffers.base
|
||||
if (face.level <= 1) return PlanetIndexBuffers.setBuffer(face.mesh, buffer)
|
||||
switch (face.index) {
|
||||
// Top left corner
|
||||
case 0:
|
||||
@ -458,7 +497,7 @@ class CubePlanet {
|
||||
}
|
||||
|
||||
static drawPlanetAtmosphere (gl, planet, atmosphere, camera, sun, atmosShader, planetShader, surfaceShader, surfaceEnvironment) {
|
||||
const height = vec3.length(subv3(camera.pos, atmosphere.pos))
|
||||
const height = vec3.length(subv3(camera.pos, planet.origin))
|
||||
|
||||
// Render the atmosphere
|
||||
atmosShader.use(gl)
|
||||
@ -486,4 +525,4 @@ class CubePlanet {
|
||||
}
|
||||
}
|
||||
|
||||
export { CubePlanet, CubeFace, PlanetGenerator }
|
||||
export { CubePlanet, CubeFace, PlanetGenerator, PlanetIndexBuffers }
|
||||
|
11
src/index.js
11
src/index.js
@ -16,7 +16,7 @@ import { GUIRenderer, GUIImage, Dim4 } from './engine/gui'
|
||||
import { FontRenderer, GUIText, Font } from './engine/gui/font'
|
||||
import { VoxelWorld, VoxelGenerator } from './engine/voxel'
|
||||
|
||||
import { CubePlanet, PlanetGenerator } from './engine/components/planet'
|
||||
import { CubePlanet, PlanetGenerator, PlanetIndexBuffers } from './engine/components/planet'
|
||||
import Atmosphere from './engine/components/planet/atmosphere'
|
||||
|
||||
const game = Engine
|
||||
@ -63,7 +63,7 @@ async function pipeline () {
|
||||
itms[0].color = [0.0, 0.2, 1.0]
|
||||
|
||||
// Create a height map based on OpenSimplex noise
|
||||
const hmap = new SimplexHeightMap(50, 64, 64, 0.2)
|
||||
const hmap = new SimplexHeightMap(1, 20, 10, 0.2)
|
||||
|
||||
// Create a terrain instance
|
||||
// let terrain = new LODTerrain([0.0, 0.0, 0.0], 1024, 1024, 850, 4)
|
||||
@ -83,7 +83,7 @@ async function pipeline () {
|
||||
|
||||
// Create and initialize the camera
|
||||
// [-1300.0, 1325.0, -1300.0], [0.8, -0.6, 0.0]
|
||||
const cam = new Camera([-37, 1107, -1583], [1.5, -0.6, 0])
|
||||
const cam = new Camera([0, 0, -2500], [1.5, -0, 0])
|
||||
cam.updateProjection(game.gl)
|
||||
|
||||
// Create skybox
|
||||
@ -97,8 +97,9 @@ async function pipeline () {
|
||||
// let block = new VoxelWorld(voxgen)
|
||||
|
||||
// Planet test
|
||||
const planet = new CubePlanet([0.0, 0.0, 0.0], new PlanetGenerator(15, 1000, hmap))
|
||||
const atmosphere = new Atmosphere([0.0, 0.0, 0.0], 1000, 1050, [0.650, 0.570, 0.475])
|
||||
PlanetIndexBuffers.generate(15)
|
||||
const planet = new CubePlanet([0.0, 0.0, 0.0], new PlanetGenerator(1000, hmap))
|
||||
const atmosphere = new Atmosphere(planet, 1050, [0.650, 0.570, 0.475])
|
||||
|
||||
planet.material = material
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user