make stuff (not working)

This commit is contained in:
ktyl 2021-07-24 01:31:35 +01:00
parent 7fe29a1ab9
commit 134e264bcd
9 changed files with 1280 additions and 16 deletions

View File

@ -2,6 +2,10 @@ SRC_DIR = src
BIN_DIR = bin BIN_DIR = bin
RES_DIR = res RES_DIR = res
SHADER_DIR = $(SRC_DIR)/_shader
SHADER_ROOT_DIR = $(SHADER_DIR)/root
SHADER_INCLUDE_DIR = $(SHADER_DIR)/common
TARGET = $(BIN_DIR)/oglc TARGET = $(BIN_DIR)/oglc
CC = gcc CC = gcc
LIBS = `pkg-config --static --libs glew sdl2` LIBS = `pkg-config --static --libs glew sdl2`
@ -10,10 +14,21 @@ CFLAGS = -I$(SRC_DIR) -Wall
SRC = $(shell find $(SRC_DIR) -name *.c) SRC = $(shell find $(SRC_DIR) -name *.c)
OBJ = $(SRC:%.c=%.o) OBJ = $(SRC:%.c=%.o)
# find files in SHADER_ROOT_DIR
# top level compute shader programs
SHADERS = $(shell find $(SHADER_ROOT_DIR) -name *.glsl)
# find files in SHADER_INCLUDE_DIR
# small chunks of shader code, included repeatedly in the top-level programs
SHADER_INCLUDES = $(shell find $(SHADER_INCLUDE_DIR) -name *.glsl)
# create dirs if they dont exist # create dirs if they dont exist
_dummy := $(shell mkdir -p $(BIN_DIR)) _dummy := $(shell mkdir -p $(BIN_DIR))
$(TARGET): $(OBJ) $(TARGET): $(OBJ)
# preprocess shaders and store results in bin/res/shader/ under root name
foreach root,$(SHADER_ROOT_DIR),$(echo $(root))
$(CC) $(CFLAGS) -o $@ $^ $(LIBS) $(CC) $(CFLAGS) -o $@ $^ $(LIBS)
cp -r $(RES_DIR) $(BIN_DIR) cp -r $(RES_DIR) $(BIN_DIR)

View File

@ -1,22 +1,106 @@
#version 430 #version 430
layout (location = 1) uniform float t; layout (location = 1) uniform vec4 t;
// size of local work group - 1 pixel layout(local_size_x = 1, local_size_y = 1) in; // size of local work group - 1 pixel
layout(local_size_x = 1, local_size_y = 1) in; layout(rgba32f, binding = 0) uniform image2D img_output; // rgba32f defines internal format, image2d for random write to output texture
// rgba32f defines internal format, image2d for random write to output texture const float INF = 1000000.0f;
layout(rgba32f, binding = 0) uniform image2D img_output;
struct Sphere
{
vec3 center;
float radius;
};
struct Ray
{
vec3 origin;
vec3 direction;
};
struct RayHit
{
vec3 position;
float distance;
vec3 normal;
};
void intersectSphere(Ray ray, inout RayHit bestHit, Sphere sphere)
{
vec3 d = ray.origin-sphere.center;
float p1 = -dot(ray.direction,d);
float p2sqr = p1*p1-dot(d,d)+sphere.radius*sphere.radius;
if (p2sqr < 0) return;
float p2 = sqrt(p2sqr);
float t = p1-p2 > 0 ? p1-p2 : p1+p2;
if (t > 0 && t < bestHit.distance)
{
bestHit.distance = t;
bestHit.position = ray.origin + t*ray.direction;
bestHit.normal = normalize(bestHit.position-sphere.center);
}
}
Ray createCameraRay(vec2 uv)
{
// transform -1..1 -> 0..1
uv = uv*0.5+0.5;
//uv.x=1-uv.x;
// transform camera origin to world space
// TODO: c2w matrix!! for now we just assume the camera is at the origin
// float3 origin = mul(_CameraToWorld, float4(0.0,0.0,0.0,1.0)).xyz;
// TODO: offset from centre of the lens for depth of field
// float2 rd = _CameraLensRadius * randomInUnitDisk();
// float3 offset = _CameraU * rd.x + _CameraV * rd.y;
// ...
float max_x = 5.0;
float max_y = 5.0;
Ray ray;
ray.origin = vec3(uv.x * max_x, uv.y * max_y, 0.0);
ray.direction = vec3(0.0,0.0,1.0); // ortho forwards
return ray;
}
void main() void main()
{ {
// base pixel colour for the image // base pixel colour for the image
vec4 pixel = vec4(1.0, t, 0.0, 1.0); vec4 pixel = vec4(0.0, 0.0, 0.0, 1.0);
// get index in global work group ie xy position // get index in global work group ie xy position
ivec2 pixel_coords = ivec2(gl_GlobalInvocationID.xy); ivec2 pixel_coords = ivec2(gl_GlobalInvocationID.xy);
// // set up ray based on pixel position, project it forward with an orthographic projection
ivec2 dims = imageSize(img_output); // fetch image dimensions
vec2 uv;
uv.x = (float(pixel_coords.x * 2 - dims.x) / dims.x) * dims.x/dims.y; // account for aspect ratio
uv.y = (float(pixel_coords.y * 2 - dims.y) / dims.y);
Ray ray = createCameraRay(uv);
RayHit hit;
hit.position = vec3(0.0,0.0,0.0);
hit.distance = INF;
hit.normal = vec3(0.0,0.0,0.0);
Sphere sphere;
sphere.center = vec3(0.0,0.0,10.0);
sphere.radius = 3.0+t.y;
// ray-sphere intersection
intersectSphere(ray, hit, sphere);
if (hit.distance < INF)
{
pixel = vec4(t.y,1.0-t.y,1.0,1.0);
}
// output to a specific pixel in the image // output to a specific pixel in the image
imageStore(img_output, pixel_coords, pixel); imageStore(img_output, pixel_coords, pixel);

506
res/shader/rt.compute Normal file
View File

@ -0,0 +1,506 @@
// Each #kernel tells which function to compile; you can have many kernels
#pragma kernel CSMain
struct Sphere
{
float3 position;
float radius;
float3 albedo;
float3 specular;
float3 emission;
};
struct Tube
{
float3 position;
float3 axis;
float radius;
float height;
float thickness;
};
struct Unit
{
float3 position;
int team;
int selected;
};
// Create a RenderTexture with enableRandomWrite flag and set it
// with cs.SetTexture
RWTexture2D<float4> Result;
float2 _Pixel;
float _Seed;
float _EmissionScale;
int _Bounces;
int _SamplesPerPixel;
// camera
float2 _Resolution;
float4x4 _CameraToWorld;
float4x4 _CameraInverseProjection;
float3 _CameraW;
float3 _CameraU;
float3 _CameraV;
float3 _CameraHorizontal;
float3 _CameraVertical;
float3 _CameraLowerLeftCorner;
float _CameraLensRadius;
float _CameraFocusDistance;
// environment
float _GroundHeight;
float3 _GroundColor;
float _SkyHeight;
float _SkyHoleRadius;
float3 _SkyColor;
int _ActiveSpheres;
int _ActiveTubes;
int _ActiveUnits;
float3 _UnitColor;
float _UnitRadius;
StructuredBuffer<Unit> _Units;
StructuredBuffer<Tube> _Tubes;
StructuredBuffer<Sphere> _Spheres;
#define GROUP_SIZE 32
static const float PI = 3.14159265f;
static const float BIG = 1000000.0f; // not infinity but close enough
static const int MAT_LAMBERT = 0;
static const int MAT_DIELECTRIC = 1;
struct Ray
{
float3 origin;
float3 direction;
float3 energy;
};
Ray createRay(float3 origin, float3 direction)
{
Ray ray;
ray.origin = origin;
ray.direction = direction;
ray.energy = float3(1.0f, 1.0f, 1.0f);
return ray;
}
struct RayHit
{
float3 position;
float distance;
float3 normal;
float3 albedo;
float3 specular;
float3 emission;
};
RayHit createRayHit()
{
RayHit hit;
hit.position = float3(0.0f, 0.0f, 0.0f);
hit.distance = BIG;
hit.normal = float3(0.0f, 0.0f, 0.0f);
hit.albedo = float3(0.0f, 0.0f, 0.0f);
hit.specular = float3(0.0f, 0.0f, 0.0f);
hit.emission = float3(0.0f, 0.0f, 0.0f);
return hit;
}
float rand()
{
float result = frac(sin(_Seed / 100.0f * dot(_Pixel, float2(12.9898f, 78.233f))) * 43758.5453f);
_Seed += 1.0f;
return result;
}
float sdot(float3 x, float3 y, float f = 1.0f)
{
return saturate(dot(x, y) * f);
}
float3x3 getTangentSpace(float3 normal)
{
// helper vector for the cross product
float3 helper = float3(1, 0, 0);
if (abs(normal.x) > 0.99f)
{
helper = float3(0, 0, 1);
}
// generate vectors
float3 tangent = normalize(cross(normal, helper));
float3 binormal = normalize(cross(normal, tangent));
return float3x3(tangent, binormal, normal);
}
float3 sampleHemisphere(float3 normal)
{
// uniformly sample hemisphere direction
float cosTheta = rand();
float sinTheta = sqrt(max(0.0f, 1.0f - cosTheta * cosTheta));
float phi = 2 * PI * rand();
float3 tangentSpaceDir = float3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta);
// transform direction to world space
return mul(tangentSpaceDir, getTangentSpace(normal));
}
float2 randomInUnitDisk()
{
// pick a random radius and angle then convert to cartesian
float r = rand();
float theta = rand() * 2 * PI;
return float2(cos(theta), sin(theta)) * r;
}
Ray createCameraRay(float2 uv)
{
// transform -1..1 -> 0..1
uv = uv * 0.5 + 0.5;
uv.x = 1 - uv.x;
// transform the camera origin to world space
float3 origin = mul(_CameraToWorld, float4(0.0f, 0.0f, 0.0f, 1.0f)).xyz;
// offset from centre of the lens for depth of field
float2 rd = _CameraLensRadius * randomInUnitDisk();
float3 offset = _CameraU * rd.x + _CameraV * rd.y;
origin += offset;
// invert the perspective projection of the view-space position
float3 direction = mul(_CameraInverseProjection, float4(uv, 0.0f, 1.0f)).xyz;
// transform the direction from camera to world space and normalize
direction = mul(_CameraToWorld, float4(direction, 0.0f)).xyz;
direction = _CameraLowerLeftConer
+ uv.x * _CameraHorizontal
+ uv.y * _CameraVertical
- origin;
// direction = mul(_CameraInverseProjection, float4(direction, 0)).xyz;
direction = normalize(direction);
return createRay(origin, direction);
}
void intersectSphere(Ray ray, inout RayHit bestHit, Sphere sphere)
{
// calculate distance along the ray where the sphere is intersected
float3 d = ray.origin - sphere.position;
float p1 = -dot(ray.direction, d);
float p2sqr = p1 * p1 - dot(d, d) + sphere.radius * sphere.radius;
if (p2sqr < 0) return;
float p2 = sqrt(p2sqr);
float t = p1 - p2 > 0 ? p1 - p2 : p1 + p2;
if (t > 0 && t < bestHit.distance)
{
bestHit.distance = t;
bestHit.position = ray.origin + t * ray.direction;
bestHit.normal = normalize(bestHit.position - sphere.position);
bestHit.albedo = sphere.albedo;
bestHit.specular = sphere.specular;
bestHit.emission = sphere.emission;
}
}
float intersectPlane(Ray ray, float3 p, float3 normal)
{
float denom = dot(normal, ray.direction);
if (abs(denom) > 0.0001)
{
float t = dot(p - ray.origin, normal) / denom;
if (t >= 0) return t;
}
return -1;
}
// https://www.iquilezles.org/www/articles/intersectors/intersectors.htm
// cylinder defined in extremes pa and pb, and radius ra
float4 intersectCylinder(Ray ray, float3 pa, float3 pb, float ra, bool inner)
{
float3 ro = ray.origin;
float3 rd = ray.direction;
// central axis
float3 ca = pb - pa;
// eye to base
float3 oc = ro - pa;
// dot products
float caca = dot(ca, ca);
float card = dot(ca, rd);
float caoc = dot(ca, oc);
// find intersects
float a = caca - card * card;
float b = caca * dot(oc, rd) - caoc * card;
float c = caca * dot(oc, oc) - caoc * caoc - ra * ra * caca;
float h = b * b - a * c;
if (h < 0.0) return float4(-1, 0, 0, 0); // no intersection
h = sqrt(h);
h = inner?-h:h;
float t = (-b - h) / a;
// body
float y = caoc + t * card;
if (y > 0.0 && y < caca) return float4(t, (oc+t*rd - ca*y/caca) / ra);
// caps
t = ((y < 0.0 ? 0.0 : caca) - caoc) / card;
if (abs(b + a * t) < h) return float4(t, ca * sign(y) / caca);
return float4(-1, 0, 0, 0); // no intersection
}
float sdSegment(float3 p, float3 a, float3 b, float r)
{
float3 pa = p - a, ba = b - a;
float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
return length(pa - ba * h) - r;
}
float opSubtraction(float d1, float d2) { return max(-d1, d2); }
void intersectTube(Ray ray, inout RayHit bestHit, Tube tube)
{
// TODO: inner tube
float height = tube.height;
float3 axis = normalize(tube.axis);
float3 pa = tube.position + axis * -height * 0.5;
float3 pb = tube.position + axis * height * 0.5;
float r_inner = (tube.radius-tube.thickness)/tube.radius;
// where the ray hit the outer surface
float4 outerHit = intersectCylinder(ray, pa, pb, tube.radius, false);
// outerHit = float4(-1,0,0,0);
// where we hit the inner surface
float4 innerHit = intersectCylinder(ray, pa, pb, tube.radius * r_inner, true);
// float4 innerHit = float4(-1,0,0,0);
// innerHit.yzw *= -1;
if (outerHit.x < 0 && innerHit.x < 0) return;
float3 pos_outer = ray.origin + outerHit.x * ray.direction;
float axis_distance = sdSegment(pos_outer, pa, pb, 0);
float t = bestHit.distance;
// hit the inner surface
if (innerHit.x > 0 && innerHit.x < bestHit.distance)
{
t = innerHit.x;
bestHit.normal = normalize(innerHit.yzw);
bestHit.position = ray.origin + t * ray.direction;
bestHit.distance = t;
bestHit.albedo = float3(.5, .5, .5);
bestHit.emission = float3(0, 0, 0);
bestHit.specular = float3(1, 1, 1);
}
// hit the outer surface
if (outerHit.x > 0 && outerHit.x < bestHit.distance && axis_distance > tube.radius*r_inner)
{
t = outerHit.x;
bestHit.normal = normalize(outerHit.yzw);
bestHit.position = ray.origin + t * ray.direction;
bestHit.distance = t;
bestHit.albedo = float3(.5, .5, .5);
bestHit.emission = float3(0, 0, 0);
bestHit.specular = float3(1, 1, 1);
}
}
void intersectGroundPlane(inout Ray ray, inout RayHit bestHit)
{
float3 albedo = _GroundColor;
float3 specular = float3(0, 0, 0);
// calculate distance along the ray where the ground plane is intersected
float t = -(ray.origin.y - _GroundHeight) / ray.direction.y;
if (t > 0 && t < bestHit.distance)
{
bestHit.distance = t;
bestHit.position = ray.origin + t * ray.direction;
bestHit.normal = float3(0.0f, 1.0f, 0.0f);
bestHit.albedo = albedo;
bestHit.specular = specular;
bestHit.emission = float3(0, 0, 0);
}
}
void intersectCeilingPlane(inout Ray ray, inout RayHit bestHit)
{
float albedo = _SkyColor;
float3 specular = float3(0, 0, 0);
// ignore plane if the ray is coming from above
if (ray.direction.y < 0) return;
float t = -(ray.origin.y - _SkyHeight) / ray.direction.y;
float3 p = ray.origin + ray.direction * t;
if (length(p.xz) < _SkyHoleRadius) return;
if (t > 0 && t < bestHit.distance)
{
bestHit.distance = t;
bestHit.position = ray.origin + t * ray.direction;
bestHit.normal = float3(0.0f, -1.0f, 0.0f);
bestHit.albedo = albedo;
bestHit.specular = specular;
bestHit.emission = float3(0, 0, 0);
}
}
void intersectWall(inout Ray ray, inout RayHit bestHit)
{
// ignore collision if ray's angle is steep or negative
float a = dot(float3(0, 1, 0), ray.direction);
if (a > 0.2 || a < 0) return;
Sphere sphere;
sphere.radius = BIG - 1;
sphere.albedo = float3(1, 1, 1) * 1.98;
sphere.specular = float3(0, 0, 0);
sphere.emission = float3(0, 0, 0);
sphere.position = float3(0, 0, 0);
intersectSphere(ray, bestHit, sphere);
}
RayHit trace(Ray ray)
{
RayHit bestHit = createRayHit();
intersectWall(ray, bestHit);
intersectGroundPlane(ray, bestHit);
intersectCeilingPlane(ray, bestHit);
uint numSpheres, numTubes, stride;
// celestial bodies
// _Spheres.GetDimensions(_ActiveSpheres, stride);
for (uint i = 0; i < _ActiveSpheres; i++)
{
intersectSphere(ray, bestHit, _Spheres[i]);
}
// _Tubes.GetDimensions(numTubes, stride);
for (uint i = 0; i < _ActiveTubes; i++)
{
intersectTube(ray, bestHit, _Tubes[i]);
}
if (_ActiveUnits > 0)
{
// units
_Units.GetDimensions(numSpheres, stride);
for (uint i = 0; i < _ActiveUnits; i++)
{
Unit unit = _Units[i];
float3 color = float3
(lerp(1, 0, unit.team),
0,
lerp(0, 1, unit.team));
Sphere s;
s.albedo = color;
s.emission = color * unit.selected;
s.specular = float3(0, 0, 0);
s.radius = _UnitRadius;
s.position = unit.position;
intersectSphere(ray, bestHit, s);
}
}
return bestHit;
}
float3 scatter_lambert(inout Ray ray, RayHit hit)
{
ray.origin = hit.position + hit.normal * 0.001f;
ray.direction = sampleHemisphere(hit.normal);
ray.energy *= 2 * hit.albedo * sdot(hit.normal, ray.direction);
return 0.0f;
}
float3 shade(inout Ray ray, RayHit hit)
{
if (any(hit.emission)) return hit.emission;
if (hit.distance < BIG)
{
return scatter_lambert(ray, hit) + hit.emission;
}
else
{
ray.energy = 0.0f;
// float theta = acos(ray.direction.y) / -PI;
// float phi = atan2(ray.direction.x, -ray.direction.z) / -PI * 0.5f;
return _SkyColor;
}
}
[numthreads(GROUP_SIZE,GROUP_SIZE,1)]
void CSMain(uint3 id : SV_DispatchThreadID)
{
_Pixel = id.xy;
// get dimensions of render texture
uint width, height;
Result.GetDimensions(width, height);
// transform pixel to -1, 1 range
float2 uv = float2(id.xy / float2(width, height) * 2.0f - 1.0f);
uv.x *= _Resolution.x / _Resolution.y;
int samples = _SamplesPerPixel;
int bounces = _Bounces;
float3 result = float3(0, 0, 0);
for (int i = 0; i < samples; i++)
{
// get a ray for the uv
Ray ray = createCameraRay(uv);
// trace and shade
for (int i = 0; i < bounces; i++)
{
RayHit hit = trace(ray);
result += ray.energy * shade(ray, hit);
if (!any(ray.energy)) break;
}
}
result /= (float)samples;
Result[id.xy] = float4(result, 1);
}

View File

@ -0,0 +1,107 @@
#version 430
layout (location = 1) uniform vec4 t;
layout(local_size_x = 1, local_size_y = 1) in; // size of local work group - 1 pixel
layout(rgba32f, binding = 0) uniform image2D img_output; // rgba32f defines internal format, image2d for random write to output texture
const float INF = 1000000.0f;
struct Sphere
{
vec3 center;
float radius;
};
struct Ray
{
vec3 origin;
vec3 direction;
};
struct RayHit
{
vec3 position;
float distance;
vec3 normal;
};
void intersectSphere(Ray ray, inout RayHit bestHit, Sphere sphere)
{
vec3 d = ray.origin-sphere.center;
float p1 = -dot(ray.direction,d);
float p2sqr = p1*p1-dot(d,d)+sphere.radius*sphere.radius;
if (p2sqr < 0) return;
float p2 = sqrt(p2sqr);
float t = p1-p2 > 0 ? p1-p2 : p1+p2;
if (t > 0 && t < bestHit.distance)
{
bestHit.distance = t;
bestHit.position = ray.origin + t*ray.direction;
bestHit.normal = normalize(bestHit.position-sphere.center);
}
}
Ray createCameraRay(vec2 uv)
{
// transform -1..1 -> 0..1
uv = uv*0.5+0.5;
//uv.x=1-uv.x;
// transform camera origin to world space
// TODO: c2w matrix!! for now we just assume the camera is at the origin
// float3 origin = mul(_CameraToWorld, float4(0.0,0.0,0.0,1.0)).xyz;
// TODO: offset from centre of the lens for depth of field
// float2 rd = _CameraLensRadius * randomInUnitDisk();
// float3 offset = _CameraU * rd.x + _CameraV * rd.y;
// ...
float max_x = 5.0;
float max_y = 5.0;
Ray ray;
ray.origin = vec3(uv.x * max_x, uv.y * max_y, 0.0);
ray.direction = vec3(0.0,0.0,1.0); // ortho forwards
return ray;
}
void main()
{
// base pixel colour for the image
vec4 pixel = vec4(0.0, 0.0, 0.0, 1.0);
// get index in global work group ie xy position
ivec2 pixel_coords = ivec2(gl_GlobalInvocationID.xy);
// set up ray based on pixel position, project it forward with an orthographic projection
ivec2 dims = imageSize(img_output); // fetch image dimensions
vec2 uv;
uv.x = (float(pixel_coords.x * 2 - dims.x) / dims.x) * dims.x/dims.y; // account for aspect ratio
uv.y = (float(pixel_coords.y * 2 - dims.y) / dims.y);
Ray ray = createCameraRay(uv);
RayHit hit;
hit.position = vec3(0.0,0.0,0.0);
hit.distance = INF;
hit.normal = vec3(0.0,0.0,0.0);
Sphere sphere;
sphere.center = vec3(0.0,0.0,10.0);
sphere.radius = 3.0+t.y;
// ray-sphere intersection
intersectSphere(ray, hit, sphere);
if (hit.distance < INF)
{
pixel = vec4(t.y,1.0-t.y,1.0,1.0);
}
// output to a specific pixel in the image
imageStore(img_output, pixel_coords, pixel);
}

506
src/_shader/root/rt.compute Normal file
View File

@ -0,0 +1,506 @@
// Each #kernel tells which function to compile; you can have many kernels
#pragma kernel CSMain
struct Sphere
{
float3 position;
float radius;
float3 albedo;
float3 specular;
float3 emission;
};
struct Tube
{
float3 position;
float3 axis;
float radius;
float height;
float thickness;
};
struct Unit
{
float3 position;
int team;
int selected;
};
// Create a RenderTexture with enableRandomWrite flag and set it
// with cs.SetTexture
RWTexture2D<float4> Result;
float2 _Pixel;
float _Seed;
float _EmissionScale;
int _Bounces;
int _SamplesPerPixel;
// camera
float2 _Resolution;
float4x4 _CameraToWorld;
float4x4 _CameraInverseProjection;
float3 _CameraW;
float3 _CameraU;
float3 _CameraV;
float3 _CameraHorizontal;
float3 _CameraVertical;
float3 _CameraLowerLeftCorner;
float _CameraLensRadius;
float _CameraFocusDistance;
// environment
float _GroundHeight;
float3 _GroundColor;
float _SkyHeight;
float _SkyHoleRadius;
float3 _SkyColor;
int _ActiveSpheres;
int _ActiveTubes;
int _ActiveUnits;
float3 _UnitColor;
float _UnitRadius;
StructuredBuffer<Unit> _Units;
StructuredBuffer<Tube> _Tubes;
StructuredBuffer<Sphere> _Spheres;
#define GROUP_SIZE 32
static const float PI = 3.14159265f;
static const float BIG = 1000000.0f; // not infinity but close enough
static const int MAT_LAMBERT = 0;
static const int MAT_DIELECTRIC = 1;
struct Ray
{
float3 origin;
float3 direction;
float3 energy;
};
Ray createRay(float3 origin, float3 direction)
{
Ray ray;
ray.origin = origin;
ray.direction = direction;
ray.energy = float3(1.0f, 1.0f, 1.0f);
return ray;
}
struct RayHit
{
float3 position;
float distance;
float3 normal;
float3 albedo;
float3 specular;
float3 emission;
};
RayHit createRayHit()
{
RayHit hit;
hit.position = float3(0.0f, 0.0f, 0.0f);
hit.distance = BIG;
hit.normal = float3(0.0f, 0.0f, 0.0f);
hit.albedo = float3(0.0f, 0.0f, 0.0f);
hit.specular = float3(0.0f, 0.0f, 0.0f);
hit.emission = float3(0.0f, 0.0f, 0.0f);
return hit;
}
float rand()
{
float result = frac(sin(_Seed / 100.0f * dot(_Pixel, float2(12.9898f, 78.233f))) * 43758.5453f);
_Seed += 1.0f;
return result;
}
float sdot(float3 x, float3 y, float f = 1.0f)
{
return saturate(dot(x, y) * f);
}
float3x3 getTangentSpace(float3 normal)
{
// helper vector for the cross product
float3 helper = float3(1, 0, 0);
if (abs(normal.x) > 0.99f)
{
helper = float3(0, 0, 1);
}
// generate vectors
float3 tangent = normalize(cross(normal, helper));
float3 binormal = normalize(cross(normal, tangent));
return float3x3(tangent, binormal, normal);
}
float3 sampleHemisphere(float3 normal)
{
// uniformly sample hemisphere direction
float cosTheta = rand();
float sinTheta = sqrt(max(0.0f, 1.0f - cosTheta * cosTheta));
float phi = 2 * PI * rand();
float3 tangentSpaceDir = float3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta);
// transform direction to world space
return mul(tangentSpaceDir, getTangentSpace(normal));
}
float2 randomInUnitDisk()
{
// pick a random radius and angle then convert to cartesian
float r = rand();
float theta = rand() * 2 * PI;
return float2(cos(theta), sin(theta)) * r;
}
Ray createCameraRay(float2 uv)
{
// transform -1..1 -> 0..1
uv = uv * 0.5 + 0.5;
uv.x = 1 - uv.x;
// transform the camera origin to world space
float3 origin = mul(_CameraToWorld, float4(0.0f, 0.0f, 0.0f, 1.0f)).xyz;
// offset from centre of the lens for depth of field
float2 rd = _CameraLensRadius * randomInUnitDisk();
float3 offset = _CameraU * rd.x + _CameraV * rd.y;
origin += offset;
// invert the perspective projection of the view-space position
float3 direction = mul(_CameraInverseProjection, float4(uv, 0.0f, 1.0f)).xyz;
// transform the direction from camera to world space and normalize
direction = mul(_CameraToWorld, float4(direction, 0.0f)).xyz;
direction = _CameraLowerLeftConer
+ uv.x * _CameraHorizontal
+ uv.y * _CameraVertical
- origin;
// direction = mul(_CameraInverseProjection, float4(direction, 0)).xyz;
direction = normalize(direction);
return createRay(origin, direction);
}
void intersectSphere(Ray ray, inout RayHit bestHit, Sphere sphere)
{
// calculate distance along the ray where the sphere is intersected
float3 d = ray.origin - sphere.position;
float p1 = -dot(ray.direction, d);
float p2sqr = p1 * p1 - dot(d, d) + sphere.radius * sphere.radius;
if (p2sqr < 0) return;
float p2 = sqrt(p2sqr);
float t = p1 - p2 > 0 ? p1 - p2 : p1 + p2;
if (t > 0 && t < bestHit.distance)
{
bestHit.distance = t;
bestHit.position = ray.origin + t * ray.direction;
bestHit.normal = normalize(bestHit.position - sphere.position);
bestHit.albedo = sphere.albedo;
bestHit.specular = sphere.specular;
bestHit.emission = sphere.emission;
}
}
float intersectPlane(Ray ray, float3 p, float3 normal)
{
float denom = dot(normal, ray.direction);
if (abs(denom) > 0.0001)
{
float t = dot(p - ray.origin, normal) / denom;
if (t >= 0) return t;
}
return -1;
}
// https://www.iquilezles.org/www/articles/intersectors/intersectors.htm
// cylinder defined in extremes pa and pb, and radius ra
float4 intersectCylinder(Ray ray, float3 pa, float3 pb, float ra, bool inner)
{
float3 ro = ray.origin;
float3 rd = ray.direction;
// central axis
float3 ca = pb - pa;
// eye to base
float3 oc = ro - pa;
// dot products
float caca = dot(ca, ca);
float card = dot(ca, rd);
float caoc = dot(ca, oc);
// find intersects
float a = caca - card * card;
float b = caca * dot(oc, rd) - caoc * card;
float c = caca * dot(oc, oc) - caoc * caoc - ra * ra * caca;
float h = b * b - a * c;
if (h < 0.0) return float4(-1, 0, 0, 0); // no intersection
h = sqrt(h);
h = inner?-h:h;
float t = (-b - h) / a;
// body
float y = caoc + t * card;
if (y > 0.0 && y < caca) return float4(t, (oc+t*rd - ca*y/caca) / ra);
// caps
t = ((y < 0.0 ? 0.0 : caca) - caoc) / card;
if (abs(b + a * t) < h) return float4(t, ca * sign(y) / caca);
return float4(-1, 0, 0, 0); // no intersection
}
float sdSegment(float3 p, float3 a, float3 b, float r)
{
float3 pa = p - a, ba = b - a;
float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
return length(pa - ba * h) - r;
}
float opSubtraction(float d1, float d2) { return max(-d1, d2); }
void intersectTube(Ray ray, inout RayHit bestHit, Tube tube)
{
// TODO: inner tube
float height = tube.height;
float3 axis = normalize(tube.axis);
float3 pa = tube.position + axis * -height * 0.5;
float3 pb = tube.position + axis * height * 0.5;
float r_inner = (tube.radius-tube.thickness)/tube.radius;
// where the ray hit the outer surface
float4 outerHit = intersectCylinder(ray, pa, pb, tube.radius, false);
// outerHit = float4(-1,0,0,0);
// where we hit the inner surface
float4 innerHit = intersectCylinder(ray, pa, pb, tube.radius * r_inner, true);
// float4 innerHit = float4(-1,0,0,0);
// innerHit.yzw *= -1;
if (outerHit.x < 0 && innerHit.x < 0) return;
float3 pos_outer = ray.origin + outerHit.x * ray.direction;
float axis_distance = sdSegment(pos_outer, pa, pb, 0);
float t = bestHit.distance;
// hit the inner surface
if (innerHit.x > 0 && innerHit.x < bestHit.distance)
{
t = innerHit.x;
bestHit.normal = normalize(innerHit.yzw);
bestHit.position = ray.origin + t * ray.direction;
bestHit.distance = t;
bestHit.albedo = float3(.5, .5, .5);
bestHit.emission = float3(0, 0, 0);
bestHit.specular = float3(1, 1, 1);
}
// hit the outer surface
if (outerHit.x > 0 && outerHit.x < bestHit.distance && axis_distance > tube.radius*r_inner)
{
t = outerHit.x;
bestHit.normal = normalize(outerHit.yzw);
bestHit.position = ray.origin + t * ray.direction;
bestHit.distance = t;
bestHit.albedo = float3(.5, .5, .5);
bestHit.emission = float3(0, 0, 0);
bestHit.specular = float3(1, 1, 1);
}
}
void intersectGroundPlane(inout Ray ray, inout RayHit bestHit)
{
float3 albedo = _GroundColor;
float3 specular = float3(0, 0, 0);
// calculate distance along the ray where the ground plane is intersected
float t = -(ray.origin.y - _GroundHeight) / ray.direction.y;
if (t > 0 && t < bestHit.distance)
{
bestHit.distance = t;
bestHit.position = ray.origin + t * ray.direction;
bestHit.normal = float3(0.0f, 1.0f, 0.0f);
bestHit.albedo = albedo;
bestHit.specular = specular;
bestHit.emission = float3(0, 0, 0);
}
}
void intersectCeilingPlane(inout Ray ray, inout RayHit bestHit)
{
float albedo = _SkyColor;
float3 specular = float3(0, 0, 0);
// ignore plane if the ray is coming from above
if (ray.direction.y < 0) return;
float t = -(ray.origin.y - _SkyHeight) / ray.direction.y;
float3 p = ray.origin + ray.direction * t;
if (length(p.xz) < _SkyHoleRadius) return;
if (t > 0 && t < bestHit.distance)
{
bestHit.distance = t;
bestHit.position = ray.origin + t * ray.direction;
bestHit.normal = float3(0.0f, -1.0f, 0.0f);
bestHit.albedo = albedo;
bestHit.specular = specular;
bestHit.emission = float3(0, 0, 0);
}
}
void intersectWall(inout Ray ray, inout RayHit bestHit)
{
// ignore collision if ray's angle is steep or negative
float a = dot(float3(0, 1, 0), ray.direction);
if (a > 0.2 || a < 0) return;
Sphere sphere;
sphere.radius = BIG - 1;
sphere.albedo = float3(1, 1, 1) * 1.98;
sphere.specular = float3(0, 0, 0);
sphere.emission = float3(0, 0, 0);
sphere.position = float3(0, 0, 0);
intersectSphere(ray, bestHit, sphere);
}
RayHit trace(Ray ray)
{
RayHit bestHit = createRayHit();
intersectWall(ray, bestHit);
intersectGroundPlane(ray, bestHit);
intersectCeilingPlane(ray, bestHit);
uint numSpheres, numTubes, stride;
// celestial bodies
// _Spheres.GetDimensions(_ActiveSpheres, stride);
for (uint i = 0; i < _ActiveSpheres; i++)
{
intersectSphere(ray, bestHit, _Spheres[i]);
}
// _Tubes.GetDimensions(numTubes, stride);
for (uint i = 0; i < _ActiveTubes; i++)
{
intersectTube(ray, bestHit, _Tubes[i]);
}
if (_ActiveUnits > 0)
{
// units
_Units.GetDimensions(numSpheres, stride);
for (uint i = 0; i < _ActiveUnits; i++)
{
Unit unit = _Units[i];
float3 color = float3
(lerp(1, 0, unit.team),
0,
lerp(0, 1, unit.team));
Sphere s;
s.albedo = color;
s.emission = color * unit.selected;
s.specular = float3(0, 0, 0);
s.radius = _UnitRadius;
s.position = unit.position;
intersectSphere(ray, bestHit, s);
}
}
return bestHit;
}
float3 scatter_lambert(inout Ray ray, RayHit hit)
{
ray.origin = hit.position + hit.normal * 0.001f;
ray.direction = sampleHemisphere(hit.normal);
ray.energy *= 2 * hit.albedo * sdot(hit.normal, ray.direction);
return 0.0f;
}
float3 shade(inout Ray ray, RayHit hit)
{
if (any(hit.emission)) return hit.emission;
if (hit.distance < BIG)
{
return scatter_lambert(ray, hit) + hit.emission;
}
else
{
ray.energy = 0.0f;
// float theta = acos(ray.direction.y) / -PI;
// float phi = atan2(ray.direction.x, -ray.direction.z) / -PI * 0.5f;
return _SkyColor;
}
}
[numthreads(GROUP_SIZE,GROUP_SIZE,1)]
void CSMain(uint3 id : SV_DispatchThreadID)
{
_Pixel = id.xy;
// get dimensions of render texture
uint width, height;
Result.GetDimensions(width, height);
// transform pixel to -1, 1 range
float2 uv = float2(id.xy / float2(width, height) * 2.0f - 1.0f);
uv.x *= _Resolution.x / _Resolution.y;
int samples = _SamplesPerPixel;
int bounces = _Bounces;
float3 result = float3(0, 0, 0);
for (int i = 0; i < samples; i++)
{
// get a ray for the uv
Ray ray = createCameraRay(uv);
// trace and shade
for (int i = 0; i < bounces; i++)
{
RayHit hit = trace(ray);
result += ray.energy * shade(ray, hit);
if (!any(ray.energy)) break;
}
}
result /= (float)samples;
Result[id.xy] = float4(result, 1);
}

View File

@ -0,0 +1,12 @@
#version 430 core
out vec4 FragColor;
in vec3 ourColor;
in vec2 TexCoord;
uniform sampler2D ourTexture;
void main()
{
FragColor = texture(ourTexture, TexCoord);
}

View File

@ -0,0 +1,14 @@
#version 430 core
layout (location = 0) in vec3 aPos; // position has attribute position 0
layout (location = 1) in vec3 aColor; // color has attribute position 1
layout (location = 2) in vec2 aTexCoord; // texture coordinate
out vec3 ourColor;
out vec2 TexCoord;
void main()
{
gl_Position = vec4(aPos, 1.0);
ourColor = aColor;
TexCoord = aTexCoord;
}

View File

@ -1,5 +1,4 @@
#include "main.h" #include "main.h"
#include "gfx.h" #include "gfx.h"
// forward declarations // forward declarations
@ -12,8 +11,8 @@ float time();
int main() int main()
{ {
int width = 800; int width = 420;
int height = 600; int height = 420;
const char* texPath = "res/tex.png"; const char* texPath = "res/tex.png";
// create a window and opengl context // create a window and opengl context
@ -40,9 +39,9 @@ int main()
// update uniforms // update uniforms
float t = time(); float t = time();
t = (sin(t)/2.0f)+0.5f; float sin_t = sin(t);
int tLocation = glGetUniformLocation(computeProgram, "t"); int tLocation = glGetUniformLocation(computeProgram, "t");
glUniform1f(tLocation, t); glUniform4f(tLocation, t, (1.0 + sin_t)*0.5, 0.0f, 0.0f);
// dispatch compute shader // dispatch compute shader
glDispatchCompute((GLuint)width, (GLuint)height, 1); glDispatchCompute((GLuint)width, (GLuint)height, 1);
@ -56,6 +55,7 @@ int main()
glClear(GL_COLOR_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT);
glActiveTexture(GL_TEXTURE0); // use computed texture glActiveTexture(GL_TEXTURE0); // use computed texture
glBindTexture(GL_TEXTURE_2D, textureOutput); glBindTexture(GL_TEXTURE_2D, textureOutput);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
// swip swap // swip swap

26
todo.md
View File

@ -1,4 +1,24 @@
* [x] basic opengl initialisation * [x] basic opengl initialisation
* [x] render a texture to a full-screen quad * [-] shader pre-processor
* [ ] output image to a file * [x] ppp.py
* [ ] render image with compute shader * [ ] shader src and out
* [ ] output frame to a file
* [ ] detect input keydown s
* [ ] get timestamp
* [ ] create and write to file (maybe with `stb_image.h`?)
* [-] render image with compute shader
* [x] render a texture to a full-screen quad
* [x] pass uniforms to texture to animate it
* [ ] ray tracing time
* [ ] acquire randomness
* [ ] acceleration time !
* [ ] auxiliary textures: g buffer
* [ ] frame blending
* [ ] maybe do some fractals
* [ ] mandelbrot
* [ ] julia
* [ ] trongle
* [ ] command line arguments
* [ ] help
* [ ] window dimensions
* [ ] scene definition