diff --git a/assets/shaders/water.fs b/assets/shaders/water.fs index 8d41d7d..d692537 100644 --- a/assets/shaders/water.fs +++ b/assets/shaders/water.fs @@ -5,24 +5,67 @@ varying vec4 clipSpace; uniform sampler2D reflectionTexture; uniform sampler2D refractionTexture; uniform sampler2D dudvMap; +uniform sampler2D normalMap; +uniform sampler2D depthMap; + +uniform float uReflectivity; +uniform vec4 uTint; +uniform float uDUDVOffset; +uniform vec3 uLightColor; varying vec2 uv; -const float waveStrength = 0.002; +varying vec3 toCameraVector; +varying vec3 fromLightVector; + +const float waterDistortionStrength = 0.02; +const float shineDamper = 20.0; +const float reflectivity = 0.6; + +const float near = 0.1; +const float far = 10000.0; void main() { vec2 ndc = (clipSpace.xy / clipSpace.w) / 2.0 + 0.5; vec2 refractTexCoords = vec2(ndc.x, ndc.y); - vec2 reflectTexCoords = vec2(ndc.x, 1.0 - ndc.y); + vec2 reflectTexCoords = vec2(ndc.x, -ndc.y); + + float depth = 2.0 * texture2D(depthMap, refractTexCoords).r - 1.0; + float floorDistance = 2.0 * near * far / (far + near - depth * (far - near)); + depth = 2.0 * gl_FragCoord.z - 1.0; + float waterDistance = 2.0 * near * far / (far + near - depth * (far - near)); + float waterDepth = floorDistance - waterDistance; + + vec2 distortedTexCoords = texture2D(dudvMap, vec2(uv.x + uDUDVOffset, uv.y)).rg * 0.1; + distortedTexCoords = uv + vec2(distortedTexCoords.x, distortedTexCoords.y + uDUDVOffset); + + // Between -1 and 1 + vec2 totalDistortion = (texture2D(dudvMap, distortedTexCoords).rg * 2.0 - 1.0) * waterDistortionStrength; + + refractTexCoords += totalDistortion; + reflectTexCoords += totalDistortion; - vec2 distortion1 = texture2D(dudvMap, uv).xy * 2.0 - 1.0 * waveStrength; - refractTexCoords += distortion1; refractTexCoords = clamp(refractTexCoords, 0.001, 0.999); - reflectTexCoords += distortion1; reflectTexCoords.x = clamp(reflectTexCoords.x, 0.001, 0.999); reflectTexCoords.y = clamp(reflectTexCoords.y, -0.999, -0.001); + reflectTexCoords.y = 1.0 + reflectTexCoords.y; + vec4 reflectColor = texture2D(reflectionTexture, reflectTexCoords); vec4 refractColor = texture2D(refractionTexture, refractTexCoords); - gl_FragColor = mix(reflectColor, refractColor, 0.5); + vec3 viewVector = normalize(toCameraVector); + float refractiveFactor = dot(viewVector, vec3(0.0, 1.0, 0.0)); + refractiveFactor = pow(refractiveFactor, uReflectivity); + + vec4 normalMapColor = texture2D(normalMap, distortedTexCoords); + vec3 normal = vec3(normalMapColor.r * 2.0 - 1.0, normalMapColor.b, normalMapColor.g * 2.0 - 1.0); + normal = normalize(normal); + + vec3 reflectedLight = reflect(normalize(fromLightVector), normal); + float specular = max(dot(reflectedLight, viewVector), 0.0); + specular = pow(specular, shineDamper); + vec3 specularHighlights = uLightColor * specular * reflectivity; + + gl_FragColor = mix(reflectColor, refractColor, refractiveFactor); + gl_FragColor = mix(gl_FragColor, vec4(uTint.xyz, 1.0), uTint.w) + vec4(specularHighlights, 0.0); } diff --git a/assets/shaders/water.vs b/assets/shaders/water.vs index f675f8d..1dd337f 100644 --- a/assets/shaders/water.vs +++ b/assets/shaders/water.vs @@ -5,14 +5,21 @@ attribute vec2 aVertexPosition; uniform mat4 uModelMatrix; uniform mat4 uViewMatrix; uniform mat4 uProjectionMatrix; +uniform vec3 uCameraPosition; +uniform vec3 uLightPosition; varying vec4 clipSpace; varying vec2 uv; +varying vec3 toCameraVector; +varying vec3 fromLightVector; -const float tiling = 6.0; +const float tiling = 8.0; void main() { - clipSpace = uProjectionMatrix * uViewMatrix * uModelMatrix * vec4(aVertexPosition.x, 0.0, aVertexPosition.y, 1.0); + vec4 worldPosition = uModelMatrix * vec4(aVertexPosition.x, 0.0, aVertexPosition.y, 1.0); + clipSpace = uProjectionMatrix * uViewMatrix * worldPosition; gl_Position = clipSpace; - uv = vec2(aVertexPosition.x/2.0+0.5,aVertexPosition.y/2.0+0.5) * tiling; + uv = (aVertexPosition / 2.0 + 0.5) * tiling; + toCameraVector = uCameraPosition - worldPosition.xyz; + fromLightVector = worldPosition.xyz - uLightPosition; } diff --git a/assets/textures/normalmap.png b/assets/textures/normalmap.png new file mode 100644 index 0000000..787590a Binary files /dev/null and b/assets/textures/normalmap.png differ diff --git a/src/engine/components/water/index.js b/src/engine/components/water/index.js index 0c496a7..6bc090e 100644 --- a/src/engine/components/water/index.js +++ b/src/engine/components/water/index.js @@ -78,10 +78,8 @@ class WaterFBOs { let texture = gl.createTexture() gl.bindTexture(gl.TEXTURE_2D, texture) gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width, height, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null) - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, texture, 0) return texture } @@ -96,9 +94,12 @@ class WaterFBOs { } class WaterTile extends Node { - constructor (pos, scale, rot) { - super(pos, [scale, 0.0, scale], rot) + constructor (pos, scale, reflectivity = 1, tint = [0.0, 0.3, 0.5, 0.2]) { + super(pos, [scale, 0.0, scale]) this.fbos = new WaterFBOs() + this.reflectivity = reflectivity + this.tint = tint + this.phase = 1.0 } initialize (gl) { @@ -107,7 +108,11 @@ class WaterTile extends Node { } async useDUDV (gl, file) { - this.dudv = await Texture.createTexture2D(gl, await Resource.loadImage(file + '.png'), false, gl.LINEAR) + this.dudvMap = await Texture.createTexture2D(gl, await Resource.loadImage(file + '.png'), false, gl.LINEAR) + } + + async useNormalMap (gl, file) { + this.normalMap = await Texture.createTexture2D(gl, await Resource.loadImage(file + '.png'), false, gl.LINEAR) } reflect (gl, cam, render) { @@ -129,20 +134,49 @@ class WaterTile extends Node { this.fbos.unbindFrameBuffer(gl) } - draw (gl, shader) { - super.draw(gl, shader) + prepare (gl, shader, cam, sun) { + const transformLoc = shader.getUniformLocation(gl, 'uModelMatrix') + const camPosLoc = shader.getUniformLocation(gl, 'uCameraPosition') + const refrLoc = shader.getUniformLocation(gl, 'refractionTexture') + const reflLoc = shader.getUniformLocation(gl, 'reflectionTexture') + const depthLoc = shader.getUniformLocation(gl, 'depthTexture') + const dudvLoc = shader.getUniformLocation(gl, 'dudvMap') + const normalLoc = shader.getUniformLocation(gl, 'dudvMap') + const reflectivityLoc = shader.getUniformLocation(gl, 'uReflectivity') + const tintLoc = shader.getUniformLocation(gl, 'uTint') + const phaseLoc = shader.getUniformLocation(gl, 'uDUDVOffset') + const sunPosLoc = shader.getUniformLocation(gl, 'uLightPosition') + const sunColorLoc = shader.getUniformLocation(gl, 'uLightColor') - const transformLocation = shader.getUniformLocation(gl, 'uModelMatrix') - gl.uniformMatrix4fv(transformLocation, false, this.transform) + gl.uniformMatrix4fv(transformLoc, false, this.transform) + gl.uniform3fv(camPosLoc, cam.pos) + gl.uniform3fv(sunPosLoc, sun.pos) + gl.uniform3fv(sunColorLoc, sun.color) + gl.uniform1f(reflectivityLoc, this.reflectivity) + gl.uniform1f(phaseLoc, this.phase) + gl.uniform4fv(tintLoc, this.tint) + gl.uniform1i(reflLoc, 0) + gl.uniform1i(refrLoc, 1) + gl.uniform1i(dudvLoc, 2) + gl.uniform1i(normalLoc, 3) + gl.uniform1i(depthLoc, 4) gl.activeTexture(gl.TEXTURE0) gl.bindTexture(gl.TEXTURE_2D, this.fbos.reflectionTexture) gl.activeTexture(gl.TEXTURE1) gl.bindTexture(gl.TEXTURE_2D, this.fbos.refractionTexture) - if (this.dudv) { - gl.activeTexture(gl.TEXTURE2) - gl.bindTexture(gl.TEXTURE_2D, this.dudv.id) - } + gl.activeTexture(gl.TEXTURE2) + gl.bindTexture(gl.TEXTURE_2D, this.dudvMap.id) + gl.activeTexture(gl.TEXTURE3) + gl.bindTexture(gl.TEXTURE_2D, this.normalMap.id) + gl.activeTexture(gl.TEXTURE4) + gl.bindTexture(gl.TEXTURE_2D, this.fbos.refractionDepthTexture) + } + + draw (gl, shader, cam, sun) { + super.draw(gl, shader) + + this.prepare(gl, shader, cam, sun) this.mesh.prepare(gl, shader) this.mesh.draw(gl, shader) diff --git a/src/index.js b/src/index.js index 322535e..fdd8e3e 100644 --- a/src/index.js +++ b/src/index.js @@ -6,7 +6,7 @@ import loadMesh from './engine/mesh/loader' import { Environment } from './engine/environment' import { LODTerrain } from './engine/components/terrain/lod' import { Skybox } from './engine/components/skybox' -// import { WaterTile } from './engine/components/water' +import { WaterTile } from './engine/components/water' import { SimplexHeightMap } from './engine/components/terrain/heightmap' import { Material, Texture } from './engine/mesh/material' import { GUIRenderer, GUIImage, Dim4 } from './engine/gui' @@ -22,13 +22,14 @@ async function pipeline () { let shader = await game.shaders.createShaderFromFiles(game.gl, 'basic', false) let terrainShader = await game.shaders.createShaderFromFiles(game.gl, 'terrain', false) let skyboxShader = await game.shaders.createShaderFromFiles(game.gl, 'skybox', false) - // let waterShader = await game.shaders.createShaderFromFiles(game.gl, 'water', false) + let waterShader = await game.shaders.createShaderFromFiles(game.gl, 'water', false) entity.setRotation([0.0, 0.0, -90.0]) - // let water = new WaterTile([100.0, 0.0, 100.0], 100.0) - // water.initialize(game.gl) - // await water.useDUDV(game.gl, 'dudv') + let water = new WaterTile([100.0, 0.0, 100.0], 100.0) + water.initialize(game.gl) + await water.useDUDV(game.gl, 'dudv') + await water.useNormalMap(game.gl, 'normalmap') let arialFont = await Font.fromFile('arial') await arialFont.loadTextures(game.gl) @@ -75,8 +76,8 @@ async function pipeline () { // Load textures and generate a mesh await skybox.initialize(game.gl) - // itms.push(new GUIImage(water.fbos.reflectionTexture, new Dim4(-0.9, 0.0, 0.9, 0.0), new Dim4(0.1, 0.0, 0.1, 0.0))) - // itms.push(new GUIImage(water.fbos.refractionTexture, new Dim4(-0.3, 0.0, 0.9, 0.0), new Dim4(0.1, 0.0, 0.1, 0.0))) + itms.push(new GUIImage(water.fbos.reflectionTexture, new Dim4(-0.9, 0.0, 0.9, 0.0), new Dim4(0.1, 0.0, 0.1, 0.0))) + itms.push(new GUIImage(water.fbos.refractionTexture, new Dim4(-0.3, 0.0, 0.9, 0.0), new Dim4(0.1, 0.0, 0.1, 0.0))) // Update function for camera and terrain game.addUpdateFunction(function (dt) { @@ -105,10 +106,6 @@ async function pipeline () { function drawEverything (gl) { game.prepare() - shader.use(gl) - cam.draw(gl, shader) - entity.draw(gl, shader) - // Use terrain shader terrainShader.use(gl) @@ -121,6 +118,10 @@ async function pipeline () { // Draw terrain terrain.draw(gl, terrainShader) + shader.use(gl) + cam.draw(gl, shader) + entity.draw(gl, shader) + // Draw the skybox skyboxShader.use(gl) skybox.draw(gl, skyboxShader, cam) @@ -128,14 +129,14 @@ async function pipeline () { // Render function for the triangle game.addRenderFunction(function (gl) { - // water.reflect(gl, cam, drawEverything) - // water.refract(gl, cam, drawEverything) + water.reflect(gl, cam, drawEverything) + water.refract(gl, cam, drawEverything) drawEverything(gl) - // waterShader.use(gl) - // cam.draw(gl, waterShader) - // water.draw(gl, waterShader) + waterShader.use(gl) + cam.draw(gl, waterShader) + water.draw(gl, waterShader, cam, env.sun) // Draw GUIs gui.draw(gl, cam, itms)