Repository: chunky/sqlraytracer
Branch: master
Commit: 9956cebb6d1c
Files: 10
Total size: 40.1 KB
Directory structure:
gitextract_4npa0ned/
├── .gitignore
├── LICENSE
├── README.md
├── anim.sh
├── create.sh
├── debug_rays.sh
├── postgres_connection.sh
├── raytracer.sql
├── setup.sql
└── show_scene.sh
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
debug_rays
show_scene
scenelist_override.txt
scenelist.txt
*.ppm
anim
================================================
FILE: LICENSE
================================================
Copyright (c) 2021 Gary Briggs <chunky@icculus.org>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# A Pure SQL Raytracer
Everyone writes a raytracer sooner or later. This is mine.
## Example Outputs
<img src="example_outputs/dielectricparty.png" width=200> <img src="example_outputs/oneglassball.png" width=200> <img src="example_outputs/onemirrorball.png" width=200> <img src="example_outputs/glassmatrix.png" width=200> <img src="example_outputs/airy.png" width=200> <img src="example_outputs/onediamondball.png" width=200> <img src="example_outputs/oneantiglassball.png" width=200> <img src="example_outputs/adjacentballs.png" width=200> <img src="example_outputs/onegreyball.png" width=200> <img src="example_outputs/reflectiontest.png" width=200> <img src="example_outputs/twodiffuseballs.png" width=200> <img src="example_outputs/onegreenball.png" width=200> <img src="example_outputs/threemirrors.png" width=200> <img src="example_outputs/twomirrorballs.png" width=200> <img src="example_outputs/busyday.png" width=200>
## Usage
```shell
sh create.sh
```
```postgres_connection.sh``` contains host/database/user/pass/etc.
There are no exotic needs other than "postgres, like version 10 and up
or something"
For what it's worth, I created mine thus on my ubuntu desktop:
```shell
sudo su - postgres
createuser --pwprompt raytracer
createdb -O raytracer raytracer
```
### Levers for development and rendering
While doing development, obviously a few-minute render time is a pretty
poor cycle time. There are a few levers you can pull to speed things up
and reduce quality. They're on "camera" and "img" in setup.sql:
* *samples\_per\_px* - This is the number of rays/sub-samples per pixel.
- 1 or 2 is fine during debugging
- 15-20 gives "workable" pictures
- Going above 50 doesn't generate much visible improvement
* *max\_ray\_depth* - The maximum number of ray bounces
- For simple scenes, it usually makes no more than 5 or so bounces
* *res\_x* and *res\_y* - Final image resolution
- Smaller is faster
The main CTE carries a lot of stuff that's unnecessary to final output.
This is so I can examine rays bouncing through the scene with:
```sql
SELECT * FROM rays WHERE img_x=100 AND img_y=250
```
There's a script to get a quick-and-dirty view of a scene using gnuplot;
the script ```show_scene.sh``` should generate a folder of outputs.
## Database
This is implemented in pure SQL. It doesn't do anything like CREATE
FUNCTION or other nonportables, except for the trigger to do animation,
which obviously doesn't count.
At the same time, there are some not-entirely-common features of SQL
that it needs:
* JOIN LATERAL
* PARTITION BY inside of a RECURSIVE CTE
* Math functions like SIN()
So although I started developing this in SQLite, I ended up leaning
on PostgreSQL. As I write this, it works in postgres and hasn't been
tested in anything else.
## Interesting Implementation Pieces
Such as it is, I did find myself solving some problems in interesting
ways.
### JOIN LATERAL
JOIN LATERAL is a way to do a correlated subquery in a JOIN, instead of
just in a WHERE clause. I use this as a way to hoist calculations and
do many of them only once and, in some cases, avoid excessive duplication.
### Diffuse Scattering
This requires sampling a uniform sphere. I generate a lot of random
samples ahead of time [sample with rejection -> scale points to sphere
surface], and number them.
Figuring out a way to join each ray to a single random row from these
precalculated scatters was weird; can't just join to RANDOM() because
every ray got joined to the same, random, scatter. Can't just select
with a typical calculation on a normal because that leads to stripes
in the picture. So, instead, I schlep out a later few decimals of one
dimension of a normal, then join to that. It's "random" but also
unique-enough-per-ray.
### Recursive CTEs
Raytracing very naturally tracks how recursive CTEs work. One of the
things I ran into was a clean way to identify which ray is the one to
account for. Using a window function ordering by intercept (t) worked
well. Every iteration, this query intersects a ray with *everything*
in front of it and does all of the associated calculations, but then in
the WHERE clause will reject everything except the thing the ray
actually hit.
Also, there's something really beautiful about the simplicity of the
core of the final rollup [edited for clarity]:
```sql
SELECT img_x, img_y,
SUM(POW(color_mult * ray_col_r/samples_per_px, gamma)) col_r,
SUM(POW(color_mult * ray_col_g/samples_per_px, gamma)) col_g,
SUM(POW(color_mult * ray_col_b/samples_per_px, gamma)) col_b
FROM rays
GROUP BY img_y, img_x
```
### Scenes, materials, etc
Because this is in SQL, I can store multiple scenes in the database. Which
one is actually rendered is selected in the "camera" table.
## Standing on the Necks of Giants
Two years before I wrote this "The most advanced MySQL raytracer on the
market right now" did the rounds on social media:
https://www.pouet.net/prod.php?which=83222
I had a few things in mind that I wanted to do differently [worse?]:
* Demoscene is an artform. I'm not golfing, this isn't minified
* Not a single query; that can be done with CTEs, but ehhhhhhhh
* Animation as an endgame [see ```anim.sh```]
* Mainly, I'm just buggering around with the wrong tool for the job
### I asked Claude for an opinion
*"Why would someone write something like this in the first place?"*
> This is a fascinating example of SQL as a general-purpose programming language taken to an extreme. Someone wrote a complete ray tracer in PostgreSQL SQL, which is both impressive and... questionable.
>
> [...]
>
> This feels like either a brilliant computer science student's thesis project, a senior engineer's weekend experiment, or someone trapped in a very restrictive database-only environment who refused to give up on their ray tracing dreams.
>
> It's the programming equivalent of building a race car out of Lego - technically impressive, but you probably shouldn't actually drive it.
I can't tell if I'm flattered or offended.
## References
Most of this is built following the "Ray Tracing in One Weekend"
series: https://raytracing.github.io/ , then making allowances for
the deliberately obtuse way I'm coding it.
Gary <chunky@icculus.org>
================================================
FILE: anim.sh
================================================
#!/bin/sh
. ./postgres_connection.sh
scenename="busyday"
outfolder=anim
dt=0.05
gravity=-9.8
psql \
--host=${PGHOST} \
--port=${PGPORT} \
--username=${PGUSER} \
--dbname=${PGDB} \
--file=setup.sql \
--file=raytracer.sql \
--command="UPDATE camera SET sceneid=(SELECT sceneid FROM scene WHERE scenename='${scenename}')"
mkdir -p ${outfolder}
for frame in `seq 0 1000`
do
echo "Frame ${frame}"
psql \
--host=${PGHOST} \
--port=${PGPORT} \
--username=${PGUSER} \
--dbname=${PGDB} \
--command="INSERT INTO updateworld(dt, grav_x, grav_y, grav_z) VALUES (${dt}, 0.0, ${gravity}, 0.0)" \
--command="\\timing" \
--command="\\copy (select * from ppm) to './${outfolder}/${scenename}_${frame}.ppm' csv"
done
ffmpeg \
-r 25 \
-i ./${outfolder}/${scenename}_%d.ppm \
-crf 25 \
-pix_fmt yuv420p \
./${outfolder}/${scenename}.mp4
================================================
FILE: create.sh
================================================
#!/bin/sh
. ./postgres_connection.sh
# Creating this file overrides which scenes get rendered
scenelist_override=scenelist_override.txt
scenelist=scenelist.txt
outputdir=example_outputs
mkdir -p ${outputdir}
psql \
--host=${PGHOST} \
--port=${PGPORT} \
--username=${PGUSER} \
--dbname=${PGDB} \
--file=setup.sql \
--file=raytracer.sql \
--command="\\timing" \
--command="\\copy (select scenename from scene) to './${outputdir}/${scenelist}' csv"
test -e ${scenelist_override} && cp ${scenelist_override} ${outputdir}/${scenelist}
while read scenename
do
echo ""
echo "Rendering scene ${scenename}"
psql \
--host=${PGHOST} \
--port=${PGPORT} \
--username=${PGUSER} \
--dbname=${PGDB} \
--command="UPDATE camera SET sceneid=(SELECT sceneid FROM scene WHERE scenename='${scenename}')" \
--command="\\timing" \
--command="\\copy (select * from ppm) to './${outputdir}/${scenename}.ppm' csv"
if [ "$(uname)" == "Darwin" ]; then
open ./${outputdir}/${scenename}.ppm
else
xdg-open ./${outputdir}/${scenename}.ppm
fi
convert ./${outputdir}/${scenename}.ppm \
-gravity SouthEast -pointsize 30 -fill black -annotate +10+10 "${scenename}" \
./${outputdir}/${scenename}.png
done < ./${outputdir}/${scenelist}
================================================
FILE: debug_rays.sh
================================================
#!/bin/sh
. ./postgres_connection.sh
debug_dir=debug_rays
mkdir -p ${debug_dir}
cd ${debug_dir}
psql \
--host=${PGHOST} \
--port=${PGPORT} \
--username=${PGUSER} \
--dbname=${PGDB} \
--command="\\copy (select sphereid, cx, cy, cz, radius from sphere inner join camera c ON c.sceneid=sphere.sceneid) to './spheres.csv' with csv header" \
--command="\\copy (select * from rays WHERE img_x=125 AND img_y between 120 and 195 and 0=img_y%5) to './rays.csv' with csv header"
img_size=2048
scale_vector=5
cat <<EOH > gnuplot.gp
set terminal png size ${img_size},${img_size}
set xrange [-60:60]
set yrange [-60:60]
set datafile separator ','
set key autotitle columnhead
set output 'debug_rays_xz.png'
set xlabel "X"
set ylabel "Z"
plot \\
"spheres.csv" u 2:4:5 w circles t 'spheres', \\
"rays.csv" u 12:14:(${scale_vector}*\$19/\$22):(${scale_vector}*\$21/\$22) w vectors t 'normals', \\
"rays.csv" u 12:14:(${scale_vector}*\$15):(${scale_vector}*\$17) w vectors filled head t 'rays'
set output 'debug_rays_zx.png'
set xlabel "Z"
set ylabel "X"
plot \\
"spheres.csv" u 4:2:5 w circles t 'spheres', \\
"rays.csv" u 14:12:(${scale_vector}*\$21/\$22):(${scale_vector}*\$19/\$22) w vectors t 'normals', \\
"rays.csv" u 14:12:(${scale_vector}*\$17):(${scale_vector}*\$15) w vectors filled head t 'rays'
set output 'debug_rays_xy.png'
set xlabel "X"
set ylabel "Y"
plot \\
"spheres.csv" u 2:3:5 w circles t 'spheres', \\
"rays.csv" u 12:13:(${scale_vector}*\$19/\$22):(${scale_vector}*\$20/\$22) w vectors t 'normals', \\
"rays.csv" u 12:13:(${scale_vector}*\$15):(${scale_vector}*\$16) w vectors t 'rays'
set output 'debug_rays_yx.png'
set xlabel "Y"
set ylabel "X"
plot \\
"spheres.csv" u 3:2:5 w circles t 'spheres', \\
"rays.csv" u 13:12:(${scale_vector}*\$20/\$22):(${scale_vector}*\$19/\$22) w vectors t 'normals', \\
"rays.csv" u 13:12:(${scale_vector}*\$16):(${scale_vector}*\$15) w vectors t 'rays'
set output 'debug_rays_yz.png'
set xlabel "Y"
set ylabel "Z"
plot \\
"spheres.csv" u 3:4:5 w circles t 'spheres', \
"rays.csv" u 13:14:(${scale_vector}*\$20/\$22):(${scale_vector}*\$21/\$22) w vectors t 'normals', \\
"rays.csv" u 13:14:(${scale_vector}*\$16):(${scale_vector}*\$17) w vectors t 'rays'
set output 'debug_rays_zy.png'
set xlabel "Z"
set ylabel "Y"
plot \\
"spheres.csv" u 4:3:5 w circles t 'spheres', \\
"rays.csv" u 14:13:(${scale_vector}*\$21/\$22):(${scale_vector}*\$20/\$22) w vectors t 'normals', \\
"rays.csv" u 14:13:(${scale_vector}*\$17):(${scale_vector}*\$16) w vectors t 'rays'
set output '3view.png'
set xrange [-120:120]
set yrange [-120:120]
set zrange [-120:120]
set xlabel "X"
set ylabel "Y"
set zlabel "Z"
splot \\
"spheres.csv" u 2:3:4:5 w circles t 'spheres', \\
"rays.csv" u 12:13:14:(${scale_vector}*\$19/\$22):(${scale_vector}*\$20/\$22):(${scale_vector}*\$21/\$22) w vectors filled head t 'normals', \\
"rays.csv" u 12:13:14:(${scale_vector}*\$15):(${scale_vector}*\$16):(${scale_vector}*\$17) w vectors filled head t 'rays'
set terminal qt
replot
EOH
gnuplot gnuplot.gp
montage \
-tile 2x2 \
-geometry ${img_size}x${img_size} \
debug_rays_xy.png \
debug_rays_zy.png \
debug_rays_xz.png \
3view.png \
debug_rays.png
xdg-open debug_rays.png
================================================
FILE: postgres_connection.sh
================================================
PGHOST=localhost
PGPORT=5432
PGUSER=raytracer
PGDB=raytracer
PGPASSWORD=raytracer
export PGPASSWORD
================================================
FILE: raytracer.sql
================================================
DROP TABLE IF EXISTS sphere_sample CASCADE;
CREATE TABLE IF NOT EXISTS sphere_sample (x DOUBLE PRECISION NOT NULL, y DOUBLE PRECISION NOT NULL, z DOUBLE PRECISION NOT NULL,
a DOUBLE PRECISION NOT NULL, b DOUBLE PRECISION NOT NULL, c DOUBLE PRECISION NOT NULL, sampleno INTEGER NOT NULL, n_samples INTEGER NOT NULL);
INSERT INTO sphere_sample
WITH square_sample AS (SELECT 2.0*(RANDOM() - 0.5) AS a1, 2.0*(RANDOM() - 0.5) AS b1, 2.0*(RANDOM() - 0.5) AS c1
FROM generate_series(1, 5000)),
ball_sample AS (SELECT a1 AS a, b1 AS b, c1 AS c, SQRT(a1*a1+b1*b1+c1*c1) AS radius FROM square_sample WHERE 1>=(a1*a1+b1*b1+c1*c1)),
sphere_sample AS (SELECT a/radius AS x, b/radius AS y, c/radius AS z, a, b, c, ROW_NUMBER() OVER () AS sampleno, COUNT(*) OVER () AS n_samples FROM ball_sample)
SELECT x,y,z,a,b,c,sampleno,n_samples FROM sphere_sample;
DROP INDEX IF EXISTS idx_ss;
CREATE INDEX IF NOT EXISTS idx_ss ON sphere_sample(sampleno);
DROP VIEW IF EXISTS rays CASCADE;
CREATE VIEW rays AS
WITH RECURSIVE
xs AS (SELECT 0 AS u, 0.0 AS img_frac_x UNION ALL SELECT u+1, (u+1.0)/img.res_x FROM xs, img WHERE xs.u<img.res_x-1),
ys AS (SELECT 0 AS v, 0.0 AS img_frac_y UNION ALL SELECT v+1, (v+1.0)/img.res_y FROM ys, img WHERE ys.v<img.res_y-1),
px_sample_n(px_sample_n, px_jitter_u, px_jitter_v) AS (SELECT 1, (RANDOM()-0.5) * (fov_rad_x/res_x), (RANDOM()-0.5) * (fov_rad_y/res_y)
FROM camera, img
UNION ALL SELECT px_sample_n+1, (RANDOM()-0.5) * (fov_rad_x/res_x), (RANDOM()-0.5) * (fov_rad_y/res_y)
FROM px_sample_n, camera, img WHERE px_sample_n<camera.samples_per_px),
rs(img_x, img_y, sceneid, depth, max_ray_depth, samples_per_px, px_sample_n, color_mult,
ray_col_r, ray_col_g, ray_col_b,
x1, y1, z1,
dir_x, dir_y, dir_z,
dir_lensquared,
n_x, n_y, n_z, n_len,
stop_tracing, ray_len_idx, hit_sphereid, n_sphere_samples, inside_dielectric) AS
-- Send out initial set of rays from camera
(SELECT xs.u, ys.v, c.sceneid, -1, max_ray_depth, samples_per_px, px_sample_n, CAST(2.0 AS DOUBLE PRECISION),
CAST(NULL AS DOUBLE PRECISION), CAST(NULL AS DOUBLE PRECISION), CAST(NULL AS DOUBLE PRECISION),
c.x, c.y, c.z,
(SIN(c.rot_y-(fov_rad_x/2.0)+img_frac_x*fov_rad_x) + px_jitter_u) /
SQRT(((SIN(c.rot_y-(fov_rad_x/2.0)+img_frac_x*fov_rad_x) + px_jitter_u)*(SIN(c.rot_y-(fov_rad_x/2.0)+img_frac_x*fov_rad_x) + px_jitter_u) +
(SIN(c.rot_x-(fov_rad_y/2.0)+img_frac_y*fov_rad_y) + px_jitter_v)*(SIN(c.rot_x-(fov_rad_y/2.0)+img_frac_y*fov_rad_y) + px_jitter_v) + 1.0)),
(SIN(c.rot_x-(fov_rad_y/2.0)+img_frac_y*fov_rad_y) + px_jitter_v) /
SQRT(((SIN(c.rot_y-(fov_rad_x/2.0)+img_frac_x*fov_rad_x) + px_jitter_u)*(SIN(c.rot_y-(fov_rad_x/2.0)+img_frac_x*fov_rad_x) + px_jitter_u) +
(SIN(c.rot_x-(fov_rad_y/2.0)+img_frac_y*fov_rad_y) + px_jitter_v)*(SIN(c.rot_x-(fov_rad_y/2.0)+img_frac_y*fov_rad_y) + px_jitter_v) + 1.0)),
CAST(1.0 AS DOUBLE PRECISION) /
SQRT(((SIN(c.rot_y-(fov_rad_x/2.0)+img_frac_x*fov_rad_x) + px_jitter_u)*(SIN(c.rot_y-(fov_rad_x/2.0)+img_frac_x*fov_rad_x) + px_jitter_u) +
(SIN(c.rot_x-(fov_rad_y/2.0)+img_frac_y*fov_rad_y) + px_jitter_v)*(SIN(c.rot_x-(fov_rad_y/2.0)+img_frac_y*fov_rad_y) + px_jitter_v) + 1.0)),
CAST(1.0 AS DOUBLE PRECISION),
CAST(NULL AS DOUBLE PRECISION), CAST(NULL AS DOUBLE PRECISION), CAST(NULL AS DOUBLE PRECISION), CAST(NULL AS DOUBLE PRECISION),
CAST(0 AS BOOLEAN), CAST(1 AS BIGINT), CAST(NULL AS INTEGER),
(SELECT COUNT(*) FROM sphere_sample), FALSE
FROM camera c, img, xs, ys, px_sample_n
UNION ALL
-- Collide all rays with spheres
SELECT img_x, img_y, rs.sceneid, depth+1, max_ray_depth, samples_per_px, px_sample_n,
(CASE WHEN norm_x IS NULL THEN 0.5 ELSE mirror_frac END)*color_mult,
CASE WHEN discrim>0 THEN (CASE
WHEN shade_normal THEN mat_col_r*(1+norm_x)/2
ELSE mat_col_r
END)
ELSE 1.0-(0.5*((dir_y/SQRT(dir_lensquared)+1.0)))+0.2*(0.5*((dir_y/SQRT(dir_lensquared)+1.0))) END,
CASE WHEN discrim>0 THEN (CASE
WHEN shade_normal THEN mat_col_g*(1+norm_y)/2
ELSE mat_col_g
END)
ELSE 1.0-(0.5*((dir_y/SQRT(dir_lensquared)+1.0)))+0.3*(0.5*((dir_y/SQRT(dir_lensquared)+1.0))) END,
CASE WHEN discrim>0 THEN (CASE
WHEN shade_normal THEN mat_col_b*(1+norm_z)/2
ELSE mat_col_b
END)
ELSE 1.0-(0.5*((dir_y/SQRT(dir_lensquared)+1.0)))+1.0*(0.5*((dir_y/SQRT(dir_lensquared)+1.0))) END,
-- x1, y1, z1
hit_x, hit_y, hit_z,
-- dir_x, dir_y, dir_z
CASE WHEN is_metal
THEN (dir_x - 2 * norm_x * dot_ray_norm) / reflection_len
WHEN is_dielectric AND must_reflect
THEN reflec_dir_x / final_dir_len
WHEN is_dielectric
THEN refrac_dir_x / final_dir_len
ELSE diffuse_dir_x/diffuse_dir_len
END,
CASE WHEN is_metal
THEN (dir_y - 2 * norm_y * dot_ray_norm) / reflection_len
WHEN is_dielectric AND must_reflect
THEN reflec_dir_y / final_dir_len
WHEN is_dielectric
THEN refrac_dir_y / final_dir_len
ELSE diffuse_dir_y/diffuse_dir_len
END,
CASE WHEN is_metal
THEN (dir_z - 2 * norm_z * dot_ray_norm) / reflection_len
WHEN is_dielectric AND must_reflect
THEN reflec_dir_z / final_dir_len
WHEN is_dielectric
THEN refrac_dir_z / final_dir_len
ELSE diffuse_dir_z/diffuse_dir_len
END,
1.0,
norm_x, norm_y, norm_z, 1.0,
discrim IS NULL, ROW_NUMBER() OVER (PARTITION BY img_x, img_y, depth+1, px_sample_n
ORDER BY t),
sphereid, n_sphere_samples, (inside_dielectric AND NOT must_reflect) OR (NOT inside_dielectric AND NOT is_dielectric)
FROM rs
LEFT JOIN LATERAL
(SELECT s.*, discrim,
CASE WHEN t_near > 0.001 THEN t_near ELSE t_far END AS t
FROM sphere s,
LATERAL (SELECT (x1-s.cx)*dir_x+(y1-s.cy)*dir_y+(z1-s.cz)*dir_z AS hb) hbv,
LATERAL (SELECT hb*hb - ((x1-s.cx)*(x1-s.cx)+(y1-s.cy)*(y1-s.cy)+(z1-s.cz)*(z1-s.cz)-s.radius2)*dir_lensquared AS discrim) dv,
LATERAL (SELECT (-hb - SQRT(discrim))/dir_lensquared AS t_near,
(-hb + SQRT(discrim))/dir_lensquared AS t_far) tv
WHERE s.sceneid=rs.sceneid AND discrim > 0
) hit_sphere ON t>0.001
LEFT JOIN LATERAL
(SELECT x1+dir_x*t AS hit_x, y1+dir_y*t AS hit_y, z1+dir_z*t AS hit_z,
x1+dir_x*t-cx AS norm_x_nonunit, y1+dir_y*t-cy AS norm_y_nonunit, z1+dir_z*t-cz AS norm_z_nonunit,
SQRT((x1+dir_x*t-cx)*(x1+dir_x*t-cx)+(y1+dir_y*t-cy)*(y1+dir_y*t-cy)+(z1+dir_z*t-cz)*(z1+dir_z*t-cz)) AS norm_len,
ROW_NUMBER() OVER (PARTITION BY img_x, img_y, depth, px_sample_n ORDER BY t ASC) AS t_idx
WHERE t>0
) sphere_normal ON t_idx=1
LEFT JOIN LATERAL
(SELECT CASE WHEN (norm_x_nonunit * dir_x + norm_y_nonunit * dir_y + norm_z_nonunit * dir_z) > 0
THEN -norm_x_nonunit/norm_len
ELSE norm_x_nonunit/norm_len END AS norm_x,
CASE WHEN (norm_x_nonunit * dir_x + norm_y_nonunit * dir_y + norm_z_nonunit * dir_z) > 0
THEN -norm_y_nonunit/norm_len
ELSE norm_y_nonunit/norm_len END AS norm_y,
CASE WHEN (norm_x_nonunit * dir_x + norm_y_nonunit * dir_y + norm_z_nonunit * dir_z) > 0
THEN -norm_z_nonunit/norm_len
ELSE norm_z_nonunit/norm_len END AS norm_z
) sphere_unit_normal ON norm_x IS NOT NULL
LEFT JOIN LATERAL
(SELECT dir_x*norm_x + dir_y*norm_y + dir_z*norm_z AS dot_ray_norm,
SQRT((dir_x - 2 * norm_x * (dir_x*norm_x + dir_y*norm_y + dir_z*norm_z)) * (dir_x - 2 * norm_x * (dir_x*norm_x + dir_y*norm_y + dir_z*norm_z)) +
(dir_y - 2 * norm_y * (dir_x*norm_x + dir_y*norm_y + dir_z*norm_z)) * (dir_y - 2 * norm_y * (dir_x*norm_x + dir_y*norm_y + dir_z*norm_z)) +
(dir_z - 2 * norm_z * (dir_x*norm_x + dir_y*norm_y + dir_z*norm_z)) * (dir_z - 2 * norm_z * (dir_x*norm_x + dir_y*norm_y + dir_z*norm_z))) AS reflection_len
) dot_ray_norm ON norm_x IS NOT NULL
LEFT JOIN material ON material.materialid=hit_sphere.materialid
LEFT JOIN LATERAL
(SELECT x, y, z,
x+norm_x AS diffuse_dir_x, y+norm_y AS diffuse_dir_y, z+norm_z AS diffuse_dir_z,
SQRT((x+norm_x)*(x+norm_x)+(y+norm_y)*(y+norm_y)+(z+norm_z)*(z+norm_z)) AS diffuse_dir_len
FROM sphere_sample ss WHERE ss.sampleno=1+CAST(FLOOR(ABS((100000*dir_x)-FLOOR(100000*dir_x))*n_sphere_samples) AS INTEGER)
) diffuse_scatter ON norm_x IS NOT NULL
LEFT JOIN LATERAL
(SELECT (CASE WHEN is_dielectric AND (norm_x_nonunit * dir_x + norm_y_nonunit * dir_y + norm_z_nonunit * dir_z) > 0
THEN eta
WHEN is_dielectric
THEN 1.0/eta
ELSE eta END) AS ir) index_of_refraction ON norm_x IS NOT NULL
LEFT JOIN LATERAL
(SELECT ABS(dir_x*norm_x + dir_y*norm_y + dir_z*norm_z) AS cos_theta,
((1.0-ir)/(1.0+ir))*((1.0-ir)/(1.0+ir)) AS r0
) refract_cos_theta ON norm_x IS NOT NULL
LEFT JOIN LATERAL
(SELECT
-- Discriminant for total internal reflection check
1.0 - ir*ir*(1.0-cos_theta*cos_theta) AS refrac_discriminant,
-- Correct refraction direction
ir * dir_x + (ir * cos_theta - SQRT(GREATEST(0.0, 1.0 - ir*ir*(1.0-cos_theta*cos_theta)))) * norm_x AS refrac_dir_x,
ir * dir_y + (ir * cos_theta - SQRT(GREATEST(0.0, 1.0 - ir*ir*(1.0-cos_theta*cos_theta)))) * norm_y AS refrac_dir_y,
ir * dir_z + (ir * cos_theta - SQRT(GREATEST(0.0, 1.0 - ir*ir*(1.0-cos_theta*cos_theta)))) * norm_z AS refrac_dir_z,
-- Fresnel reflectance
r0 + (1.0 - r0)*pow(1.0-cos_theta, 5) AS reflectance
) refrac_vec ON norm_x IS NOT NULL
LEFT JOIN LATERAL
(SELECT
dir_x - 2.0 * (dir_x*norm_x + dir_y*norm_y + dir_z*norm_z) * norm_x AS reflec_dir_x,
dir_y - 2.0 * (dir_x*norm_x + dir_y*norm_y + dir_z*norm_z) * norm_y AS reflec_dir_y,
dir_z - 2.0 * (dir_x*norm_x + dir_y*norm_y + dir_z*norm_z) * norm_z AS reflec_dir_z,
-- Total internal reflection check
(refrac_discriminant < 0) OR (reflectance > RANDOM()) AS must_reflect
) reflec_vec ON norm_x IS NOT NULL
LEFT JOIN LATERAL
(SELECT
CASE WHEN must_reflect
THEN SQRT(reflec_dir_x*reflec_dir_x + reflec_dir_y*reflec_dir_y + reflec_dir_z*reflec_dir_z)
ELSE SQRT(refrac_dir_x*refrac_dir_x + refrac_dir_y*refrac_dir_y + refrac_dir_z*refrac_dir_z)
END AS final_dir_len
) final_dir_len ON norm_x IS NOT NULL
LEFT JOIN LATERAL
(SELECT SQRT((reflec_dir_x+refrac_dir_x)*(reflec_dir_x+refrac_dir_x)+
(reflec_dir_y+refrac_dir_y)*(reflec_dir_y+refrac_dir_y)+
(reflec_dir_z+refrac_dir_z)*(reflec_dir_z+refrac_dir_z)) refrac_len
) refrac_len ON norm_x IS NOT NULL
WHERE depth<max_ray_depth AND NOT stop_tracing AND ray_len_idx=1
)
SELECT * FROM rs WHERE ray_len_idx=1;
-- select * from rays;
DROP VIEW IF EXISTS do_render;
CREATE VIEW do_render AS
SELECT A.img_x, -A.img_y,
GREATEST(0.0, LEAST(1.0, SUM(POW(A.color_mult * COALESCE(A.ray_col_r, 0.0)/A.samples_per_px, gamma)))) col_r,
GREATEST(0.0, LEAST(1.0, SUM(POW(A.color_mult * COALESCE(A.ray_col_g, 0.0)/A.samples_per_px, gamma)))) col_g,
GREATEST(0.0, LEAST(1.0, SUM(POW(A.color_mult * COALESCE(A.ray_col_b, 0.0)/A.samples_per_px, gamma)))) col_b
FROM rays A, img
WHERE A.depth>=0
GROUP BY -A.img_y, A.img_x
ORDER BY -A.img_y, A.img_x;
DROP VIEW IF EXISTS ppm;
CREATE VIEW ppm AS
WITH maxcol(mc) AS (SELECT 255)
SELECT 'P3'
UNION ALL
SELECT res_x || ' ' || res_y || ' ' || mc FROM img, maxcol
UNION ALL
SELECT CAST(col_r*mc AS INTEGER) || ' ' || CAST(col_g*mc AS INTEGER) || ' ' || CAST(col_b*mc AS INTEGER)
FROM do_render, maxcol;
;
-- SELECT * FROM rays WHERE img_x=2 AND img_y=2;
================================================
FILE: setup.sql
================================================
DROP TABLE IF EXISTS material CASCADE;
CREATE TABLE material (materialid SERIAL PRIMARY KEY, name TEXT,
mat_col_r DOUBLE PRECISION, mat_col_g DOUBLE PRECISION, mat_col_b DOUBLE PRECISION,
is_metal BOOLEAN NOT NULL, shade_normal BOOLEAN NOT NULL, mirror_frac DOUBLE PRECISION NOT NULL,
is_dielectric BOOLEAN NOT NULL, eta DOUBLE PRECISION NOT NULL DEFAULT 1.0);
INSERT INTO material (name, mat_col_r, mat_col_g, mat_col_b, is_metal, shade_normal, mirror_frac, is_dielectric, eta) VALUES
('dark', 0.1, 0.1, 0.1, FALSE, FALSE, 0.1, FALSE, 1.0),
('red', 0.95, 0.0, 0.0, FALSE, TRUE, 0.5, FALSE, 1.0),
('green', 0.0, 0.95, 0.0, FALSE, TRUE, 0.5, FALSE, 1.0),
('blue', 0.0, 0.0, 0.95, TRUE, TRUE, 0.5, FALSE, 1.0),
('grey', 0.1, 0.1, 0.1, FALSE, FALSE, 0.5, FALSE, 1.0),
('bright', 1.0, 1.0, 1.0, TRUE, TRUE, 0.5, FALSE, 1.0),
('mirror', NULL, NULL, NULL, TRUE, FALSE, 0.99, FALSE, 1.0),
('bluemirror', 0.0, 0.0, 0.3, TRUE, FALSE, 0.9, FALSE, 1.0),
('greenmirror', 0.0, 0.2, 0.0, TRUE, FALSE, 0.9, FALSE, 1.0),
('notquiteair', NULL, NULL, NULL, FALSE, FALSE, 1.0, TRUE, 1.00000001),
('glass', NULL, NULL, NULL, FALSE, FALSE, 0.95, TRUE, 1.5),
('greenglass', 0.0, 0.2, 0.0, FALSE, FALSE, 0.8, TRUE, 1.5),
('diamond', NULL, NULL, NULL, FALSE, FALSE, 0.99, TRUE, 2.4),
('antiglass', NULL, NULL, NULL, FALSE, FALSE, 0.99, TRUE, 0.2)
;
DROP TABLE IF EXISTS scene CASCADE;
CREATE TABLE IF NOT EXISTS scene (sceneid SERIAL PRIMARY KEY,
scenename TEXT UNIQUE NOT NULL);
INSERT INTO scene (scenename) VALUES ('dielectricparty'),
('oneglassball'),
('onediamondball'),
('oneantiglassball'),
('onegreyball'),
('onegreenball'),
('twomirrorballs'),
('twodiffuseballs'),
('onemirrorball'),
('reflectiontest'),
('threemirrors'),
('adjacentballs'),
('glassmatrix'),
('airy'),
('busyday');
DROP TABLE IF EXISTS sphere CASCADE;
CREATE TABLE sphere (sphereid SERIAL, sceneid INTEGER NOT NULL REFERENCES scene(sceneid),
cx DOUBLE PRECISION NOT NULL, cy DOUBLE PRECISION NOT NULL, cz DOUBLE PRECISION NOT NULL,
radius DOUBLE PRECISION, radius2 DOUBLE PRECISION, materialid INTEGER NOT NULL REFERENCES material(materialid) DEFERRABLE,
vel_x DOUBLE PRECISION NOT NULL DEFAULT 0.0, vel_y DOUBLE PRECISION NOT NULL DEFAULT 0.0, vel_z DOUBLE PRECISION NOT NULL DEFAULT 0.0,
coefficient_of_restitution DOUBLE PRECISION NOT NULL DEFAULT 1.0);
INSERT INTO sphere (cx, cy, cz, radius, materialid, sceneid) VALUES
(0, 24, -10, 5,
(SELECT materialid FROM material WHERE name='bright'), (SELECT sceneid FROM scene WHERE scenename='reflectiontest')),
(0, 5, 0, 5,
(SELECT materialid FROM material WHERE name='red'), (SELECT sceneid FROM scene WHERE scenename='reflectiontest')),
(-17, 15, -30, 15,
(SELECT materialid FROM material WHERE name='bluemirror'), (SELECT sceneid FROM scene WHERE scenename='reflectiontest')),
(24, 23, 10, 23,
(SELECT materialid FROM material WHERE name='greenmirror'), (SELECT sceneid FROM scene WHERE scenename='reflectiontest')),
(0, -1250, 0, 1250,
(SELECT materialid FROM material WHERE name='grey'), (SELECT sceneid FROM scene WHERE scenename='twomirrorballs')),
(20, 25, -30, 25,
(SELECT materialid FROM material WHERE name='greenmirror'), (SELECT sceneid FROM scene WHERE scenename='twomirrorballs')),
(-20, 25, 0, 25,
(SELECT materialid FROM material WHERE name='mirror'), (SELECT sceneid FROM scene WHERE scenename='twomirrorballs')),
(0, -1250, 0, 1250,
(SELECT materialid FROM material WHERE name='grey'), (SELECT sceneid FROM scene WHERE scenename='twodiffuseballs')),
(20, 25, -30, 25,
(SELECT materialid FROM material WHERE name='dark'), (SELECT sceneid FROM scene WHERE scenename='twodiffuseballs')),
(-20, 25, 0, 25,
(SELECT materialid FROM material WHERE name='green'), (SELECT sceneid FROM scene WHERE scenename='twodiffuseballs')),
(0, -1250, 0, 1250,
(SELECT materialid FROM material WHERE name='grey'), (SELECT sceneid FROM scene WHERE scenename='onemirrorball')),
(-20, 25, 0, 25,
(SELECT materialid FROM material WHERE name='mirror'), (SELECT sceneid FROM scene WHERE scenename='onemirrorball')),
(0, -1250, 0, 1250,
(SELECT materialid FROM material WHERE name='grey'), (SELECT sceneid FROM scene WHERE scenename='oneglassball')),
(0, 25, -10, 25,
(SELECT materialid FROM material WHERE name='glass'), (SELECT sceneid FROM scene WHERE scenename='oneglassball')),
(20, 25, 80, 25,
(SELECT materialid FROM material WHERE name='red'), (SELECT sceneid FROM scene WHERE scenename='oneglassball')),
(0, -1250, 0, 1250,
(SELECT materialid FROM material WHERE name='grey'), (SELECT sceneid FROM scene WHERE scenename='onediamondball')),
(0, 25, -10, 25,
(SELECT materialid FROM material WHERE name='diamond'), (SELECT sceneid FROM scene WHERE scenename='onediamondball')),
(20, 25, 80, 25,
(SELECT materialid FROM material WHERE name='red'), (SELECT sceneid FROM scene WHERE scenename='onediamondball')),
(0, -1250, 0, 1250,
(SELECT materialid FROM material WHERE name='grey'), (SELECT sceneid FROM scene WHERE scenename='oneantiglassball')),
(0, 25, -10, 25,
(SELECT materialid FROM material WHERE name='antiglass'), (SELECT sceneid FROM scene WHERE scenename='oneantiglassball')),
(20, 25, 80, 25,
(SELECT materialid FROM material WHERE name='red'), (SELECT sceneid FROM scene WHERE scenename='oneantiglassball')),
(0, -1250, 0, 1250,
(SELECT materialid FROM material WHERE name='grey'), (SELECT sceneid FROM scene WHERE scenename='dielectricparty')),
(0, 12, 0, NULL,
(SELECT materialid FROM material WHERE name='glass'), (SELECT sceneid FROM scene WHERE scenename='dielectricparty')),
(25, 12, 0, NULL,
(SELECT materialid FROM material WHERE name='antiglass'), (SELECT sceneid FROM scene WHERE scenename='dielectricparty')),
(-25, 12, 0, NULL,
(SELECT materialid FROM material WHERE name='diamond'), (SELECT sceneid FROM scene WHERE scenename='dielectricparty')),
(15, 10, 20, NULL,
(SELECT materialid FROM material WHERE name='red'), (SELECT sceneid FROM scene WHERE scenename='dielectricparty')),
(-5, 10, 30, NULL,
(SELECT materialid FROM material WHERE name='green'), (SELECT sceneid FROM scene WHERE scenename='dielectricparty')),
(0, -1250, 0, 1250,
(SELECT materialid FROM material WHERE name='grey'), (SELECT sceneid FROM scene WHERE scenename='airy')),
(0, 12, 0, NULL,
(SELECT materialid FROM material WHERE name='notquiteair'), (SELECT sceneid FROM scene WHERE scenename='airy')),
(25, 12, 0, NULL,
(SELECT materialid FROM material WHERE name='notquiteair'), (SELECT sceneid FROM scene WHERE scenename='airy')),
(-25, 12, 0, NULL,
(SELECT materialid FROM material WHERE name='notquiteair'), (SELECT sceneid FROM scene WHERE scenename='airy')),
(15, 10, 20, NULL,
(SELECT materialid FROM material WHERE name='red'), (SELECT sceneid FROM scene WHERE scenename='airy')),
(-5, 10, 30, NULL,
(SELECT materialid FROM material WHERE name='green'), (SELECT sceneid FROM scene WHERE scenename='airy')),
(0, -1250, 0, 1250,
(SELECT materialid FROM material WHERE name='green'), (SELECT sceneid FROM scene WHERE scenename='adjacentballs')),
(-24, 12, 0, 12,
(SELECT materialid FROM material WHERE name='red'), (SELECT sceneid FROM scene WHERE scenename='adjacentballs')),
(0, 12, 0, 12,
(SELECT materialid FROM material WHERE name='mirror'), (SELECT sceneid FROM scene WHERE scenename='adjacentballs')),
(24, 12, 0, 12,
(SELECT materialid FROM material WHERE name='bright'), (SELECT sceneid FROM scene WHERE scenename='adjacentballs')),
(0, -1250, 0, 1250,
(SELECT materialid FROM material WHERE name='grey'), (SELECT sceneid FROM scene WHERE scenename='onegreyball')),
(20, 25, 0, 25,
(SELECT materialid FROM material WHERE name='grey'), (SELECT sceneid FROM scene WHERE scenename='onegreyball')),
(0, -1250, 0, 1250,
(SELECT materialid FROM material WHERE name='grey'), (SELECT sceneid FROM scene WHERE scenename='onegreenball')),
(20, 25, 0, 25,
(SELECT materialid FROM material WHERE name='green'), (SELECT sceneid FROM scene WHERE scenename='onegreenball')),
(-20, 15, -15, 22,
(SELECT materialid FROM material WHERE name='mirror'), (SELECT sceneid FROM scene WHERE scenename='threemirrors')),
(0, 0, 0, 5,
(SELECT materialid FROM material WHERE name='mirror'), (SELECT sceneid FROM scene WHERE scenename='threemirrors')),
(30, -15, 0, 25,
(SELECT materialid FROM material WHERE name='mirror'), (SELECT sceneid FROM scene WHERE scenename='threemirrors')),
(0, -1250, 0, 1250,
(SELECT materialid FROM material WHERE name='grey'), (SELECT sceneid FROM scene WHERE scenename='busyday')),
(0, -1250, 0, 1250,
(SELECT materialid FROM material WHERE name='grey'), (SELECT sceneid FROM scene WHERE scenename='glassmatrix')),
(20, 25, 80, 25,
(SELECT materialid FROM material WHERE name='red'), (SELECT sceneid FROM scene WHERE scenename='glassmatrix'))
;
INSERT INTO sphere (cx, cy, cz, radius, materialid, sceneid, coefficient_of_restitution)
SELECT (RANDOM()-0.5) * 100, 50 + RANDOM() * 50, (RANDOM()-0.5) * 100, RANDOM() * 5.0,
1+CAST((RANDOM()*(SELECT MAX(materialid)-1 FROM material)) AS INTEGER), (SELECT sceneid FROM scene WHERE scenename='busyday'),
0.7*RANDOM()+0.3
FROM generate_series(1, 20)
GROUP BY generate_series;
INSERT INTO sphere (cx, cy, cz, radius, materialid, sceneid, coefficient_of_restitution)
WITH params(r, x1, y1) AS (SELECT 5, -60, -15)
SELECT x1 + 2 * r * x, y1 + 2 * r * y, -30, r,
(SELECT materialid FROM material WHERE name='glass'), (SELECT sceneid FROM scene WHERE scenename='glassmatrix'),
1.0
FROM generate_series(1, 11) X, generate_series(1, 11) Y, params;
UPDATE sphere SET radius = cy WHERE radius IS NULL;
UPDATE sphere SET radius2 = radius*radius WHERE radius2 IS NULL;
DROP TABLE IF EXISTS camera CASCADE;
CREATE TABLE camera (cameraid INTEGER PRIMARY KEY, sceneid INTEGER NOT NULL REFERENCES scene(sceneid),
x DOUBLE PRECISION NOT NULL, y DOUBLE PRECISION NOT NULL, z DOUBLE PRECISION NOT NULL,
rot_x DOUBLE PRECISION NOT NULL, rot_y DOUBLE PRECISION NOT NULL, rot_z DOUBLE PRECISION NOT NULL,
fov_rad_x DOUBLE PRECISION NOT NULL, fov_rad_y DOUBLE PRECISION NOT NULL,
max_ray_depth INTEGER NOT NULL, samples_per_px INTEGER NOT NULL);
INSERT INTO camera (cameraid, x, y, z, rot_x, rot_y, rot_z, fov_rad_x, fov_rad_y, max_ray_depth, samples_per_px, sceneid)
VALUES (1.0, 0.0, 65.0, -120.0, -0.34, 0.0, 0.0, PI()/3.0, PI()/3.0,
30, 50, (SELECT sceneid FROM scene WHERE scenename='busyday'));
DROP TABLE IF EXISTS img CASCADE;
CREATE TABLE img (res_x INTEGER NOT NULL, res_y INTEGER NOT NULL, gamma DOUBLE PRECISION);
INSERT INTO img (res_x, res_y, gamma)
VALUES (650, 650, 1.0);
CREATE OR REPLACE FUNCTION animate_spheres()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS $$
BEGIN
UPDATE sphere SET vel_x = vel_x + NEW.grav_x*NEW.dt,
vel_y = vel_y + NEW.grav_y*NEW.dt,
vel_z = vel_z + NEW.grav_z*NEW.dt
WHERE sceneid=(SELECT sceneid FROM camera) AND cy>0;
UPDATE sphere SET vel_y = -vel_y*coefficient_of_restitution WHERE radius>cy
AND sceneid=(SELECT sceneid FROM camera);
UPDATE sphere SET cx=cx+vel_x*NEW.dt,
cy=cy+vel_y*NEW.dt,
cz=cz+vel_z*NEW.dt
WHERE sceneid=(SELECT sceneid FROM camera);
RETURN NEW;
END;
$$ ;
DROP VIEW IF EXISTS updateworld;
CREATE VIEW updateworld AS (SELECT 0.0 AS dt, 0.0 AS grav_x, 0.0 AS grav_y, 0.0 AS grav_z);
CREATE TRIGGER trig_update_world INSTEAD OF INSERT ON updateworld FOR EACH ROW
EXECUTE PROCEDURE animate_spheres();
-- INSERT INTO updateworld (dt, grav_x, grav_y, grav_z) VALUES (0.1, 0.0, -9.8, 0.0);
-- select cx, cy, cz, vel_x, vel_y, vel_z from sphere
-- where sceneid=(SELECT sceneid FROM scene WHERE scenename='busyday');
================================================
FILE: show_scene.sh
================================================
#!/bin/sh
. ./postgres_connection.sh
show_scene_dir=show_scene
scenelist=scenelist.txt
psql \
--host=${PGHOST} \
--port=${PGPORT} \
--username=${PGUSER} \
--dbname=${PGDB} \
--file=setup.sql \
--file=raytracer.sql \
--command="\\timing" \
--command="\\copy (select scenename from scene) to './${show_scene_dir}/${scenelist}' csv"
mkdir -p ${show_scene_dir}
cd ${show_scene_dir}
while read scenename
do
echo ""
echo "Rendering scene ${scenename}"
psql \
--host=${PGHOST} \
--port=${PGPORT} \
--username=${PGUSER} \
--dbname=${PGDB} \
--command="\\copy (select sphereid, cx, cy, cz, radius from sphere inner join scene s on s.sceneid=sphere.sceneid where s.scenename='${scenename}') to './spheres.csv' with csv header" \
img_size=2048
scale_vector=5
cat <<EOH > gnuplot.gp
set terminal png size ${img_size},${img_size}
set xrange [-60:60]
set yrange [-60:60]
set datafile separator ','
set key autotitle columnhead
set output '${scenename}_xz.png'
set xlabel "X"
set ylabel "Z"
plot \\
"spheres.csv" u 2:4:5 w circles t 'spheres'
set output '${scenename}_zx.png'
set xlabel "Z"
set ylabel "X"
plot \\
"spheres.csv" u 4:2:5 w circles t 'spheres'
set output '${scenename}_xy.png'
set xlabel "X"
set ylabel "Y"
plot \\
"spheres.csv" u 2:3:5 w circles t 'spheres'
set output '${scenename}_yx.png'
set xlabel "Y"
set ylabel "X"
plot \\
"spheres.csv" u 3:2:5 w circles t 'spheres'
set output '${scenename}_yz.png'
set xlabel "Y"
set ylabel "Z"
plot \\
"spheres.csv" u 3:4:5 w circles t 'spheres'
set output '${scenename}_zy.png'
set xlabel "Z"
set ylabel "Y"
plot \\
"spheres.csv" u 4:3:5 w circles t 'spheres'
set output '${scenename}_3view.png'
set xrange [-120:120]
set yrange [-120:120]
set zrange [-120:120]
set xlabel "X"
set ylabel "Y"
set zlabel "Z"
splot \\
"spheres.csv" u 2:3:4:5 w circles t 'spheres'
set terminal qt
replot
EOH
gnuplot gnuplot.gp
montage \
-tile 2x2 \
-geometry ${img_size}x${img_size} \
${scenename}_xy.png \
${scenename}_zy.png \
${scenename}_xz.png \
${scenename}_3view.png \
fullscene_${scenename}.png
done < ./${scenelist}
xdg-open .
gitextract_4npa0ned/ ├── .gitignore ├── LICENSE ├── README.md ├── anim.sh ├── create.sh ├── debug_rays.sh ├── postgres_connection.sh ├── raytracer.sql ├── setup.sql └── show_scene.sh
SYMBOL INDEX (12 symbols across 2 files) FILE: raytracer.sql type sphere_sample (line 2) | CREATE TABLE IF NOT EXISTS sphere_sample (x DOUBLE PRECISION NOT NULL, y... type idx_ss (line 11) | CREATE INDEX IF NOT EXISTS idx_ss ON sphere_sample(sampleno) type rays (line 14) | CREATE VIEW rays AS type do_render (line 188) | CREATE VIEW do_render AS type ppm (line 199) | CREATE VIEW ppm AS FILE: setup.sql type material (line 2) | CREATE TABLE material (materialid SERIAL PRIMARY KEY, name TEXT, type scene (line 24) | CREATE TABLE IF NOT EXISTS scene (sceneid SERIAL PRIMARY KEY, type sphere (line 43) | CREATE TABLE sphere (sphereid SERIAL, sceneid INTEGER NOT NULL REFERENCE... type camera (line 178) | CREATE TABLE camera (cameraid INTEGER PRIMARY KEY, sceneid INTEGER NOT N... type img (line 188) | CREATE TABLE img (res_x INTEGER NOT NULL, res_y INTEGER NOT NULL, gamma ... function animate_spheres (line 192) | CREATE OR REPLACE FUNCTION animate_spheres() type updateworld (line 212) | CREATE VIEW updateworld AS (SELECT 0.0 AS dt, 0.0 AS grav_x, 0.0 AS grav...
Condensed preview — 10 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (43K chars).
[
{
"path": ".gitignore",
"chars": 70,
"preview": "debug_rays\nshow_scene\nscenelist_override.txt\nscenelist.txt\n*.ppm\nanim\n"
},
{
"path": "LICENSE",
"chars": 1076,
"preview": "Copyright (c) 2021 Gary Briggs <chunky@icculus.org>\n\nPermission is hereby granted, free of charge, to any person obtaini"
},
{
"path": "README.md",
"chars": 6272,
"preview": "# A Pure SQL Raytracer\n\nEveryone writes a raytracer sooner or later. This is mine.\n\n## Example Outputs\n\n<img src=\"exampl"
},
{
"path": "anim.sh",
"chars": 865,
"preview": "#!/bin/sh\n\n. ./postgres_connection.sh\n\nscenename=\"busyday\"\noutfolder=anim\ndt=0.05\ngravity=-9.8\n\npsql \\\n\t--host=${PGHOST}"
},
{
"path": "create.sh",
"chars": 1266,
"preview": "#!/bin/sh\n\n. ./postgres_connection.sh\n\n# Creating this file overrides which scenes get rendered\nscenelist_override=scene"
},
{
"path": "debug_rays.sh",
"chars": 3316,
"preview": "#!/bin/sh\n\n. ./postgres_connection.sh\n\ndebug_dir=debug_rays\nmkdir -p ${debug_dir}\ncd ${debug_dir}\n\npsql \\\n\t--host=${PGHO"
},
{
"path": "postgres_connection.sh",
"chars": 102,
"preview": "PGHOST=localhost\nPGPORT=5432\nPGUSER=raytracer\nPGDB=raytracer\n\nPGPASSWORD=raytracer\nexport PGPASSWORD\n\n"
},
{
"path": "raytracer.sql",
"chars": 13414,
"preview": "DROP TABLE IF EXISTS sphere_sample CASCADE;\nCREATE TABLE IF NOT EXISTS sphere_sample (x DOUBLE PRECISION NOT NULL, y DOU"
},
{
"path": "setup.sql",
"chars": 12408,
"preview": "DROP TABLE IF EXISTS material CASCADE;\nCREATE TABLE material (materialid SERIAL PRIMARY KEY, name TEXT,\n mat_col_r DOUB"
},
{
"path": "show_scene.sh",
"chars": 2264,
"preview": "#!/bin/sh\n\n. ./postgres_connection.sh\n\nshow_scene_dir=show_scene\n\nscenelist=scenelist.txt\n\npsql \\\n --host=${PGHOS"
}
]
About this extraction
This page contains the full source code of the chunky/sqlraytracer GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 10 files (40.1 KB), approximately 12.3k tokens, and a symbol index with 12 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.