diff --git a/assets/shaders/atmosphere.fs b/assets/shaders/atmosphere.fs new file mode 100644 index 0000000..dce5f74 --- /dev/null +++ b/assets/shaders/atmosphere.fs @@ -0,0 +1,5 @@ +precision mediump float; + +void main() { + gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0); +} diff --git a/assets/shaders/atmosphere.vs b/assets/shaders/atmosphere.vs new file mode 100644 index 0000000..5a5aea9 --- /dev/null +++ b/assets/shaders/atmosphere.vs @@ -0,0 +1,12 @@ +precision mediump float; + +attribute vec3 aVertexPosition; +attribute vec2 aTexCoords; + +uniform mat4 uModelMatrix; +uniform mat4 uViewMatrix; +uniform mat4 uProjectionMatrix; + +void main() { + gl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix * vec4(aVertexPosition,1); +} diff --git a/assets/textures/grass-plain-1024.jpg b/assets/textures/grass-plain-1024.jpg new file mode 100644 index 0000000..1ed9fc4 Binary files /dev/null and b/assets/textures/grass-plain-1024.jpg differ diff --git a/src/engine/components/planet/atmosphere.js b/src/engine/components/planet/atmosphere.js new file mode 100644 index 0000000..39cf77a --- /dev/null +++ b/src/engine/components/planet/atmosphere.js @@ -0,0 +1,29 @@ +import { MeshInstance } from '../' +import Sphere from '../../mesh/geometry/sphere' +import { vec3 } from 'gl-matrix' +import { normalv3 } from '../../utility' + +class Atmosphere extends MeshInstance { + constructor (pos, innerRadius, outerRadius, scale, color) { + super(Sphere.new(outerRadius, 64, 64), pos) + this.outerRadius = outerRadius + this.innerRadius = innerRadius + this.scale = scale + this.color = color + } + + draw (gl, shader, camera, sun, sky) { + // Set model transform matrix uniform + gl.uniformMatrix4fv(shader.getUniformLocation(gl, 'uModelMatrix'), false, this.transform) + + // Draw the mesh + gl.disable(gl.CULL_FACE) + this.mesh.prepare(gl, shader) + this.mesh.draw(gl, shader) + this.mesh.postdraw(gl, shader) + gl.enable(gl.CULL_FACE) + gl.cullFace(gl.BACK) + } +} + +export default Atmosphere diff --git a/src/engine/components/planet/index.js b/src/engine/components/planet/index.js index 8480e85..b0a9687 100644 --- a/src/engine/components/planet/index.js +++ b/src/engine/components/planet/index.js @@ -45,11 +45,10 @@ class CubeFace { if (this.generated) return let VERTICES = this.generator.resolution - let count = VERTICES * VERTICES - let vertices = new Array(count * 3) - let normals = new Array(count * 3) - let textureCoords = new Array(count * 2) - let indices = new Array(6 * (VERTICES - 1) * (VERTICES - 1)) + let vertices = [] + let normals = [] + let textureCoords = [] + let indices = [] let radius = this.generator.radius let divisionLevel = Math.pow(2, this.level) @@ -69,7 +68,7 @@ class CubeFace { // Normalize and multiply by radius to create a spherical mesh let normal = normalv3(vertex) - let pos = mulv3(normal, (radius)) + let pos = mulv3(normal, (this.generator.noise.getNoise3D(this.level + 1, normal[0], normal[1], normal[2]) + radius)) vertices[vertexPointer * 3] = pos[0] vertices[vertexPointer * 3 + 1] = pos[1] @@ -101,6 +100,41 @@ class CubeFace { } } + // Generate normals + for (let i = 0; i < indices.length; i += 3) { + // Extract face from vertices + // First vertex position + let vertexA = [ + vertices[indices[i] * 3], + vertices[indices[i] * 3 + 1], + vertices[indices[i] * 3 + 2] + ] + + // Second vertex position + let vertexB = [ + vertices[indices[i + 1] * 3], + vertices[indices[i + 1] * 3 + 1], + vertices[indices[i + 1] * 3 + 2] + ] + + // Third vertex position + let vertexC = [ + vertices[indices[i + 2] * 3], + vertices[indices[i + 2] * 3 + 1], + vertices[indices[i + 2] * 3 + 2] + ] + + // Normalize + let dir = crossv3(subv3(vertexB, vertexA), subv3(vertexC, vertexA)) + let normal = normalv3(dir) + + for (let k = 0; k < 3; k++) { + normals[indices[i + k] * 3] = normal[0] + normals[indices[i + k] * 3 + 1] = normal[1] + normals[indices[i + k] * 3 + 2] = normal[2] + } + } + this.mesh = Mesh.construct(Screen.gl, vertices, indices, textureCoords, normals) this.generated = true } @@ -162,10 +196,10 @@ class CubeFace { let divisionLevel = Math.pow(2, this.level) let splitDistance = this.generator.radius / divisionLevel - if (camToOrigin < splitDistance * 1.5 && this.children.length === 0) { + if (camToOrigin < splitDistance * 2 && this.children.length === 0) { this.subdivide() return - } else if (camToOrigin > splitDistance * 2 && this.children.length > 0) { + } else if (camToOrigin > splitDistance * 2.5 && this.children.length > 0) { this.merge() return } diff --git a/src/engine/components/terrain/heightmap.js b/src/engine/components/terrain/heightmap.js index f3eba22..ec6baed 100644 --- a/src/engine/components/terrain/heightmap.js +++ b/src/engine/components/terrain/heightmap.js @@ -62,10 +62,8 @@ class SimplexHeightMap extends HeightMap { // octaves - Number of noise layers // period - Distance above which we start to see similarities. The higher, the longer "hills" will be on a terrain. // lacunarity - Controls period change across octaves. 2 is usually a good value to address all detail levels. - constructor (offsetX, offsetY, size, seed, amplitude = 15, persistence = 0.4, octaves = 5, period = 80, lacunarity = 2) { - super(size) - this.ix = offsetX - this.iy = offsetY + constructor (seed, amplitude = 15, period = 80, persistence = 0.4, lacunarity = 2, octaves = 5) { + super(0) this.seed = seed this.osn = new OpenSimplexNoise(seed) @@ -73,32 +71,48 @@ class SimplexHeightMap extends HeightMap { this.amplitude = amplitude this.period = period this.lacunarity = lacunarity - this.octaves = octaves this.persistence = persistence + this.octaves = octaves } - getNoise (zx, zy) { - let x = ((this.size * this.ix) + zx) / this.period - let y = ((this.size * this.iy) + zy) / this.period + // Fractal/Fractional Brownian Motion (fBm) summation of 3D Perlin Simplex noise + getNoise2D (o, x, y) { + let output = 0.0 + let denom = 0.0 + let frequency = this.period + let amplitude = this.amplitude - let amp = 1.0 - let max = 1.0 - let sum = this.osn.noise2D(x, y) + for (let i = 0; i < o; i++) { + output += (amplitude * this.osn.noise3D(x * frequency, y * frequency)) + denom += amplitude - let i = 0 - while (++i < this.octaves) { - x *= this.lacunarity - y *= this.lacunarity - amp *= this.persistence - max += amp - sum += this.osn.noise2D(x, y) * amp + frequency *= this.lacunarity + amplitude *= this.persistence } - return sum / max + return (output / denom) } + getNoise3D (o, x, y, z) { + let output = 0.0 + let denom = 0.0 + let frequency = this.period + let amplitude = this.amplitude + + for (let i = 0; i < o; i++) { + output += (amplitude * this.osn.noise3D(x * frequency, y * frequency, z * frequency)) + denom += amplitude + + frequency *= this.lacunarity + amplitude *= this.persistence + } + + return (output / denom) + } + + // 2D Height Map getHeight (x, y) { - return this.getNoise(x, y) * this.amplitude + return this.getNoise2D(this.octaves, x, y) } } diff --git a/src/engine/environment.js b/src/engine/environment.js index 44061cf..02f11b3 100644 --- a/src/engine/environment.js +++ b/src/engine/environment.js @@ -42,7 +42,7 @@ class Environment { this.fogEnd = 0 this.fogColor = [0.8, 0.8, 0.8] - this.sun = new DirectionalLight([0.0, 1000.0, -2000.0], [-1.0, -1.0, 0.0], [1.0, 1.0, 1.0]) + this.sun = new Light([0.0, 1000.0, -2000.0], [1.0, 1.0, 1.0]) this.lights = [ this.sun ] this.maxEnvironmentLights = ENV_MAX_LIGHTS diff --git a/src/engine/mesh/geometry/sphere.js b/src/engine/mesh/geometry/sphere.js new file mode 100644 index 0000000..e07d9d5 --- /dev/null +++ b/src/engine/mesh/geometry/sphere.js @@ -0,0 +1,50 @@ +import { Mesh } from '..' +import Screen from '../../screen' + +class Sphere extends Mesh { + static new (radius, rings, sectors) { + const R = 1.0 / (rings - 1) + const S = 1.0 / (sectors - 1) + + const vertices = [] + const normals = [] + const textureCoords = [] + const indices = [] + + for (let r = 0, vertexPointer = 0; r < rings; r++) { + for (let s = 0; s < sectors; s++, vertexPointer++) { + const y = Math.sin(Math.PI / 2 + Math.PI * r * R) + const x = Math.cos(2 * Math.PI * s * S) * Math.sin(Math.PI * r * R) + const z = Math.sin(2 * Math.PI * s * S) * Math.sin(Math.PI * r * R) + + vertices[vertexPointer * 3] = x * radius + vertices[vertexPointer * 3 + 1] = y * radius + vertices[vertexPointer * 3 + 2] = z * radius + normals[vertexPointer * 3] = x + normals[vertexPointer * 3 + 1] = y + normals[vertexPointer * 3 + 2] = z + textureCoords[vertexPointer * 2] = s * S + textureCoords[vertexPointer * 2 + 1] = r * R + } + } + + for (let r = 0, pointer = 0; r < rings - 1; r++) { + for (let s = 0; s < sectors - 1; s++) { + const topLeft = r * sectors + s + const topRight = topLeft + 1 + const bottomLeft = (r + 1) * sectors + s + const bottomRight = bottomLeft + 1 + indices[pointer++] = bottomLeft + indices[pointer++] = topLeft + indices[pointer++] = topRight + indices[pointer++] = topRight + indices[pointer++] = bottomRight + indices[pointer++] = bottomLeft + } + } + + return Mesh.construct(Screen.gl, vertices, indices, textureCoords, normals) + } +} + +export default Sphere diff --git a/src/index.js b/src/index.js index 46e55d5..8593bf5 100644 --- a/src/index.js +++ b/src/index.js @@ -6,6 +6,7 @@ import { randomInt } from './engine/utility' import { Environment } from './engine/environment' import { LODTerrain } from './engine/components/terrain/lod' +import { MeshInstance } from './engine/components' import { Skybox } from './engine/components/skybox' import { WaterRenderer, WaterTile } from './engine/components/water' import { Particle, ParticleTexture, ParticleSystem, ParticleRenderer } from './engine/components/particles' @@ -14,6 +15,7 @@ import { Material, Texture } from './engine/mesh/material' import { GUIRenderer, GUIImage, Dim4 } from './engine/gui' import { FontRenderer, GUIText, Font } from './engine/gui/font' import { VoxelWorld, VoxelGenerator } from './engine/voxel' +import Atmosphere from './engine/components/planet/atmosphere' import { CubePlanet, PlanetGenerator } from './engine/components/planet' @@ -27,6 +29,7 @@ async function pipeline () { let entity = await loadMesh(game.gl, 'test') let terrainShader = await game.shaders.createShaderFromFiles(game.gl, 'terrain', false) let skyboxShader = await game.shaders.createShaderFromFiles(game.gl, 'skybox', false) + // let atmosShader = await game.shaders.createShaderFromFiles(game.gl, 'atmosphere', false) entity.setRotation([0.0, 0.0, -90.0]) @@ -51,21 +54,21 @@ async function pipeline () { let particleTexture = new ParticleTexture(await Texture.fromFile(game.gl, 'particleAtlas.png'), 4) let itms = [ - new GUIImage(await Texture.fromFile(game.gl, 'noisy.png', false, game.gl.LINEAR), - new Dim4(-0.9, 0.0, 0.9, 0.0), new Dim4(0.1, 0.0, 0.1, 0.0)) + // new GUIImage(await Texture.fromFile(game.gl, 'noisy.png', false, game.gl.LINEAR), + // new Dim4(-0.9, 0.0, 0.9, 0.0), new Dim4(0.1, 0.0, 0.1, 0.0)) ] // Nesting test - itms[0].addChild(new GUIText('', arialFont, 0.8, new Dim4(0.2, 0.0, 0.0, 0.0), new Dim4(1.0, 0.0, 0.3, 0.0), false)) - itms[0].children[0].color = [0.0, 0.2, 1.0] + itms[0] = new GUIText('', arialFont, 0.8, new Dim4(0.0, 0.0, 0.0, 0.0), new Dim4(0.1, 0.0, 0.1, 0.0), false) + itms[0].color = [0.0, 0.2, 1.0] // Create a height map based on OpenSimplex noise - let hmap = new SimplexHeightMap(1, 1, 256, 50) + let hmap = new SimplexHeightMap(50, 20, 128) // Create a terrain instance // let terrain = new LODTerrain([0.0, 0.0, 0.0], 1024, 1024, 850, 4) // Terrain material - let material = new Material(['grass-1024.jpg']) + let material = new Material(['grass-plain-1024.jpg']) await material.loadTextures(game.gl) // test code @@ -93,6 +96,7 @@ async function pipeline () { // Planet test let planet = new CubePlanet([0.0, 0.0, 0.0], new PlanetGenerator(16, 1000, hmap)) + // let atmosphere = new Atmosphere([0.0, 0.0, 0.0], 1000, 1025, 1, [0.0, 0.0, 1.0]) // Update function for camera and terrain let fpsTimer = 0 @@ -141,7 +145,7 @@ async function pipeline () { // Set text to FPS fpsTimer++ if (fpsTimer === 10) { - itms[0].children[0].setText(game.fps + ' fps') + itms[0].setText(game.fps + ' fps') fpsTimer = 0 } @@ -171,6 +175,10 @@ async function pipeline () { material.apply(gl, terrainShader) planet.draw(gl, terrainShader) + + // atmosShader.use(gl) + // cam.draw(gl, atmosShader) + // atmosphere.draw(gl, atmosShader, cam, env.sun, true) } // Render function for the triangle