From deee9a818b32118d8a788745b5c2cc6d4da63511 Mon Sep 17 00:00:00 2001 From: Quantum Date: Sun, 26 Aug 2018 03:38:26 -0400 Subject: [PATCH] Convert planets to use shaders. --- punyverse/entity.py | 80 ++++++++++++++----- punyverse/glgeom.py | 39 +++++++++ punyverse/shader.py | 9 +++ punyverse/shaders/sphere.planet.fragment.glsl | 57 +++++++++++++ punyverse/shaders/sphere.vertex.glsl | 30 +++++++ punyverse/world.json | 1 + punyverse/world.py | 9 +++ 7 files changed, 205 insertions(+), 20 deletions(-) create mode 100644 punyverse/shaders/sphere.planet.fragment.glsl create mode 100644 punyverse/shaders/sphere.vertex.glsl diff --git a/punyverse/entity.py b/punyverse/entity.py index d071c84..69d9279 100644 --- a/punyverse/entity.py +++ b/punyverse/entity.py @@ -5,7 +5,7 @@ from pyglet.gl import * # noinspection PyUnresolvedReferences from six.moves import range -from punyverse.glgeom import compile, glRestore, belt, Sphere, Disk, OrbitVBO, Matrix4f, SimpleSphere +from punyverse.glgeom import compile, glRestore, belt, Sphere, Disk, OrbitVBO, Matrix4f, SimpleSphere, TangentSphere from punyverse.model import load_model, WavefrontVBO from punyverse.orbit import KeplerOrbit from punyverse.texture import get_best_texture, load_clouds @@ -24,11 +24,16 @@ class Entity(object): self.rotation = rotation self.direction = direction + @cached_property + def model_matrix(self): + return Matrix4f.from_angles(self.location, self.rotation) + @cached_property def mv_matrix(self): - return self.world.view_matrix() * Matrix4f.from_angles(self.location, self.rotation) + return self.world.view_matrix() * self.model_matrix def update(self): + self.model_matrix = None self.mv_matrix = None x, y, z = self.location dx, dy, dz = self.direction @@ -266,15 +271,27 @@ class Body(Entity): class SphericalBody(Body): + _sphere_cache = {} + + @classmethod + def _get_sphere(cls, division): + if division in cls._sphere_cache: + return cls._sphere_cache[division] + cls._sphere_cache[division] = sphere = TangentSphere(division, division) + return sphere + def __init__(self, name, world, info, parent=None): super(SphericalBody, self).__init__(name, world, info, parent) self.radius = world.evaluate(info.get('radius', world.length)) / world.length division = info.get('division', max(min(int(self.radius / 8), 60), 10)) - self.light_source = info.get('light_source', False) - self.texture = get_best_texture(info['texture']) - self.sphere = Sphere(self.radius, division, division) + self.light_source = info.get('light_source', False) + self.shininess = info.get('shininess', 0) + self.type = info.get('type', 'planet') + + self.diffuse_texture = get_best_texture(info['texture']) + self.sphere = self._get_sphere(division) self.atmosphere = None self.clouds = None @@ -306,24 +323,47 @@ class SphericalBody(Body): self.ring_texture = get_best_texture(info['ring'].get('texture', None), clamp=True) self.ring = Disk(distance, distance + size, 30) - def _draw_sphere(self, fv4=GLfloat * 4): - with glRestore(GL_LIGHTING_BIT | GL_ENABLE_BIT | GL_TEXTURE_BIT): - glLoadMatrixf(self.mv_matrix.as_gl()) - glEnable(GL_CULL_FACE) - glCullFace(GL_BACK) + def _draw_planet(self): + shader = self.world.activate_shader('planet') + shader.uniform_float('u_radius', self.radius) + shader.uniform_mat4('u_modelMatrix', self.model_matrix) + shader.uniform_mat4('u_mvMatrix', self.mv_matrix) + shader.uniform_mat4('u_mvpMatrix', self.world.vp_matrix * self.model_matrix) - glEnable(GL_TEXTURE_2D) - glBindTexture(GL_TEXTURE_2D, self.texture) + glActiveTexture(GL_TEXTURE0) + glBindTexture(GL_TEXTURE_2D, self.diffuse_texture) + shader.uniform_bool('u_planet.hasDiffuse', True) + shader.uniform_texture('u_planet.diffuseMap', 0) - if self.light_source: - glDisable(GL_LIGHTING) - else: - glDisable(GL_BLEND) - glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, fv4(1, 1, 1, 0)) - glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, fv4(1, 1, 1, 0)) - glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 125) + shader.uniform_bool('u_planet.hasNormal', False) + shader.uniform_bool('u_planet.hasSpecular', False) + shader.uniform_bool('u_planet.hasEmission', False) - self.sphere.draw() + shader.uniform_vec3('u_planet.ambient', 1, 1, 1) + shader.uniform_vec3('u_planet.diffuse', 1, 1, 1) + shader.uniform_vec3('u_planet.specular', 0, 0, 0) + shader.uniform_vec3('u_planet.emission', 0, 0, 0) + + shader.uniform_float('u_planet.shininess', 0) + + glBindBuffer(GL_ARRAY_BUFFER, self.sphere.vbo) + shader.vertex_attribute('a_normal', self.sphere.direction_size, self.sphere.type, GL_FALSE, + self.sphere.stride, self.sphere.direction_offset) + shader.vertex_attribute('a_tangent', self.sphere.tangent_size, self.sphere.type, GL_FALSE, + self.sphere.stride, self.sphere.tangent_offset) + shader.vertex_attribute('a_uv', self.sphere.uv_size, self.sphere.type, GL_FALSE, + self.sphere.stride, self.sphere.uv_offset) + + glDrawArrays(GL_TRIANGLE_STRIP, 0, self.sphere.vertex_count) + + shader.deactivate_attributes() + glBindBuffer(GL_ARRAY_BUFFER, 0) + self.world.activate_shader(None) + glActiveTexture(GL_TEXTURE0) + + def _draw_sphere(self): + if self.type == 'planet': + self._draw_planet() def _draw_atmosphere(self): with glRestore(GL_ENABLE_BIT | GL_CURRENT_BIT | GL_TEXTURE_BIT): diff --git a/punyverse/glgeom.py b/punyverse/glgeom.py index 4d9e424..bf3053d 100644 --- a/punyverse/glgeom.py +++ b/punyverse/glgeom.py @@ -236,6 +236,45 @@ class SimpleSphere(object): self.vbo = array_to_gl_buffer(buffer) +class TangentSphere(object): + type = GL_FLOAT + stride = 7 * 4 + direction_offset = 0 + direction_size = 3 + tangent_offset = direction_size * 4 + tangent_size = 2 + uv_offset = tangent_offset + tangent_size * 4 + uv_size = 2 + + def __init__(self, lats, longs): + tau = pi * 2 + phi_div = tau / longs + theta_div = pi / lats + + self.vertex_count = (lats + 1) * (longs + 1) * 2 + buffer = self.vertex_count * 8 * [0] + index = 0 + reverse = False + for i in range(longs + 1): + phi1, phi2 = i * phi_div, (i + 1) * phi_div + for j in range(lats + 1): + theta = j * theta_div + if reverse: + theta = pi - theta + sine = sin(theta) + dz = cos(theta) + t = 1 - theta / pi + sphi2, cphi2 = sin(phi2), cos(phi2) + sphi1, cphi1 = sin(phi1), cos(phi1) + buffer[index:index + 14] = [ + sine * cphi2, sine * sphi2, dz, sine * -sphi2, sine * cphi2, phi2 / tau, t, + sine * cphi1, sine * sphi1, dz, sine * -sphi1, sine * cphi1, phi1 / tau, t, + ] + index += 14 + reverse ^= True + self.vbo = array_to_gl_buffer(buffer) + + class Sphere(object): def __init__(self, r, lats, longs): tau = pi * 2 diff --git a/punyverse/shader.py b/punyverse/shader.py index 38f1629..1263cd7 100644 --- a/punyverse/shader.py +++ b/punyverse/shader.py @@ -90,6 +90,15 @@ class Program(object): def uniform_texture(self, name, index): glUniform1i(self.uniforms[name], index) + def uniform_float(self, name, value): + glUniform1f(self.uniforms[name], value) + + def uniform_bool(self, name, value): + glUniform1i(self.uniforms[name], bool(value)) + + def uniform_vec3(self, name, a, b, c): + glUniform3f(self.uniforms[name], a, b, c) + def _variable_locations(self, count_type, get_func, loc_func): variables = {} count = GLint() diff --git a/punyverse/shaders/sphere.planet.fragment.glsl b/punyverse/shaders/sphere.planet.fragment.glsl new file mode 100644 index 0000000..7335681 --- /dev/null +++ b/punyverse/shaders/sphere.planet.fragment.glsl @@ -0,0 +1,57 @@ +#version 130 + +in vec2 v_uv; +in vec3 v_normal; +in vec3 v_position; +in vec3 v_camDirection; +in mat3 v_TBN; + +struct Surface { + bool hasDiffuse; + sampler2D diffuseMap; + bool hasNormal; + sampler2D normalMap; + bool hasSpecular; + sampler2D specularMap; + bool hasEmission; + sampler2D emissionMap; + vec3 ambient; + vec3 diffuse; + vec3 specular; + vec3 emission; + float shininess; +}; + +struct Sun { + vec3 ambient; + vec3 diffuse; + vec3 specular; + vec3 position; + float intensity; +}; + +uniform Sun u_sun; +uniform Surface u_planet; + +void main() { + vec3 normal = u_planet.hasNormal ? normalize(v_TBN * texture2D(u_planet.normalMap, v_uv).rgb * 2 - 1) : v_normal; + vec3 diffuse = u_planet.hasDiffuse ? texture2D(u_planet.diffuseMap, v_uv).rgb : vec3(1); + vec3 specular = u_planet.hasSpecular ? texture2D(u_planet.specularMap, v_uv).rgb : vec3(1); + vec3 emission = u_planet.hasEmission ? texture2D(u_planet.emissionMap, v_uv).rgb : vec3(1); + + vec3 incident = normalize(u_sun.position - v_position); + vec3 reflected = normalize(-reflect(incident, normal)); + + float diffuseIntensity = max(dot(normal, incident), 0.0); + float shininess = pow(clamp(dot(normalize(v_camDirection), reflected), 0.0, 1.0), u_planet.shininess); + + vec3 ambient = u_planet.ambient * u_sun.ambient * diffuse; + diffuse *= u_planet.diffuse * u_sun.diffuse * diffuseIntensity; + emission *= u_planet.emission * (1 - min(diffuseIntensity * 2, 1)); + if (u_planet.shininess > 0) + specular *= u_planet.specular * u_sun.specular * clamp(shininess, 0, 1); + else + specular = vec3(0); + + gl_FragColor = vec4((ambient + diffuse + emission + specular) * u_sun.intensity, 1); +} diff --git a/punyverse/shaders/sphere.vertex.glsl b/punyverse/shaders/sphere.vertex.glsl new file mode 100644 index 0000000..72d087d --- /dev/null +++ b/punyverse/shaders/sphere.vertex.glsl @@ -0,0 +1,30 @@ +#version 130 + +in vec3 a_normal; +in vec2 a_tangent; +in vec2 a_uv; + +out vec2 v_uv; +out vec3 v_normal; +out vec3 v_position; +out vec3 v_camDirection; +out mat3 v_TBN; + +uniform float u_radius; +uniform mat4 u_mvpMatrix; +uniform mat4 u_mvMatrix; +uniform mat4 u_modelMatrix; + +void main() { + vec3 position = u_radius * a_normal; + + gl_Position = u_mvpMatrix * vec4(position, 1); + + v_normal = normalize(vec3(u_modelMatrix * vec4(a_normal, 0))); + v_uv = a_uv; + v_position = (u_modelMatrix * vec4(position, 1)).xyz; + v_camDirection = (u_mvMatrix * vec4(position, 1)).xyz; + + vec3 tangent = normalize((u_modelMatrix * vec4(a_tangent, a_normal.z, 0)).xyz); + v_TBN = mat3(tangent, cross(tangent, v_normal), v_normal); +} diff --git a/punyverse/world.json b/punyverse/world.json index c68e071..00f6d79 100644 --- a/punyverse/world.json +++ b/punyverse/world.json @@ -22,6 +22,7 @@ "mass": 1.9891e+30, "rotation": 2164320, "light_source": true, + "type": "star", "atmosphere": { "diffuse_texture": "sun_diffuse.png", "diffuse_size": 300 diff --git a/punyverse/world.py b/punyverse/world.py index 9fd6373..e96167b 100644 --- a/punyverse/world.py +++ b/punyverse/world.py @@ -19,6 +19,7 @@ def load_world(file, callback=lambda message, completion: None): class World(object): PROGRAMS = { 'sky': ('sky.vertex.glsl', 'sky.fragment.glsl'), + 'planet': ('sphere.vertex.glsl', 'sphere.planet.fragment.glsl'), } def __init__(self, file, callback): @@ -44,6 +45,14 @@ class World(object): for entity in self.tracker: entity.update() + shader = self.activate_shader('planet') + shader.uniform_vec3('u_sun.ambient', 0.1, 0.1, 0.1) + shader.uniform_vec3('u_sun.diffuse', 1, 1, 1) + shader.uniform_vec3('u_sun.specular', 0.5, 0.5, 0.5) + shader.uniform_vec3('u_sun.position', 0, 0, 0) + shader.uniform_float('u_sun.intensity', 1) + self.activate_shader(None) + def _load_programs(self): programs = {} count = len(self.PROGRAMS)