Implicit surface rendering via ray tracing
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

280 lines
7.2 KiB

#define _USE_MATH_DEFINES
#include <iostream>
#include <math.h>
#include <random>
struct vec3
{
float x, y, z;
vec3 operator+(const vec3& other)
{
return { x + other.x, y + other.y, z + other.z };
}
vec3 operator-(const vec3& other)
{
return { x - other.x, y - other.y, z - other.z };
}
vec3 operator-()
{
return { -x, -y, -z };
}
vec3 operator*(const float val)
{
return { x * val, y * val,z * val };
}
vec3 operator/(const vec3& other)
{
return { x / other.x, y / other.y, z / other.z };
}
vec3 operator/(const float val)
{
return { x / val, y / val,z / val };
}
vec3 normalize()
{
float norm = std::sqrt(x * x + y * y + z * z);
return { x / norm, y / norm, z / norm };
}
};
inline vec3 operator*(const vec3& a, const float b)
{
return { a.x * b, a.y * b, a.z * b };
}
inline vec3 operator-(const vec3& a, const vec3& b)
{
return { a.x - b.x, a.y - b.y, a.z - b.z };
}
float dot(const vec3& a, const vec3& b)
{
return { a.x * b.x + a.y * b.y + a.z * b.z };
}
vec3 cross(const vec3& a, const vec3& b)
{
return { a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x };
}
vec3 min(const vec3& a, const vec3& b)
{
return { std::min(a.x, b.x), std::min(a.y, b.y), std::min(a.z, b.z) };
}
float min_component(const vec3& a)
{
return std::min(a.x, std::min(a.y, a.z));
}
vec3 max(const vec3& a, const vec3& b)
{
return { std::max(a.x, b.x), std::max(a.y, b.y), std::max(a.z, b.z) };
}
float max_component(const vec3& a)
{
return std::max(a.x, std::max(a.y, a.z));
}
vec3 abs(const vec3& a)
{
return { std::abs(a.x), std::abs(a.y), std::abs(a.z) };
}
float length(const vec3& a)
{
return std::sqrt(a.x * a.x + a.y * a.y + a.z * a.z);
}
vec3 cos(const vec3& a)
{
return { std::cos(a.x), std::cos(a.y), std::cos(a.z) };
}
vec3 sin(const vec3& a)
{
return { std::sin(a.x), std::sin(a.y), std::sin(a.z) };
}
constexpr size_t renderWidth = 1024;
constexpr size_t renderHeight = 1024;
constexpr vec3 startBounding{ -M_PI, -M_PI, -M_PI };
constexpr vec3 endBounding{ 399.f * M_PI, 399.f * M_PI, 99.f * M_PI };
inline float evalBoundingBox(const vec3& p)
{
vec3 q = abs(p - startBounding * .5f - endBounding * .5f) - (endBounding * .5f - startBounding * .5f);
return length(max(q, { .0f, .0f, .0f })) + std::min(.0f, max_component(q));
}
inline float eval(const vec3& p)
{
float t0 = evalBoundingBox(p);
float t1 = std::abs(std::cos(p.x) + std::cos(p.y) + std::cos(p.z)); // inverted sign for keeping S(p) > 0 outside the surface
return std::max(t0, t1);
}
inline float evalTest(const vec3& p)
{
return -(std::cos(p.x) + std::cos(p.y) + std::cos(p.z));
}
inline bool testIntersection(const vec3& p, float scaling = 1.001f)
{
float t0 = -(std::cos(p.x) + std::cos(p.y) + std::cos(p.z));
float t1 = -(std::cos(p.x + t0 * scaling) + std::cos(p.y + t0 * scaling) + std::cos(p.z + t0 * scaling));
return t0 * t1 < .0f;
}
inline std::pair<float, float> eval(const vec3& p, const vec3& dir)
{
vec3 cosf = cos(p);
vec3 sinf = sin(p);
float t0 = evalBoundingBox(p);
return { std::max(t0, -(cosf.x + cosf.y + cosf.z)), dot(-sinf, dir) };
}
int main(int argc, char** argv)
{
vec3 eye = { 199.f * M_PI, 199.f * M_PI, -50.f },
center = { 199.f * M_PI, 199.f * M_PI, -1.f },
up = { .0f, 1.f, .0f };
float fov = 60.f;
float near = .001f, far = 400.f * M_PI;
vec3 direction = (center - eye).normalize();
vec3 right = cross(up, direction);
std::default_random_engine e(time(nullptr));
std::uniform_real_distribution<float> u(0, 1);
std::pair<float, float> uv = { u(e) * 2 - 1, u(e) * 2 - 1 };
vec3 rayDir = (direction + right * uv.first + up * uv.second).normalize();
float proj = 1.f / dot(rayDir, direction);
vec3 t0 = (-eye + startBounding) / rayDir;
vec3 t1 = (-eye + endBounding) / rayDir;
vec3 tmin = min(t0, t1), tmax = max(t0, t1);
float tNear = std::max(max_component(tmin), near * proj), tFar = std::min(min_component(tmax), far * proj);
float t = tNear;
int i = 0;
bool hit = false;
vec3 p, endPoint;
std::vector<float> ST_t;
while (t < tFar && tNear < tFar && tFar > .0f && i <= 1024)
{
p = eye + rayDir * t;
float val = eval(p);
//bool test = testIntersection(p);
endPoint = eye + rayDir * (t + val);
auto evalStart = eval(p, rayDir);
auto evalEnd = eval(endPoint, rayDir);
std::initializer_list testPoints = { evalStart.first, evalStart.first + evalStart.second * val * .5f, evalEnd.first, evalEnd.first + evalEnd.second * val * .5f };
float test[] = { std::min(testPoints), std::max(testPoints) };
if (val < .001f)
{
vec3 temp = p, temp2 = endPoint;
for (int i = 0; i < 0; ++i)
{
vec3 midPoint = (p + endPoint) * .5f;
auto evalMid = eval(midPoint, rayDir);
std::initializer_list testPoints = { evalStart.first, evalStart.first + evalStart.second * val * .5f, evalMid.first, evalMid.first + evalMid.second * val * .5f };
float test[] = { std::min(testPoints), std::max(testPoints) };
bool testRes = test[0] * test[1] < .0f;
p = testRes ? p : midPoint;
evalStart = testRes ? evalStart : evalMid;
endPoint = testRes ? midPoint : endPoint;
evalEnd = testRes ? evalMid : evalEnd;
val *= .5f;
}
p = (p + endPoint) * .5f;
std::cout << "Hit after " << i << " steps with p = (" << p.x << ", " << p.y << ", " << p.z << ")!!!" << std::endl;
std::cout << "\tOriginal p = (" << temp.x << ", " << temp.y << ", " << temp.z << ")" << std::endl;
std::cout << "\tOriginal endPos = (" << temp2.x << ", " << temp2.y << ", " << temp2.z << ")" << std::endl;
std::cout << "\tError: " << evalTest(temp2) << std::endl;
std::cout << "\tError with p: " << evalTest(temp) << std::endl;
hit = true;
break;
}
t += val;
i++;
ST_t.push_back(t);
}
if (!hit)
std::cout << "No hit!" << std::endl;
t = tNear;
float s;
i = 0;
vec3 startPoint;
std::vector<float> RM_t;
while (t < tFar && tNear < tFar && tFar > .0f)
{
startPoint = eye + rayDir * t;
auto evalStart = eval(startPoint, rayDir);
if (abs(evalStart.first) > .02f)
s = .01f * 2;
else if (abs(evalStart.first) < .01f)
{
if (abs(evalStart.second) < .005f)
s = .01f * .25f;
else
s = .01f * .5f;
}
else
s = .01f;
vec3 endPoint = eye + rayDir * (t + s);
auto evalEnd = eval(endPoint, rayDir);
std::initializer_list testPoints = { evalStart.first, evalStart.first + evalStart.second * s * .5f, evalEnd.first, evalEnd.first + evalEnd.second * s * .5f };
float test[] = { std::min(testPoints), std::max(testPoints) };
if (test[0] * test[1] < .0f)
{
std::cout << "Hit after " << i << " steps with p = (" << startPoint.x << ", " << startPoint.y << ", " << startPoint.z << ")!!!" << std::endl;
std::cout << "\tError: " << evalTest(startPoint) << std::endl;
break;
}
t += s;
i++;
RM_t.push_back(t);
}
std::cout << "Error: " << length(abs(startPoint - endPoint)) << std::endl;
if (length(abs(startPoint - endPoint)) > .01f)
{
std::cout << "Trace(Sphere Tracing): " << std::endl;
std::cout << "\tRay steps: ";
for (auto& i : ST_t)
std::cout << i << ", ";
std::cout << std::endl << std::endl;
std::cout << "Trace(Nvidia's Ray Marching): " << std::endl;
std::cout << "\tRay steps: ";
for (auto& i : RM_t)
std::cout << i << ", ";
std::cout << std::endl << std::endl;
}
return 0;
}