Skip to content

Instantly share code, notes, and snippets.

@sporsh
Last active August 3, 2022 20:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sporsh/95f6eacb6d0a6be59a92046319a22d49 to your computer and use it in GitHub Desktop.
Save sporsh/95f6eacb6d0a6be59a92046319a22d49 to your computer and use it in GitHub Desktop.
WebAssembly SDF rendering
#ifndef BASIS_H
#define BASIS_H
#include "vector3.h"
typedef struct {
v3 normal;
v3 tangent;
v3 bitangent;
} basis;
static inline v3 v3_from_local_basis(const basis* b, const v3* v)
{
// Transform a vector from a local basis to wold
return (v3) {
.x = v3_dot(v, &b->tangent),
.y = v3_dot(v, &b->bitangent),
.z = v3_dot(v, &b->normal)
};
}
static inline v3 v3_to_local_basis(const basis* b, const v3* v)
{
// Transform a vector from world coordinates to a local basis
return (v3) {
.x = b->tangent.x * v->x + b->bitangent.x * v->y + b->normal.x * v->z,
.y = b->tangent.y * v->x + b->bitangent.y * v->y + b->normal.y * v->z,
.z = b->tangent.z * v->x + b->bitangent.z * v->y + b->normal.z * v->z,
};
}
static inline v3 orthoginal_unit_vector(const v3* v)
{
double f = sqrt(v->x * v->x + v->z * v->z);
return v3_normalize(&(v3) {
.x = v->z * f,
.y = 0,
.z = -v->x * f });
}
static inline basis arbitrary_basis_from_normal(const v3 normal)
{
v3 tangent = orthoginal_unit_vector(&normal);
return (basis) {
.normal = normal,
.tangent = tangent,
.bitangent = v3_cross(&tangent, &normal)
};
}
#endif // BASIS_H
#!/bin/sh
clang \
--pedantic \
--std=c11 \
--target=wasm32 \
-O3 \
-flto \
-nostdlib \
-Wl,--no-entry \
-Wl,--export-all \
-Wl,--lto-O3 \
-Wl,--allow-undefined-file=wasm.syms \
-Wl,--import-memory \
-o dist/render.wasm \
render.c
# -Wl,-z,stack-size=8388608 \
#ifndef CAMERA_H
#define CAMERA_H
#include "basis.h"
#include "math.h"
#include "vector3.h"
typedef struct {
v3 position;
double aperture;
double field_of_view;
double focal_length;
basis b;
} camera;
typedef struct {
v3 origin;
v3 direction;
double t_min;
double t_max;
} ray;
ray get_ray_through(const camera* c, double u, double v)
{
v3 origin = random_origin_within_aperture(c->aperture, &c->position, &c->b);
v3 target = v3_add(
&c->position,
&v3_to_local_basis(&c->b,
&(v3) {
.x = u * c->field_of_view * c->focal_length,
.y = v * c->field_of_view * c->focal_length,
.z = c->focal_length }));
v3 direction = v3_normalize(&v3_sub(&target, &origin));
return (ray)
{
.direction = direction,
.origin = origin,
t_min = EPSILON,
t_max =
}
}
v3 random_origin_within_aperture(double aperture, const v3* position, const basis* b)
{
const v2 point_on_disc = random_point_on_disc(aperture);
return v3_add(
position,
&v3_from_local_basis(
b,
&(v3) {
.x = point_on_disc.x,
.y = point_on_disc.y,
.z = 0 }));
}
typedef struct {
double x;
double y;
} v2;
static inline double random()
{
return rand() / RAND_MAX;
}
v2 random_point_on_disc(double radius)
{
double random_angle = random() * 2 * M_PI;
double r = sqrt(random()) * radius;
return polar_to_cartesian(r, random_angle);
}
v2 polar_to_cartesian(double r, double phi)
{
return (v2) {
.x = r * cos(phi),
.y = r * sin(phi)
};
}
#endif // CAMERA_H
#ifndef COLOR_H
#define COLOR_H
#include <stdint.h>
typedef union {
struct {
unsigned char r;
unsigned char g;
unsigned char b;
unsigned char a;
};
uint32_t value;
} color;
#endif // COLOR_H
FROM silkeh/clang:14 as build
WORKDIR /app
COPY . .
RUN mkdir dist
RUN ./build.sh
FROM nginx:1.23.1
COPY --from=build /app /usr/share/nginx/html
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8" />
<title></title>
<script type="module">
const width = 400;
const height = 400;
const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
document.body.appendChild(canvas);
// Memory sizes are in # pages (each 64KiB)
const bufferSize = Math.ceil((4 * width * height) / 64 / 1000); // Make sure it's enough to contain our buffer
const memory = new WebAssembly.Memory({ initial: 1 + bufferSize });
async function init() {
var importObject = {
env: {
sin: Math.sin,
cos: Math.cos,
pow: Math.pow,
memory,
},
};
const { instance } = await WebAssembly.instantiateStreaming(
fetch("./dist/render.wasm"),
importObject
);
console.log("instance", instance);
// instance.memory.grow(10);
// const buffer_address = instance.exports.data.value;
const buffer_address = instance.exports.__heap_base.value;
console.log("buffer_address", buffer_address);
const data = new Uint8ClampedArray(
memory.buffer,
buffer_address,
// 4 * width * 31 // * height
4 * width * height
);
const image = new ImageData(data, width, height);
const ctx = canvas.getContext("2d");
const render = (time) => {
instance.exports.render(buffer_address, width, height, time);
ctx.putImageData(image, 0, 0);
window.requestAnimationFrame(render);
};
render(performance.now());
}
init();
</script>
</head>
<body></body>
</html>
#include "color.h"
#include "vector3.h"
// #define WIDTH 512
// #define HEIGHT 512
// #define DATA_SIZE (WIDTH * HEIGHT)
// unsigned int data[DATA_SIZE];
// WASM imports
double sin(double);
double cos(double);
double pow(double, double);
// #define pow __builtin_pow
const double t_min = 0.0001;
const double t_max = 20.0;
const int MAX_STEPS = 512;
typedef struct {
double radius;
v3 center;
} sphere;
static inline double min(double a, double b)
{
return a < b ? a : b;
}
static inline double max(double a, double b)
{
return a > b ? a : b;
}
static inline double op_union(double d1, double d2)
{
return min(d1, d2);
}
static inline double op_subtract(double d1, double d2)
{
return max(-d1, d2);
}
static inline double sphere_distance(const sphere* sphere, const v3* point)
{
const v3 sp = v3_sub(point, &sphere->center);
return v3_length(&sp) - sphere->radius;
}
double distance_at_point(const v3* point, double time)
{
double d1 = sphere_distance(
&(sphere) {
.radius = 1,
.center = (v3) {
// .x = cos(time / 1000) * 1.5,
.x = 1.5,
// .y = sin(time / 1000) * 1.5,
.y = 0,
.z = 6 } },
point);
double d2 = sphere_distance(
&(sphere) {
.radius = 0.8,
.center = (v3) {
// .x = 1.5,
.x = 1.5 + cos(-time / 1000) * 0.5,
.y = 0,
// .z = 6.5 } },
.z = 6 + sin(-time / 1000) * 0.5 } },
point);
double d_spheres = op_subtract(d2, d1);
// double d_spheres = op_subtract(d1, d2);
// double d_spheres = op_union(d1, d2);
// return d_spheres;
// double d_ground = point->y + 2;
double d_ground = sphere_distance(
&(sphere) {
.radius = 1000,
.center = (v3) {
.x = 0,
.y = -1000 - 1,
.z = 0 } },
point);
// return d_ground;
return op_union(d_spheres, d_ground);
}
v3 normal_at_point(const v3* point, double time)
{
// Approximate the normal for the SDF using central difference
const double d = distance_at_point(point, time);
const double x = point->x, y = point->y, z = point->z;
return v3_normalize(&(v3) {
.x = d - distance_at_point(&(v3) { x - t_min, y, z }, time),
.y = d - distance_at_point(&(v3) { x, y - t_min, z }, time),
.z = d - distance_at_point(&(v3) { x, y, z - t_min }, time) });
}
static inline double clamp(double value)
{
return value < 0 ? 0 : (value > 1 ? 1 : value);
}
static inline double gamma(double color)
{
return pow(color, 1 / 2.2);
}
unsigned int get_color_at_point(const v3* point, double time)
{
// const v3 key_light_position = (v3) {
// .x = -0.5703,
// // .x = cos(time / 1000) * 1.5,
// .y = 0.5703,
// // .y = cos(time / 2000) * 1.5,
// .z = -0.5703
// // .z = sin(time / 1000) * 1.5,
// };
const v3 key_light_position = (v3) { -1, 1, -1 };
// const v3 key_light_color = (v3) { 0.7, 0.6, 0.3 };
const v3 key_light_color = (v3) { 0.4, 0.4, 0.4 };
// const v3 ambient_light_color = (v3) { 0.2, 0.3, 0.4 };
const v3 ambient_light_color = (v3) { 0, 0, 0 };
const v3 normal = normal_at_point(point, time);
const v3 key_light = v3_scale(&key_light_color, clamp(v3_dot(&normal, &key_light_position)));
const v3 rim_light_color = (v3) { 0.4, 0.6, 0.8 };
const v3 rim_light_position = (v3) { 2.5, 2.5, 5 };
const v3 rim_light = v3_scale(&rim_light_color, clamp(v3_dot(&normal, &rim_light_position)));
const v3 fill_light_color = (v3) { 0.1, 0.1, 0.1 };
const v3 fill_light_position = (v3) { 1, -1, -1 };
const v3 fill_light = v3_scale(&fill_light_color, clamp(v3_dot(&normal, &fill_light_position)));
const v3 ambient = v3_scale(&ambient_light_color, clamp(0.5 + 0.5 * normal.y));
// const v3 radiance = v3_add(&key_light, &ambient);
const v3 radiance = (v3) {
.x = key_light.x + fill_light.x + rim_light.x + ambient.x,
.y = key_light.y + fill_light.y + rim_light.y + ambient.y,
.z = key_light.z + fill_light.z + rim_light.z + ambient.z
};
return (color) {
.r = (int)(clamp(gamma(radiance.x)) * 0xff),
.g = (int)(clamp(gamma(radiance.y)) * 0xff),
.b = (int)(clamp(gamma(radiance.z)) * 0xff),
.a = 0xff
}
.value;
}
unsigned int sample(double u, double v, double time)
{
const v3 direction = v3_normalize(&(v3) { .x = u, .y = -v, .z = 1 });
// March along ray
double t = 0.5;
for (int i = 0; i < MAX_STEPS && t < t_max; i++) {
v3 intersection_point = v3_scale(&direction, t);
const double d = distance_at_point(&intersection_point, time);
if (d < t_min) {
return get_color_at_point(&intersection_point, time);
}
t += d;
}
return (color) {
.r = (unsigned char)(0.2 * 0xff),
.g = (unsigned char)(0.3 * 0xff),
.b = (unsigned char)(0.4 * 0xff),
.a = 0xff
}
.value;
}
void render(unsigned int data[], unsigned int width, unsigned int height, double time)
{
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
unsigned int i = x + y * width;
double u = (double)x / width - 0.5;
double v = (double)y / height - 0.5;
data[i] = sample(u, v, time);
}
}
}
#!/bin/sh
docker run --rm -it -p 8080:80 $(docker build -q .)
#ifndef VECTOR3_H
#define VECTOR3_H
// TODO: Stop relying on clang builting sqrt for WASM.
#define sqrt __builtin_sqrt
typedef struct {
const double
x,
y,
z;
} v3;
static inline v3 v3_add(const v3* a, const v3* b)
{
return (v3) {
.x = a->x + b->x,
.y = a->y + b->y,
.z = a->z + b->z
};
}
static inline v3 v3_sub(const v3* a, const v3* b)
{
return (v3) {
.x = a->x - b->x,
.y = a->y - b->y,
.z = a->z - b->z
};
}
static inline double v3_length2(const v3* v)
{
return (v->x * v->x) + (v->y * v->y) + (v->z * v->z);
}
static inline double v3_length(const v3* v)
{
return sqrt(v3_length2(v));
}
static inline double v3_dot(const v3* a, const v3* b)
{
return a->x * b->x + a->y * b->y + a->z * b->z;
}
static inline v3 v3_cross(const v3* a, const v3* b)
{
return (v3) {
.x = a->y * b->z - a->z * b->y,
.y = a->z * b->x - a->x * b->z,
.z = a->x * b->y - a->y * b->x
};
}
static inline v3 v3_scale(const v3* v, const double s)
{
return (v3) {
.x = v->x * s,
.y = v->y * s,
.z = v->z * s
};
}
static inline v3 v3_normalize(const v3* v)
{
return v3_scale(v, 1 / v3_length(v));
}
#endif // VECTOR3_H
sin
cos
pow
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment