Simple GUI, Simple Skybox, fixed sequential model rendering warnings

This commit is contained in:
Evert Prants 2019-12-28 22:51:31 +02:00
parent a7d760858c
commit 4ffd8e333f
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
21 changed files with 343 additions and 24 deletions

9
assets/shaders/skybox.fs Normal file
View File

@ -0,0 +1,9 @@
precision mediump float;
varying vec3 uv;
uniform samplerCube cubeMap;
void main() {
gl_FragColor = textureCube(cubeMap, uv);
}

13
assets/shaders/skybox.vs Normal file
View File

@ -0,0 +1,13 @@
precision mediump float;
attribute vec3 aVertexPosition;
uniform mat4 uViewMatrix;
uniform mat4 uProjectionMatrix;
varying vec3 uv;
void main() {
gl_Position = uProjectionMatrix * uViewMatrix * vec4(aVertexPosition, 1.0);
uv = aVertexPosition;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 991 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 938 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1003 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 KiB

View File

@ -23,6 +23,9 @@ class Camera extends Node {
this.right = vec3.create() this.right = vec3.create()
this.worldUp = vec3.fromValues(0.0, 1.0, 0.0) this.worldUp = vec3.fromValues(0.0, 1.0, 0.0)
this.nearPlane = ZNEAR
this.farPlane = ZFAR
this.updateTransform() this.updateTransform()
} }
@ -99,7 +102,7 @@ class Camera extends Node {
updateProjection (gl) { updateProjection (gl) {
let aspect = gl.canvas.width / gl.canvas.height let aspect = gl.canvas.width / gl.canvas.height
mat4.perspective(this.projection, this.fov, aspect, ZNEAR, ZFAR) mat4.perspective(this.projection, this.fov, aspect, this.nearPlane, this.farPlane)
} }
// Calculate the view matrix on-the-go // Calculate the view matrix on-the-go

View File

@ -224,6 +224,7 @@ class MeshInstance extends Node {
// Draw the mesh // Draw the mesh
this.mesh.prepare(gl, shader) this.mesh.prepare(gl, shader)
this.mesh.draw(gl, shader) this.mesh.draw(gl, shader)
this.mesh.postdraw(gl, shader)
// Draw children // Draw children
super.draw(gl, shader) super.draw(gl, shader)

View File

@ -0,0 +1,106 @@
import { Mesh } from '../../mesh'
import { Texture } from '../../mesh/material'
import { mat4 } from 'gl-matrix'
import Resource from '../../resource'
const FNAMES = ['right', 'left', 'top', 'bottom', 'back', 'front']
const SIZE = 500
class Skybox {
constructor (name, size = SIZE) {
this.name = name
this.size = size
}
async initialize (gl) {
await this.createTexture(gl)
this.createMesh(gl)
}
async createTexture (gl) {
let ready = []
for (let i in FNAMES) {
let real = this.name + '/' + FNAMES[i] + '.png'
let loaded = await Resource.loadImage(real)
ready[i] = loaded
}
let imgCube = Texture.createTextureCubeMap(gl, ready)
this.cubemap = imgCube
}
createMesh (gl) {
const vertices = [
-this.size, this.size, -this.size,
-this.size, -this.size, -this.size,
this.size, -this.size, -this.size,
this.size, -this.size, -this.size,
this.size, this.size, -this.size,
-this.size, this.size, -this.size,
-this.size, -this.size, this.size,
-this.size, -this.size, -this.size,
-this.size, this.size, -this.size,
-this.size, this.size, -this.size,
-this.size, this.size, this.size,
-this.size, -this.size, this.size,
this.size, -this.size, -this.size,
this.size, -this.size, this.size,
this.size, this.size, this.size,
this.size, this.size, this.size,
this.size, this.size, -this.size,
this.size, -this.size, -this.size,
-this.size, -this.size, this.size,
-this.size, this.size, this.size,
this.size, this.size, this.size,
this.size, this.size, this.size,
this.size, -this.size, this.size,
-this.size, -this.size, this.size,
-this.size, this.size, -this.size,
this.size, this.size, -this.size,
this.size, this.size, this.size,
this.size, this.size, this.size,
-this.size, this.size, this.size,
-this.size, this.size, -this.size,
-this.size, -this.size, -this.size,
-this.size, -this.size, this.size,
this.size, -this.size, -this.size,
this.size, -this.size, -this.size,
-this.size, -this.size, this.size,
this.size, -this.size, this.size
]
this.mesh = Mesh.constructFromVertices(gl, vertices)
}
updateCamera (gl, shader, camera) {
const projloc = shader.getUniformLocation(gl, 'uProjectionMatrix')
const viewloc = shader.getUniformLocation(gl, 'uViewMatrix')
// Set translation to zero to prevent the skybox from moving in relation to the camera
let view = mat4.clone(camera.view)
view[12] = 0
view[13] = 0
view[14] = 0
gl.uniformMatrix4fv(projloc, false, camera.projection)
gl.uniformMatrix4fv(viewloc, false, view)
}
draw (gl, shader, camera) {
camera && this.updateCamera(gl, shader, camera)
gl.activeTexture(gl.TEXTURE0)
gl.bindTexture(this.cubemap.type, this.cubemap.id)
this.mesh.prepare(gl, shader)
this.mesh.draw(gl, shader)
this.mesh.postdraw(gl, shader)
}
}
export { Skybox }

View File

@ -59,6 +59,7 @@ class Terrain extends Node {
} }
draw (gl, shader) { draw (gl, shader) {
super.draw(gl, shader)
if (!this.mesh) return if (!this.mesh) return
// Set model transform matrix uniform // Set model transform matrix uniform
const transformLocation = shader.getUniformLocation(gl, 'uModelMatrix') const transformLocation = shader.getUniformLocation(gl, 'uModelMatrix')
@ -66,8 +67,7 @@ class Terrain extends Node {
this.mesh.prepare(gl, shader) this.mesh.prepare(gl, shader)
this.mesh.draw(gl, shader) this.mesh.draw(gl, shader)
this.mesh.postdraw(gl, shader)
super.draw(gl, shader)
} }
} }

View File

@ -105,6 +105,7 @@ class TerrainNode extends Node {
this.mesh.prepare(gl, shader) this.mesh.prepare(gl, shader)
this.mesh.draw(gl, shader) this.mesh.draw(gl, shader)
this.mesh.postdraw(gl, shader)
} }
get generated () { get generated () {

View File

@ -1,4 +1,25 @@
import { clamp } from '../utility' import { Mesh } from '../mesh'
import { mat4 } from 'gl-matrix'
const VERTEX_SHADER = `
precision mediump float;
attribute vec2 aVertexPosition;
varying vec2 uv;
uniform mat4 uTransformationMatrix;
void main() {
gl_Position = uTransformationMatrix * vec4(aVertexPosition, 0.0, 1.0);
uv = vec2((aVertexPosition.x+1.0)/2.0, 1.0 - (aVertexPosition.y+1.0)/2.0);
}
`
const FRAGMENT_SHADER = `
precision mediump float;
varying vec2 uv;
uniform sampler2D texture0;
void main() {
gl_FragColor = texture2D(texture0, uv);
}
`
class Dim4 { class Dim4 {
constructor (scX, ofX, scY, ofY) { constructor (scX, ofX, scY, ofY) {
@ -67,10 +88,34 @@ class Node2D {
this.parent = null this.parent = null
this.children = [] this.children = []
this.transform = mat4.create()
this.updateTransform()
} }
updateTransform () { updateTransform () {
let matrix = mat4.create()
mat4.translate(matrix, matrix, [this.pos[0], this.pos[2], 0.0])
mat4.scale(matrix, matrix, [this.size[0], this.size[2], 1.0])
if (this.rotation !== 0.0) {
mat4.rotate(matrix, matrix, this.rotation * Math.PI / 180, [0.0, 0.0, 1.0])
}
// Add parent's transform to this
if (this.parent) {
mat4.mul(matrix, this.parent.transform, matrix)
}
// Set the matrix
this.transform = matrix
// Update children's transforms
for (let i in this.children) {
let child = this.children[i]
if (!(child instanceof Node2D)) continue
child.updateTransform()
}
} }
// Getters // Getters
@ -83,12 +128,12 @@ class Node2D {
} }
// Draw base // Draw base
draw (gl) { draw (gl, shader, quad) {
// Nothing to draw here, so just draw children // Nothing to draw here, so just draw children
for (let i in this.children) { for (let i in this.children) {
let child = this.children[i] let child = this.children[i]
if (!(child instanceof Node2D)) continue if (!(child instanceof Node2D)) continue
child.draw(gl) child.draw(gl, shader, quad)
} }
} }
@ -118,3 +163,55 @@ class Node2D {
this.parent = p this.parent = p
} }
} }
class GUIImage extends Node2D {
constructor (texture, pos, size, rotation) {
super(pos, size, rotation)
this.texture = texture
this.active = true
}
draw (gl, shader, quad) {
super.draw(gl, shader, quad)
if (!this.active) return
gl.activeTexture(gl.TEXTURE0)
gl.bindTexture(this.texture.type, this.texture.id)
// Set transformation matrix
const transformLocation = shader.getUniformLocation(gl, 'uTransformationMatrix')
gl.uniformMatrix4fv(transformLocation, false, this.transform)
// Draw the quad
quad.draw(gl, shader, gl.TRIANGLE_STRIP)
}
}
class GUIRenderer {
async initialize (game) {
this.shader = await game.shaders.createShader(game.gl, 'gui', VERTEX_SHADER, FRAGMENT_SHADER)
this.createQuad(game.gl)
}
// Reusable quad mesh for rendering GUIs
createQuad (gl) {
if (this.quad) return this.quad
this.quad = Mesh.constructFromVertices(gl, [-1, 1, -1, -1, 1, 1, 1, -1], 2)
return this.quad
}
draw (gl, nodes) {
if (typeof nodes !== 'object') nodes = [ nodes ]
this.shader.use(gl)
this.quad.prepare(gl, this.shader)
gl.enable(gl.BLEND)
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
gl.disable(gl.DEPTH_TEST)
for (let i in nodes) {
nodes[i].draw(gl, this.shader, this.quad)
}
gl.enable(gl.DEPTH_TEST)
gl.disable(gl.BLEND)
}
}
export { Dim4, GUIRenderer, GUIImage }

View File

@ -37,7 +37,7 @@ class Engine {
render () { render () {
// Set clear color to black, fully opaque // Set clear color to black, fully opaque
gl.clearColor(0.0, 0.7, 1.0, 1.0) gl.clearColor(0.0, 0.0, 0.0, 1.0)
// Clear the color buffer with specified clear color // Clear the color buffer with specified clear color
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)

View File

@ -38,21 +38,38 @@ class Mesh {
return mesh return mesh
} }
static constructFromVertices (gl, vertices, dimensions = 3) {
let pos = Mesh.loadToBuffer(gl, gl.ARRAY_BUFFER, new Float32Array(vertices))
let mesh = new Mesh()
mesh.posBuffer = pos
mesh.vertices = vertices
mesh.vertexCount = vertices.length / dimensions
mesh.vertexLayout = dimensions
gl.bindBuffer(gl.ARRAY_BUFFER, null)
return mesh
}
bindBuffers (gl, shader) { bindBuffers (gl, shader) {
this._bufferCount = 1
let d = this.vertexLayout || 3
gl.bindBuffer(gl.ARRAY_BUFFER, this.posBuffer) gl.bindBuffer(gl.ARRAY_BUFFER, this.posBuffer)
shader.setAttribute(gl, 'aVertexPosition', 3, false, 3 * Float32Array.BYTES_PER_ELEMENT, 0) shader.setAttribute(gl, 'aVertexPosition', d, false, d * Float32Array.BYTES_PER_ELEMENT, 0)
if (this.nms && shader.hasAttribute(gl, 'aNormal')) { if (this.nms && shader.hasAttribute(gl, 'aNormal')) {
gl.bindBuffer(gl.ARRAY_BUFFER, this.nms) gl.bindBuffer(gl.ARRAY_BUFFER, this.nms)
shader.setAttribute(gl, 'aNormal', 3, false, 3 * Float32Array.BYTES_PER_ELEMENT, 0) shader.setAttribute(gl, 'aNormal', 3, false, 3 * Float32Array.BYTES_PER_ELEMENT, 0)
this._bufferCount++
} }
if (this.uvs && shader.hasAttribute(gl, 'aTexCoords')) { if (this.uvs && shader.hasAttribute(gl, 'aTexCoords')) {
gl.bindBuffer(gl.ARRAY_BUFFER, this.uvs) gl.bindBuffer(gl.ARRAY_BUFFER, this.uvs)
shader.setAttribute(gl, 'aTexCoords', 2, false, 2 * Float32Array.BYTES_PER_ELEMENT, 0) shader.setAttribute(gl, 'aTexCoords', 2, false, 2 * Float32Array.BYTES_PER_ELEMENT, 0)
this._bufferCount++
} }
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.ebo) this.ebo && gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.ebo)
} }
prepare (gl, shader) { prepare (gl, shader) {
@ -69,7 +86,14 @@ class Mesh {
if (this.indices) { if (this.indices) {
gl.drawElements(mode, this.indices.length, gl.UNSIGNED_SHORT, 0) gl.drawElements(mode, this.indices.length, gl.UNSIGNED_SHORT, 0)
} else { } else {
gl.drawArrays(mode, 0, this.vertices.length) gl.drawArrays(mode, 0, this.vertexCount || this.vertices.length / (this.vertexLayout || 3))
}
}
// Unbind all attrib arrays after drawing something
postdraw (gl, shader) {
for (let i = 0; i < this._bufferCount; i++) {
gl.disableVertexAttribArray(i)
} }
} }

View File

@ -1,13 +1,22 @@
import Resource from '../resource' import Resource from '../resource'
class Texture { class Texture {
static createTexture2D (gl, img) { static createTexture2D (gl, img, flip = true, filtering, repeat = gl.REPEAT) {
let tex = gl.createTexture() let tex = gl.createTexture()
gl.bindTexture(gl.TEXTURE_2D, tex) gl.bindTexture(gl.TEXTURE_2D, tex)
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true) gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flip)
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img) gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img)
gl.generateMipmap(gl.TEXTURE_2D) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, repeat)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, repeat)
if (filtering) {
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filtering)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filtering)
} else {
gl.generateMipmap(gl.TEXTURE_2D)
}
gl.bindTexture(gl.TEXTURE_2D, null) gl.bindTexture(gl.TEXTURE_2D, null)
let oTex = new Texture() let oTex = new Texture()
@ -16,9 +25,39 @@ class Texture {
return oTex return oTex
} }
static createTextureCubeMap (gl, img) {
if (!img || img.length !== 6) throw new Error('createTextureCubeMap() requires six images!')
let tex = gl.createTexture()
gl.bindTexture(gl.TEXTURE_CUBE_MAP, tex)
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false)
gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img[0])
gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_X, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img[1])
gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_Y, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img[2])
gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img[3])
gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_Z, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img[4])
gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img[5])
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
gl.bindTexture(gl.TEXTURE_CUBE_MAP, null)
let oTex = new Texture()
oTex.type = gl.TEXTURE_CUBE_MAP
oTex.id = tex
return oTex
}
} }
class Material { class Material {
constructor (textures) {
this.textures = textures
}
async loadTextures (gl) { async loadTextures (gl) {
if (this.textures) { if (this.textures) {
for (let ti in this.textures) { for (let ti in this.textures) {

View File

@ -4,7 +4,7 @@ class Screen {
constructor () { constructor () {
this._el = document.createElement('canvas') this._el = document.createElement('canvas')
this.resize() this.resize()
this._gl = this._el.getContext('webgl') this._gl = this._el.getContext('webgl', { alpha: false })
if (!this._gl) { if (!this._gl) {
alert('Your machine or browser does not support WebGL!') alert('Your machine or browser does not support WebGL!')

View File

@ -1,34 +1,50 @@
import Engine from './engine' import Engine from './engine'
import Camera from './engine/camera' import Camera from './engine/camera'
import Resource from './engine/resource'
import loadMesh from './engine/mesh/loader' import loadMesh from './engine/mesh/loader'
import { Environment } from './engine/environment' import { Environment } from './engine/environment'
import { LODTerrain } from './engine/components/terrain/lod' import { LODTerrain } from './engine/components/terrain/lod'
import { Skybox } from './engine/components/skybox'
import { SimplexHeightMap } from './engine/components/terrain/heightmap' import { SimplexHeightMap } from './engine/components/terrain/heightmap'
import { Material } from './engine/mesh/material' import { Material, Texture } from './engine/mesh/material'
import { GUIRenderer, GUIImage, Dim4 } from './engine/gui'
let game = new Engine() let game = new Engine()
let env = new Environment() let env = new Environment()
let gui = new GUIRenderer()
// let t = 0
async function pipeline () { async function pipeline () {
let entity = await loadMesh(game.gl, 'test') let entity = await loadMesh(game.gl, 'test')
let shader = await game.shaders.createShaderFromFiles(game.gl, 'basic', false) let shader = await game.shaders.createShaderFromFiles(game.gl, 'basic', false)
let terrainShader = await game.shaders.createShaderFromFiles(game.gl, 'terrain', false) let terrainShader = await game.shaders.createShaderFromFiles(game.gl, 'terrain', false)
let skyboxShader = await game.shaders.createShaderFromFiles(game.gl, 'skybox', false)
entity.setRotation([0.0, 0.0, -90.0]) entity.setRotation([0.0, 0.0, -90.0])
// Initialize GUI
await gui.initialize(game)
let itms = [
new GUIImage(await Texture.createTexture2D(game.gl, await Resource.loadImage('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))
]
// Create a height map based on OpenSimplex noise // Create a height map based on OpenSimplex noise
let hmap = new SimplexHeightMap(1, 1, 256, 50) let hmap = new SimplexHeightMap(1, 1, 256, 50)
// Create a terrain // Create a terrain instance
let terrain = new LODTerrain([0.0, 0.0, 0.0], 1024, 1024, 850, 4) let terrain = new LODTerrain([0.0, 0.0, 0.0], 1024, 1024, 850, 4)
// Terrain material // Terrain material
let material = new Material() let material = new Material(['grass-1024.jpg'])
material.textures = ['grass-1024.jpg']
await material.loadTextures(game.gl) await material.loadTextures(game.gl)
// test code
for (let i in entity.children) {
entity.children[i].mesh.material = material
}
// Set generator and material for terrain
terrain.setGenerator(hmap) terrain.setGenerator(hmap)
terrain.setMaterial(material) terrain.setMaterial(material)
@ -36,7 +52,13 @@ async function pipeline () {
let cam = new Camera([-200.0, 1.0, 0.0]) let cam = new Camera([-200.0, 1.0, 0.0])
cam.updateProjection(game.gl) cam.updateProjection(game.gl)
// Update function for camera // Create skybox
let skybox = new Skybox('skybox', cam.farPlane / 2)
// Load textures and generate a mesh
await skybox.initialize(game.gl)
// Update function for camera and terrain
game.addUpdateFunction(function (dt) { game.addUpdateFunction(function (dt) {
if (game.input.isDown('w')) { if (game.input.isDown('w')) {
cam.processKeyboard(0, dt) cam.processKeyboard(0, dt)
@ -50,6 +72,7 @@ async function pipeline () {
cam.processKeyboard(3, dt) cam.processKeyboard(3, dt)
} }
// Panning
if (game.input.mouseMoved && game.input.mouse.btn0) { if (game.input.mouseMoved && game.input.mouse.btn0) {
cam.processMouseMove(game.input.mouseOffset) cam.processMouseMove(game.input.mouseOffset)
} }
@ -57,10 +80,6 @@ async function pipeline () {
// Update detail levels // Update detail levels
terrain.update(game.gl, cam) terrain.update(game.gl, cam)
terrain.updateLODMesh(game.gl) terrain.updateLODMesh(game.gl)
// TESTING: Move model forward
// t = t + 0.1
// entity.setPosition([t, 0.0, 0.0])
}) })
// Render function for the triangle // Render function for the triangle
@ -80,6 +99,13 @@ async function pipeline () {
// Draw terrain // Draw terrain
terrain.draw(gl, terrainShader) terrain.draw(gl, terrainShader)
// Draw the skybox
skyboxShader.use(gl)
skybox.draw(gl, skyboxShader, cam)
})
game.addRenderFunction(function (gl) {
gui.draw(gl, itms)
}) })
game.startGameLoop() game.startGameLoop()