#version 330 // This source code is property of the Computer Graphics and Visualization // chair of the TU Dresden. Do not distribute! // Copyright (C) CGV TU Dresden - All Rights Reserved in vec4 position; out vec3 n; out vec2 uv; out vec2 road_uv; uniform mat4 mvp; //Returns the height of the procedural terrain at a given xz position float getTerrainHeight(vec2 p); vec3 calculate_normal(vec4 terrain_position); vec4 offset_point(vec4 base, vec2 offset); const vec2 offsets[6] = vec2[6]( vec2(0.0, 0.0), vec2(0.0, 1.0), vec2(1.0, 0.0), vec2(0.0, 1.0), vec2(1.0, 0.0), vec2(1.0, 1.0) ); void main() { vec4 terrain_position = vec4(position.x, getTerrainHeight(vec2(position.x, position.z)), position.z, position.w); n = calculate_normal(terrain_position); //n = vec3(0.0, 1.0, 0.0); uv = vec2(terrain_position.x / 255.0, terrain_position.z / 255.0); road_uv = offsets[gl_VertexID % 6]; gl_Position = mvp * terrain_position; } //source: https://gist.github.com/patriciogonzalezvivo/670c22f3966e662d2f83 float rand(vec2 c) { return 2 * fract(sin(dot(c.xy ,vec2(12.9898,78.233))) * 43758.5453) - 1; } float perlinNoise(vec2 p ) { vec2 ij = floor(p); vec2 xy = p - ij; xy = 3.*xy*xy-2.*xy*xy*xy; //xy = .5*(1.-cos(3.1415926 * xy)); float a = rand((ij+vec2(0.,0.))); float b = rand((ij+vec2(1.,0.))); float c = rand((ij+vec2(0.,1.))); float d = rand((ij+vec2(1.,1.))); float x1 = mix(a, b, xy.x); float x2 = mix(c, d, xy.x); return mix(x1, x2, xy.y); } //based on https://www.seedofandromeda.com/blogs/58-procedural-heightmap-terrain-generation float getTerrainHeight(vec2 p) { float total = 0.0; float maxAmplitude = 0.0; float amplitude = 1.0; float frequency = 0.02; for (int i = 0; i < 11; i++) { total += ((1.0 - abs(perlinNoise(p * frequency))) * 2.0 - 1.0) * amplitude; frequency *= 2.0; maxAmplitude += amplitude; amplitude *= 0.45; } return 15 * total / maxAmplitude; } vec4 offset_point(vec4 base, vec2 offset) { vec2 new_pos = vec2(base.x, base.z) + offset; float y = getTerrainHeight(new_pos); return vec4(new_pos.x, y, new_pos.y, base.w); } // calculate the position of the first vertex in this square vec4 pos_to_base(vec4 pos, int v_id) { return vec4(pos.x - offsets[v_id].x, pos.y, pos.z - offsets[v_id].y, pos.w); } // calculates the normal of terrain_position // it generates the complete triangle based on the gl_VertexID and then normal math vec3 calculate_normal(vec4 terrain_position) { int offset_index = gl_VertexID % 6; vec4 base_vertex = pos_to_base(terrain_position, offset_index); // the other 2 points from the triangle int points_index = 0; vec4 points[2]; int iterator_offset = 0; // second triangle offset if (offset_index > 2) { iterator_offset = 3; } for (int i = iterator_offset; i < (3 + iterator_offset); i++) { // skip for current vertex if (i == offset_index) { continue; } points[points_index] = offset_point(base_vertex, offsets[i]); points_index++; } // create connection vectors vec3 v1 = (points[0] - terrain_position).xyz; vec3 v2 = (points[1] - terrain_position).xyz; // calculate normal vec3 normal = normalize(cross(v1, v2)); // naively assume that a normal always has to look upwards if (normal.y < 0.0) { return -normal; } else { return normal; } }