1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
|
"""
Demonstrates redering a terrain/height map on the fly without any
pre-generated geometry.
"""
import numpy as np
from pyrr import Matrix44, Matrix33
import moderngl
from _example import Example
class HeightmapOnTheFly(Example):
title = "Heightmap - On the fly"
gl_version = (3, 3)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.prog = self.ctx.program(
vertex_shader="""
#version 330
uniform int dim;
out vec2 uv;
void main() {
// grid position from gl_VertexID normalized
vec2 pos = vec2(gl_VertexID % dim, gl_VertexID / dim) / dim;
gl_Position = vec4(pos, 0.0, 1.0);
}
""",
geometry_shader="""
#version 330
uniform sampler2D heightmap;
uniform mat4 projection;
uniform mat4 modelview;
uniform mat3 normal_matrix;
uniform int dim;
uniform float terrain_size;
out vec2 g_uv;
// out vec3 g_pos;
out vec3 normal;
layout(points) in;
layout(triangle_strip, max_vertices = 4) out;
const float scale = 0.5;
const float height = -0.15;
float calculateHeight(float h) {
return h * scale + height;
}
vec3 calculateNormal(vec2 uv, float step, float size) {
float hl = calculateHeight(texture(heightmap, uv + vec2(-step, 0.0)).r);
float hr = calculateHeight(texture(heightmap, uv + vec2(step, 0.0)).r);
float hu = calculateHeight(texture(heightmap, uv + vec2(0.0, step)).r);
float hd = calculateHeight(texture(heightmap, uv + vec2(0.0, -step)).r);
return normalize(vec3(hl - hr, hd - hu, size));
}
void main() {
// width and height of a quad
float size = terrain_size / dim;
// lower left corner of the quad
vec2 pos = gl_in[0].gl_Position.xy * terrain_size - terrain_size / 2.0;
vec2 uv = gl_in[0].gl_Position.xy;
float uv_step = 1.0 / dim;
// Calculate mvp
mat4 mvp = projection * modelview;
// Read heights for each corner
vec2 uv1 = uv + vec2(0.0, uv_step);
float h1 = calculateHeight(texture(heightmap, uv1).r);
vec2 uv2 = uv;
float h2 = calculateHeight(texture(heightmap, uv2).r);
vec2 uv3 = uv + vec2(uv_step, uv_step);
float h3 = calculateHeight(texture(heightmap, uv3).r);
vec2 uv4 = uv + vec2(uv_step, 0.0);
float h4 = calculateHeight(texture(heightmap, uv4).r);
// Upper left
vec4 pos1 = vec4(pos + vec2(0.0, size), h1, 1.0);
gl_Position = mvp * pos1;
g_uv = uv1;
normal = normal_matrix * calculateNormal(uv1, uv_step, size);
// g_pos = (modelview * pos1).xyz;
EmitVertex();
// Lower left
vec4 pos2 = vec4(pos, h2, 1.0);
gl_Position = mvp * pos2;
g_uv = uv2;
normal = normal_matrix * calculateNormal(uv2, uv_step, size);
// g_pos = (modelview * pos2).xyz;
EmitVertex();
// Upper right
vec4 pos3 = vec4(pos + vec2(size, size), h3, 1.0);
gl_Position = mvp * pos3;
g_uv = uv3;
normal = normal_matrix * calculateNormal(uv3, uv_step, size);
// g_pos = (modelview * pos3).xyz;
EmitVertex();
// Lower right
vec4 pos4 = vec4(pos + vec2(size, 0.0), h4, 1.0);
gl_Position = mvp * pos4;
g_uv = uv4;
normal = normal_matrix * calculateNormal(uv4, uv_step, size);
// g_pos = (modelview * pos4).xyz;
EmitVertex();
EndPrimitive();
}
""",
fragment_shader="""
#version 330
uniform sampler2D heightmap;
out vec4 fragColor;
in vec2 g_uv;
// in vec3 g_pos;
in vec3 normal;
void main() {
// vec3 normal = normalize(cross(dFdx(g_pos), dFdy(g_pos)));
float l = abs(dot(vec3(0, 0, 1), normal));
// fragColor = vec4(vec3(texture(heightmap, g_uv).r) * l, 1.0);
// fragColor = vec4(normal * l, 1.0);
fragColor = vec4(vec3(1.0) * l, 1.0);
}
""",
)
self.heightmap = self.load_texture_2d('heightmap_detailed.png')
self.heightmap.repeat_x = False
self.heightmap.repeat_y = False
self.dim = self.heightmap.width
self.vao = self.ctx.vertex_array(self.prog, [])
projection = Matrix44.perspective_projection(45.0, self.aspect_ratio, 0.1, 1000.0, dtype='f4')
self.prog['projection'].write(projection)
self.prog['dim'] = self.dim - 1
self.prog['terrain_size'] = 1.0
def render(self, time, frame_time):
self.ctx.clear()
self.ctx.enable(moderngl.DEPTH_TEST | moderngl.CULL_FACE)
angle = time * 0.2
lookat = Matrix44.look_at(
(np.cos(angle), np.sin(angle), 0.4),
(0.0, 0.0, 0.0),
(0.0, 0.0, 1.0),
dtype='f4',
)
normal_matrix = Matrix33.from_matrix44(lookat).inverse.transpose()
self.prog['modelview'].write(lookat)
self.prog['normal_matrix'].write(normal_matrix.astype('f4').tobytes())
self.heightmap.use(0)
self.vao.render(moderngl.POINTS, vertices=(self.dim - 1) ** 2)
if __name__ == '__main__':
HeightmapOnTheFly.run()
|