├── .DS_Store ├── .gitignore ├── GPU Milestone 1.pdf ├── README.md ├── img ├── .DS_Store ├── cloudtop_bk.jpg ├── cloudtop_bk.tga ├── cloudtop_dn.jpg ├── cloudtop_dn.tga ├── cloudtop_ft.jpg ├── cloudtop_ft.tga ├── cloudtop_lf.jpg ├── cloudtop_lf.tga ├── cloudtop_rt.jpg ├── cloudtop_rt.tga ├── cloudtop_up.jpg ├── cloudtop_up.tga ├── stormydays_bk.jpg ├── stormydays_dn.jpg ├── stormydays_ft.jpg ├── stormydays_lf.jpg ├── stormydays_rt.jpg └── stormydays_up.jpg ├── index.html ├── models └── .DS_Store ├── package.json ├── screenshots ├── Capture.PNG ├── Capture1024.PNG ├── Capture128.PNG ├── Capture2.PNG ├── Capture256.PNG ├── Capture3.PNG ├── Capture4.PNG ├── Capture5.PNG ├── Capture512.PNG ├── DebugView.mp4 ├── GPUFinalBlooper.mp4 ├── OceanView2.mp4 ├── OceanWaves.mp4 ├── chart1.png ├── chart2.png ├── debug1.PNG ├── largewaves.png ├── milestone2.png ├── screenshot1.png ├── screenshot2.png ├── screenshot3.png ├── screenshot4.png ├── screenshot5.png ├── screenshot6.png ├── screenshot7.png └── wireframe1.png ├── src ├── .DS_Store ├── init.js ├── main.js ├── renderers │ ├── renderer.js │ └── textureBuffer.js ├── scene.js ├── shaders │ ├── ocean.frag.glsl.js │ ├── ocean.vert.glsl │ ├── quad.vert.glsl │ ├── skybox.frag.glsl.js │ ├── skybox.vert.glsl │ ├── terrain.frag.glsl.js │ └── terrain.vert.glsl └── utils.js └── webpack.config.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /GPU Milestone 1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/GPU Milestone 1.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Realtime-Ocean-Rendering-WebGL 2 | 3 |  4 | 5 | Members: 6 | * [Ricky Rajani](https://github.com/rickyrajani) 7 | * [Wenli Zhao](https://github.com/wpchop) 8 | 9 | [Live Demo](http://rickyrajani.com/Realtime-Ocean-Rendering-WebGL/) 10 | 11 | ### Overview 12 | In this project, we implemented realistic real-time ocean wave rendering in WebGL 2.0, referencing [Realistic Real-time Rendering of Ocean Waves](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/12/rtwave.pdf) and Simulating Ocean Water (Tessendorf 2001). 13 | 14 | We implemented realistic waves by generating a heightfield using Fast Fourier Transformations and a realistic lighting model which includes reflection, refraction, and alpha blending. In order to have an expansive ocean render in real-time, we 15 | implemented view-dependent geometry that has lower detail as we move away from the center of the ocean. 16 | 17 | We also implemented procedural terrain using perlin noise in order to provide a sense of perspective to the ocean scene. 18 | 19 | ### Key Features 20 | 21 | * FFT Height Map 22 | * View Dependent Wave Geometry 23 | * Lighting 24 | * Procedural Terrain 25 | 26 | ### Milestone 1 27 |  28 |  29 |  30 | 31 | ### Milestone 2 32 |  33 |  34 | 35 | ### Milestone 3 36 |  37 |  38 |  39 | 40 | ### Milestone 4 41 |  42 |  43 |  44 | 45 | ### Performance Analysis 46 |  47 | 48 | Our first major optimization was view-dependent geometry. In our scene, we construct the ocean plane as a square mesh. Our view-dependent geometry breaks this mesh down into nine square patches. The center patch of the ocean has a higher resolution and the surrounding patches are rendered at a lower resolution. In order to determine the performance gain from view-dependent geometry, we rendered the ocean at a high resolution for the entirety of the plane and compared it to a view-dependent construction. There was a significant performance gain as you can see in the graph above. As the resolution increased, the performance gain decreases. 49 | 50 |  51 | 52 | Our next attempted optimization was moving the calculation of the Phillips spectrum from the GPU to a preprocessing step on the CPU. The calculation of the Phillips spectrum included sampling random Gaussian distributions for two numbers and a number of exponential calculations that we thought would be costly on the GPU. Since the values only needed to be computed once for each point in the mesh, we thought that it would be faster as a preprocessing step. As you can see in the graph above, it was not a significant performance improvement. In some cases, the preprocessing took longer. This led us to look into our construction of the buffers. 53 | 54 | We soon realized that we were creating some of our buffers and filling them for every frame, which was terrible for performance. After fixing up the creation of our buffers, we had a huge performance gain. For all resolutions up to and including 2048, we had a frame rate of greater than 60 fps. 55 | 56 | Tested on: Windows 10 Pro, Intel Xeon CPU CPU E5-1630 v4 @ 3.70GHz 32GB, NVIDIA GeForce GTX 24465MB 57 | 58 | ### Credits 59 | 60 | * [Skybox Images](http://www.custommapmakers.org/skyboxes.php) 61 | * [Skybox tutorial](http://math.hws.edu/eck/cs424/notes2013/webgl/skybox-and-reflection/skybox.html) 62 | -------------------------------------------------------------------------------- /img/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/img/.DS_Store -------------------------------------------------------------------------------- /img/cloudtop_bk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/img/cloudtop_bk.jpg -------------------------------------------------------------------------------- /img/cloudtop_bk.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/img/cloudtop_bk.tga -------------------------------------------------------------------------------- /img/cloudtop_dn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/img/cloudtop_dn.jpg -------------------------------------------------------------------------------- /img/cloudtop_dn.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/img/cloudtop_dn.tga -------------------------------------------------------------------------------- /img/cloudtop_ft.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/img/cloudtop_ft.jpg -------------------------------------------------------------------------------- /img/cloudtop_ft.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/img/cloudtop_ft.tga -------------------------------------------------------------------------------- /img/cloudtop_lf.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/img/cloudtop_lf.jpg -------------------------------------------------------------------------------- /img/cloudtop_lf.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/img/cloudtop_lf.tga -------------------------------------------------------------------------------- /img/cloudtop_rt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/img/cloudtop_rt.jpg -------------------------------------------------------------------------------- /img/cloudtop_rt.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/img/cloudtop_rt.tga -------------------------------------------------------------------------------- /img/cloudtop_up.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/img/cloudtop_up.jpg -------------------------------------------------------------------------------- /img/cloudtop_up.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/img/cloudtop_up.tga -------------------------------------------------------------------------------- /img/stormydays_bk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/img/stormydays_bk.jpg -------------------------------------------------------------------------------- /img/stormydays_dn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/img/stormydays_dn.jpg -------------------------------------------------------------------------------- /img/stormydays_ft.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/img/stormydays_ft.jpg -------------------------------------------------------------------------------- /img/stormydays_lf.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/img/stormydays_lf.jpg -------------------------------------------------------------------------------- /img/stormydays_rt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/img/stormydays_rt.jpg -------------------------------------------------------------------------------- /img/stormydays_up.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/img/stormydays_up.jpg -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /models/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/models/.DS_Store -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "webpack-dev-server", 4 | "start:production": "webpack-dev-server --env.production", 5 | "build": "webpack --env.production" 6 | }, 7 | "dependencies": { 8 | "dat-gui": "^0.5.0", 9 | "gl-matrix": "^2.4.0", 10 | "spectorjs": "^0.9.0", 11 | "stats-js": "^1.0.0-alpha1", 12 | "three": "^0.87.1", 13 | "three-js": "^79.0.0", 14 | "three-orbitcontrols": "^1.2.1", 15 | "webgl-debug": "^1.0.2" 16 | }, 17 | "devDependencies": { 18 | "babel-core": "^6.26.0", 19 | "babel-loader": "^7.1.2", 20 | "babel-minify-webpack-plugin": "^0.2.0", 21 | "babel-preset-env": "^1.6.0", 22 | "webpack": "^3.7.1", 23 | "webpack-dev-server": "^2.9.2", 24 | "webpack-glsl-loader": "^1.0.1" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /screenshots/Capture.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/screenshots/Capture.PNG -------------------------------------------------------------------------------- /screenshots/Capture1024.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/screenshots/Capture1024.PNG -------------------------------------------------------------------------------- /screenshots/Capture128.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/screenshots/Capture128.PNG -------------------------------------------------------------------------------- /screenshots/Capture2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/screenshots/Capture2.PNG -------------------------------------------------------------------------------- /screenshots/Capture256.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/screenshots/Capture256.PNG -------------------------------------------------------------------------------- /screenshots/Capture3.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/screenshots/Capture3.PNG -------------------------------------------------------------------------------- /screenshots/Capture4.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/screenshots/Capture4.PNG -------------------------------------------------------------------------------- /screenshots/Capture5.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/screenshots/Capture5.PNG -------------------------------------------------------------------------------- /screenshots/Capture512.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/screenshots/Capture512.PNG -------------------------------------------------------------------------------- /screenshots/DebugView.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/screenshots/DebugView.mp4 -------------------------------------------------------------------------------- /screenshots/GPUFinalBlooper.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/screenshots/GPUFinalBlooper.mp4 -------------------------------------------------------------------------------- /screenshots/OceanView2.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/screenshots/OceanView2.mp4 -------------------------------------------------------------------------------- /screenshots/OceanWaves.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/screenshots/OceanWaves.mp4 -------------------------------------------------------------------------------- /screenshots/chart1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/screenshots/chart1.png -------------------------------------------------------------------------------- /screenshots/chart2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/screenshots/chart2.png -------------------------------------------------------------------------------- /screenshots/debug1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/screenshots/debug1.PNG -------------------------------------------------------------------------------- /screenshots/largewaves.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/screenshots/largewaves.png -------------------------------------------------------------------------------- /screenshots/milestone2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/screenshots/milestone2.png -------------------------------------------------------------------------------- /screenshots/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/screenshots/screenshot1.png -------------------------------------------------------------------------------- /screenshots/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/screenshots/screenshot2.png -------------------------------------------------------------------------------- /screenshots/screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/screenshots/screenshot3.png -------------------------------------------------------------------------------- /screenshots/screenshot4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/screenshots/screenshot4.png -------------------------------------------------------------------------------- /screenshots/screenshot5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/screenshots/screenshot5.png -------------------------------------------------------------------------------- /screenshots/screenshot6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/screenshots/screenshot6.png -------------------------------------------------------------------------------- /screenshots/screenshot7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/screenshots/screenshot7.png -------------------------------------------------------------------------------- /screenshots/wireframe1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/screenshots/wireframe1.png -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickyrajani/Realtime-Ocean-Rendering-WebGL/0f51bbdc8b1164d92e3ee8296d2d09deeecec4ce/src/.DS_Store -------------------------------------------------------------------------------- /src/init.js: -------------------------------------------------------------------------------- 1 | import DAT from 'dat-gui'; 2 | import WebGLDebug from 'webgl-debug'; 3 | import Stats from 'stats-js'; 4 | import { PerspectiveCamera } from 'three'; 5 | import OrbitControls from 'three-orbitcontrols'; 6 | import { Spector } from 'spectorjs'; 7 | 8 | export var ABORTED = false; 9 | export function abort(message) { 10 | ABORTED = true; 11 | throw message; 12 | } 13 | 14 | // Get the canvas element 15 | export const canvas = document.getElementById('canvas'); 16 | 17 | // Initialize the WebGL context 18 | const glContext = canvas.getContext("webgl2", { 19 | premultipliedAlpha: false // Ask for non-premultiplied alpha 20 | }); 21 | export const gl = glContext; 22 | 23 | gl.enable(gl.CULL_FACE); 24 | 25 | export const gui = new DAT.GUI(); 26 | 27 | // initialize statistics widget 28 | const stats = new Stats(); 29 | stats.setMode(1); // 0: fps, 1: ms 30 | stats.domElement.style.position = 'absolute'; 31 | stats.domElement.style.left = '0px'; 32 | stats.domElement.style.top = '0px'; 33 | document.body.appendChild(stats.domElement); 34 | 35 | // Initialize camera 36 | export const camera = new PerspectiveCamera(75, canvas.clientWidth / canvas.clientHeight, 0.1, 5000); 37 | 38 | // Initialize camera controls 39 | export const cameraControls = new OrbitControls(camera, canvas); 40 | cameraControls.enableDamping = true; 41 | cameraControls.enableZoom = true; 42 | cameraControls.rotateSpeed = 0.3; 43 | cameraControls.zoomSpeed = 1.0; 44 | cameraControls.panSpeed = 2.0; 45 | 46 | function setSize(width, height) { 47 | canvas.width = width; 48 | canvas.height = height; 49 | camera.aspect = width / height; 50 | camera.updateProjectionMatrix(); 51 | } 52 | 53 | setSize(canvas.clientWidth, canvas.clientHeight); 54 | window.addEventListener('resize', () => setSize(canvas.clientWidth, canvas.clientHeight)); 55 | 56 | // Creates a render loop that is wrapped with camera update and stats logging 57 | export function makeRenderLoop(render) { 58 | return function tick() { 59 | cameraControls.update(); 60 | stats.begin(); 61 | render(); 62 | stats.end(); 63 | if (!ABORTED) { 64 | requestAnimationFrame(tick) 65 | } 66 | } 67 | } 68 | 69 | // import the main application 70 | require('./main'); 71 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import { makeRenderLoop, camera, cameraControls, gui, gl } from './init'; 2 | import Renderer from './renderers/renderer'; 3 | import Scene from './scene'; 4 | import { Vector3 } from 'three'; 5 | 6 | const scene = new Scene(); 7 | 8 | camera.position.set(3, 61, 21); 9 | cameraControls.target.set(0, 60, 21); 10 | // camera.position.set(0,0,0); 11 | // cameraControls.target.set(0,0,-1); 12 | gl.enable(gl.DEPTH_TEST); 13 | gl.enable( gl.BLEND ); 14 | gl.blendEquation( gl.FUNC_ADD ); 15 | gl.blendFunc( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA ); 16 | 17 | const params = { 18 | noise: 0.15, 19 | size: 300, 20 | amplitude: 0.0001, 21 | wind: 10.0, 22 | speed: 0.1, 23 | choppiness: 1.0, 24 | wireframe: false, 25 | terrain: false, 26 | _renderer: null, 27 | }; 28 | 29 | params._renderer = new Renderer(scene, params); 30 | 31 | function updateRenderer() { 32 | params._renderer = new Renderer(scene, params); 33 | } 34 | 35 | gui.add(params, 'amplitude', .0001, .0009).onChange(updateRenderer); 36 | gui.add(params, 'wind', 1, 100).onChange(updateRenderer); 37 | gui.add(params, 'speed', 0.001, 0.3).onChange(updateRenderer); 38 | gui.add(params, 'choppiness', 0.01, 2).onChange(updateRenderer); 39 | gui.add(params, 'wireframe').onChange(updateRenderer); 40 | gui.add(params, 'terrain').onChange(updateRenderer); 41 | 42 | function render() { 43 | scene.update(); 44 | params._renderer.render(camera, scene); 45 | } 46 | 47 | makeRenderLoop(render)(); 48 | -------------------------------------------------------------------------------- /src/renderers/renderer.js: -------------------------------------------------------------------------------- 1 | import { gl } from '../init'; 2 | import { mat3, mat4, vec4 } from 'gl-matrix'; 3 | import { loadShaderProgram } from '../utils'; 4 | import vsSourceOcean from '../shaders/ocean.vert.glsl'; 5 | import fsSourceOcean from '../shaders/ocean.frag.glsl.js'; 6 | import vsSourceTerrain from '../shaders/terrain.vert.glsl'; 7 | import fsSourceTerrain from '../shaders/terrain.frag.glsl.js'; 8 | import vsSourceSkybox from '../shaders/skybox.vert.glsl'; 9 | import fsSourceSkybox from '../shaders/skybox.frag.glsl.js'; 10 | import TextureBuffer from './textureBuffer'; 11 | 12 | export default class Renderer { 13 | constructor(scene, params) { 14 | // Initialize a shader program. The fragment shader source is compiled based on the number of lights 15 | this._shaderProgramTerrain = loadShaderProgram(vsSourceTerrain, fsSourceTerrain(), { 16 | uniforms: ['u_viewProjectionMatrix', 'u_noise'], 17 | attribs: ['a_position'], 18 | }); 19 | 20 | this._shaderProgramOcean = loadShaderProgram(vsSourceOcean, fsSourceOcean(), { 21 | uniforms: ['u_viewProjectionMatrix', 'u_viewMatrix', 'u_noise', 'u_time', 'u_L', 'u_resolution', 'u_cameraPos', 'u_A', 'u_V', 'u_choppiness'], 22 | attribs: ['a_position', 'a_heightMap', 'a_w'], 23 | }); 24 | 25 | this._shaderProgramSkybox = loadShaderProgram(vsSourceSkybox, fsSourceSkybox(), { 26 | uniforms: ['u_viewProjectionMatrix'], 27 | attribs: ['a_coords'], 28 | }); 29 | 30 | this._projectionMatrix = mat4.create(); 31 | this._viewMatrix = mat4.create(); 32 | this._viewProjectionMatrix = mat4.create(); 33 | 34 | this._noise = params.noise; 35 | this._wind = params.wind; 36 | this._speed = params.speed; 37 | this._choppiness = params.choppiness; 38 | this._terrain = params.terrain; 39 | 40 | scene.wireframe = params.wireframe; 41 | scene.OCEAN_SIZE = params.size; 42 | scene.amplitude = params.amplitude; 43 | scene.createBuffers(); 44 | scene.createPatchBuffers(); 45 | scene.createTerrainBuffers(); 46 | scene.loadTexture(); 47 | scene.createSkybox(); 48 | } 49 | 50 | render(camera, scene) { 51 | // Update the camera matrices 52 | camera.updateMatrixWorld(); 53 | mat4.invert(this._viewMatrix, camera.matrixWorld.elements); 54 | mat4.copy(this._projectionMatrix, camera.projectionMatrix.elements); 55 | mat4.multiply(this._viewProjectionMatrix, this._projectionMatrix, this._viewMatrix); 56 | 57 | // Bind the default null framebuffer which is the screen 58 | gl.bindFramebuffer(gl.FRAMEBUFFER, null); 59 | 60 | // Render to the whole screen 61 | gl.viewport(0, 0, canvas.width, canvas.height); 62 | 63 | // Clear the frame 64 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 65 | 66 | // Draw the skybox 67 | gl.useProgram(this._shaderProgramSkybox.glShaderProgram); 68 | gl.uniformMatrix4fv(this._shaderProgramSkybox.u_viewProjectionMatrix, false, this._viewProjectionMatrix); 69 | scene.drawSkybox(this._shaderProgramSkybox); 70 | 71 | // Draw the terrain 72 | if(this._terrain) { 73 | gl.useProgram(this._shaderProgramTerrain.glShaderProgram); 74 | gl.uniformMatrix4fv(this._shaderProgramTerrain.u_viewProjectionMatrix, false, this._viewProjectionMatrix); 75 | gl.uniform1f(this._shaderProgramTerrain.u_noise, this._noise); 76 | scene.drawTerrain(this._shaderProgramTerrain); 77 | } 78 | 79 | // Draw the ocean 80 | gl.useProgram(this._shaderProgramOcean.glShaderProgram); 81 | gl.uniformMatrix4fv(this._shaderProgramOcean.u_viewProjectionMatrix, false, this._viewProjectionMatrix); 82 | gl.uniformMatrix4fv(this._shaderProgramOcean.u_viewMatrix, false, this._viewMatrix); 83 | gl.uniform3f(this._shaderProgramOcean.u_cameraPos, camera.position.x, camera.position.y, camera.position.z); 84 | gl.uniform1f(this._shaderProgramOcean.u_time, scene.time * this._speed); 85 | gl.uniform1f(this._shaderProgramOcean.u_L, scene.OCEAN_SIZE); 86 | gl.uniform1i(this._shaderProgramOcean.u_resolution, scene.OCEAN_RESOLUTION); 87 | gl.uniform1f(this._shaderProgramOcean.u_A, scene.amplitude); 88 | gl.uniform1f(this._shaderProgramOcean.u_V, this._wind); 89 | gl.uniform1f(this._shaderProgramOcean.u_choppiness, this._choppiness); 90 | scene.drawOcean(this._shaderProgramOcean); 91 | 92 | gl.uniform1i(this._shaderProgramOcean.u_resolution, scene.OCEAN_LOW_RES); 93 | scene.bindOceanLowResBuffers(this._shaderProgramOcean); 94 | for(let i = 0; i < 9; i++) { 95 | if (i == 4) { 96 | continue; 97 | } 98 | scene.drawOceanLowRes(this._shaderProgramOcean, i); 99 | } 100 | } 101 | }; 102 | -------------------------------------------------------------------------------- /src/renderers/textureBuffer.js: -------------------------------------------------------------------------------- 1 | import { gl } from '../init'; 2 | 3 | export default class TextureBuffer { 4 | /** 5 | * This class represents a buffer in a shader. Unforunately we can't bind arbitrary buffers so we need to pack the data as a texture 6 | * @param {Number} elementCount The number of items in the buffer 7 | * @param {Number} elementSize The number of values in each item of the buffer 8 | */ 9 | constructor(elementCount, elementSize) { 10 | // Initialize the texture. We use gl.NEAREST for texture filtering because we don't want to blend between values in the buffer. We want the exact value 11 | this._glTexture = gl.createTexture(); 12 | gl.bindTexture(gl.TEXTURE_2D, this._glTexture); 13 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 14 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); 15 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 16 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 17 | 18 | // The texture stores 4 values in each "pixel". Thus, the texture we create is elementCount x ceil(elementSize / 4) 19 | this._pixelsPerElement = Math.ceil(elementSize / 4); 20 | this._elementCount = elementCount; 21 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, elementCount, this._pixelsPerElement, 0, gl.RGBA, gl.FLOAT, null); 22 | gl.bindTexture(gl.TEXTURE_2D, null); 23 | 24 | // Create a buffer to use to upload to the texture 25 | this._buffer = new Float32Array(elementCount * 4 * this._pixelsPerElement); 26 | } 27 | 28 | get glTexture() { 29 | return this._glTexture; 30 | } 31 | 32 | get buffer() { 33 | return this._buffer; 34 | } 35 | 36 | /** 37 | * Computes the starting buffer index to a particular item. 38 | * @param {*} index The index of the item 39 | * @param {*} component The ith float of an element is located in the (i/4)th pixel 40 | */ 41 | bufferIndex(index, component) { 42 | return 4 * index + 4 * component * this._elementCount; 43 | } 44 | 45 | /** 46 | * Update the texture with the data in the buffer 47 | */ 48 | update() { 49 | gl.bindTexture(gl.TEXTURE_2D, this._glTexture); 50 | gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, this._elementCount, this._pixelsPerElement, gl.RGBA, gl.FLOAT, this._buffer); 51 | gl.bindTexture(gl.TEXTURE_2D, null); 52 | } 53 | }; -------------------------------------------------------------------------------- /src/scene.js: -------------------------------------------------------------------------------- 1 | import { gl } from './init'; 2 | import { Vector3, Vector2 } from 'three'; 3 | 4 | const FLOAT_SIZE = 4; 5 | const g = 9.81; 6 | 7 | var texId; 8 | 9 | class Scene { 10 | constructor() { 11 | this.aCoords_Skybox; 12 | this.uProjection_Skybox; 13 | this.uModelview_Skybox; 14 | this._texID; 15 | this.cube; 16 | 17 | this.noise = []; 18 | this.time = 0; 19 | this.wind = new Vector2(1.0, 1.0); 20 | this.amplitude = 0.00001; 21 | 22 | this.OCEAN_RESOLUTION = 512.0; 23 | this.vertices = []; 24 | this.indices = []; 25 | 26 | this.TERRAIN_RESOLUTION = 256.0; 27 | this.terrainVertices = []; 28 | this.terrainIndices = []; 29 | 30 | this.OCEAN_LOW_RES = 128.0; 31 | this.verticesLowRes = []; 32 | this.indicesLowRes = []; 33 | 34 | this.patches = []; 35 | } 36 | 37 | update() { 38 | this.time += 1; 39 | } 40 | 41 | createTerrainBuffers() { 42 | this.terrainVertices = []; 43 | for (let z = 0; z < this.TERRAIN_RESOLUTION; z++) { 44 | for (let x = 0; x < this.TERRAIN_RESOLUTION; x++) { 45 | this.terrainVertices.push((x * this.OCEAN_SIZE)/ (this.TERRAIN_RESOLUTION - 1) - this.OCEAN_SIZE/2.0); 46 | this.terrainVertices.push(0.0) 47 | this.terrainVertices.push((z * this.OCEAN_SIZE)/ (this.TERRAIN_RESOLUTION - 1) - this.OCEAN_SIZE/2.0); 48 | } 49 | } 50 | 51 | this.terrainIndices = []; 52 | for (let z = 0; z < this.TERRAIN_RESOLUTION - 1; z++) { 53 | for (let x = 0; x < this.TERRAIN_RESOLUTION - 1; x++) { 54 | let UL = z * this.TERRAIN_RESOLUTION + x; 55 | let UR = UL + 1; 56 | let BL = UL + this.TERRAIN_RESOLUTION; 57 | let BR = BL + 1; 58 | this.terrainIndices.push(UL); 59 | this.terrainIndices.push(BL); 60 | this.terrainIndices.push(BR); 61 | this.terrainIndices.push(BR); 62 | this.terrainIndices.push(UR); 63 | this.terrainIndices.push(UL); 64 | } 65 | } 66 | this.vertexBufferTerrain = gl.createBuffer(); 67 | this.indicesBufferTerrain = gl.createBuffer(); 68 | 69 | // Bind vertex positions 70 | gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBufferTerrain); 71 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this.terrainVertices), gl.STATIC_DRAW); 72 | 73 | // Bind indices 74 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indicesBufferTerrain); 75 | gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint32Array(this.terrainIndices), gl.STATIC_DRAW); 76 | } 77 | 78 | createBuffers() { 79 | this.vertices = []; 80 | for (let z = 0; z < this.OCEAN_RESOLUTION; z++) { 81 | for (let x = 0; x < this.OCEAN_RESOLUTION; x++) { 82 | this.vertices.push((x * this.OCEAN_SIZE)/ (this.OCEAN_RESOLUTION - 1) - this.OCEAN_SIZE/2.0); 83 | this.vertices.push(0.0) 84 | this.vertices.push((z * this.OCEAN_SIZE)/ (this.OCEAN_RESOLUTION - 1) - this.OCEAN_SIZE/2.0); 85 | } 86 | } 87 | 88 | this.indices = []; 89 | for (let z = 0; z < this.OCEAN_RESOLUTION - 1; z++) { 90 | for (let x = 0; x < this.OCEAN_RESOLUTION - 1; x++) { 91 | let UL = z * this.OCEAN_RESOLUTION + x; 92 | let UR = UL + 1; 93 | let BL = UL + this.OCEAN_RESOLUTION; 94 | let BR = BL + 1; 95 | this.indices.push(UL); 96 | this.indices.push(BL); 97 | this.indices.push(BR); 98 | this.indices.push(BR); 99 | this.indices.push(UR); 100 | this.indices.push(UL); 101 | } 102 | } 103 | 104 | this.vertexBuffer = gl.createBuffer(); 105 | this.indicesBuffer = gl.createBuffer(); 106 | 107 | gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); 108 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this.vertices), gl.STATIC_DRAW); 109 | 110 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer); 111 | gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint32Array(this.indices), gl.STATIC_DRAW); 112 | } 113 | 114 | createPatchBuffers() { 115 | this.verticesLowRes = []; 116 | for (let i = 0; i < 3; i++) { 117 | for (let j = 0; j < 3; j++) { 118 | for (let z = 0; z < this.OCEAN_LOW_RES; z++) { 119 | for (let x = 0; x < this.OCEAN_LOW_RES; x++) { 120 | this.verticesLowRes.push((x * this.OCEAN_SIZE)/ (this.OCEAN_LOW_RES - 1) 121 | - this.OCEAN_SIZE/2.0 + (i - 1) * this.OCEAN_SIZE); 122 | this.verticesLowRes.push(0.0) 123 | this.verticesLowRes.push((z * this.OCEAN_SIZE)/ (this.OCEAN_LOW_RES - 1) 124 | - this.OCEAN_SIZE/2.0 + (j - 1) * this.OCEAN_SIZE); 125 | } 126 | } 127 | } 128 | } 129 | 130 | this.indicesLowRes = []; 131 | for (let z = 0; z < this.OCEAN_LOW_RES - 1; z++) { 132 | for (let x = 0; x < this.OCEAN_LOW_RES - 1; x++) { 133 | let UL = z * this.OCEAN_LOW_RES + x; 134 | let UR = UL + 1; 135 | let BL = UL + this.OCEAN_LOW_RES; 136 | let BR = BL + 1; 137 | this.indicesLowRes.push(UL); 138 | this.indicesLowRes.push(BL); 139 | this.indicesLowRes.push(BR); 140 | this.indicesLowRes.push(BR); 141 | this.indicesLowRes.push(UR); 142 | this.indicesLowRes.push(UL); 143 | } 144 | } 145 | 146 | for (let i = 0; i < 9; i++) { 147 | if (i == 4) { 148 | this.patches.push(undefined); 149 | continue; 150 | } 151 | let start = this.OCEAN_LOW_RES * this.OCEAN_LOW_RES * 3 * i; 152 | let end = start + (this.OCEAN_LOW_RES * this.OCEAN_LOW_RES * 3); 153 | let verticesPatch = this.verticesLowRes.slice(start, end); 154 | var vertexBuffer = gl.createBuffer(); 155 | gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); 156 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verticesPatch), gl.STATIC_DRAW); 157 | this.patches.push(vertexBuffer); 158 | } 159 | 160 | this.indicesBufferLowRes = gl.createBuffer(); 161 | 162 | // Bind ocean vertex indices 163 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indicesBufferLowRes); 164 | gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint32Array(this.indicesLowRes), gl.STATIC_DRAW); 165 | } 166 | 167 | createSkybox() { 168 | this.modelData = this.skybox(1000); 169 | this.model = {}; 170 | this.model.coordsBuffer = gl.createBuffer(); 171 | this.model.indexBuffer = gl.createBuffer(); 172 | this.model.count = this.modelData.indices.length; 173 | 174 | gl.bindBuffer(gl.ARRAY_BUFFER, this.model.coordsBuffer); 175 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this.modelData.vertexPositions), gl.STATIC_DRAW); 176 | 177 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.model.indexBuffer); 178 | gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(this.modelData.indices), gl.STATIC_DRAW); 179 | } 180 | 181 | skybox(side) { 182 | var s = (side || 1)/2; 183 | var coords = []; 184 | var normals = []; 185 | var texCoords = []; 186 | var indices = []; 187 | function face(xyz, nrm) { 188 | var start = coords.length/3; 189 | var i; 190 | for (i = 0; i < 12; i++) { 191 | coords.push(xyz[i]); 192 | } 193 | for (i = 0; i < 4; i++) { 194 | normals.push(nrm[0], nrm[1], nrm[2]); 195 | } 196 | texCoords.push(0, 0, 1, 0, 1, 1, 0, 1); 197 | indices.push(start + 3, start + 2, start, start + 2, start + 1, start); 198 | } 199 | face( [-s,-s, s, s,-s, s, s, s, s, -s, s, s], [0, 0, 1] ); 200 | face( [-s,-s,-s, -s, s,-s, s, s,-s, s,-s,-s], [0, 0, -1] ); 201 | face( [-s, s, -s, -s, s, s, s, s, s, s, s,-s], [0, 1, 0] ); 202 | face( [-s, -s, -s, s, -s, -s, s, -s, s, -s, -s, s], [0, -1, 0] ); 203 | face( [s, -s, -s, s, s, -s, s, s, s, s,-s, s], [1, 0, 0] ); 204 | face( [-s, -s, -s, -s, -s, s, -s, s, s, -s, s, -s], [-1, 0, 0] ); 205 | return { 206 | vertexPositions: new Float32Array(coords), 207 | vertexNormals: new Float32Array(normals), 208 | vertexTextureCoords: new Float32Array(texCoords), 209 | indices: new Uint16Array(indices) 210 | } 211 | } 212 | 213 | loadTexture() { 214 | var count = 0; 215 | var img = new Array(6); 216 | var urls = [ 217 | "../img/cloudtop_ft.jpg", "../img/cloudtop_bk.jpg", 218 | "../img/cloudtop_up.jpg", "../img/cloudtop_dn.jpg", 219 | "../img/cloudtop_rt.jpg", "../img/cloudtop_lf.jpg" 220 | ]; 221 | for (var i = 0; i < 6; i++) { 222 | img[i] = new Image(); 223 | img[i].onload = function() { 224 | count++; 225 | if (count == 6) { 226 | texId = gl.createTexture(); 227 | gl.bindTexture(gl.TEXTURE_CUBE_MAP, texId); 228 | var targets = [ 229 | gl.TEXTURE_CUBE_MAP_POSITIVE_X, gl.TEXTURE_CUBE_MAP_NEGATIVE_X, 230 | gl.TEXTURE_CUBE_MAP_POSITIVE_Y, gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, 231 | gl.TEXTURE_CUBE_MAP_POSITIVE_Z, gl.TEXTURE_CUBE_MAP_NEGATIVE_Z 232 | ]; 233 | for (var j = 0; j < 6; j++) { 234 | gl.texImage2D(targets[j], 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img[j]); 235 | gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 236 | gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 237 | } 238 | gl.generateMipmap(gl.TEXTURE_CUBE_MAP); 239 | } 240 | } 241 | this._texID = texId; 242 | img[i].src = urls[i]; 243 | } 244 | } 245 | 246 | drawSkybox(shaderProgram) { 247 | gl.bindBuffer(gl.ARRAY_BUFFER, this.model.coordsBuffer); 248 | gl.enableVertexAttribArray(shaderProgram.a_coords); 249 | gl.vertexAttribPointer(shaderProgram.a_coords, 3, gl.FLOAT, false, 0, 0); 250 | 251 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.model.indexBuffer); 252 | 253 | gl.drawElements(gl.TRIANGLES, this.model.count, gl.UNSIGNED_SHORT, 0); 254 | } 255 | 256 | drawOcean(shaderProgram) { 257 | // Bind ocean vertex positions 258 | gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); 259 | gl.enableVertexAttribArray(shaderProgram.a_position); 260 | gl.vertexAttribPointer(shaderProgram.a_position, 3, gl.FLOAT, false, 3 * FLOAT_SIZE, 0); 261 | 262 | // Bind ocean vertex indices 263 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer); 264 | 265 | var mode = gl.TRIANGLES; 266 | if(this.wireframe) { 267 | mode = gl.LINES 268 | } 269 | gl.drawElements(mode, this.indices.length, gl.UNSIGNED_INT, 0); 270 | } 271 | 272 | drawOceanLowRes(shaderProgram, count) { 273 | let start = this.OCEAN_LOW_RES * this.OCEAN_LOW_RES * 3 * count; 274 | let end = start + (this.OCEAN_LOW_RES * this.OCEAN_LOW_RES * 3); 275 | let verticesPatch = this.verticesLowRes.slice(start, end); 276 | var vertexBuffer = this.patches[count]; 277 | 278 | gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); 279 | gl.enableVertexAttribArray(shaderProgram.a_position); 280 | gl.vertexAttribPointer(shaderProgram.a_position, 3, gl.FLOAT, false, 3 * FLOAT_SIZE, 0); 281 | 282 | var mode = gl.TRIANGLES; 283 | if(this.wireframe) { 284 | mode = gl.LINES 285 | } 286 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indicesBufferLowRes); 287 | gl.drawElements(mode, this.indicesLowRes.length, gl.UNSIGNED_INT, 0); 288 | } 289 | 290 | bindOceanLowResBuffers(shaderProgram) { 291 | if (this._texID) { 292 | // Bind ocean vertex indices 293 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indicesBufferLowRes); 294 | } 295 | } 296 | 297 | drawTerrain(shaderProgram) { 298 | // Bind vertex positions 299 | gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBufferTerrain); 300 | gl.enableVertexAttribArray(shaderProgram.a_position); 301 | gl.vertexAttribPointer(shaderProgram.a_position, 3, gl.FLOAT, false, 3 * FLOAT_SIZE, 0); 302 | 303 | // Bind indices 304 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indicesBufferTerrain); 305 | 306 | var mode = gl.TRIANGLES; 307 | if(this.wireframe) { 308 | mode = gl.LINES 309 | } 310 | gl.drawElements(mode, this.terrainIndices.length, gl.UNSIGNED_INT, 0); 311 | } 312 | 313 | } 314 | 315 | export default Scene; -------------------------------------------------------------------------------- /src/shaders/ocean.frag.glsl.js: -------------------------------------------------------------------------------- 1 | export default function(params) { 2 | return `#version 300 es 3 | precision highp float; 4 | 5 | in vec3 v_position; 6 | in vec3 v_normal; 7 | in vec3 v_viewCoords; 8 | in vec3 v_R; 9 | in float v_random; 10 | 11 | out vec4 v_color; 12 | 13 | uniform samplerCube skybox; 14 | uniform mat4 u_viewMatrix; 15 | 16 | void main() { 17 | vec3 albedo = vec3(0.6, 0.6, 0.6); 18 | vec3 normal = v_normal; 19 | vec3 pos = v_position; 20 | 21 | vec3 fragColor = vec3(0.0); 22 | 23 | vec3 lightDir = normalize(vec3(0, 75, -5) - pos); 24 | float NdotL = clamp(dot(normal, lightDir), 0.1, 1.0); 25 | 26 | mat4 invViewMatrix = inverse(u_viewMatrix); 27 | vec4 cameraWorldPos = invViewMatrix * vec4(0.0, 0.0, 0.0, 1.0); 28 | vec3 V = normalize(cameraWorldPos.xyz - v_position); 29 | vec3 H = normalize(lightDir + V); 30 | float NdotH = max(dot(H, normal), 0.0); 31 | float specular = pow(NdotH, 50.0); 32 | fragColor += (albedo + vec3(specular)) * NdotL; 33 | 34 | vec3 darkBlue = vec3(27.0/255.0, 56.0/255.0, 81.0/255.0); 35 | 36 | // Refraction 37 | float rIndex = 1.5; 38 | float R_0 = pow((1.0 - rIndex) / (1.0 + rIndex), 2.0); // Reflective index 39 | float dot = abs(dot(normal, lightDir)); 40 | float fresnel = R_0 + (1.0 - R_0) * pow(1.0 - dot, 5.0); 41 | 42 | fragColor += (fresnel + 0.1) * vec3(texture(skybox, v_R)) + (1.0 - fresnel) * darkBlue; 43 | v_color = vec4(fragColor, 0.9); 44 | } 45 | `; 46 | } 47 | -------------------------------------------------------------------------------- /src/shaders/ocean.vert.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | 4 | const float PI = 3.14159265359; 5 | const float g = 9.81; 6 | const vec2 wind = vec2(1.0, 1.0); 7 | 8 | uniform mat4 u_viewProjectionMatrix; 9 | uniform mat4 u_viewMatrix; 10 | 11 | uniform float u_time; 12 | uniform float u_L; 13 | uniform float u_A; 14 | uniform float u_V; 15 | uniform int u_resolution; 16 | uniform vec3 u_cameraPos; 17 | uniform float u_choppiness; 18 | 19 | in vec3 a_position; 20 | // in vec4 a_heightMap; 21 | // in float a_w; 22 | 23 | out vec3 v_position; 24 | out vec3 v_normal; 25 | out vec3 v_R; 26 | out float v_random; 27 | 28 | // taken from http://byteblacksmith.com/improvements-to-the-canonical-one-liner-glsl-rand-for-opengl-es-2-0/ 29 | highp float rand(vec2 co) 30 | { 31 | highp float a = 12.9898; 32 | highp float b = 78.233; 33 | highp float c = 43758.5453; 34 | highp float dt = dot(co.xy ,vec2(a,b)); 35 | highp float sn = mod(dt,3.14); 36 | return fract(sin(sn) * c); 37 | } 38 | 39 | vec2 complexExp(float x) { 40 | return vec2(cos(x), sin(x)); 41 | } 42 | 43 | vec2 complexProduct(vec2 a, vec2 b) { 44 | return vec2(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x); 45 | } 46 | 47 | float gausRand(float r1, float r2) { 48 | return sqrt(-2.0 * log(r1)) * cos(2.0 * PI * r2); 49 | } 50 | 51 | vec2 getH_0 (vec2 k, float P) { 52 | float r1 = rand(vec2(k.x, k.y)) * 2.0 - 1.0; 53 | float r2 = rand(vec2((mod(k.y * r1, 11.0), mod(k.x, 17.0)))) * 2.0 - 1.0; 54 | 55 | return vec2( 1.0 / pow (2.0, 0.5) * r1 * sqrt(P), 1.0 / sqrt(2.0) * r2 * pow(P, 0.5)); 56 | } 57 | 58 | // first value is heightfield, second is x displacement 59 | vec2 getHeightField(vec3 pos) { 60 | float n = pos.x; 61 | float m = pos.z; 62 | vec2 k = vec2(2.0 * PI * n / u_L, 2.0 * PI * m / u_L); 63 | float lengthK = length(k); 64 | 65 | // largest possible waves arising from a continuous wind of speed 66 | float L = u_V * u_V / g; 67 | 68 | float cosP = length(dot (normalize(k), normalize(wind))); 69 | float temp = lengthK * L; 70 | float P = u_A * exp( -1.0 / (temp * temp)) * pow(lengthK, -4.0) * cosP * cosP; 71 | 72 | float wl = L / 10000.0; 73 | P *= exp(lengthK * lengthK * (wl * wl)); 74 | 75 | vec2 h_0 = getH_0(k, P); 76 | vec2 h_0_star = getH_0(-k, P); 77 | 78 | float w = sqrt(g * lengthK); 79 | 80 | vec2 h_01 = complexProduct(h_0, complexExp( w * u_time)); 81 | vec2 h_0_star1 = complexProduct(h_0_star, complexExp(-w * u_time)); 82 | 83 | // h(k, t) 84 | vec2 h_t = h_01 + h_0_star1; 85 | 86 | vec2 h_x_t = complexProduct(h_t, complexExp(dot(k, vec2(n,m)))); 87 | 88 | vec2 k_normalized = normalize(k); 89 | 90 | float lambda = u_choppiness; 91 | vec2 d_x_t = lambda * clamp( complexProduct(-k_normalized, vec2(h_x_t.y, h_x_t.x)), 0.0,0.3); 92 | 93 | return vec2(h_x_t.x, d_x_t.x); 94 | } 95 | 96 | void main() { 97 | vec3 a = a_position; 98 | float scale = 10.0; 99 | float random = rand(vec2(a.x, a.z * u_time)); 100 | float waveDisplacement = 1.5 * sin(0.07*(a.x + 10.0 * u_time)) * cos(0.06*(a.z + u_time)); 101 | 102 | float delta = u_L/float(u_resolution); 103 | vec2 a_delta = getHeightField(a); 104 | float y = clamp(a_delta.x, 0.0, 0.3) + 55.0; 105 | a.y = y + waveDisplacement; 106 | a.x += a_delta.y; 107 | 108 | vec3 b = vec3(a_position.x + delta, a_position.y, a_position.z); 109 | vec2 b_delta = getHeightField(b); 110 | b.y = b_delta.x + 55.0 + waveDisplacement; 111 | b.x += b_delta.y; 112 | 113 | vec3 c = vec3(a_position.x, a_position.y, a_position.z + delta); 114 | vec2 c_delta = getHeightField(c); 115 | c.y = c_delta.x + 55.0 + waveDisplacement; 116 | c.x += c_delta.y; 117 | 118 | vec3 dir = normalize(cross((b - a), (c - a))); 119 | v_normal = dir; 120 | 121 | v_position = a; 122 | gl_Position = u_viewProjectionMatrix * vec4(a, 1.0); 123 | 124 | // Reflection 125 | vec3 eyePos = normalize(a - u_cameraPos); 126 | vec4 NN = u_viewMatrix * vec4(v_normal, 1.0); 127 | vec3 N = normalize(NN.xyz); 128 | v_R = reflect(eyePos, v_normal); 129 | 130 | // v_random = rand(vec2(a.x, a.z)); 131 | 132 | // gl_Position = u_viewProjectionMatrix * vec4(a, 1.0); 133 | } -------------------------------------------------------------------------------- /src/shaders/quad.vert.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | 4 | in vec3 a_position; 5 | 6 | out vec2 v_uv; 7 | 8 | out vec4 v_position; 9 | 10 | void main() { 11 | gl_Position = vec4(a_position, 1.0); 12 | v_uv = a_position.xy * 0.5 + 0.5; 13 | v_position = vec4(a_position, 1.0); 14 | } -------------------------------------------------------------------------------- /src/shaders/skybox.frag.glsl.js: -------------------------------------------------------------------------------- 1 | export default function(params) { 2 | return `#version 300 es 3 | precision highp float; 4 | 5 | uniform samplerCube skybox; 6 | 7 | in vec3 v_coords; 8 | 9 | out vec4 v_color; 10 | 11 | void main() { 12 | v_color = texture(skybox, v_coords); 13 | } 14 | `; 15 | } 16 | -------------------------------------------------------------------------------- /src/shaders/skybox.vert.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | 4 | uniform mat4 u_viewProjectionMatrix; 5 | 6 | in vec3 a_coords; 7 | 8 | out vec3 v_coords; 9 | 10 | void main() { 11 | gl_Position = u_viewProjectionMatrix * vec4(a_coords, 1.0); 12 | v_coords = a_coords; 13 | } -------------------------------------------------------------------------------- /src/shaders/terrain.frag.glsl.js: -------------------------------------------------------------------------------- 1 | export default function(params) { 2 | return `#version 300 es 3 | precision highp float; 4 | 5 | in vec3 v_position; 6 | in vec3 v_normal; 7 | 8 | out vec4 v_color; 9 | 10 | void main() { 11 | vec3 albedo = vec3(0.9, 0.9, 0.9); 12 | vec3 normal = v_normal; 13 | vec3 pos = v_position; 14 | 15 | vec3 fragColor = vec3(0.0); 16 | 17 | vec3 lightDir = normalize(vec3(0, 65, -100) - pos); 18 | float NdotL = clamp(dot(normal, lightDir), 0.2, 0.7); 19 | fragColor += albedo * NdotL; 20 | 21 | const vec3 ambientLight = vec3(0.25); 22 | fragColor += albedo * ambientLight; 23 | v_color = vec4(fragColor, 1.0); 24 | } 25 | `; 26 | } 27 | -------------------------------------------------------------------------------- /src/shaders/terrain.vert.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | 4 | uniform mat4 u_viewProjectionMatrix; 5 | 6 | uniform float u_noise; 7 | 8 | in vec3 a_position; 9 | 10 | out vec3 v_position; 11 | out vec3 v_normal; 12 | 13 | float Noise(float x, float y) { 14 | int n = int(x) + int(y) * 57; 15 | n = (n<<13) ^ n; 16 | float noise = float( 1 - ( (n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824); 17 | noise = (noise + 1.0) / 2.0; 18 | return noise; 19 | } 20 | 21 | float LinearInterpolate(float a, float b, float x) { 22 | return (a * (1.0 - x)) + b * x; 23 | } 24 | 25 | float SmoothNoise(float x, float y) { 26 | float corners = (Noise(x - 1.0, y - 1.0) + Noise(x + 1.0, y - 1.0) + Noise(x - 1.0, y + 1.0) + Noise(x + 1.0, y + 1.0)) / 16.0; 27 | float sides = (Noise(x - 1.0, y) + Noise(x + 1.0, y) + Noise(x, y - 1.0) + Noise(x, y + 1.0)) / 8.0; 28 | float center = Noise(x, y) / 4.0; 29 | return corners + sides + center; 30 | } 31 | 32 | float InterpolateNoise(float x, float y) { 33 | int integer_X = int(x); 34 | float fractional_X = abs(x - float(integer_X)); 35 | int integer_Y = int(y); 36 | float fractional_Y = abs(y - float(integer_Y)); 37 | 38 | float v1 = SmoothNoise(float(integer_X), float(integer_Y)); 39 | float v2 = SmoothNoise(float(integer_X) + 1.0, float(integer_Y)); 40 | float v3 = SmoothNoise(float(integer_X), float(integer_Y) + 1.0); 41 | float v4 = SmoothNoise(float(integer_X) + 1.0, float(integer_Y) + 1.0); 42 | 43 | float i1 = LinearInterpolate(v1, v2, fractional_X); 44 | float i2 = LinearInterpolate(v3, v4, fractional_X); 45 | 46 | return LinearInterpolate(i1, i2, fractional_Y); 47 | } 48 | 49 | float PerlinNoise(float x, float y, float c) { 50 | x = x * c; 51 | y = y * c; 52 | float total = 0.0; 53 | float p = 0.5; 54 | // number of octaves 55 | int n = 6; 56 | float max = 1.4; 57 | 58 | for (int i = 0; i < n; i++) { 59 | float frequency = pow(2.0, float(i)); 60 | float amplitude = pow(p, float(i)); 61 | 62 | total = total + InterpolateNoise(float(x) * frequency, float(y) * frequency) * amplitude; 63 | } 64 | return (total/max); 65 | } 66 | 67 | void main() { 68 | float amplitude = u_noise; 69 | float y = PerlinNoise(a_position.x, a_position.z, amplitude) * 40.0 + 8.0; 70 | vec3 a = a_position; 71 | 72 | float delta = 0.1; 73 | a.y = y; 74 | vec3 b = vec3(a_position.x + delta, a_position.y, a_position.z); 75 | b.y = PerlinNoise(b.x, b.z, amplitude) * 40.0 + 8.0; 76 | vec3 c = vec3(a_position.x, a_position.y, a_position.z + delta); 77 | c.y = PerlinNoise(c.x, c.z, amplitude) * 40.0 + 8.0; 78 | 79 | vec3 dir = normalize(cross((b - a), (c - a))); 80 | v_normal = dir; 81 | 82 | 83 | v_position = a; 84 | gl_Position = u_viewProjectionMatrix * vec4(a, 1.0); 85 | } -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | import { gl, canvas, abort } from './init'; 2 | import QuadVertSource from './shaders/quad.vert.glsl'; 3 | 4 | function downloadURI(uri, name) { 5 | var link = document.createElement('a'); 6 | link.download = name; 7 | link.href = uri; 8 | document.body.appendChild(link); 9 | link.click(); 10 | document.body.removeChild(link); 11 | }; 12 | 13 | export function saveCanvas() { 14 | downloadURI(canvas.toDataURL('image/png'), 'webgl-canvas-' + Date.now() + '.png'); 15 | } 16 | 17 | function compileShader(shaderSource, shaderType) { 18 | var shader = gl.createShader(shaderType); 19 | gl.shaderSource(shader, shaderSource); 20 | gl.compileShader(shader); 21 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { 22 | console.error(shaderSource); 23 | abort('shader compiler error:\n' + gl.getShaderInfoLog(shader)); 24 | } 25 | 26 | return shader; 27 | }; 28 | 29 | function linkShader(vs, fs) { 30 | var prog = gl.createProgram(); 31 | gl.attachShader(prog, vs); 32 | gl.attachShader(prog, fs); 33 | gl.linkProgram(prog); 34 | if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) { 35 | abort('shader linker error:\n' + gl.getProgramInfoLog(prog)); 36 | } 37 | return prog; 38 | }; 39 | 40 | function addShaderLocations(result, shaderLocations) { 41 | if (shaderLocations && shaderLocations.uniforms && shaderLocations.uniforms.length) { 42 | for (let i = 0; i < shaderLocations.uniforms.length; ++i) { 43 | result = Object.assign(result, { 44 | [shaderLocations.uniforms[i]]: gl.getUniformLocation(result.glShaderProgram, shaderLocations.uniforms[i]), 45 | }); 46 | } 47 | } 48 | if (shaderLocations && shaderLocations.attribs && shaderLocations.attribs.length) { 49 | for (let i = 0; i < shaderLocations.attribs.length; ++i) { 50 | result = Object.assign(result, { 51 | [shaderLocations.attribs[i]]: gl.getAttribLocation(result.glShaderProgram, shaderLocations.attribs[i]), 52 | }); 53 | } 54 | } 55 | return result; 56 | } 57 | 58 | export function loadShaderProgram(vsSource, fsSource, shaderLocations) { 59 | const vs = compileShader(vsSource, gl.VERTEX_SHADER); 60 | const fs = compileShader(fsSource, gl.FRAGMENT_SHADER); 61 | return addShaderLocations({ 62 | glShaderProgram: linkShader(vs, fs), 63 | }, shaderLocations); 64 | } 65 | 66 | const quadPositions = new Float32Array([ 67 | -1.0, -1.0, 0.0, 68 | 1.0, -1.0, 0.0, 69 | -1.0, 1.0, 0.0, 70 | 1.0, 1.0, 0.0 71 | ]); 72 | 73 | const quadBuffer = gl.createBuffer(); 74 | gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer); 75 | gl.bufferData(gl.ARRAY_BUFFER, quadPositions, gl.STATIC_DRAW); 76 | 77 | export function renderFullscreenQuad(program) { 78 | // Bind the program to use to draw the quad 79 | gl.useProgram(program.glShaderProgram); 80 | 81 | // Bind the VBO as the gl.ARRAY_BUFFER 82 | gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer); 83 | 84 | // Enable the bound buffer as the vertex attrib array for 85 | // program.a_position, using gl.enableVertexAttribArray 86 | gl.enableVertexAttribArray(program.a_position); 87 | 88 | // Use gl.vertexAttribPointer to tell WebGL the type/layout for 89 | // program.a_position's access pattern. 90 | gl.vertexAttribPointer(program.a_position, 3, gl.FLOAT, gl.FALSE, 0, 0); 91 | 92 | // Use gl.drawArrays to draw the quad 93 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); 94 | 95 | // Disable the enabled vertex attrib array 96 | gl.disableVertexAttribArray(program.a_position); 97 | 98 | // Unbind the array buffer. 99 | gl.bindBuffer(gl.ARRAY_BUFFER, null); 100 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const MinifyPlugin = require('babel-minify-webpack-plugin'); 4 | 5 | module.exports = function(env) { 6 | const isProduction = env && env.production === true; 7 | 8 | return { 9 | entry: path.join(__dirname, 'src/init'), 10 | output: { 11 | path: path.join(__dirname, 'build'), 12 | filename: 'bundle.js', 13 | }, 14 | module: { 15 | loaders: [ 16 | { 17 | test: /\.js$/, 18 | exclude: /(node_modules|bower_components)/, 19 | loader: 'babel-loader', 20 | query: { 21 | presets: [['env', { 22 | targets: { 23 | browsers: ['> 1%', 'last 2 major versions'], 24 | }, 25 | loose: true, 26 | modules: false, 27 | }]], 28 | }, 29 | }, 30 | { 31 | test: /\.glsl$/, 32 | loader: 'webpack-glsl-loader' 33 | }, 34 | ], 35 | }, 36 | plugins: [ 37 | isProduction ? new MinifyPlugin({ 38 | keepFnName: true, 39 | keepClassName: true, 40 | }) : undefined, 41 | new webpack.DefinePlugin({ 42 | 'process.env': { 43 | 'NODE_ENV': (isProduction ? JSON.stringify('production'): JSON.stringify('development')), 44 | } 45 | }), 46 | ].filter(p => p), 47 | devtool: 'source-map', 48 | devServer: { 49 | port: 5650, 50 | publicPath: '/build/' 51 | }, 52 | }; 53 | }; 54 | --------------------------------------------------------------------------------