In this MP you will
This MP is core, with no elective components. It assumes you have already completed the Logo MP.
You will submit a webpage that has
Submit an HTML file and any number of js and glsl files. No image files, JSON files, or CSS files are permitted for this assignment. Do not include spaces in file names as the submission server does not process them well.
You are welcome to use a JavaScript math library, such as the one used in in-class examples or others you might know.
HTML input elements, styling, and event handling are beyond the scope of this class, so we simply give you what you need here.
Your HTML file should have the following after the various
<script
elements:
<style>
body {margin: 0; border: none; padding: 0;
display: flex; flex-direction: column;
width: 100%; height: 100vh;
}.controls {
flex: 0 0 auto;
}.controls > * { margin: 1em; }
.display {
flex-grow: 1;
line-height: 0rem;
}</style>
</head>
<body>
<form class="controls" action="javascript:void(0);">
<label>Grid size: <input id="gridsize" type="number" value="50"/></label>
<label>Faults: <input id="faults" type="number" value="50"/></label>
<input id="submit" type="submit" value="Regenerate Terrain"/>
</form>
<div class="display">
<canvas width="300" height="300"></canvas>
</div>
</body>
</html>
You should register the following with a window.addEventListener('resize',fillscreen)
in your setup function after setting window.gl
to handle
canvas resize:
function fillScreen() {
let canvas = document.querySelector('canvas')
document.body.style.margin = '0'
.style.width = '100%'
canvas.style.height = '100%'
canvas.width = canvas.clientWidth
canvas.height = canvas.clientHeight
canvas.style.width = ''
canvas.style.height = ''
canvas.viewport(0,0, canvas.width, canvas.height)
gl// TO DO: compute a new projection matrix based on the width/height aspect ratio
}
You should listen to input events like so:
document.querySelector('#submit').addEventListener('click', event => {
const gridsize = Number(document.querySelector('#gridsize').value) || 2
const faults = Number(document.querySelector('#faults').value) || 0
// TO DO: generate a new gridsize-by-gridsize grid here, then apply faults to it
})
Because the program starts with no terrain generated yet, add a check in your draw callback that skips drawing if there’s nothing to draw.
When the button is clicked,
Create a square grid
with gridsize
vertices per side.
Don’t duplicate vertices. We will check that it has exactly
gridsize*gridsize
vertices and
(gridsize-1)*(gridsize-1)*2
triangles.
Your code should wok for any integer gridsize
between 2
and 255, inclusive.
gridsize
, as that simplifies setting up the view and
projection matrices. Positions between -1 and +1
are the most popular choice.
Larger gridsize
should result in higher-resolution
terrain, but not otherwise change the visual appearance; for example,
the terrain should occupy the same part of the field of view, have the
same ratio of height to width, etc, at all
gridsize
s.
Displace the vertices in the grid with faults
faults.
For 0 faults, the result should be perfectly flat. For more faults it should approach a fractal bumpy terrain.
Your code should wok for any non-negative integer
faults
; there is no upper limit to what we might
provide.
Faults should be distributed uniformly on average, with no bias towards particular fault directions no parts of the grid that get many fewer faults than other parts.
Normalize heights so that the terrain has the same height regardless of the number of faults (assuming there is at least one fault).
To do this, find the min and max heights after faulting, then replace each height with \text{height}' = c \dfrac{\text{height} - \frac{1}{2}(\text{max} + \text{min})}{\text{max} - \text{min}} where c is a constant that changes how tall the highest peak is.
Compute correct grid-based normals.
Because grids usually have 6 triangles around each vertex, generic triangle-based normals tend to look asymmetric. But we can do much better with less work by taking advantage of the structure of the grid.
Consider a vertex v with four neighboring vertices: s to the south, w to the west, n to the north, and e to the east. Then the normal at v is (n - s) \times (w - e). If v is on the edge of the grid and one or more of those neighbors is missing, use v itself in place of the missing neighbors in the normal computation.
Render the terrain with one white light source and with both diffuse and specular lighting. Use an earth tone (i.e. 1 > r > g > b > 0) for the diffuse color, picking a color light enough that lighting is obvious. Use white for the specular highlights. Have the light source coming from above the terrain, but not directly above it.
Have the camera moving around the terrain. The terrain should fill most of the camera’s field of view, and most of the terrain should be in the camera’s field of view. The camera should be a little above the highest peak in the terrain.
The light source should be fixed relative to the terrain: that is, changing the view should not change how much diffuse lighting any given part of the terrain has.
Lighting is created based on three vectors:
The lighting example we gave in class had light fixed relative to the camera. It did this by having the eye and light vectors fixed (stationary camera and light source) and changing the normal vector every frame in the vertex shader (moving object).
For this MP, you need to have the light fixed relative to the terrain. The natural way to do this is to have the light and normal vectors fixed (stationary terrain and sun) and change the eye vector every frame (moving camera).
In all cases we could change between view and world coordinates to do this in a different way. The monkey example used view coordinates, but could use world coordinates instead; the way I described the terrain sounds like world coordinates, but could be transformed into view coordinates instead. Which one you pick is up to you, but you must be consistent: if the vector to the eye is in view coordinates, the normal vector and and vector to the light must also be in
On both your development machine and when submitted to the submission server and then viewed by clicking the HTML link, the resulting page should initially be blank. Once the button is pressed a terrain should appear, filling most of the screen with a moving camera. One example might be the following:
To help with lighting we also share three videos where the terrain is set to look like a sphere; in particular, the terrain height is \sqrt{1-r^2}, where r is the distance from the point and the center of the terrain divided by the radius of the terrain; if that square root is an imaginary number, the terrain height is 0.
First we show a correctly-lit example:
We also show two incorrectly lit bad examples: