From f0a2eacd4108cdd0c6f4b6282017f9e4ef8bc219 Mon Sep 17 00:00:00 2001 From: Evert Prants Date: Fri, 3 Apr 2020 19:50:08 +0300 Subject: [PATCH] mediocre frustum culling, very simple biomes --- assets/shaders/planet.fs | 6 +- assets/shaders/planet.vs | 6 +- src/engine/camera.js | 94 +++++++++++++++++++++- src/engine/components/planet/atmosphere.js | 14 ++-- src/engine/components/planet/index.js | 65 ++++++++++++--- src/index.js | 11 +-- 6 files changed, 163 insertions(+), 33 deletions(-) diff --git a/assets/shaders/planet.fs b/assets/shaders/planet.fs index 667f425..f4a009b 100644 --- a/assets/shaders/planet.fs +++ b/assets/shaders/planet.fs @@ -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); } diff --git a/assets/shaders/planet.vs b/assets/shaders/planet.vs index 75d63a1..19613d4 100644 --- a/assets/shaders/planet.vs +++ b/assets/shaders/planet.vs @@ -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; } diff --git a/src/engine/camera.js b/src/engine/camera.js index b6648c5..698f20a 100644 --- a/src/engine/camera.js +++ b/src/engine/camera.js @@ -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 diff --git a/src/engine/components/planet/atmosphere.js b/src/engine/components/planet/atmosphere.js index 5abfe92..5b1f50b 100644 --- a/src/engine/components/planet/atmosphere.js +++ b/src/engine/components/planet/atmosphere.js @@ -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 diff --git a/src/engine/components/planet/index.js b/src/engine/components/planet/index.js index fa0cb44..2019399 100644 --- a/src/engine/components/planet/index.js +++ b/src/engine/components/planet/index.js @@ -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 } diff --git a/src/index.js b/src/index.js index 1e062a2..9fe5dd6 100644 --- a/src/index.js +++ b/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