From abbdbe64d12af732983bc11aac772d3d32682bb5 Mon Sep 17 00:00:00 2001 From: Adrian Kummerlaender Date: Mon, 20 Jan 2020 22:40:44 +0100 Subject: Extract OpenCL setup into Renderer --- bluedot.py | 32 ++------ fancy_local_sunrise.py | 36 ++------- firmament/__init__.py | 3 + firmament/planets.py | 11 +++ firmament/raymarch.cl | 209 +++++++++++++++++++++++++++++++++++++++++++++++++ firmament/renderer.py | 51 ++++++++++++ firmament/sun.py | 32 ++++++++ local_sunrise.py | 36 ++------- planets.py | 11 --- raymarch.cl | 209 ------------------------------------------------- sun.py | 32 -------- sunrise.py | 34 ++------ 12 files changed, 330 insertions(+), 366 deletions(-) create mode 100644 firmament/__init__.py create mode 100644 firmament/planets.py create mode 100644 firmament/raymarch.cl create mode 100644 firmament/renderer.py create mode 100644 firmament/sun.py delete mode 100644 planets.py delete mode 100644 raymarch.cl delete mode 100644 sun.py diff --git a/bluedot.py b/bluedot.py index 42bb9bc..975b106 100644 --- a/bluedot.py +++ b/bluedot.py @@ -1,13 +1,8 @@ import numpy import matplotlib.pyplot as plt -from string import Template -import pyopencl as cl -from pyopencl.cltypes import make_double3 - -mf = cl.mem_flags - -from planets import earth +from firmament import Renderer +from firmament.planets import earth config = { 'size_x': 1000, @@ -25,29 +20,12 @@ config = { sun_range = (0, 360, 10) -cl_platform = cl.get_platforms()[0] -cl_context = cl.Context(properties=[(cl.context_properties.PLATFORM, cl_platform)]) -cl_queue = cl.CommandQueue(cl_context) - -cl_picture = cl.Buffer(cl_context, mf.WRITE_ONLY, size=config['size_x']*config['size_y']*3*numpy.float64(0).nbytes) -program = None - -with open('raymarch.cl') as f: - program = cl.Program(cl_context, Template(f.read()).substitute( - {**config, **earth} - )).build() +renderer = Renderer(config, earth) for i in numpy.arange(*sun_range): - sun = make_double3(numpy.cos(i*2*numpy.pi/360),numpy.sin(i*2*numpy.pi/360),0) + sun = (numpy.cos(i*2*numpy.pi/360), numpy.sin(i*2*numpy.pi/360), 0) print(sun) - program.render_pinhole( - cl_queue, (config['size_x'], config['size_y']), None, cl_picture, - make_double3(*(config['eye_pos'] * earth['earth_radius'])), - make_double3(*(config['eye_dir'] * earth['earth_radius'])), - sun) - - np_picture = numpy.ndarray(shape=(config['size_y'], config['size_x'], 3), dtype=numpy.float64) - cl.enqueue_copy(cl_queue, np_picture, cl_picture).wait(); + np_picture = renderer.render_pinhole(sun) plt.imsave("bluedot_%05.1f.png" % i, np_picture, origin='lower') diff --git a/fancy_local_sunrise.py b/fancy_local_sunrise.py index 39cb292..bdb23b4 100644 --- a/fancy_local_sunrise.py +++ b/fancy_local_sunrise.py @@ -1,17 +1,12 @@ import numpy as np import matplotlib.pyplot as plt -from string import Template -import pyopencl as cl -from pyopencl.cltypes import make_double3 - -mf = cl.mem_flags - -from planets import earth - -from sun import sun_direction from datetime import datetime +from firmament import Renderer +from firmament.planets import earth +from firmament.sun import sun_direction + config = { 'size_x': 1000, 'size_y': 1000, @@ -35,37 +30,20 @@ config = { time_range = (6, 20, 1) -cl_platform = cl.get_platforms()[0] -cl_context = cl.Context(properties=[(cl.context_properties.PLATFORM, cl_platform)]) -cl_queue = cl.CommandQueue(cl_context) - -cl_picture = cl.Buffer(cl_context, mf.WRITE_ONLY, size=config['size_x']*config['size_y']*3*np.float64(0).nbytes) -program = None - -with open('raymarch.cl') as f: - program = cl.Program(cl_context, Template(f.read()).substitute( - {**config, **earth} - )).build() +renderer = Renderer(config, earth) for time in np.arange(*time_range): pit = datetime(*config['date'], int(np.floor(time)), int((time-np.floor(time))*60), 0) sun_dir = sun_direction(config['latitude'], config['longitude'], pit, config['timezone'], 1.0 if config['summertime'] else 0.0) - sun = make_double3( + sun = ( np.cos(sun_dir[0])*np.sin(sun_dir[1]), np.cos(sun_dir[0])*np.cos(sun_dir[1]), np.sin(sun_dir[0]) ) print(sun_dir) - program.render_fisheye( - cl_queue, (config['size_x'], config['size_y']), None, cl_picture, - make_double3(*(config['eye_pos'] * earth['earth_radius'])), - make_double3(*(config['eye_dir'] * earth['earth_radius'])), - sun) - - np_picture = np.ndarray(shape=(config['size_y'], config['size_x'], 3), dtype=np.float64) - cl.enqueue_copy(cl_queue, np_picture, cl_picture).wait(); + np_picture = renderer.render_fisheye(sun) fig = plt.gcf() diff --git a/firmament/__init__.py b/firmament/__init__.py new file mode 100644 index 0000000..26d56ec --- /dev/null +++ b/firmament/__init__.py @@ -0,0 +1,3 @@ +from . import renderer + +Renderer = renderer.Renderer diff --git a/firmament/planets.py b/firmament/planets.py new file mode 100644 index 0000000..4570019 --- /dev/null +++ b/firmament/planets.py @@ -0,0 +1,11 @@ +earth = { + 'earth_radius': 6371e3, + 'atmos_height': 100e3, + + 'rayleigh_atmos_height': 7994, + 'mie_atmos_height' : 1200, + + 'rayleigh_beta': (5.4717e-6, 12.7853e-6, 31.2142e-6), + 'mie_beta' : (21e-6), + 'mie_g' : 0.75 +} diff --git a/firmament/raymarch.cl b/firmament/raymarch.cl new file mode 100644 index 0000000..d0092d9 --- /dev/null +++ b/firmament/raymarch.cl @@ -0,0 +1,209 @@ +__constant double earth_radius = $earth_radius; +__constant double atmos_height = $atmos_height; + +__constant double3 rayleigh_beta = (double3)$rayleigh_beta; +__constant double3 mie_beta = (double3)$mie_beta; +__constant double mie_g = $mie_g; + +__constant int ray_samples = $ray_samples; +__constant int light_samples = $light_samples; + +__constant double exposure = $exposure; + +bool insideAtmosphere(double3 pos) { + return length(pos) < earth_radius + atmos_height; +} + +double altitude(double3 pos) { + return length(pos) - earth_radius; +} + +/// Solve x^2 + px + q +bool solvePolynomialOfDegreeTwo(double p, double q, double* x1, double* x2) { + const double pHalf = 0.5*p; + const double inner = pHalf*pHalf - q; + + if (inner >= 0.0) { + *x1 = -pHalf + sqrt(inner); + *x2 = -pHalf - sqrt(inner); + return true; + } else { + return false; + } +} + +/// Interection of origin + d*dir and a sphere of radius r for normalized dir +bool solveRaySphereIntersection(double3 origin, double3 dir, double r, double* d0, double* d1) { + const double p = 2 * dot(dir,origin); + const double q = dot(origin,origin) - r*r; + + if (solvePolynomialOfDegreeTwo(p, q, d0, d1)) { + if (*d0 > *d1) { + double tmp = *d1; + *d1 = *d0; + *d0 = tmp; + } + return true; + } else { + return false; + } +} + +/// Map {0,...,screenX}x{0,...,screenY} to [-1,1]^2 +double2 getNormalizedScreenPos(double x, double y) { + return (double2)( + 2.0 * (0.5 - x / $size_x) * $size_x.0 / $size_y.0, + 2.0 * (0.5 - y / $size_y) + ); +} + +/// Pinhole camera +double3 getEyeRayDir(double2 screen_pos, double3 eye_pos, double3 eye_target) { + const double3 forward = normalize(eye_target - eye_pos); + const double3 right = normalize(cross((double3)(0.0, 0.0, -1.0), forward)); + const double3 up = normalize(cross(forward, right)); + + return normalize(screen_pos.x*right + screen_pos.y*up + $zoom*forward); +} + +/// Fisheye camera +bool inFishEyeView(double2 screen_pos) { + return length(screen_pos) <= 1.0; +} + +double3 getFishEyeRayDir(double2 screen_pos) { + const double phi = atan2(screen_pos.y, screen_pos.x); + const double theta = acos(1.0 - (screen_pos.x*screen_pos.x + screen_pos.y*screen_pos.y)); + return (double3)(sin(theta)*cos(phi), sin(theta)*sin(phi), cos(theta)); +} + +/// Return true iff earth is hit by rays along dir +bool isVisible(double3 origin, double3 dir) { + double e0, e1; + return !solveRaySphereIntersection(origin, dir, earth_radius, &e0, &e1) + || (e0 < 0 && e1 < 0); +} + +/// Return distance between entering and exiting the atmosphere +double lengthOfRayInAtmosphere(double3 origin, double3 dir) { + double d0, d1; + solveRaySphereIntersection(origin, dir, earth_radius + atmos_height, &d0, &d1); + return d1 - d0; +} + +/// Return light depths of secondary rays +double2 lightDepth(double3 curr, double3 sun) { + const double h = lengthOfRayInAtmosphere(curr, sun) / light_samples; + + double2 depth = 0.0; + + for (unsigned i = 0; i < light_samples; ++i) { + const double height = altitude(curr + (i+0.5)*h*sun); + depth += exp(-height / (double2)($rayleigh_atmos_height, $mie_atmos_height)) * h; + } + + return depth; +} + +/// Calculate color of light along ray +double3 scatter(double3 origin, double3 dir, double dist, double3 sun) { + double3 rayleigh_sum = 0.0; + double3 mie_sum = 0.0; + + double rayleigh_depth = 0.0; + double mie_depth = 0.0; + + const double h = dist / ray_samples; + + for (unsigned i = 0; i < ray_samples; ++i) { + double3 curr = origin + (i+0.5)*h*dir; + + if (isVisible(curr, sun)) { + const double height = altitude(curr); + + const double rayleigh_h = exp(-height / $rayleigh_atmos_height) * h; + const double mie_h = exp(-height / $mie_atmos_height) * h; + + rayleigh_depth += rayleigh_h; + mie_depth += mie_h; + + const double2 light_depth = lightDepth(curr, sun); + const double3 tau = rayleigh_beta * (rayleigh_depth + light_depth.x) + + mie_beta * (mie_depth + light_depth.y); + const double3 attenuation = exp(-tau); + + rayleigh_sum += attenuation * rayleigh_h; + mie_sum += attenuation * mie_h; + } + } + + const double mu = dot(dir,sun); + + const double rayleigh_phase = 0.75 * (1.0 + mu*mu); + const double mie_phase = 0.5*(1-mie_g*mie_g)/pow(1+mie_g*mie_g-2*mie_g*mu,1.5); + + return rayleigh_sum*rayleigh_beta*rayleigh_phase + mie_sum*mie_beta*mie_phase; +} + +void setColor(__global double* result, unsigned x, unsigned y, double3 color) { + result[3*$size_x*y + 3*x + 0] = color.x; + result[3*$size_x*y + 3*x + 1] = color.y; + result[3*$size_x*y + 3*x + 2] = color.z; +} + +void render(__global double* result, unsigned x, unsigned y, double3 origin, double3 ray_dir, double3 sun) { + double d0, d1; + if (!solveRaySphereIntersection(origin, ray_dir, earth_radius + atmos_height, &d0, &d1)) { + setColor(result, x, y, 0.0); + return; + } + + double min_dist = d0; + double max_dist = d1; + + if (insideAtmosphere(origin)) { + min_dist = 0.0; + + if (solveRaySphereIntersection(origin, ray_dir, earth_radius, &d0, &d1) && d1 > 0) { + max_dist = max(0.0, d0); + } + } else { + if (solveRaySphereIntersection(origin, ray_dir, earth_radius, &d0, &d1)) { + max_dist = d0; + } + } + + const double3 ray_origin = origin + min_dist*ray_dir; + const double ray_length = max_dist - min_dist; + + double3 color = scatter(ray_origin, ray_dir, ray_length, normalize(sun)); + color = 1.0 - exp(-exposure * color); + + setColor(result, x, y, color); +} + +__kernel void render_fisheye(__global double* result, double3 eye_pos, double3 eye_dir, double3 sun) { + const unsigned x = get_global_id(0); + const unsigned y = get_global_id(1); + + const double2 screen_pos = getNormalizedScreenPos(x, y); + + if (!inFishEyeView(screen_pos)) { + setColor(result, x, y, 1.0); + return; + } + + const double3 ray_dir = getFishEyeRayDir(screen_pos); + + render(result, x, y, eye_pos, ray_dir, sun); +} + +__kernel void render_pinhole(__global double* result, double3 eye_pos, double3 eye_dir, double3 sun) { + const unsigned x = get_global_id(0); + const unsigned y = get_global_id(1); + + const double2 screen_pos = getNormalizedScreenPos(x, y); + const double3 ray_dir = getEyeRayDir(screen_pos, eye_pos, eye_pos + eye_dir); + + render(result, x, y, eye_pos, ray_dir, sun); +} diff --git a/firmament/renderer.py b/firmament/renderer.py new file mode 100644 index 0000000..d020cdb --- /dev/null +++ b/firmament/renderer.py @@ -0,0 +1,51 @@ +import numpy +from string import Template + +import pyopencl as cl +from pyopencl.cltypes import make_double3 + +mf = cl.mem_flags + +class Renderer: + def __init__(self, config, planet): + self.config = config + self.planet = planet + + self.cl_platform = cl.get_platforms()[0] + self.cl_context = cl.Context(properties=[(cl.context_properties.PLATFORM, self.cl_platform)]) + self.cl_queue = cl.CommandQueue(self.cl_context) + + self.cl_picture = cl.Buffer(self.cl_context, mf.WRITE_ONLY, size=self.config['size_x']*self.config['size_y']*3*numpy.float64(0).nbytes) + + with open('firmament/raymarch.cl') as f: + self.program = cl.Program(self.cl_context, Template(f.read()).substitute( + {**self.config, **self.planet} + )).build() + + def render_pinhole(self, sun): + self.program.render_pinhole( + self.cl_queue, + (self.config['size_x'], self.config['size_y']), + None, + self.cl_picture, + make_double3(*(self.config['eye_pos'] * self.planet['earth_radius'])), + make_double3(*(self.config['eye_dir'] * self.planet['earth_radius'])), + make_double3(*sun)) + + np_picture = numpy.ndarray(shape=(self.config['size_y'], self.config['size_x'], 3), dtype=numpy.float64) + cl.enqueue_copy(self.cl_queue, np_picture, self.cl_picture).wait(); + return np_picture + + def render_fisheye(self, sun): + self.program.render_fisheye( + self.cl_queue, + (self.config['size_x'], self.config['size_y']), + None, + self.cl_picture, + make_double3(*(self.config['eye_pos'] * self.planet['earth_radius'])), + make_double3(*(self.config['eye_dir'] * self.planet['earth_radius'])), + make_double3(*sun)) + + np_picture = numpy.ndarray(shape=(self.config['size_y'], self.config['size_x'], 3), dtype=numpy.float64) + cl.enqueue_copy(self.cl_queue, np_picture, self.cl_picture).wait(); + return np_picture diff --git a/firmament/sun.py b/firmament/sun.py new file mode 100644 index 0000000..7ceec5d --- /dev/null +++ b/firmament/sun.py @@ -0,0 +1,32 @@ +import numpy as np +from datetime import datetime + +## Sun direction depending on time and place +# As described in Appendix D of "ME 4131 Thermal Environmental Engineering Laboratory Manual" + +def sun_declination(time): + day_of_year = time.timetuple().tm_yday + return 23.45 * np.sin(np.radians((360/365)*(284+day_of_year))) + +def equation_of_time(time): + day_of_year = time.timetuple().tm_yday + b = np.radians(360*(day_of_year-81)/364) + return 0.165*np.sin(2*b) - 0.126*np.cos(b) - 0.025*np.sin(b) + +def sun_direction(lat, lon, time, time_diff, summertime_shift = 0): + lon_std = time_diff * 15 + clock_time = time.hour + time.minute/60 + local_solar_time = clock_time + (1/15)*(lon - lon_std) + equation_of_time(time) - summertime_shift + hour_angle = 15*(local_solar_time - 12) + + l = np.radians(lat) + h = np.radians(hour_angle) + d = np.radians(sun_declination(time)) + + altitude = np.arcsin(np.cos(l) * np.cos(h) * np.cos(d) + np.sin(l) * np.sin(d)) + azimuth = np.arccos((np.cos(d) * np.sin(l) * np.cos(h) - np.sin(d) * np.cos(l)) / np.cos(altitude)) + + if h < 0: + return (altitude, azimuth) + else: + return (altitude, -azimuth) diff --git a/local_sunrise.py b/local_sunrise.py index 7032847..b413579 100644 --- a/local_sunrise.py +++ b/local_sunrise.py @@ -1,17 +1,12 @@ import numpy as np import matplotlib.pyplot as plt -from string import Template -import pyopencl as cl -from pyopencl.cltypes import make_double3 - -mf = cl.mem_flags - -from planets import earth - -from sun import sun_direction from datetime import datetime +from firmament import Renderer +from firmament.planets import earth +from firmament.sun import sun_direction + fish_eye = False config = { @@ -37,36 +32,19 @@ config = { time_range = (6, 20, 0.5) -cl_platform = cl.get_platforms()[0] -cl_context = cl.Context(properties=[(cl.context_properties.PLATFORM, cl_platform)]) -cl_queue = cl.CommandQueue(cl_context) - -cl_picture = cl.Buffer(cl_context, mf.WRITE_ONLY, size=config['size_x']*config['size_y']*3*np.float64(0).nbytes) -program = None - -with open('raymarch.cl') as f: - program = cl.Program(cl_context, Template(f.read()).substitute( - {**config, **earth} - )).build() +renderer = Renderer(config, earth) for time in np.arange(*time_range): pit = datetime(*config['date'], int(np.floor(time)), int((time-np.floor(time))*60), 0) sun_dir = sun_direction(config['latitude'], config['longitude'], pit, config['timezone'], 1.0 if config['summertime'] else 0.0) - sun = make_double3( + sun =( np.cos(sun_dir[0])*np.sin(sun_dir[1]), np.cos(sun_dir[0])*np.cos(sun_dir[1]), np.sin(sun_dir[0]) ) print(sun_dir) - (program.render_fisheye if fish_eye else program.render_pinhole)( - cl_queue, (config['size_x'], config['size_y']), None, cl_picture, - make_double3(*(config['eye_pos'] * earth['earth_radius'])), - make_double3(*(config['eye_dir'] * earth['earth_radius'])), - sun) - - np_picture = np.ndarray(shape=(config['size_y'], config['size_x'], 3), dtype=np.float64) - cl.enqueue_copy(cl_queue, np_picture, cl_picture).wait(); + np_picture = (renderer.render_fisheye if fish_eye else renderer.render_pinhole)(sun) plt.imsave("sky_%05.1f.png" % time, np_picture, origin='lower') diff --git a/planets.py b/planets.py deleted file mode 100644 index 4570019..0000000 --- a/planets.py +++ /dev/null @@ -1,11 +0,0 @@ -earth = { - 'earth_radius': 6371e3, - 'atmos_height': 100e3, - - 'rayleigh_atmos_height': 7994, - 'mie_atmos_height' : 1200, - - 'rayleigh_beta': (5.4717e-6, 12.7853e-6, 31.2142e-6), - 'mie_beta' : (21e-6), - 'mie_g' : 0.75 -} diff --git a/raymarch.cl b/raymarch.cl deleted file mode 100644 index d0092d9..0000000 --- a/raymarch.cl +++ /dev/null @@ -1,209 +0,0 @@ -__constant double earth_radius = $earth_radius; -__constant double atmos_height = $atmos_height; - -__constant double3 rayleigh_beta = (double3)$rayleigh_beta; -__constant double3 mie_beta = (double3)$mie_beta; -__constant double mie_g = $mie_g; - -__constant int ray_samples = $ray_samples; -__constant int light_samples = $light_samples; - -__constant double exposure = $exposure; - -bool insideAtmosphere(double3 pos) { - return length(pos) < earth_radius + atmos_height; -} - -double altitude(double3 pos) { - return length(pos) - earth_radius; -} - -/// Solve x^2 + px + q -bool solvePolynomialOfDegreeTwo(double p, double q, double* x1, double* x2) { - const double pHalf = 0.5*p; - const double inner = pHalf*pHalf - q; - - if (inner >= 0.0) { - *x1 = -pHalf + sqrt(inner); - *x2 = -pHalf - sqrt(inner); - return true; - } else { - return false; - } -} - -/// Interection of origin + d*dir and a sphere of radius r for normalized dir -bool solveRaySphereIntersection(double3 origin, double3 dir, double r, double* d0, double* d1) { - const double p = 2 * dot(dir,origin); - const double q = dot(origin,origin) - r*r; - - if (solvePolynomialOfDegreeTwo(p, q, d0, d1)) { - if (*d0 > *d1) { - double tmp = *d1; - *d1 = *d0; - *d0 = tmp; - } - return true; - } else { - return false; - } -} - -/// Map {0,...,screenX}x{0,...,screenY} to [-1,1]^2 -double2 getNormalizedScreenPos(double x, double y) { - return (double2)( - 2.0 * (0.5 - x / $size_x) * $size_x.0 / $size_y.0, - 2.0 * (0.5 - y / $size_y) - ); -} - -/// Pinhole camera -double3 getEyeRayDir(double2 screen_pos, double3 eye_pos, double3 eye_target) { - const double3 forward = normalize(eye_target - eye_pos); - const double3 right = normalize(cross((double3)(0.0, 0.0, -1.0), forward)); - const double3 up = normalize(cross(forward, right)); - - return normalize(screen_pos.x*right + screen_pos.y*up + $zoom*forward); -} - -/// Fisheye camera -bool inFishEyeView(double2 screen_pos) { - return length(screen_pos) <= 1.0; -} - -double3 getFishEyeRayDir(double2 screen_pos) { - const double phi = atan2(screen_pos.y, screen_pos.x); - const double theta = acos(1.0 - (screen_pos.x*screen_pos.x + screen_pos.y*screen_pos.y)); - return (double3)(sin(theta)*cos(phi), sin(theta)*sin(phi), cos(theta)); -} - -/// Return true iff earth is hit by rays along dir -bool isVisible(double3 origin, double3 dir) { - double e0, e1; - return !solveRaySphereIntersection(origin, dir, earth_radius, &e0, &e1) - || (e0 < 0 && e1 < 0); -} - -/// Return distance between entering and exiting the atmosphere -double lengthOfRayInAtmosphere(double3 origin, double3 dir) { - double d0, d1; - solveRaySphereIntersection(origin, dir, earth_radius + atmos_height, &d0, &d1); - return d1 - d0; -} - -/// Return light depths of secondary rays -double2 lightDepth(double3 curr, double3 sun) { - const double h = lengthOfRayInAtmosphere(curr, sun) / light_samples; - - double2 depth = 0.0; - - for (unsigned i = 0; i < light_samples; ++i) { - const double height = altitude(curr + (i+0.5)*h*sun); - depth += exp(-height / (double2)($rayleigh_atmos_height, $mie_atmos_height)) * h; - } - - return depth; -} - -/// Calculate color of light along ray -double3 scatter(double3 origin, double3 dir, double dist, double3 sun) { - double3 rayleigh_sum = 0.0; - double3 mie_sum = 0.0; - - double rayleigh_depth = 0.0; - double mie_depth = 0.0; - - const double h = dist / ray_samples; - - for (unsigned i = 0; i < ray_samples; ++i) { - double3 curr = origin + (i+0.5)*h*dir; - - if (isVisible(curr, sun)) { - const double height = altitude(curr); - - const double rayleigh_h = exp(-height / $rayleigh_atmos_height) * h; - const double mie_h = exp(-height / $mie_atmos_height) * h; - - rayleigh_depth += rayleigh_h; - mie_depth += mie_h; - - const double2 light_depth = lightDepth(curr, sun); - const double3 tau = rayleigh_beta * (rayleigh_depth + light_depth.x) - + mie_beta * (mie_depth + light_depth.y); - const double3 attenuation = exp(-tau); - - rayleigh_sum += attenuation * rayleigh_h; - mie_sum += attenuation * mie_h; - } - } - - const double mu = dot(dir,sun); - - const double rayleigh_phase = 0.75 * (1.0 + mu*mu); - const double mie_phase = 0.5*(1-mie_g*mie_g)/pow(1+mie_g*mie_g-2*mie_g*mu,1.5); - - return rayleigh_sum*rayleigh_beta*rayleigh_phase + mie_sum*mie_beta*mie_phase; -} - -void setColor(__global double* result, unsigned x, unsigned y, double3 color) { - result[3*$size_x*y + 3*x + 0] = color.x; - result[3*$size_x*y + 3*x + 1] = color.y; - result[3*$size_x*y + 3*x + 2] = color.z; -} - -void render(__global double* result, unsigned x, unsigned y, double3 origin, double3 ray_dir, double3 sun) { - double d0, d1; - if (!solveRaySphereIntersection(origin, ray_dir, earth_radius + atmos_height, &d0, &d1)) { - setColor(result, x, y, 0.0); - return; - } - - double min_dist = d0; - double max_dist = d1; - - if (insideAtmosphere(origin)) { - min_dist = 0.0; - - if (solveRaySphereIntersection(origin, ray_dir, earth_radius, &d0, &d1) && d1 > 0) { - max_dist = max(0.0, d0); - } - } else { - if (solveRaySphereIntersection(origin, ray_dir, earth_radius, &d0, &d1)) { - max_dist = d0; - } - } - - const double3 ray_origin = origin + min_dist*ray_dir; - const double ray_length = max_dist - min_dist; - - double3 color = scatter(ray_origin, ray_dir, ray_length, normalize(sun)); - color = 1.0 - exp(-exposure * color); - - setColor(result, x, y, color); -} - -__kernel void render_fisheye(__global double* result, double3 eye_pos, double3 eye_dir, double3 sun) { - const unsigned x = get_global_id(0); - const unsigned y = get_global_id(1); - - const double2 screen_pos = getNormalizedScreenPos(x, y); - - if (!inFishEyeView(screen_pos)) { - setColor(result, x, y, 1.0); - return; - } - - const double3 ray_dir = getFishEyeRayDir(screen_pos); - - render(result, x, y, eye_pos, ray_dir, sun); -} - -__kernel void render_pinhole(__global double* result, double3 eye_pos, double3 eye_dir, double3 sun) { - const unsigned x = get_global_id(0); - const unsigned y = get_global_id(1); - - const double2 screen_pos = getNormalizedScreenPos(x, y); - const double3 ray_dir = getEyeRayDir(screen_pos, eye_pos, eye_pos + eye_dir); - - render(result, x, y, eye_pos, ray_dir, sun); -} diff --git a/sun.py b/sun.py deleted file mode 100644 index 7ceec5d..0000000 --- a/sun.py +++ /dev/null @@ -1,32 +0,0 @@ -import numpy as np -from datetime import datetime - -## Sun direction depending on time and place -# As described in Appendix D of "ME 4131 Thermal Environmental Engineering Laboratory Manual" - -def sun_declination(time): - day_of_year = time.timetuple().tm_yday - return 23.45 * np.sin(np.radians((360/365)*(284+day_of_year))) - -def equation_of_time(time): - day_of_year = time.timetuple().tm_yday - b = np.radians(360*(day_of_year-81)/364) - return 0.165*np.sin(2*b) - 0.126*np.cos(b) - 0.025*np.sin(b) - -def sun_direction(lat, lon, time, time_diff, summertime_shift = 0): - lon_std = time_diff * 15 - clock_time = time.hour + time.minute/60 - local_solar_time = clock_time + (1/15)*(lon - lon_std) + equation_of_time(time) - summertime_shift - hour_angle = 15*(local_solar_time - 12) - - l = np.radians(lat) - h = np.radians(hour_angle) - d = np.radians(sun_declination(time)) - - altitude = np.arcsin(np.cos(l) * np.cos(h) * np.cos(d) + np.sin(l) * np.sin(d)) - azimuth = np.arccos((np.cos(d) * np.sin(l) * np.cos(h) - np.sin(d) * np.cos(l)) / np.cos(altitude)) - - if h < 0: - return (altitude, azimuth) - else: - return (altitude, -azimuth) diff --git a/sunrise.py b/sunrise.py index 47457f6..7cde57e 100644 --- a/sunrise.py +++ b/sunrise.py @@ -1,13 +1,8 @@ import numpy import matplotlib.pyplot as plt -from string import Template -import pyopencl as cl -from pyopencl.cltypes import make_double3 - -mf = cl.mem_flags - -from planets import earth +from firmament import Renderer +from firmament.planets import earth config = { 'size_x': 1920//4, @@ -25,31 +20,12 @@ config = { sun_range = (-10, 90, 10) -cl_platform = cl.get_platforms()[0] -cl_context = cl.Context(properties=[(cl.context_properties.PLATFORM, cl_platform)]) -cl_queue = cl.CommandQueue(cl_context) - -cl_picture = cl.Buffer(cl_context, mf.WRITE_ONLY, size=config['size_x']*config['size_y']*3*numpy.float64(0).nbytes) -program = None - -print('height: %d' % (earth['earth_radius']*config['eye_pos'][2] - earth['earth_radius'])) - -with open('raymarch.cl') as f: - program = cl.Program(cl_context, Template(f.read()).substitute( - {**config, **earth} - )).build() +renderer = Renderer(config, earth) for i in numpy.arange(*sun_range): - sun = make_double3(0.0,numpy.cos(i*2*numpy.pi/360),numpy.sin(i*2*numpy.pi/360)) + sun = (0.0, numpy.cos(i*2*numpy.pi/360), numpy.sin(i*2*numpy.pi/360)) print(sun) - program.render_pinhole( - cl_queue, (config['size_x'], config['size_y']), None, cl_picture, - make_double3(*(config['eye_pos'] * earth['earth_radius'])), - make_double3(*(config['eye_dir'] * earth['earth_radius'])), - sun) - - np_picture = numpy.ndarray(shape=(config['size_y'], config['size_x'], 3), dtype=numpy.float64) - cl.enqueue_copy(cl_queue, np_picture, cl_picture).wait(); + np_picture = renderer.render_pinhole(sun) plt.imsave("sky_%05.1f.png" % (i-sun_range[0]), np_picture, origin='lower') -- cgit v1.2.3