diff --git a/punyverse/camera.py b/punyverse/camera.py index 6d7fd0d..0a343d3 100644 --- a/punyverse/camera.py +++ b/punyverse/camera.py @@ -16,7 +16,7 @@ class Camera(object): self.fov = radians(45) self.aspect = 1 self.znear = 1 - self.zfar = 50000000 + self.zfar = 3000000 self.speed = 0 self.roll_left = False diff --git a/punyverse/entity.py b/punyverse/entity.py index fad0845..d071c84 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 +from punyverse.glgeom import compile, glRestore, belt, Sphere, Disk, OrbitVBO, Matrix4f, SimpleSphere from punyverse.model import load_model, WavefrontVBO from punyverse.orbit import KeplerOrbit from punyverse.texture import get_best_texture, load_clouds @@ -120,20 +120,30 @@ class Sky(Entity): self.texture = get_best_texture(info['texture']) division = info.get('division', 30) - self.sphere = Sphere(info.get('radius', 1000000), division, division) + self.sphere = SimpleSphere(division, division) def draw(self, options): cam = self.world.cam - with glRestore(GL_TEXTURE_BIT | GL_ENABLE_BIT): - matrix = self.world.view_matrix() * Matrix4f.from_angles((-cam.x, -cam.y, -cam.z), self.rotation) - glLoadMatrixf(matrix.as_gl()) - glEnable(GL_CULL_FACE) - glEnable(GL_TEXTURE_2D) - glDisable(GL_LIGHTING) + shader = self.world.activate_shader('sky') + shader.uniform_mat4('u_mvpMatrix', self.world.projection_matrix() * + Matrix4f.from_angles(rotation=(cam.pitch, cam.yaw, cam.roll)) * + Matrix4f.from_angles(rotation=self.rotation)) - glCullFace(GL_FRONT) - glBindTexture(GL_TEXTURE_2D, self.texture) - self.sphere.draw() + glActiveTexture(GL_TEXTURE0) + glBindTexture(GL_TEXTURE_2D, self.texture) + shader.uniform_texture('u_skysphere', 0) + + glBindBuffer(GL_ARRAY_BUFFER, self.sphere.vbo) + shader.vertex_attribute('a_direction', self.sphere.direction_size, self.sphere.type, GL_FALSE, + self.sphere.stride, self.sphere.direction_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) class Body(Entity): diff --git a/punyverse/glgeom.py b/punyverse/glgeom.py index be5104f..4d9e424 100644 --- a/punyverse/glgeom.py +++ b/punyverse/glgeom.py @@ -202,6 +202,40 @@ class Disk(object): glBindBuffer(GL_ARRAY_BUFFER, 0) +class SimpleSphere(object): + type = GL_FLOAT + stride = 5 * 4 + direction_offset = 0 + direction_size = 3 + uv_offset = direction_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 * 5 * [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 + buffer[index:index + 10] = [sine * cos(phi2), sine * sin(phi2), dz, phi2 / tau, t, + sine * cos(phi1), sine * sin(phi1), dz, phi1 / tau, t] + index += 10 + 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 new file mode 100644 index 0000000..38f1629 --- /dev/null +++ b/punyverse/shader.py @@ -0,0 +1,104 @@ +import os +from ctypes import pointer, byref, create_string_buffer, POINTER, cast + +from pyglet.gl import * +# noinspection PyUnresolvedReferences +from six.moves import range + +SHADERS_DIR = os.path.join(os.path.dirname(__file__), 'shaders') + + +class CompileError(ValueError): + pass + + +class glShader(object): + def __init__(self, type): + self.type = type + + def __enter__(self): + self.shader = glCreateShader(self.type) + return self.shader + + def __exit__(self, exc_type, exc_val, exc_tb): + glDeleteShader(self.shader) + + +class Program(object): + @classmethod + def load_file(cls, file): + with open(os.path.join(SHADERS_DIR, file), 'rb') as f: + return f.read() + + @classmethod + def compile_shader(cls, shader, source): + buffer = create_string_buffer(source) + glShaderSource(shader, 1, cast(pointer(pointer(buffer)), POINTER(POINTER(GLchar))), None) + glCompileShader(shader) + + succeeded = GLint() + log_length = GLint() + glGetShaderiv(shader, GL_COMPILE_STATUS, byref(succeeded)) + if not succeeded: + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, byref(log_length)) + buffer = create_string_buffer(log_length.value + 1) + glGetShaderInfoLog(shader, log_length.value, None, buffer) + raise CompileError(buffer.value) + + def __init__(self, vertex_file, fragment_file): + with glShader(GL_VERTEX_SHADER) as vertex_shader, glShader(GL_FRAGMENT_SHADER) as fragment_shader: + self.compile_shader(vertex_shader, self.load_file(vertex_file)) + self.compile_shader(fragment_shader, self.load_file(fragment_file)) + + program = glCreateProgram() + glAttachShader(program, vertex_shader) + glAttachShader(program, fragment_shader) + glLinkProgram(program) + + succeeded = GLint() + log_length = GLint() + glGetProgramiv(program, GL_LINK_STATUS, byref(succeeded)) + if not succeeded: + glGetProgramiv(program, GL_INFO_LOG_LENGTH, byref(log_length)) + buffer = create_string_buffer(log_length.value + 1) + glGetProgramInfoLog(program, log_length.value, None, buffer) + glDeleteProgram(program) + raise CompileError(buffer.value) + + glDetachShader(program, vertex_shader) + glDetachShader(program, fragment_shader) + + self.program = program + self.attributes = self._variable_locations(GL_ACTIVE_ATTRIBUTES, glGetActiveAttrib, glGetAttribLocation) + self.uniforms = self._variable_locations(GL_ACTIVE_UNIFORMS, glGetActiveUniform, glGetUniformLocation) + self.active_attributes = set() + + def vertex_attribute(self, name, size, type, normalized, stride, offset): + location = self.attributes[name] + glVertexAttribPointer(location, size, type, normalized, stride, offset) + glEnableVertexAttribArray(location) + self.active_attributes.add(location) + + def deactivate_attributes(self): + for location in self.active_attributes: + glDisableVertexAttribArray(location) + self.active_attributes.clear() + + def uniform_mat4(self, name, matrix): + glUniformMatrix4fv(self.uniforms[name], 1, GL_FALSE, matrix.as_gl()) + + def uniform_texture(self, name, index): + glUniform1i(self.uniforms[name], index) + + def _variable_locations(self, count_type, get_func, loc_func): + variables = {} + count = GLint() + glGetProgramiv(self.program, count_type, byref(count)) + buffer = create_string_buffer(256) + size = GLint() + type = GLenum() + + for index in range(count.value): + get_func(self.program, index, 256, None, byref(size), byref(type), buffer) + variables[buffer.value.decode('ascii')] = loc_func(self.program, buffer) + return variables diff --git a/punyverse/shaders/sky.fragment.glsl b/punyverse/shaders/sky.fragment.glsl new file mode 100644 index 0000000..304c754 --- /dev/null +++ b/punyverse/shaders/sky.fragment.glsl @@ -0,0 +1,8 @@ +#version 130 + +in vec2 v_uv; +uniform sampler2D u_skysphere; + +void main() { + gl_FragColor = vec4(texture(u_skysphere, v_uv).rgb, 1); +} diff --git a/punyverse/shaders/sky.vertex.glsl b/punyverse/shaders/sky.vertex.glsl new file mode 100644 index 0000000..075207e --- /dev/null +++ b/punyverse/shaders/sky.vertex.glsl @@ -0,0 +1,11 @@ +#version 130 + +in vec3 a_direction; +in vec2 a_uv; +out vec2 v_uv; +uniform mat4 u_mvpMatrix; + +void main() { + gl_Position = (u_mvpMatrix * vec4(a_direction, 1)).xyww; + v_uv = a_uv; +} diff --git a/punyverse/world.json b/punyverse/world.json index 6772c11..c68e071 100644 --- a/punyverse/world.json +++ b/punyverse/world.json @@ -298,7 +298,6 @@ "sky": { "texture": "sky.jpg", "rotation": 0, - "radius": 305000000, "division": 30, "pitch": 90, "yaw": 30, diff --git a/punyverse/world.py b/punyverse/world.py index a9f76f5..9fd6373 100644 --- a/punyverse/world.py +++ b/punyverse/world.py @@ -9,6 +9,7 @@ import six from punyverse import texture from punyverse.camera import Camera from punyverse.entity import * +from punyverse.shader import Program def load_world(file, callback=lambda message, completion: None): @@ -16,6 +17,10 @@ def load_world(file, callback=lambda message, completion: None): class World(object): + PROGRAMS = { + 'sky': ('sky.vertex.glsl', 'sky.fragment.glsl'), + } + def __init__(self, file, callback): self.tracker = [] self.x = None @@ -28,6 +33,9 @@ class World(object): self.callback = callback self._parse(file) + + self._program = None + self.programs = self._load_programs() del self.callback # So it can't be used after loading finishes self._time_accumulate = 0 @@ -36,6 +44,15 @@ class World(object): for entity in self.tracker: entity.update() + def _load_programs(self): + programs = {} + count = len(self.PROGRAMS) + for i, (name, (vertex, fragment)) in enumerate(six.iteritems(self.PROGRAMS)): + self.callback('Loading shaders (%d of %d)...' % (i, count), + 'Loading shader "%s" (%s, %s).' % (name, vertex, fragment), i / count) + programs[name] = Program(vertex, fragment) + return programs + def evaluate(self, value): return eval(str(value), {'__builtins__': None}, self._context) @@ -128,6 +145,7 @@ class World(object): def update(self, dt, move, tick): c = self.cam c.update(dt, move) + self.vp_matrix = None if tick: delta = self.tick_length * dt @@ -151,6 +169,24 @@ class World(object): def projection_matrix(self): return self._projection_matrix + @cached_property + def vp_matrix(self): + return self._projection_matrix * self.cam.view_matrix + def resize(self, width, height): self.cam.aspect = width / max(height, 1) self._projection_matrix = self.cam.projection_matrix() + self.vp_matrix = None + + def activate_shader(self, name): + program = None + if self._program != name: + if name is None: + glUseProgram(0) + else: + program = self.programs[name] + glUseProgram(program.program) + self._program = name + elif self._program is not None: + program = self.programs[self._program] + return program