diff --git a/README.md b/README.md index c636328..493328f 100644 --- a/README.md +++ b/README.md @@ -1,77 +1,28 @@ -# HW 0: Noisy Planet Part 1 (Intro to Javascript and WebGL) +# CIS 566 Project 1: Noisy Planets +Name : Samantha Lee +PennKey : smlee18 -

- -

-

(source: Ken Perlin)

+Sources: https://gist.github.com/patriciogonzalezvivo/670c22f3966e662d2f83 -## Objective -- Check that the tools and build configuration we will be using for the class works. -- Start learning Typescript and WebGL2 -- Practice implementing noise +Live Demo: https://18smlee.github.io/hw00-webgl-intro/ -## Forking the Code -Rather than cloning the homework repository, please __fork__ the code into your own repository using the `Fork` button in the upper-right hand corner of the Github UI. This will enable you to have your own personal repository copy of the code, and let you make a live demo (described later in this document). +![image](images/planet.PNG) -## Running the Code +## Biomes +There are four different types of terrain: ocean, beach, forest, and mountains. The elevation of a given vertex is based on a fractional brownian noise function from the above source. The lighter noise values correspond to higher elevation and the darker noise values correspond to lower elevation. -1. [Install Node.js](https://nodejs.org/en/download/). Node.js is a JavaScript runtime. It basically allows you to run JavaScript when not in a browser. For our purposes, this is not necessary. The important part is that with it comes `npm`, the Node Package Manager. This allows us to easily declare and install external dependencies such as [dat.GUI](https://workshop.chromeexperiments.com/examples/gui/#1--Basic-Usage), and [glMatrix](http://glmatrix.net/). +I then applied gain and bias functions to certain ranges that transition between different biome thresholds. This was to give the slope of the mountains and beaches a more curved look, rather than just being extruded straight up from the surface of the sphere. -2. Using a command terminal, run `npm install` in the root directory of your project. This will download all of those dependencies. +The water has a warped noise function applied to it to give it an unconvential marbled look. The vertices are also slightly raised with the moving water to emulate slow moving waves. -3. Do either of the following (but we highly recommend the first one for reasons we will explain later). +The land biome has a couple of different noise functions applied to it. There is one to soften the look of the land by interpolating a darker green into a lighter green. There is also a forestNoise value that affects both the color and the vertices. This noise function is made to simulate vegetation or forests scattered across the planet. I created the forest noise function with an fbm function with a lower frequency and a perlin noise function with a higher frequency and interpolated between the two. This gave an interesting output with more variation between sizes of random "blobs", more closely mimicking natural vegetaion. The user can control the density of the forest with the forest density parameter. - a. Run `npm start` and then go to `localhost:5660` in your web browser +The mountain biome has its own mountainNoise fbm function to further its surface and color to mimick natural rock formations. - b. Run `npm run build` and then go open `dist/index.html` in your web browser +## Color Palette and Terrain Frequency +I created two color palettes: earth and alien. The earth color palette creates a planet most resembling earth, with green forests, blue water, and grey mountains. The alien color palette however has a more foreign look with orange beaches, purple vegetation, and violet mountains. The final color palette is an interpolation between these two palettes, with the interpolation value being controlled by the user. So the user can decide how earthlike or alienlike they want their planet to be! -## Module Bundling -One of the most important dependencies of our projects is [Webpack](https://webpack.js.org/concepts/). Webpack is a module bundler which allows us to write code in separate files and use `import`s and `export`s to load classes and functions for other files. It also allows us to preprocess code before compiling to a single file. We will be using [Typescript](https://www.typescriptlang.org/docs/home.html) for this course which is Javascript augmented with type annotations. Webpack will convert Typescript files to Javascript files on compilation and in doing so will also check for proper type-safety and usage. Read more about Javascript modules in the resources section below. +The terrain frequency can also be controled by the user. This is a frequency value that is multiplied by the noise input of the original noise function that determines the elevation of a point. Giving control of the terrain's variation to the user allows them to create differently shaped planets in the blink of an eye! -## Developing Your Code -All of the JavaScript code is living inside the `src` directory. The main file that gets executed when you load the page as you may have guessed is `main.ts`. Here, you can make any changes you want, import functions from other files, etc. The reason that we highly suggest you build your project with `npm start` is that doing so will start a process that watches for any changes you make to your code. If it detects anything, it'll automagically rebuild your project and then refresh your browser window for you. Wow. That's cool. If you do it the other way, you'll need to run `npm build` and then refresh your page every time you want to test something. - -We would suggest editing your project with Visual Studio Code https://code.visualstudio.com/. Microsoft develops it and Microsoft also develops Typescript so all of the features work nicely together. Sublime Text and installing the Typescript plugins should probably work as well. - -## Assignment Details -1. Take some time to go through the existing codebase so you can get an understanding of syntax and how the code is architected. Much of the code is designed to mirror the class structures used in CIS 460's OpenGL assignments, so it should hopefully be somewhat familiar. -2. Take a look at the resources linked in the section below. Definitely read about Javascript modules and Typescript. The other links provide documentation for classes used in the code. -3. Add a `Cube` class that inherits from `Drawable` and at the very least implement a constructor and its `create` function. Then, add a `Cube` instance to the scene to be rendered. -4. Read the documentation for dat.GUI below. Update the existing GUI in `main.ts` with a parameter to alter the color passed to `u_Color` in the Lambert shader. -5. Write a custom fragment shader that implements FBM, Worley Noise, or Perlin Noise based on 3D inputs (as opposed to the 2D inputs in the slides). This noise must be used to modify your fragment color. If your custom shader is particularly interesting, you'll earn some bonus points. -6. Write a custom vertex shader that uses a trigonometric function (e.g. `sin`, `tan`) to non-uniformly modify your cube's vertex positions over time. This will necessitate instantiating an incrementing variable in your Typescript code that you pass to your shader every tick. Refer to the base code's methods of passing variables to shaders if you are unsure how to do so. -7. Feel free to update any of the files when writing your code. The implementation of the `OpenGLRenderer` is currently very simple. - -## Making a Live Demo -When you push changes to the `master` branch of your repository on Github, a Github workflow will run automatically which builds your code and pushes the build to a new branch `gh-pages`. The configuration file which handles this is located at `.github/workflows/build-and-deploy.yml`. If you want to modify this, you can read more about workflows [here](https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions). - -Once your built code is pushed to `gh-pages`, Github can automatically publish a live site. Configure that by: - - 1. Open the Settings tab of your repository in Github. - - 2. Scroll down to the Pages tab of the Settings (in the table on the left) and choose which branch to make the source for the deployed project. This should be the `gh-pages` branch which is automatically created after the first successful build of the `master` branch. - - 3. Done! Now, any new commits on the `master` branch will be built and pushed to `gh-pages`. The project should be visible at http://username.github.io/repo-name. -  - -To check if everything is on the right track: - -1. Make sure the `gh-pages` branch of your repo has a files called `index.html`, `bundle.js`, and `bundle.js.map` - -2. In the settings tab of the repo, under Pages, make sure it says your site is published at some url. - -## Submission -1. Create a pull request to this repository with your completed code. -2. Update README.md to contain a solid description of your project with a screenshot of some visuals, and a link to your live demo. -3. Submit the link to your pull request on Canvas, and add a comment to your submission with a hyperlink to your live demo. -4. Include a link to your live site. - -## Resources -- Javascript modules https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import -- Typescript https://www.typescriptlang.org/docs/home.html -- dat.gui https://workshop.chromeexperiments.com/examples/gui/ -- glMatrix http://glmatrix.net/docs/ -- WebGL - - Interfaces https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API - - Types https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Types - - Constants https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Constants +## Shading +I've applied lambert shading to the entire planet with a slowly rotating light. There is blinn phong shading on just the mountains to accentuate their jagged formations. diff --git a/images/Capture.PNG b/images/Capture.PNG new file mode 100644 index 0000000..4c35e41 Binary files /dev/null and b/images/Capture.PNG differ diff --git a/images/alien.PNG b/images/alien.PNG new file mode 100644 index 0000000..3eb0512 Binary files /dev/null and b/images/alien.PNG differ diff --git a/images/beach.PNG b/images/beach.PNG new file mode 100644 index 0000000..8d09d3e Binary files /dev/null and b/images/beach.PNG differ diff --git a/images/half_alien.PNG b/images/half_alien.PNG new file mode 100644 index 0000000..339bf08 Binary files /dev/null and b/images/half_alien.PNG differ diff --git a/images/high_freq.PNG b/images/high_freq.PNG new file mode 100644 index 0000000..6acd48d Binary files /dev/null and b/images/high_freq.PNG differ diff --git a/images/low_freq.PNG b/images/low_freq.PNG new file mode 100644 index 0000000..07fe97f Binary files /dev/null and b/images/low_freq.PNG differ diff --git a/images/mountains.PNG b/images/mountains.PNG new file mode 100644 index 0000000..51b7c81 Binary files /dev/null and b/images/mountains.PNG differ diff --git a/images/planet.PNG b/images/planet.PNG new file mode 100644 index 0000000..25ebc53 Binary files /dev/null and b/images/planet.PNG differ diff --git a/images/water.PNG b/images/water.PNG new file mode 100644 index 0000000..025e6a6 Binary files /dev/null and b/images/water.PNG differ diff --git a/src/Camera.ts b/src/Camera.ts index 77a7610..7f67172 100644 --- a/src/Camera.ts +++ b/src/Camera.ts @@ -36,6 +36,10 @@ class Camera { vec3.add(this.target, this.position, this.direction); mat4.lookAt(this.viewMatrix, this.controls.eye, this.controls.center, this.controls.up); } + + getEye() { + return this.controls.eye; + } }; export default Camera; diff --git a/src/geometry/Cube.ts b/src/geometry/Cube.ts new file mode 100644 index 0000000..8f02158 --- /dev/null +++ b/src/geometry/Cube.ts @@ -0,0 +1,107 @@ +import {vec3, vec4} from 'gl-matrix'; +import Drawable from '../rendering/gl/Drawable'; +import {gl} from '../globals'; + +class Cube extends Drawable { + indices: Uint32Array; + positions: Float32Array; + normals: Float32Array; + center: vec4; + + constructor(center: vec3) { + super(); // Call the constructor of the super class. This is required. + this.center = vec4.fromValues(center[0], center[1], center[2], 1); + } + + create() { + + this.indices = new Uint32Array([0, 1, 2, + 0, 2, 3, + 4, 5, 6, + 4, 6, 7, + 8, 9, 10, + 8, 10, 11, + 12, 13, 14, + 12, 14, 15, + 16, 17, 18, + 16, 18, 19, + 20, 21, 22, + 20, 22, 23]); + this.normals = new Float32Array([0, 0, -1, 0, + 0, 0, -1, 0, + 0, 0, -1, 0, + 0, 0, -1, 0, + + 0, 0, 1, 0, + 0, 0, 1, 0, + 0, 0, 1, 0, + 0, 0, 1, 0, + + -1, 0, 0, 0, + -1, 0, 0, 0, + -1, 0, 0, 0, + -1, 0, 0, 0, + + 1, 0, 0, 0, + 1, 0, 0, 0, + 1, 0, 0, 0, + 1, 0, 0, 0, + + 0, 1, 0, 0, + 0, 1, 0, 0, + 0, 1, 0, 0, + 0, 1, 0, 0, + + 0, -1, 0, 0, + 0, -1, 0, 0, + 0, -1, 0, 0, + 0, -1, 0, 0,]); + this.positions = new Float32Array([-1, -1, 1, 1, + -1, -1, -1, 1, + 1, -1, -1, 1, + 1, -1, 1, 1, + + -1, 1, 1, 1, + -1, 1, -1, 1, + 1, 1, -1, 1, + 1, 1, 1, 1, + + -1, 1, 1, 1, + -1, 1, -1, 1, + -1, -1, -1, 1, + -1, -1, 1, 1, + + 1, 1, 1, 1, + 1, 1, -1, 1, + 1, -1, -1, 1, + 1, -1, 1, 1, + + -1, 1, 1, 1, + -1, -1, 1, 1, + 1, -1, 1, 1, + 1, 1, 1, 1, + + -1, 1, -1, 1, + -1, -1, -1, 1, + 1, -1, -1, 1, + 1, 1, -1, 1,]); + + this.generateIdx(); + this.generatePos(); + this.generateNor(); + + this.count = this.indices.length; + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.bufIdx); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.bufNor); + gl.bufferData(gl.ARRAY_BUFFER, this.normals, gl.STATIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.bufPos); + gl.bufferData(gl.ARRAY_BUFFER, this.positions, gl.STATIC_DRAW); + + console.log(`Created cube`); + } +}; + +export default Cube; diff --git a/src/main.ts b/src/main.ts index 65a9461..7f5485c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,7 +1,8 @@ -import {vec3} from 'gl-matrix'; +import {vec3, vec4} from 'gl-matrix'; const Stats = require('stats-js'); import * as DAT from 'dat.gui'; import Icosphere from './geometry/Icosphere'; +import Cube from './geometry/Cube'; import Square from './geometry/Square'; import OpenGLRenderer from './rendering/gl/OpenGLRenderer'; import Camera from './Camera'; @@ -11,19 +12,30 @@ import ShaderProgram, {Shader} from './rendering/gl/ShaderProgram'; // Define an object with application parameters and button callbacks // This will be referred to by dat.GUI's functions that add GUI elements. const controls = { - tesselations: 5, + 'terrain frequency': 0.5, + 'earth to alien': 0.0, + 'forest density': 0.2, 'Load Scene': loadScene, // A function pointer, essentially }; let icosphere: Icosphere; +let cube: Cube; let square: Square; + let prevTesselations: number = 5; +let cubeColor: vec4 = vec4.fromValues(1, 0, 1, 1); + +// Procedural Controls +let terrainFreq: number = 0.5; +let earthToAlien: number = 0.0; +let forestScale: number = 0.2; + +let time: number = 0; +let tesselations: number = 5; function loadScene() { - icosphere = new Icosphere(vec3.fromValues(0, 0, 0), 1, controls.tesselations); + icosphere = new Icosphere(vec3.fromValues(0, 0, 0), 1, tesselations); icosphere.create(); - square = new Square(vec3.fromValues(0, 0, 0)); - square.create(); } function main() { @@ -37,7 +49,10 @@ function main() { // Add controls to the gui const gui = new DAT.GUI(); - gui.add(controls, 'tesselations', 0, 8).step(1); + + gui.add(controls, 'terrain frequency', 0.3, 2.0).step(0.05).onChange(function() { terrainFreq = controls['terrain frequency'] }); + gui.add(controls, 'earth to alien', 0.0, 1.0).step(0.05).onChange(function() { earthToAlien = controls['earth to alien'] }); + gui.add(controls, 'forest density', 0.0, 1.0).step(0.05).onChange(function() { forestScale = controls['forest density'] }); gui.add(controls, 'Load Scene'); // get canvas and webgl context @@ -56,7 +71,10 @@ function main() { const camera = new Camera(vec3.fromValues(0, 0, 5), vec3.fromValues(0, 0, 0)); const renderer = new OpenGLRenderer(canvas); - renderer.setClearColor(0.2, 0.2, 0.2, 1); + + // Create starry outerspace clear color + let darkBlue = vec4.fromValues(0.0 / 11.0, 0.0 / 255.0, .0 / 255.0, 1); + renderer.setClearColor(darkBlue); gl.enable(gl.DEPTH_TEST); const lambert = new ShaderProgram([ @@ -64,22 +82,41 @@ function main() { new Shader(gl.FRAGMENT_SHADER, require('./shaders/lambert-frag.glsl')), ]); + const planet_shader = new ShaderProgram([ + new Shader(gl.VERTEX_SHADER, require('./shaders/planet-vert.glsl')), + new Shader(gl.FRAGMENT_SHADER, require('./shaders/planet-frag.glsl')), + ]); + + // const custom_shader = new ShaderProgram([ + // new Shader(gl.VERTEX_SHADER, require('./shaders/custom-vert.glsl')), + // new Shader(gl.FRAGMENT_SHADER, require('./shaders/custom-frag.glsl')), + // ]); + // This function will be called every frame function tick() { + + planet_shader.setTime(time); + planet_shader.setTerrainFreq(terrainFreq); + planet_shader.setEarthToAlien(earthToAlien); + planet_shader.setForestScale(forestScale); + planet_shader.setCamera([camera.getEye()[0], camera.getEye()[1], camera.getEye()[2], 1.0]); + + time++; + camera.update(); stats.begin(); gl.viewport(0, 0, window.innerWidth, window.innerHeight); renderer.clear(); - if(controls.tesselations != prevTesselations) + if(tesselations != prevTesselations) { - prevTesselations = controls.tesselations; + prevTesselations = 5; icosphere = new Icosphere(vec3.fromValues(0, 0, 0), 1, prevTesselations); icosphere.create(); } - renderer.render(camera, lambert, [ + + renderer.render(camera, planet_shader, [ icosphere, - // square, - ]); + ], cubeColor); stats.end(); // Tell the browser to call `tick` again whenever it renders a new frame diff --git a/src/rendering/gl/OpenGLRenderer.ts b/src/rendering/gl/OpenGLRenderer.ts index 7e527c2..3e3b656 100644 --- a/src/rendering/gl/OpenGLRenderer.ts +++ b/src/rendering/gl/OpenGLRenderer.ts @@ -9,8 +9,9 @@ class OpenGLRenderer { constructor(public canvas: HTMLCanvasElement) { } - setClearColor(r: number, g: number, b: number, a: number) { - gl.clearColor(r, g, b, a); + setClearColor(color: vec4) { + let black = vec4.fromValues(0.0 / 255.0, 0.0 / 255.0, 0.0 / 255.0, 1.0); + gl.clearColor(color[0], color[1], color[2], color[3]); } setSize(width: number, height: number) { @@ -22,10 +23,10 @@ class OpenGLRenderer { gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); } - render(camera: Camera, prog: ShaderProgram, drawables: Array) { + render(camera: Camera, prog: ShaderProgram, drawables: Array, cubeColor: vec4) { let model = mat4.create(); let viewProj = mat4.create(); - let color = vec4.fromValues(1, 0, 0, 1); + let color = cubeColor; mat4.identity(model); mat4.multiply(viewProj, camera.projectionMatrix, camera.viewMatrix); diff --git a/src/rendering/gl/ShaderProgram.ts b/src/rendering/gl/ShaderProgram.ts index 67fef40..f4d3fe0 100644 --- a/src/rendering/gl/ShaderProgram.ts +++ b/src/rendering/gl/ShaderProgram.ts @@ -1,4 +1,4 @@ -import {vec4, mat4} from 'gl-matrix'; +import {vec3, vec4, mat4} from 'gl-matrix'; import Drawable from './Drawable'; import {gl} from '../../globals'; @@ -29,6 +29,12 @@ class ShaderProgram { unifModelInvTr: WebGLUniformLocation; unifViewProj: WebGLUniformLocation; unifColor: WebGLUniformLocation; + unifTime: WebGLUniformLocation; + unifCamera: WebGLUniformLocation; + + unifTerrainFreq: WebGLUniformLocation; + unifEarthToAlien: WebGLUniformLocation; + unifForestScale: WebGLUniformLocation; constructor(shaders: Array) { this.prog = gl.createProgram(); @@ -48,6 +54,13 @@ class ShaderProgram { this.unifModelInvTr = gl.getUniformLocation(this.prog, "u_ModelInvTr"); this.unifViewProj = gl.getUniformLocation(this.prog, "u_ViewProj"); this.unifColor = gl.getUniformLocation(this.prog, "u_Color"); + this.unifTime = gl.getUniformLocation(this.prog, "u_Time"); + this.unifCamera = gl.getUniformLocation(this.prog, "u_Camera"); + + // Procedural Controls + this.unifTerrainFreq = gl.getUniformLocation(this.prog, "terrainFreq"); + this.unifEarthToAlien = gl.getUniformLocation(this.prog, "earthToAlien"); + this.unifForestScale = gl.getUniformLocation(this.prog, "forestScale"); } use() { @@ -85,6 +98,42 @@ class ShaderProgram { } } + setTime(time: number) { + this.use(); + if (this.unifTime !== -1) { + gl.uniform1f(this.unifTime, time); + } + } + + setCamera(cam: vec4) { + this.use(); + if (this.unifCamera !== -1) { + gl.uniform4fv(this.unifCamera, cam); + } + } + + setTerrainFreq(freq: number) { + this.use(); + if (this.unifTerrainFreq !== -1) { + gl.uniform1f(this.unifTerrainFreq, freq); + } + } + + setEarthToAlien(earthToAlien: number) { + this.use(); + if (this.unifEarthToAlien !== -1) { + gl.uniform1f(this.unifEarthToAlien, earthToAlien); + } + } + + setForestScale(forestScale: number) { + this.use(); + if (this.unifForestScale !== -1) { + gl.uniform1f(this.unifForestScale, forestScale); + } + } + + draw(d: Drawable) { this.use(); diff --git a/src/shaders/custom-frag.glsl b/src/shaders/custom-frag.glsl new file mode 100644 index 0000000..2fe890a --- /dev/null +++ b/src/shaders/custom-frag.glsl @@ -0,0 +1,148 @@ +#version 300 es +#define PI 3.1415926535897932384626433832795 + +// This is a fragment shader. If you've opened this file first, please +// open and read lambert.vert.glsl before reading on. +// Unlike the vertex shader, the fragment shader actually does compute +// the shading of geometry. For every pixel in your program's output +// screen, the fragment shader is run for every bit of geometry that +// particular pixel overlaps. By implicitly interpolating the position +// data passed into the fragment shader by the vertex shader, the fragment shader +// can compute what color to apply to its pixel based on things like vertex +// position, light position, and vertex color. +precision highp float; + +uniform vec4 u_Color; // The color with which to render this instance of geometry. + +// These are the interpolated values out of the rasterizer, so you can't know +// their specific values without knowing the vertices that contributed to them +in vec4 fs_Nor; +in vec4 fs_LightVec; +in vec4 fs_Col; +in vec4 fs_Pos; + +out vec4 out_Col; // This is the final output color that you will see on your + // screen for the pixel that is currently being processed. + +/** + * Linearly Re-maps a value from one range to another + */ +float map(float value, float old_lo, float old_hi, float new_lo, float new_hi) +{ + float old_range = old_hi - old_lo; + if (old_range == 0.0) { + return new_lo; + } else { + float new_range = new_hi - new_lo; + return (((value - old_lo) * new_range) / old_range) + new_lo; + } +} + +/** + * The canonical GLSL hash function + */ +float hash(float x) +{ + return fract(sin(x) * 43758.5453123); +} + +/** + * Nothing is mathematically sound about anything below: + * I just chose values based on experimentation and some + * intuitions I have about what makes a good hash function + */ +vec3 gradient(vec3 cell) +{ + float h_i = hash(cell.x); + float h_j = hash(cell.y + pow(h_i, 3.0)); + float h_k = hash(cell.z + pow(h_j, 5.0)); + float ii = map(fract(h_i + h_j + h_k), 0.0, 1.0, -1.0, 1.0); + float jj = map(fract(h_j + h_k), 0.0, 1.0, -1.0, 1.0); + float kk = map(h_k, 0.0, 1.0, -1.0, 1.0); + return normalize(vec3(ii, jj, kk)); +} + +/** + * Perlin's "ease-curve" fade function + */ +float fade(float t) +{ + float t3 = t * t * t; + float t4 = t3 * t; + float t5 = t4 * t; + return (6.0 * t5) - (15.0 * t4) + (10.0 * t3); +} + +float noise(in vec3 coord) +{ + vec3 cell = floor(coord); + vec3 unit = fract(coord); + + vec3 unit_000 = unit; + vec3 unit_100 = unit - vec3(1.0, 0.0, 0.0); + vec3 unit_001 = unit - vec3(0.0, 0.0, 1.0); + vec3 unit_101 = unit - vec3(1.0, 0.0, 1.0); + vec3 unit_010 = unit - vec3(0.0, 1.0, 0.0); + vec3 unit_110 = unit - vec3(1.0, 1.0, 0.0); + vec3 unit_011 = unit - vec3(0.0, 1.0, 1.0); + vec3 unit_111 = unit - 1.0; + + vec3 c_000 = cell; + vec3 c_100 = cell + vec3(1.0, 0.0, 0.0); + vec3 c_001 = cell + vec3(0.0, 0.0, 1.0); + vec3 c_101 = cell + vec3(1.0, 0.0, 1.0); + vec3 c_010 = cell + vec3(0.0, 1.0, 0.0); + vec3 c_110 = cell + vec3(1.0, 1.0, 0.0); + vec3 c_011 = cell + vec3(0.0, 1.0, 1.0); + vec3 c_111 = cell + 1.0; + + float wx = fade(unit.x); + float wy = fade(unit.y); + float wz = fade(unit.z); + + float x000 = dot(gradient(c_000), unit_000); + float x100 = dot(gradient(c_100), unit_100); + float x001 = dot(gradient(c_001), unit_001); + float x101 = dot(gradient(c_101), unit_101); + float x010 = dot(gradient(c_010), unit_010); + float x110 = dot(gradient(c_110), unit_110); + float x011 = dot(gradient(c_011), unit_011); + float x111 = dot(gradient(c_111), unit_111); + + float y0 = mix(x000, x100, wx); + float y1 = mix(x001, x101, wx); + float y2 = mix(x010, x110, wx); + float y3 = mix(x011, x111, wx); + + float z0 = mix(y0, y2, wy); + float z1 = mix(y1, y3, wy); + + return mix(z0, z1, wz); +} + + +void main() +{ + // Material base color (before shading) + vec4 diffuseColor = u_Color; + + // Calculate the diffuse term for Lambert shading + float diffuseTerm = dot(normalize(fs_Nor), normalize(fs_LightVec)); + // Avoid negative lighting values + diffuseTerm = clamp(diffuseTerm, 0.0, 1.0); + + float ambientTerm = 0.2; + + float lightIntensity = diffuseTerm + ambientTerm; //Add a small float value to the color multiplier + //to simulate ambient lighting. This ensures that faces that are not + //lit by our point light are not completely black. + + float freq = 7.0; + float noiseVal = 1.0 - abs(noise(freq * fs_Pos.xyz)); + + if (noiseVal > 0.97) { + out_Col = vec4(vec3(1,1,1), 1); + } else { + out_Col = vec4(noiseVal * diffuseColor.rgb * lightIntensity, diffuseColor.a); + } +} diff --git a/src/shaders/custom-vert.glsl b/src/shaders/custom-vert.glsl new file mode 100644 index 0000000..97849be --- /dev/null +++ b/src/shaders/custom-vert.glsl @@ -0,0 +1,68 @@ +#version 300 es + +//This is a vertex shader. While it is called a "shader" due to outdated conventions, this file +//is used to apply matrix transformations to the arrays of vertex data passed to it. +//Since this code is run on your GPU, each vertex is transformed simultaneously. +//If it were run on your CPU, each vertex would have to be processed in a FOR loop, one at a time. +//This simultaneous transformation allows your program to run much faster, especially when rendering +//geometry with millions of vertices. + +uniform mat4 u_Model; // The matrix that defines the transformation of the + // object we're rendering. In this assignment, + // this will be the result of traversing your scene graph. + +uniform mat4 u_ModelInvTr; // The inverse transpose of the model matrix. + // This allows us to transform the object's normals properly + // if the object has been non-uniformly scaled. + +uniform mat4 u_ViewProj; // The matrix that defines the camera's transformation. + // We've written a static matrix for you to use for HW2, + // but in HW3 you'll have to generate one yourself + +uniform highp float u_Time; + +in vec4 vs_Pos; // The array of vertex positions passed to the shader + +in vec4 vs_Nor; // The array of vertex normals passed to the shader + +in vec4 vs_Col; // The array of vertex colors passed to the shader. + +out vec4 fs_Nor; // The array of normals that has been transformed by u_ModelInvTr. This is implicitly passed to the fragment shader. +out vec4 fs_LightVec; // The direction in which our virtual light lies, relative to each vertex. This is implicitly passed to the fragment shader. +out vec4 fs_Col; // The color of each vertex. This is implicitly passed to the fragment shader. +out vec4 fs_Pos; + +const vec4 lightPos = vec4(5, 5, 3, 1); //The position of our virtual light, which is used to compute the shading of + //the geometry in the fragment shader. + +void main() +{ + fs_Col = vs_Col; // Pass the vertex colors to the fragment shader for interpolation + fs_Pos = vs_Pos; + + mat3 invTranspose = mat3(u_ModelInvTr); + fs_Nor = vec4(invTranspose * vec3(vs_Nor), 0); // Pass the vertex normals to the fragment shader for interpolation. + // Transform the geometry's normals by the inverse transpose of the + // model matrix. This is necessary to ensure the normals remain + // perpendicular to the surface after the surface is transformed by + // the model matrix. + + vec4 final_Pos = vs_Pos; + final_Pos.y += 1.0 / 2.5 * sin(0.01 * u_Time) * fs_Nor.z; + final_Pos.z += 1.0 / 2.5 * sin(0.01 * u_Time) * fs_Nor.y; + final_Pos.x += 1.0 / 2.5 * sin(0.01 * u_Time) * fs_Nor.x; + + vec4 sphere_Pos = final_Pos; + sphere_Pos.x = sqrt(vs_Pos.y * vs_Pos.y - vs_Pos.z * vs_Pos.z); + sphere_Pos.y = sqrt(vs_Pos.x * vs_Pos.x - vs_Pos.z * vs_Pos.z); + sphere_Pos.z = sqrt(vs_Pos.x * vs_Pos.x - vs_Pos.y * vs_Pos.y); + + vec4 modelposition_mix = mix(final_Pos, sphere_Pos, 1.0 / 4.0 * sin(0.01 * u_Time)); + + vec4 modelposition = u_Model * modelposition_mix; // Temporarily store the transformed vertex positions for use below + + fs_LightVec = lightPos - modelposition; // Compute the direction in which the light source lies + + gl_Position = u_ViewProj * modelposition;// gl_Position is a built-in variable of OpenGL which is + // used to render the final positions of the geometry's vertices +} diff --git a/src/shaders/lambert-frag.glsl b/src/shaders/lambert-frag.glsl index 2b8e11b..a1385c8 100644 --- a/src/shaders/lambert-frag.glsl +++ b/src/shaders/lambert-frag.glsl @@ -40,4 +40,4 @@ void main() // Compute final shaded color out_Col = vec4(diffuseColor.rgb * lightIntensity, diffuseColor.a); -} +} \ No newline at end of file diff --git a/src/shaders/lambert-vert.glsl b/src/shaders/lambert-vert.glsl index 7f95a37..3aeffd5 100644 --- a/src/shaders/lambert-vert.glsl +++ b/src/shaders/lambert-vert.glsl @@ -50,4 +50,4 @@ void main() gl_Position = u_ViewProj * modelposition;// gl_Position is a built-in variable of OpenGL which is // used to render the final positions of the geometry's vertices -} +} \ No newline at end of file diff --git a/src/shaders/planet-frag.glsl b/src/shaders/planet-frag.glsl new file mode 100644 index 0000000..dc767f8 --- /dev/null +++ b/src/shaders/planet-frag.glsl @@ -0,0 +1,491 @@ +#version 300 es +#define PI 3.1415926535897932384626433832795 + +// This is a fragment shader. If you've opened this file first, please +// open and read lambert.vert.glsl before reading on. +// Unlike the vertex shader, the fragment shader actually does compute +// the shading of geometry. For every pixel in your program's output +// screen, the fragment shader is run for every bit of geometry that +// particular pixel overlaps. By implicitly interpolating the position +// data passed into the fragment shader by the vertex shader, the fragment shader +// can compute what color to apply to its pixel based on things like vertex +// position, light position, and vertex color. +precision highp float; + +uniform vec4 u_Color; // The color with which to render this instance of geometry. + +uniform highp float u_Time; +uniform vec4 u_Camera; + +// Procedural Controls +uniform highp float terrainFreq; // Sets the frequency of noise that outputs terrain elevations +uniform highp float earthToAlien; // 0.0 -> earth color palette, 1.0 -> alien color palette +uniform highp float forestScale; // 0.0 -> earth color palette, 1.0 -> alien color palette + +// These are the interpolated values out of the rasterizer, so you can't know +// their specific values without knowing the vertices that contributed to them +in vec4 fs_Nor; +in vec4 fs_LightVec; +in vec4 fs_Col; +in vec4 fs_Pos; + +out vec4 out_Col; // This is the final output color that you will see on your + // screen for the pixel that is currently being processed. + + +// FBM Noise ------------------------------------ +float noise3D(vec3 p) +{ + return fract(sin(dot(p ,vec3(12.9898,78.233,128.852))) * 43758.5453)*2.0-1.0; +} + +float simplex3D(vec3 p) +{ + + float f3 = 1.0/3.0; + float s = (p.x+p.y+p.z)*f3; + int i = int(floor(p.x+s)); + int j = int(floor(p.y+s)); + int k = int(floor(p.z+s)); + + float g3 = 1.0/6.0; + float t = float((i+j+k))*g3; + float x0 = float(i)-t; + float y0 = float(j)-t; + float z0 = float(k)-t; + x0 = p.x-x0; + y0 = p.y-y0; + z0 = p.z-z0; + + int i1,j1,k1; + int i2,j2,k2; + + if(x0>=y0) + { + if(y0>=z0){ i1=1; j1=0; k1=0; i2=1; j2=1; k2=0; } // X Y Z order + else if(x0>=z0){ i1=1; j1=0; k1=0; i2=1; j2=0; k2=1; } // X Z Y order + else { i1=0; j1=0; k1=1; i2=1; j2=0; k2=1; } // Z X Z order + } + else + { + if(y0=0.0) + { + t0*=t0; + n0 = t0 * t0 * dot(gr0, vec3(x0, y0, z0)); + } + float t1 = 0.5 - x1*x1 - y1*y1 - z1*z1; + if(t1>=0.0) + { + t1*=t1; + n1 = t1 * t1 * dot(gr1, vec3(x1, y1, z1)); + } + float t2 = 0.5 - x2*x2 - y2*y2 - z2*z2; + if(t2>=0.0) + { + t2 *= t2; + n2 = t2 * t2 * dot(gr2, vec3(x2, y2, z2)); + } + float t3 = 0.5 - x3*x3 - y3*y3 - z3*z3; + if(t3>=0.0) + { + t3 *= t3; + n3 = t3 * t3 * dot(gr3, vec3(x3, y3, z3)); + } + return 96.0*(n0+n1+n2+n3); + +} + +float fbm(vec3 p) +{ + float f; + f = 0.50000*simplex3D( p ); p = p*2.01; + f += 0.25000*simplex3D( p ); p = p*2.02; + f += 0.12500*simplex3D( p ); p = p*2.03; + f += 0.06250*simplex3D( p ); p = p*2.04; + f += 0.03125*simplex3D( p ); + return f*0.5+0.5; +} + +// Noise2 ------------------------------------ +float hash(float n) { return fract(sin(n) * 1e4); } +float hash(vec2 p) { return fract(1e4 * sin(17.0 * p.x + p.y * 0.1) * (0.1 + abs(sin(p.y * 13.0 + p.x)))); } + +float noise2(vec3 x) { + const vec3 step = vec3(110, 241, 171); + + vec3 i = floor(x); + vec3 f = fract(x); + + float n = dot(i, step); + + vec3 u = f * f * (3.0 - 2.0 * f); + return mix(mix(mix( hash(n + dot(step, vec3(0, 0, 0))), hash(n + dot(step, vec3(1, 0, 0))), u.x), + mix( hash(n + dot(step, vec3(0, 1, 0))), hash(n + dot(step, vec3(1, 1, 0))), u.x), u.y), + mix(mix( hash(n + dot(step, vec3(0, 0, 1))), hash(n + dot(step, vec3(1, 0, 1))), u.x), + mix( hash(n + dot(step, vec3(0, 1, 1))), hash(n + dot(step, vec3(1, 1, 1))), u.x), u.y), u.z); +} + +// Noise3 ------------------------------------ +float noise3(float x) { + float i = floor(x); + float f = fract(x); + float u = f * f * (3.0 - 2.0 * f); + return mix(hash(i), hash(i + 1.0), u); +} + +// Perlin Noise ------------------------------------ +float map(float value, float old_lo, float old_hi, float new_lo, float new_hi) +{ + float old_range = old_hi - old_lo; + if (old_range == 0.0) { + return new_lo; + } else { + float new_range = new_hi - new_lo; + return (((value - old_lo) * new_range) / old_range) + new_lo; + } +} + +/** + * The canonical GLSL hash function + */ +float hash1(float x) +{ + return fract(sin(x) * 43758.5453123); +} + +/** + * Nothing is mathematically sound about anything below: + * I just chose values based on experimentation and some + * intuitions I have about what makes a good hash function + */ +vec3 gradient(vec3 cell) +{ + float h_i = hash1(cell.x); + float h_j = hash1(cell.y + pow(h_i, 3.0)); + float h_k = hash1(cell.z + pow(h_j, 5.0)); + float ii = map(fract(h_i + h_j + h_k), 0.0, 1.0, -1.0, 1.0); + float jj = map(fract(h_j + h_k), 0.0, 1.0, -1.0, 1.0); + float kk = map(h_k, 0.0, 1.0, -1.0, 1.0); + return normalize(vec3(ii, jj, kk)); +} + +/** + * Perlin's "ease-curve" fade function + */ +float fade(float t) +{ + float t3 = t * t * t; + float t4 = t3 * t; + float t5 = t4 * t; + return (6.0 * t5) - (15.0 * t4) + (10.0 * t3); +} + +float pnoise(in vec3 coord) +{ + vec3 cell = floor(coord); + vec3 unit = fract(coord); + + vec3 unit_000 = unit; + vec3 unit_100 = unit - vec3(1.0, 0.0, 0.0); + vec3 unit_001 = unit - vec3(0.0, 0.0, 1.0); + vec3 unit_101 = unit - vec3(1.0, 0.0, 1.0); + vec3 unit_010 = unit - vec3(0.0, 1.0, 0.0); + vec3 unit_110 = unit - vec3(1.0, 1.0, 0.0); + vec3 unit_011 = unit - vec3(0.0, 1.0, 1.0); + vec3 unit_111 = unit - 1.0; + + vec3 c_000 = cell; + vec3 c_100 = cell + vec3(1.0, 0.0, 0.0); + vec3 c_001 = cell + vec3(0.0, 0.0, 1.0); + vec3 c_101 = cell + vec3(1.0, 0.0, 1.0); + vec3 c_010 = cell + vec3(0.0, 1.0, 0.0); + vec3 c_110 = cell + vec3(1.0, 1.0, 0.0); + vec3 c_011 = cell + vec3(0.0, 1.0, 1.0); + vec3 c_111 = cell + 1.0; + + float wx = fade(unit.x); + float wy = fade(unit.y); + float wz = fade(unit.z); + + float x000 = dot(gradient(c_000), unit_000); + float x100 = dot(gradient(c_100), unit_100); + float x001 = dot(gradient(c_001), unit_001); + float x101 = dot(gradient(c_101), unit_101); + float x010 = dot(gradient(c_010), unit_010); + float x110 = dot(gradient(c_110), unit_110); + float x011 = dot(gradient(c_011), unit_011); + float x111 = dot(gradient(c_111), unit_111); + + float y0 = mix(x000, x100, wx); + float y1 = mix(x001, x101, wx); + float y2 = mix(x010, x110, wx); + float y3 = mix(x011, x111, wx); + + float z0 = mix(y0, y2, wy); + float z1 = mix(y1, y3, wy); + + return mix(z0, z1, wz); +} + +// Forest noise function +float forestNoise(vec3 noiseInput) { + float smallForestFreq = 30.0 * forestScale; + float largeForestFreq = 25.0 * forestScale; + float smallForestNoise = fbm(smallForestFreq * noiseInput + 20.0); + float largeForestNoise = 1.0 - pnoise(largeForestFreq * noiseInput); + float sizeNoise = fbm(smallForestFreq - largeForestFreq * noiseInput + 20.0); + return mix(smallForestNoise, largeForestNoise, sizeNoise); +} + +float GetBias(float time, float bias) { + return (time / ((((1.0/bias) - 2.0) * (1.0 - time)) + 1.0)); +} + +float GetGain(float time, float gain) { + if (time < 0.5) { + return GetBias(time * 2.0, gain) / 2.0; + } else { + return GetBias(time * 2.0 - 1.0, 1.0 - gain) / 2.0 + 0.5; + } +} + +vec3 rgb(float r, float g, float b) { + return vec3(r / 255.0, g / 255.0, b / 255.0); +} + +mat4 rotationX( in float angle ) { + return mat4( 1.0, 0, 0, 0, + 0, cos(angle), -sin(angle), 0, + 0, sin(angle), cos(angle), 0, + 0, 0, 0, 1); +} + +mat4 rotationY( in float angle ) { + return mat4( cos(angle), 0, sin(angle), 0, + 0, 1.0, 0, 0, + -sin(angle), 0, cos(angle), 0, + 0, 0, 0, 1); +} + +mat4 rotationZ( in float angle ) { + return mat4( cos(angle), -sin(angle), 0, 0, + sin(angle), cos(angle), 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); +} + +// Calculates the elevation of a given point based on its noise value +float getElevation(vec3 noiseInput) { + + float noise = fbm(noiseInput); + + float waterElevation = 0.9; + float beachElevation = 0.93; + float landElevation = 1.0; + float mountElevation1 = 1.05; + float mountElevation2 = 1.7; + float mountElevation3 = 1.7; + + float waveNoise = noise2(5.0 * noise2((0.0006 * u_Time) + vec3(noiseInput) + noiseInput) + noiseInput); + float elevation = mix(waterElevation, waterElevation + 0.02, waveNoise); + + //float elevation = waterElevation; + + // Creates beach level + if (noise > 0.4 && noise < 0.52) { + elevation = beachElevation; + } else if (noise > 0.52 && noise < 0.53) { + float x = GetBias((noise - 0.52) / 0.01, 0.3); + elevation = mix(beachElevation, waterElevation, x); + } + + // Creates land level + float forestNoise = forestNoise(noiseInput); + if (noise > 0.48 && noise < 0.5) { + float x = GetBias((noise - 0.48) / 0.02, 0.7); + elevation = mix(landElevation, beachElevation, x); + } else if (noise > 0.4 && noise < 0.48) { + float x = GetGain((noise - 0.4) / 0.08, 0.9); + elevation = mix(landElevation * ((forestNoise * 0.08) + landElevation), landElevation, x); + } + + // Creates mountain level + float mountainNoise = fbm(10.0 * noiseInput + 20.0); + if (noise > 0.37 && noise < 0.4) { + float x = GetBias((noise - 0.37) / 0.03, 0.5); + elevation = mix(landElevation * ((forestNoise * 0.08) + landElevation), mountElevation1, x); + } else if (noise < 0.37) { + float x = GetGain(noise / 0.37, mountainNoise); + elevation = mix(mountElevation2, mountElevation1, x); + } + + return elevation; + +} + +void main() +{ + // Material base color (before shading) + vec4 diffuseColor = u_Color; + vec3 noiseInput = fs_Pos.xyz * terrainFreq; + + float noise = fbm(noiseInput); + + // Calculate new normal based on elevated neighbors passed in from vertex shader + // Get tangent and bitangent vectors + vec3 tangent = cross(vec3(0.0, 1.0, 0.0), fs_Nor.xyz); + vec3 bitangent = cross(fs_Nor.xyz, tangent); + + // Get offset amount for epsilon distance away + float e = 0.00001; + + // Get neighbors + vec3 p1 = noiseInput + vec3(e) * tangent; + vec3 p2 = noiseInput + vec3(e) * bitangent; + vec3 p3 = noiseInput - vec3(e) * tangent; + vec3 p4 = noiseInput - vec3(e) * bitangent; + + float p1_e = getElevation(p1); + float p2_e = getElevation(p2); + float p3_e = getElevation(p3); + float p4_e = getElevation(p4); + + vec3 p1_deformed = p1 + fs_Nor.xyz * p1_e; + vec3 p2_deformed = p2 + fs_Nor.xyz * p2_e; + vec3 p3_deformed = p3 + fs_Nor.xyz * p3_e; + vec3 p4_deformed = p4 + fs_Nor.xyz * p4_e; + + vec4 norm_deformed = vec4(cross(normalize(p1_deformed - p3_deformed), normalize(p2_deformed - p4_deformed)), 1.0); + // out_Col = norm_deformed * 0.5 + 0.5; + // return; + + // Calculate the diffuse term for Lambert shading + vec4 rotated_lightVec = fs_LightVec * rotationY(0.008 * u_Time); + float diffuseTerm = dot(normalize(norm_deformed), normalize(rotated_lightVec)) * 10.0; + diffuseTerm = clamp(diffuseTerm, 0.0, 1.0); + + // Initialize specular value so it can be conditionally changed for mountains + vec3 view = normalize(u_Camera.xyz - fs_Pos.xyz); + vec3 light_distance = normalize(fs_LightVec.xyz - fs_Pos.xyz); + vec3 h = (light_distance + view) / 2.0; + float spec = 0.0; + float distance = length(fs_LightVec); + + vec3 surfaceColor = vec3(noise); + + // Earth color palette + vec3 waterCol_e = rgb(10.0, 145.0, 175.0); + vec3 deepWaterCol_e = rgb(0.0, 36.0, 118.0) * waterCol_e; + vec3 landCol_e = rgb(12.0, 145.0, 82.0); + vec3 deepLandCol_e = rgb(33.0, 125.0, 1.0) * landCol_e; + vec3 beachCol_e = rgb(255.0, 234.0, 200.0); + vec3 dirtCol_e = rgb(38.0, 11.0, 11.0); + vec3 mountainCol_e = rgb(53.0, 43.0, 53.0); + vec3 deepMountainCol_e = rgb(125.0, 97.0, 118.0) * mountainCol_e; + + // Alien color palette + vec3 waterCol_a = rgb(84.0, 195.0, 195.0); + vec3 deepWaterCol_a = rgb(42.0, 162.0, 147.0) * waterCol_a; + vec3 landCol_a = rgb(122.0, 93.0, 122.0); + vec3 deepLandCol_a = rgb(205.0, 16.0, 139.0) * landCol_a; + vec3 beachCol_a = rgb(236.0, 148.0, 111.0); + vec3 dirtCol_a = rgb(163.0, 8.0, 0.0); + vec3 mountainCol_a = rgb(68.0, 39.0, 122.0); + vec3 deepMountainCol_a = rgb(61.0, 61.0, 93.0) * mountainCol_a; + + // Earth To Alien color palette + vec3 waterCol = mix(waterCol_e, waterCol_a, earthToAlien); + vec3 deepWaterCol = mix(deepWaterCol_e, deepWaterCol_a, earthToAlien) * waterCol; + vec3 landCol = mix(landCol_e, landCol_a, earthToAlien); + vec3 deepLandCol = mix(deepLandCol_e, deepLandCol_a, earthToAlien) * landCol; + vec3 beachCol = mix(beachCol_e, beachCol_a, earthToAlien); + vec3 dirtCol = mix(dirtCol_e, dirtCol_a, earthToAlien); + vec3 mountainCol = mix(mountainCol_e, mountainCol_a, earthToAlien); + vec3 deepMountainCol = mix(deepMountainCol_a, deepMountainCol_e, earthToAlien) * mountainCol; + + vec3 black = rgb(0.0, 0.0, 0.0); + vec3 white = rgb(255.0, 255.0, 255.0); + + // Creates water level + float x = noise2(5.0 * noise2((0.0006 * u_Time) + vec3(noiseInput) + noiseInput) + noiseInput); + vec3 waterFinalCol = mix(deepWaterCol, waterCol, x); + surfaceColor = waterFinalCol; + + // Creates beach level + if (noise > 0.48 && noise < 0.52) { + surfaceColor = beachCol; + } else if (noise > 0.52 && noise < 0.53) { + float x = GetBias((noise - 0.52) / 0.01, 0.3); + surfaceColor = mix(beachCol, waterFinalCol, x); + } + + // Creates land level + float forestNoise = forestNoise(noiseInput); + if (noise > 0.48 && noise < 0.5) { + float x = GetBias((noise - 0.48) / 0.02, 0.3); + surfaceColor = mix(dirtCol, beachCol, x); + } else if (noise > 0.4 && noise < 0.48) { + float x = GetGain((noise - 0.4) / 0.08, 0.4); + surfaceColor = mix(landCol * forestNoise, deepLandCol, x); + } + + // Creates mountain level + float mountainNoise = fbm(10.0 * noiseInput + 20.0); + if (noise > 0.37 && noise < 0.4) { + float x = GetGain((noise - 0.37) / 0.03, mountainNoise); + surfaceColor = mix(deepMountainCol, landCol * forestNoise, x); + } else if (noise > 0.32 && noise < 0.37) { + float x = GetGain((noise - 0.32) / 0.05, mountainNoise); + float shininess = 3.0; + float specVal = clamp((pow(dot(h, norm_deformed.xyz), shininess)), 0.0, 1.0); + spec = mix(specVal, 0.0, x); + surfaceColor = mix(mountainCol, deepMountainCol, x); + } else if (noise < 0.4) { + float shininess = 3.0; + spec = clamp((pow(dot(h, norm_deformed.xyz), shininess)), 0.0, 1.0); + float x = GetGain(noise / 0.32, mountainNoise); + surfaceColor = mix(white, mountainCol, x); + } + + float ambientTerm = 0.3; + + float lightIntensity = diffuseTerm + ambientTerm; //Add a small float value to the color multiplier + //to simulate ambient lighting. This ensures that faces that are not + //lit by our point light are not completely black. + + + // Compute final shaded color + out_Col = vec4(surfaceColor.rgb * lightIntensity + spec, diffuseColor.a); + //out_Col = vec4(surfaceColor.rgb, diffuseColor.a); +} diff --git a/src/shaders/planet-vert.glsl b/src/shaders/planet-vert.glsl new file mode 100644 index 0000000..5e2ea26 --- /dev/null +++ b/src/shaders/planet-vert.glsl @@ -0,0 +1,367 @@ +#version 300 es + +//This is a vertex shader. While it is called a "shader" due to outdated conventions, this file +//is used to apply matrix transformations to the arrays of vertex data passed to it. +//Since this code is run on your GPU, each vertex is transformed simultaneously. +//If it were run on your CPU, each vertex would have to be processed in a FOR loop, one at a time. +//This simultaneous transformation allows your program to run much faster, especially when rendering +//geometry with millions of vertices. + +uniform mat4 u_Model; // The matrix that defines the transformation of the + // object we're rendering. In this assignment, + // this will be the result of traversing your scene graph. + +uniform mat4 u_ModelInvTr; // The inverse transpose of the model matrix. + // This allows us to transform the object's normals properly + // if the object has been non-uniformly scaled. + +uniform mat4 u_ViewProj; // The matrix that defines the camera's transformation. + // We've written a static matrix for you to use for HW2, + // but in HW3 you'll have to generate one yourself + +uniform highp float u_Time; + +uniform vec4 center; + +// Procedural Controls +uniform highp float terrainFreq; // Sets the frequency of noise that outputs terrain elevations +uniform highp float forestScale; // Sets the density of the forest + +in vec4 vs_Pos; // The array of vertex positions passed to the shader + +in vec4 vs_Nor; // The array of vertex normals passed to the shader + +in vec4 vs_Col; // The array of vertex colors passed to the shader. + +out vec4 fs_Nor; // The array of normals that has been transformed by u_ModelInvTr. This is implicitly passed to the fragment shader. +out vec4 fs_LightVec; // The direction in which our virtual light lies, relative to each vertex. This is implicitly passed to the fragment shader. +out vec4 fs_Col; // The color of each vertex. This is implicitly passed to the fragment shader. +out vec4 fs_Pos; + +out vec3 p1; // Neighbors a tiny epsilon away from our point which we will use to calculate the deformed normal +out vec3 p2; +out vec3 p3; +out vec3 p4; + +const vec4 lightPos = vec4(5, 5, 3, 1); //The position of our virtual light, which is used to compute the shading of + //the geometry in the fragment shader. + +// FBM Noise ------------------------------------ +float noise3D(vec3 p) +{ + return fract(sin(dot(p ,vec3(12.9898,78.233,128.852))) * 43758.5453)*2.0-1.0; +} + +float simplex3D(vec3 p) +{ + + float f3 = 1.0/3.0; + float s = (p.x+p.y+p.z)*f3; + int i = int(floor(p.x+s)); + int j = int(floor(p.y+s)); + int k = int(floor(p.z+s)); + + float g3 = 1.0/6.0; + float t = float((i+j+k))*g3; + float x0 = float(i)-t; + float y0 = float(j)-t; + float z0 = float(k)-t; + x0 = p.x-x0; + y0 = p.y-y0; + z0 = p.z-z0; + + int i1,j1,k1; + int i2,j2,k2; + + if(x0>=y0) + { + if(y0>=z0){ i1=1; j1=0; k1=0; i2=1; j2=1; k2=0; } // X Y Z order + else if(x0>=z0){ i1=1; j1=0; k1=0; i2=1; j2=0; k2=1; } // X Z Y order + else { i1=0; j1=0; k1=1; i2=1; j2=0; k2=1; } // Z X Z order + } + else + { + if(y0=0.0) + { + t0*=t0; + n0 = t0 * t0 * dot(gr0, vec3(x0, y0, z0)); + } + float t1 = 0.5 - x1*x1 - y1*y1 - z1*z1; + if(t1>=0.0) + { + t1*=t1; + n1 = t1 * t1 * dot(gr1, vec3(x1, y1, z1)); + } + float t2 = 0.5 - x2*x2 - y2*y2 - z2*z2; + if(t2>=0.0) + { + t2 *= t2; + n2 = t2 * t2 * dot(gr2, vec3(x2, y2, z2)); + } + float t3 = 0.5 - x3*x3 - y3*y3 - z3*z3; + if(t3>=0.0) + { + t3 *= t3; + n3 = t3 * t3 * dot(gr3, vec3(x3, y3, z3)); + } + return 96.0*(n0+n1+n2+n3); + +} + +float fbm(vec3 p) +{ + float f; + f = 0.50000*simplex3D( p ); p = p*2.01; + f += 0.25000*simplex3D( p ); p = p*2.02; + f += 0.12500*simplex3D( p ); p = p*2.03; + f += 0.06250*simplex3D( p ); p = p*2.04; + f += 0.03125*simplex3D( p ); + return f*0.5+0.5; +} + +// Noise2 ------------------------------------ +float hash(float n) { return fract(sin(n) * 1e4); } +float hash(vec2 p) { return fract(1e4 * sin(17.0 * p.x + p.y * 0.1) * (0.1 + abs(sin(p.y * 13.0 + p.x)))); } + +float noise2(vec3 x) { + const vec3 step = vec3(110, 241, 171); + + vec3 i = floor(x); + vec3 f = fract(x); + + float n = dot(i, step); + + vec3 u = f * f * (3.0 - 2.0 * f); + return mix(mix(mix( hash(n + dot(step, vec3(0, 0, 0))), hash(n + dot(step, vec3(1, 0, 0))), u.x), + mix( hash(n + dot(step, vec3(0, 1, 0))), hash(n + dot(step, vec3(1, 1, 0))), u.x), u.y), + mix(mix( hash(n + dot(step, vec3(0, 0, 1))), hash(n + dot(step, vec3(1, 0, 1))), u.x), + mix( hash(n + dot(step, vec3(0, 1, 1))), hash(n + dot(step, vec3(1, 1, 1))), u.x), u.y), u.z); +} + +// Noise3 ------------------------------------ +float noise3(float x) { + float i = floor(x); + float f = fract(x); + float u = f * f * (3.0 - 2.0 * f); + return mix(hash(i), hash(i + 1.0), u); +} + +// Perlin Noise ------------------------------ +float map(float value, float old_lo, float old_hi, float new_lo, float new_hi) +{ + float old_range = old_hi - old_lo; + if (old_range == 0.0) { + return new_lo; + } else { + float new_range = new_hi - new_lo; + return (((value - old_lo) * new_range) / old_range) + new_lo; + } +} + +float hash1(float x) +{ + return fract(sin(x) * 43758.5453123); +} + +vec3 gradient(vec3 cell) +{ + float h_i = hash1(cell.x); + float h_j = hash1(cell.y + pow(h_i, 3.0)); + float h_k = hash1(cell.z + pow(h_j, 5.0)); + float ii = map(fract(h_i + h_j + h_k), 0.0, 1.0, -1.0, 1.0); + float jj = map(fract(h_j + h_k), 0.0, 1.0, -1.0, 1.0); + float kk = map(h_k, 0.0, 1.0, -1.0, 1.0); + return normalize(vec3(ii, jj, kk)); +} + +/** + * Perlin's "ease-curve" fade function + */ +float fade(float t) +{ + float t3 = t * t * t; + float t4 = t3 * t; + float t5 = t4 * t; + return (6.0 * t5) - (15.0 * t4) + (10.0 * t3); +} + +float pnoise(in vec3 coord) +{ + vec3 cell = floor(coord); + vec3 unit = fract(coord); + + vec3 unit_000 = unit; + vec3 unit_100 = unit - vec3(1.0, 0.0, 0.0); + vec3 unit_001 = unit - vec3(0.0, 0.0, 1.0); + vec3 unit_101 = unit - vec3(1.0, 0.0, 1.0); + vec3 unit_010 = unit - vec3(0.0, 1.0, 0.0); + vec3 unit_110 = unit - vec3(1.0, 1.0, 0.0); + vec3 unit_011 = unit - vec3(0.0, 1.0, 1.0); + vec3 unit_111 = unit - 1.0; + + vec3 c_000 = cell; + vec3 c_100 = cell + vec3(1.0, 0.0, 0.0); + vec3 c_001 = cell + vec3(0.0, 0.0, 1.0); + vec3 c_101 = cell + vec3(1.0, 0.0, 1.0); + vec3 c_010 = cell + vec3(0.0, 1.0, 0.0); + vec3 c_110 = cell + vec3(1.0, 1.0, 0.0); + vec3 c_011 = cell + vec3(0.0, 1.0, 1.0); + vec3 c_111 = cell + 1.0; + + float wx = fade(unit.x); + float wy = fade(unit.y); + float wz = fade(unit.z); + + float x000 = dot(gradient(c_000), unit_000); + float x100 = dot(gradient(c_100), unit_100); + float x001 = dot(gradient(c_001), unit_001); + float x101 = dot(gradient(c_101), unit_101); + float x010 = dot(gradient(c_010), unit_010); + float x110 = dot(gradient(c_110), unit_110); + float x011 = dot(gradient(c_011), unit_011); + float x111 = dot(gradient(c_111), unit_111); + + float y0 = mix(x000, x100, wx); + float y1 = mix(x001, x101, wx); + float y2 = mix(x010, x110, wx); + float y3 = mix(x011, x111, wx); + + float z0 = mix(y0, y2, wy); + float z1 = mix(y1, y3, wy); + + return mix(z0, z1, wz); +} + +float GetBias(float time, float bias) { + return (time / ((((1.0/bias) - 2.0) * (1.0 - time)) + 1.0)); +} + +float GetGain(float time, float gain) { + if (time < 0.5) { + return GetBias(time * 2.0, gain) / 2.0; + } else { + return GetBias(time * 2.0 - 1.0, 1.0 - gain) / 2.0 + 0.5; + } +} +vec3 rgb(float r, float g, float b) { + return vec3(r / 255.0, g / 255.0, b / 255.0); +} + +// Forest noise function +float forestNoise(vec3 noiseInput) { + float smallForestFreq = 30.0 * forestScale; + float largeForestFreq = 25.0 * forestScale; + float smallForestNoise = fbm(smallForestFreq * noiseInput + 20.0); + float largeForestNoise = 1.0 - pnoise(largeForestFreq * noiseInput); + float sizeNoise = fbm(noiseInput + 20.0); + return mix(smallForestNoise, largeForestNoise, sizeNoise); +} + +// Calculates the elevation of a given point based on its noise value +float getElevation(vec3 noiseInput) { + + float noise = fbm(noiseInput); + + float waterElevation = 0.9; + float beachElevation = 0.93; + float landElevation = 1.0; + float mountElevation1 = 1.05; + float mountElevation2 = 1.7; + float mountElevation3 = 1.7; + + float waveNoise= noise2(10.0 * noise2((0.0006 * u_Time) + 3.0 * vec3(noiseInput) + noiseInput) + noiseInput); + float elevation = mix(waterElevation, waterElevation + 0.05, waveNoise); + + // Creates beach level + if (noise > 0.4 && noise < 0.52) { + elevation = beachElevation; + } else if (noise > 0.52 && noise < 0.53) { + float x = GetBias((noise - 0.52) / 0.01, 0.3); + elevation = mix(beachElevation, waterElevation, x); + } + + // Creates land level + float forestNoise = forestNoise(noiseInput); + if (noise > 0.48 && noise < 0.5) { + float x = GetBias((noise - 0.48) / 0.02, 0.7); + elevation = mix(landElevation, beachElevation, x); + } else if (noise > 0.4 && noise < 0.48) { + float x = GetGain((noise - 0.4) / 0.08, 0.9); + elevation = mix(landElevation * ((forestNoise * 0.08) + landElevation), landElevation, x); + } + + // Creates mountain level + float mountainNoise = fbm(10.0 * noiseInput + 20.0); + if (noise > 0.37 && noise < 0.4) { + float x = GetBias((noise - 0.37) / 0.03, 0.5); + elevation = mix(mountElevation1, landElevation * ((forestNoise * 0.08) + landElevation), x); + } else if (noise < 0.37) { + float x = GetGain(noise / 0.37, mountainNoise); + elevation = mix(mountElevation2, mountElevation1, x); + } + + return elevation; + +} + +void main() +{ + fs_Col = vs_Col; // Pass the vertex colors to the fragment shader for interpolation + + mat3 invTranspose = mat3(u_ModelInvTr); + fs_Nor = vec4(invTranspose * vec3(vs_Nor), 0); // Pass the vertex normals to the fragment shader for interpolation. + // Transform the geometry's normals by the inverse transpose of the + // model matrix. This is necessary to ensure the normals remain + // perpendicular to the surface after the surface is transformed by + // the model matrix. + + vec4 modelposition = u_Model * vs_Pos; // Temporarily store the transformed vertex positions for use below + + fs_LightVec = lightPos - modelposition; // Compute the direction in which the light source lies + + gl_Position = u_ViewProj * modelposition;// gl_Position is a built-in variable of OpenGL which is + // used to render the final positions of the geometry's vertices + + vec3 noiseInput = modelposition.xyz * terrainFreq; + float noise = fbm(noiseInput); + + float elevation = getElevation(noiseInput); + + vec3 offsetAmount = vec3(vs_Nor) * elevation; + vec3 noisyModelPosition = modelposition.xyz + offsetAmount; + gl_Position = u_ViewProj * vec4(noisyModelPosition, 1.0); + + fs_Pos = vs_Pos; + + } \ No newline at end of file