Move sky to shaders.

This commit is contained in:
Quantum 2018-08-25 20:53:21 -04:00
parent 999dc78410
commit 66c54a9dbb
8 changed files with 215 additions and 13 deletions

View file

@ -16,7 +16,7 @@ class Camera(object):
self.fov = radians(45) self.fov = radians(45)
self.aspect = 1 self.aspect = 1
self.znear = 1 self.znear = 1
self.zfar = 50000000 self.zfar = 3000000
self.speed = 0 self.speed = 0
self.roll_left = False self.roll_left = False

View file

@ -5,7 +5,7 @@ from pyglet.gl import *
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
from six.moves import range 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.model import load_model, WavefrontVBO
from punyverse.orbit import KeplerOrbit from punyverse.orbit import KeplerOrbit
from punyverse.texture import get_best_texture, load_clouds from punyverse.texture import get_best_texture, load_clouds
@ -120,20 +120,30 @@ class Sky(Entity):
self.texture = get_best_texture(info['texture']) self.texture = get_best_texture(info['texture'])
division = info.get('division', 30) division = info.get('division', 30)
self.sphere = Sphere(info.get('radius', 1000000), division, division) self.sphere = SimpleSphere(division, division)
def draw(self, options): def draw(self, options):
cam = self.world.cam cam = self.world.cam
with glRestore(GL_TEXTURE_BIT | GL_ENABLE_BIT): shader = self.world.activate_shader('sky')
matrix = self.world.view_matrix() * Matrix4f.from_angles((-cam.x, -cam.y, -cam.z), self.rotation) shader.uniform_mat4('u_mvpMatrix', self.world.projection_matrix() *
glLoadMatrixf(matrix.as_gl()) Matrix4f.from_angles(rotation=(cam.pitch, cam.yaw, cam.roll)) *
glEnable(GL_CULL_FACE) Matrix4f.from_angles(rotation=self.rotation))
glEnable(GL_TEXTURE_2D)
glDisable(GL_LIGHTING)
glCullFace(GL_FRONT) glActiveTexture(GL_TEXTURE0)
glBindTexture(GL_TEXTURE_2D, self.texture) glBindTexture(GL_TEXTURE_2D, self.texture)
self.sphere.draw() 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): class Body(Entity):

View file

@ -202,6 +202,40 @@ class Disk(object):
glBindBuffer(GL_ARRAY_BUFFER, 0) 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): class Sphere(object):
def __init__(self, r, lats, longs): def __init__(self, r, lats, longs):
tau = pi * 2 tau = pi * 2

104
punyverse/shader.py Normal file
View file

@ -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

View file

@ -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);
}

View file

@ -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;
}

View file

@ -298,7 +298,6 @@
"sky": { "sky": {
"texture": "sky.jpg", "texture": "sky.jpg",
"rotation": 0, "rotation": 0,
"radius": 305000000,
"division": 30, "division": 30,
"pitch": 90, "pitch": 90,
"yaw": 30, "yaw": 30,

View file

@ -9,6 +9,7 @@ import six
from punyverse import texture from punyverse import texture
from punyverse.camera import Camera from punyverse.camera import Camera
from punyverse.entity import * from punyverse.entity import *
from punyverse.shader import Program
def load_world(file, callback=lambda message, completion: None): 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): class World(object):
PROGRAMS = {
'sky': ('sky.vertex.glsl', 'sky.fragment.glsl'),
}
def __init__(self, file, callback): def __init__(self, file, callback):
self.tracker = [] self.tracker = []
self.x = None self.x = None
@ -28,6 +33,9 @@ class World(object):
self.callback = callback self.callback = callback
self._parse(file) self._parse(file)
self._program = None
self.programs = self._load_programs()
del self.callback # So it can't be used after loading finishes del self.callback # So it can't be used after loading finishes
self._time_accumulate = 0 self._time_accumulate = 0
@ -36,6 +44,15 @@ class World(object):
for entity in self.tracker: for entity in self.tracker:
entity.update() 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): def evaluate(self, value):
return eval(str(value), {'__builtins__': None}, self._context) return eval(str(value), {'__builtins__': None}, self._context)
@ -128,6 +145,7 @@ class World(object):
def update(self, dt, move, tick): def update(self, dt, move, tick):
c = self.cam c = self.cam
c.update(dt, move) c.update(dt, move)
self.vp_matrix = None
if tick: if tick:
delta = self.tick_length * dt delta = self.tick_length * dt
@ -151,6 +169,24 @@ class World(object):
def projection_matrix(self): def projection_matrix(self):
return self._projection_matrix return self._projection_matrix
@cached_property
def vp_matrix(self):
return self._projection_matrix * self.cam.view_matrix
def resize(self, width, height): def resize(self, width, height):
self.cam.aspect = width / max(height, 1) self.cam.aspect = width / max(height, 1)
self._projection_matrix = self.cam.projection_matrix() 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