From 757185442a3f2f54eac999aa6763d259b2076056 Mon Sep 17 00:00:00 2001 From: arceryz Date: Wed, 8 May 2024 20:20:04 +0200 Subject: [PATCH] Hard work on Curved 3D tracer. --- Notes/formulae.md | 45 +++++- Shaders/ray3.frag | 148 +++++++++++++------ Shaders/ray3.vert | 2 +- Shaders/ray3testing.frag | 282 ++++++++++++++++++++++++++++++++++++ Source/Ray3/Ray3.h | 28 +++- Source/Ray3/Ray3Program.cpp | 36 ++--- Source/Ray3/Ray3Program.h | 8 +- Source/Ray3/Ray3Scene.cpp | 25 ++-- Source/Ray3/Ray3Scene.h | 16 +- 9 files changed, 492 insertions(+), 98 deletions(-) create mode 100644 Shaders/ray3testing.frag diff --git a/Notes/formulae.md b/Notes/formulae.md index 23bda9c..318e453 100644 --- a/Notes/formulae.md +++ b/Notes/formulae.md @@ -87,4 +87,47 @@ $$ \vec{r} = \vec{v} - 2\vec{n}\langle \vec{n}, \vec{v}\rangle $$ -If we wish to compute the reflection *off* the surface, then we simply mirror the incoming vector pointing towards the normal. So this formula is a *mirror* reflection when $\vec{v}$ points in the same direction as the normal, and a *surface* reflection if $\vec{v}$ points in the opposite direction. \ No newline at end of file +If we wish to compute the reflection *off* the surface, then we simply mirror the incoming vector pointing towards the normal. So this formula is a *mirror* reflection when $\vec{v}$ points in the same direction as the normal, and a *surface* reflection if $\vec{v}$ points in the opposite direction. + +## 4. Raytracer3D overview + +The rays are cast by the fragment shader, which computes the ray direction using the projection and view matrices. NDC Coordinates are inverted to local space by multiplying with the inverse of the model-view-projection matrix. + +```mermaid +graph LR; +3dm["3D Model"]-->vs["Vertex Shader"]--NDC coords [-1, 1]-->fs["Fragment Shader"] +``` + +The fragment shader is where the raytracing happens. In general for raytracing, the essence is to + +1. Compute the local space ray origin $\vec{o}$ and direction $\vec{d}$ from NDC coordinates. +2. For each reflection (numBounces) do the following: +2.1 Determine which mirror has been collided with. This can be optimized but we simply iterate every mirror. +2.2. Given the collided mirror and collision point, determine if there is an edge intersection. If so, stop and give color. This edge intersection test requires a distance check for each edge (straight or curved) which adds up to the cost of the algorithm. Edge visualising is not mandatory and can be substituted for corner visualisation or even single prop visualiation. +2.3. Reflect the ray in the normal and set new origin. +3. Set the fragment color. + +```mermaid +graph TD +Ray-->rayloop["For each reflection"]-->mloop["For each mirror"]-->det["Determine collision step"] +det--Set new closest-->mloop +mloop--Closest hit found-->edgeloop["For each edge"]-->detdi["Determine hit distance to edge"] +detdi--Edge collision-->break["Break loops and set color"] +edgeloop--No edge collision-->compref["Set new ray origin and direction"] +compref--Next reflection-->rayloop +rayloop--End of loop-->black["Set background color (Black)"] +``` + +## 5. Determine If Point In Polygon + +For the 3D curved mirrors we use a region of a circle as the surface. We must be able to determine of a collision occured inside that surface, which is determine by a polygon on a sphere. So given a point and a polygon, we have to determine if the point is inside. + +There are two ways to do this + +### Convex Polygons + +Here we determine the winding number as a sum of internal angles of the vertices to the point. The sum of internal angles must be 360. + +### Any Polygon + +Here we must trace a ray from the point to any constant direction. The number of intersections with the polygon must be an odd number. This is more costly. \ No newline at end of file diff --git a/Shaders/ray3.frag b/Shaders/ray3.frag index fdee1c9..b19183b 100644 --- a/Shaders/ray3.frag +++ b/Shaders/ray3.frag @@ -3,15 +3,23 @@ // Output fragment color out vec4 finalColor; +struct MirrorInfo { + vec4 normal; + vec4 center; + int offset; + int vertexCount; + float p2, p3; +}; + // First buffer for vertices. // Second buffer for polygon counts. layout(std430, binding=0) buffer ssbo0 { vec4 vertices[]; }; -layout(std430, binding=1) buffer ssbo1 { vec4 normals[]; }; -layout(std430, binding=2) buffer ssbo2 { int sizes[]; }; +layout(std430, binding=1) buffer ssbo1 { MirrorInfo mirrors[]; }; layout(location=0) uniform int numMirrors; layout(location=1) uniform float edgeThickness; layout(location=2) uniform mat4 invMvp; +layout(location=3) uniform float sphereFocus; uniform mat4 mvp; const float STEP_MIN = 0.001f; @@ -45,59 +53,107 @@ void main() // Iterate all mirrors and keep track of individual // mirror offset indices in the main vertex buffer. - for (int b = 0; b < 16; b++) { - int offset = 0; - int mirrorIndex = 0; - int mirrorOffset = 0; - int mirrorSize = 0; - HitInfo mirrorHit; - mirrorHit.t = -1; + for (int b = 0; b < 1; b++) { + // We keep track of the closest mirror hit. + HitInfo closestMirrorHit; + closestMirrorHit.t = -1; + MirrorInfo closestMirror; + float closestEdgeProj = -1; // Find the nearest plane to intersect with valid normal. - for (int i = 0; i < numMirrors; i++) { - int size = sizes[i]; - vec3 normal = normals[i].xyz; - vec3 surfacePoint = vertices[offset].xyz; - HitInfo hit = RayPlaneHit(rayOrigin, rayDir, normal, surfacePoint); - - bool validity = dot(normal, rayDir) < 0 && hit.t > STEP_MIN; - bool optimality = mirrorHit.t < 0 || hit.t < mirrorHit.t; + for (int i = 0; i < 1; i++) { + // For spherical collision we must test three things. + // 1. The ray must collide with the sphere. + // We take the second collision and test if it is on the correct side. + // 2. We test if the collision point is within the bounds of the edge planes. + MirrorInfo mirror = mirrors[i]; + vec3 normal = mirror.normal.xyz; + vec3 center = mirror.center.xyz; + vec3 surfacePoint = vertices[mirror.offset].xyz; + + // First we collide with the sphere. + vec3 sphereCenter = center + sphereFocus*normal; + float rsq = DistanceSq(sphereCenter, surfacePoint); + HitInfo hit = RaySphereHit(rayOrigin, rayDir, sphereCenter, rsq, false); + + // Test if the collision occured at the correct side. + // For focus > 0, we have spherical space and we collide with the outside. + // For focus < 0, we have hyperbolic space and we collide with the inside. + // Hyperbolic space does not require any special tests. + float planeProj = dot(hit.point-center, normal); + + // Now we compute the closest edge projection. + // We iterate each edge and project on the normal given by the plane spanned by + // the outward normal at a point and its edge vector. + // We face the normal of the plane toward our side, then a projection > 0 is correct. + float edgeProj = -1; + for (int j = 0; j < mirror.vertexCount; j++) { + vec3 u = vertices[mirror.offset + j].xyz; + vec3 v = vertices[mirror.offset + (j+1)%mirror.vertexCount].xyz; + + // Now the normal is given by the (v-u) x u, since our vertices lie on the sphere. + // We flip the normal to face the center of the face. + vec3 edgeNormal = normalize(cross(v-u, u)); + edgeNormal *= sign(dot(edgeNormal, center-u)); + + // Now we project with the vector from u to the hit point. + float proj = dot(edgeNormal, hit.point-u); + if (proj >= 0 && (proj < edgeProj || edgeProj < 0)) { + edgeProj = proj; + } + } + edgeProj = 0.01; + + bool validity = planeProj < 0 && hit.t > STEP_MIN; + bool optimality = closestMirrorHit.t < 0 || hit.t < closestMirrorHit.t; + + // HitInfo hit = RayPlaneHit(rayOrigin, rayDir, normal, surfacePoint); + // bool validity = hit.t > STEP_MIN && dot(rayDir, normal) < 0; + // bool optimality = closestMirrorHit.t < 0 || hit.t < closestMirrorHit.t; + if (optimality && validity) { - mirrorHit = hit; - mirrorIndex = i; - mirrorOffset = offset; - mirrorSize = size; + closestMirrorHit = hit; + closestMirror = mirror; + closestEdgeProj = edgeProj; } - offset += size; } - - if (mirrorHit.t < 0) + + // if (closestMirrorHit.t < 0) { + // break; + // } + + if (closestEdgeProj > 0 && closestEdgeProj < 0.05) { + //col = vec3(0, (1-0.5*closestEdgeProj/edgeThickness)*pow(0.85, float(b)), 0); + col = vec3(0, 0, 1); break; - - // Test collision with an edge. - bool hasHit = false; - for (int j = 0; j < mirrorSize; j++) { - vec3 u = vertices[mirrorOffset+j].xyz; - vec3 v = vertices[mirrorOffset+(j+1)%mirrorSize].xyz; - LineProjection lp = PointLineProject(mirrorHit.point, u, v); - bool bounds = 0 <= lp.utov && lp.utov <= 1; - float ds = DistanceSq(lp.projection, mirrorHit.point); - if (ds < edgeThickness && bounds) { - - col = vec3(0, (1-0.5*ds/edgeThickness)*pow(0.85, float(b+1)), 0); - hasHit = true; - } } - if (hasHit) break; - - rayDir = reflect(rayDir, mirrorHit.normal); - rayOrigin = mirrorHit.point; + // bool hasHit = false; + // for (int j = 0; j < closestMirror.vertexCount; j++) { + // vec3 u = vertices[closestMirror.offset+j].xyz; + // vec3 v = vertices[closestMirror.offset+(j+1)%closestMirror.vertexCount].xyz; + + // // Project point on the line and use distance as color. + // LineProjection lp = PointLineProject(closestMirrorHit.point, u, v); + // bool bounds = 0 <= lp.utov && lp.utov <= 1; + // float ds = DistanceSq(lp.projection, closestMirrorHit.point); + // if (ds < edgeThickness && bounds) { + // col = vec3(0, (1-0.5*ds/edgeThickness)*pow(0.85, float(b)), 0); + // hasHit = true; + // break; + // } + // } + // if (hasHit) break; + + // Dont reflect on backward facing normals, but do allow collision + // with the edges of that mirror. + rayOrigin = closestMirrorHit.point; + rayDir = reflect(rayDir, closestMirrorHit.normal); } finalColor = vec4(col, 1); } -HitInfo RaySphereHit(vec3 o, vec3 d, vec3 cc, float rsq, bool convex) +HitInfo RaySphereHit(vec3 o, vec3 d, vec3 cc, float rsq, bool outside) { // Make the point relative to the circle. vec3 rel = o-cc; @@ -113,10 +169,8 @@ HitInfo RaySphereHit(vec3 o, vec3 d, vec3 cc, float rsq, bool convex) float t1 = (-b - sqrtD) / (2*a); float t2 = (-b + sqrtD) / (2*a); - // Convex means that we take the last collision point, which relative - // to the ray is a convex curve. This is default spherical. - // Hyperbolic is not convex but arched toward the ray so concave. - float t = float(convex)*max(t1, t2) + (1-float(convex))*min(t1, t2); + // Outside collides only from outside. Inside only after entering. + float t = float(outside)*min(t1, t2) + (1-float(outside))*max(t1, t2); HitInfo hit; hit.point = o + t*d; diff --git a/Shaders/ray3.vert b/Shaders/ray3.vert index db75fd7..f8bd30a 100644 --- a/Shaders/ray3.vert +++ b/Shaders/ray3.vert @@ -11,5 +11,5 @@ uniform mat4 mvp; void main() { - gl_Position = mvp*vec4(vertexPosition, 1.0); + gl_Position = mvp*vec4(vertexPosition*1.3, 1.0); } \ No newline at end of file diff --git a/Shaders/ray3testing.frag b/Shaders/ray3testing.frag new file mode 100644 index 0000000..215a371 --- /dev/null +++ b/Shaders/ray3testing.frag @@ -0,0 +1,282 @@ +#version 430 + +// Output fragment color +out vec4 finalColor; + +struct MirrorInfo { + vec4 normal; + vec4 center; + int offset; + int vertexCount; + float p2, p3; +}; + +// First buffer for vertices. +// Second buffer for polygon counts. +layout(std430, binding=0) buffer ssbo0 { vec4 vertices[]; }; +layout(std430, binding=1) buffer ssbo1 { MirrorInfo mirrors[]; }; + +layout(location=0) uniform int numMirrors; +layout(location=1) uniform float edgeThickness; +layout(location=2) uniform mat4 invMvp; +layout(location=3) uniform float sphereFocus; +layout(location=4) uniform int numBounces; +uniform mat4 mvp; + +const float STEP_MIN = 0.001f; +const float OUTSIDE_MARGIN = 0.001f; + +struct HitInfo { + float t, t2; + vec3 point; + vec3 normal; +}; +struct LineProjection { + vec3 point; + float utov; +}; +HitInfo RaySphereHit(vec3 o, vec3 d, vec3 cc, float rsq, bool convex); +HitInfo RayPlaneHit(vec3 o, vec3 d, vec3 n, vec3 u); +LineProjection PointLineProject(vec3 p, vec3 u, vec3 v); +float DistanceSq(vec3 a, vec3 b); +void EdgeCollide(vec3 o, vec3 d, out vec3 color); +void CurvedEdgeCollide(vec3 o, vec3 d, out vec3 color); + +void main() +{ + // Compute the necessary ray data in view space. + // We can't do this in vertex space due to projection.; + vec2 ndc = 2*gl_FragCoord.xy / 800 - 1; + vec4 originHom = invMvp*vec4(ndc.xy, -1, 1); + vec4 dirHom = invMvp*vec4(ndc.xy, 1, 1); + vec3 dirWorld = dirHom.xyz / dirHom.w; + vec3 rayOriginStart = originHom.xyz / originHom.w; + vec3 rayDirStart = normalize(dirWorld-rayOriginStart); + + vec3 rayOrigin = rayOriginStart; + vec3 rayDir = rayDirStart; + + vec3 col = vec3(0, 0, 0); + + // We iterate every reflection. + // Finding the first mirror collided with. + for (int b=0; b < numBounces; b++) { + HitInfo minHit; + minHit.t = -1; + int mirrorIndex = 0; + float minEdgeProj = 0; + + // Iterate every mirror and collide spheres. + for (int i=0; i < numMirrors; i++) { + MirrorInfo mirror = mirrors[i]; + vec3 cornerPoint = vertices[mirror.offset].xyz; + + // 1. Hit with the correct half of the sphere. + // Spherical=Inside (outside=false) + vec3 cc = mirror.center.xyz + sphereFocus * mirror.normal.xyz; + float rsq = DistanceSq(cornerPoint, cc); + HitInfo hit = RaySphereHit(rayOrigin, rayDir, cc, rsq, false); + + // 2. Validate if outside. + // Checking if normal dot product is positive or negative. + // Negative means OUTSIDE since normals point inward. + vec3 centertohit = normalize(hit.point - mirror.center.xyz); + bool isOutside = dot(centertohit, mirror.normal.xyz) <= OUTSIDE_MARGIN; + + // 3. Validate if within edge bounds. + // Iterate each edge and perform circularity check. + float edgeProj = -1; + bool inBounds = true; + for (int j = 0; j < mirror.vertexCount; j++) { + vec3 u = vertices[mirror.offset + j].xyz; + vec3 v = vertices[mirror.offset + (j+1)%mirror.vertexCount].xyz; + + // Now the normal is given by the (v-u) x u, since our vertices lie on the sphere. + // We flip the normal to face the center of the face. + vec3 edgeNormal = normalize(cross(v-u, u)); + edgeNormal *= sign(dot(edgeNormal, mirror.center.xyz-u)); + + // Now we project with the vector from u to the hit point. + // Negative projections indicate we are outside the polygonal region! + // We ignore this mirror collision then. + float proj = dot(edgeNormal, hit.point-u); + if (proj < 0) { + inBounds = false; + break; + } + else if (proj < edgeProj || edgeProj < 0) { + edgeProj = proj; + } + } + + bool validity = hit.t > STEP_MIN && isOutside && inBounds; + bool optimality = minHit.t < 0 || hit.t < minHit.t; + if (validity && optimality) { + minHit = hit; + mirrorIndex = i; + minEdgeProj = edgeProj; + } + } + + if (minHit.t > 0 && minEdgeProj < edgeThickness) { + col = vec3(0, (1-0.5*minEdgeProj/edgeThickness)*pow(0.85, float(b)), 0); + break; + } + if (minHit.t < 0) break; + + // Reflect. + rayOrigin = minHit.point; + rayDir = reflect(rayDir, minHit.normal); + } + CurvedEdgeCollide(rayOriginStart, rayDirStart, col); + finalColor = vec4(col, 1); +} + +HitInfo RaySphereHit(vec3 o, vec3 d, vec3 cc, float rsq, bool outside) +{ + // Make the point relative to the circle. + vec3 rel = o-cc; + + float a = 1; + float b = 2*dot(rel, d); + float c = dot(rel, rel) - rsq; + float D = b*b - 4*a*c; + + // We take an absolute value here, but if the + // square root ends up negative we set t to zero. + float sqrtD = sqrt(abs(D)); + float t1 = (-b - sqrtD) / (2*a); + float t2 = (-b + sqrtD) / (2*a); + + // Outside collides only from outside. Inside only after entering. + float t = float(outside)*min(t1, t2) + (1-float(outside))*max(t1, t2); + + HitInfo hit; + hit.point = o + t*d; + // We must also flip normals if we do inside or outside collisions! + // Outside will evaluate to *1. + hit.normal = (2*float(outside) - 1)*normalize(hit.point-cc); + hit.t = t * float(D >= 0); + hit.t2 = t == t1 ? t2 : t1; + return hit; +} + +HitInfo RayPlaneHit(vec3 o, vec3 d, vec3 n, vec3 u) +{ + float nd = dot(n, d); + float t = dot(n, u-o) / (nd + float(nd==0)*0.001); + + HitInfo hit; + hit.point = o + t*d; + hit.normal = n; + hit.t = t; + + return hit; +} + +LineProjection PointLineProject(vec3 p, vec3 u, vec3 v) +{ + float uv = length(v-u); + vec3 dir = (v-u) / uv; + float utov = dot(dir, p-u); + vec3 proj = u + dir*utov; + LineProjection lp; + lp.point = proj; + lp.utov = utov / uv; + return lp; +} + +float DistanceSq(vec3 a, vec3 b) +{ + return dot(a-b, a-b); +} + +void EdgeCollide(vec3 o, vec3 d, out vec3 color) +{ + // We draw the outside seperately from mirror reflections. + // For this we intersect with the spheres but add a validity test + // that the reflection point must be within the so-called edge-planes. + for (int i=0; i < numMirrors; i++) { + MirrorInfo mirror = mirrors[i]; + vec3 cornerPoint = vertices[mirror.offset].xyz; + + HitInfo hit = RayPlaneHit(o, d, mirror.normal.xyz, cornerPoint); + if (hit.t < 0) continue; + + // STAGE 1: EDGE VISUALISATION. + bool stop = false; + for (int j=0; j < mirror.vertexCount; j++) { + vec3 u = vertices[mirror.offset+j].xyz; + vec3 v = vertices[mirror.offset+(j+1)%mirror.vertexCount].xyz; + + LineProjection lp = PointLineProject(hit.point, u, v); + float dist = DistanceSq(lp.point, hit.point); + float r = edgeThickness; + if (dist < r*r && 0 <= lp.utov && lp.utov <= 1) { + color = vec3(0, 0, dist/(r*r)); + stop = true; + } + } + if (stop) break; + } +} + +void CurvedEdgeCollide(vec3 o, vec3 d, out vec3 color) +{ + // We draw the outside seperately from mirror reflections. + // For this we intersect with the spheres but add a validity test + // that the reflection point must be within the so-called edge-planes. + float closestProj = 0; + float closestHit = -1; + + for (int i=0; i < numMirrors; i++) { + MirrorInfo mirror = mirrors[i]; + vec3 cornerPoint = vertices[mirror.offset].xyz; + + // For hyperbolic or non-hyperbolic we must consider either + // the collisions inside or outside the shape. + vec3 cc = mirror.center.xyz + sphereFocus*mirror.normal.xyz; + float rsq = DistanceSq(cc, cornerPoint); + HitInfo hit = RaySphereHit(o, d, cc, rsq, false); + if (hit.t <= 0) continue; + + bool isCorrect1 = dot(normalize(hit.point - mirror.center.xyz), mirror.normal.xyz) <= OUTSIDE_MARGIN; + bool isCorrect2 = dot(normalize(o + d*hit.t2 - mirror.center.xyz), mirror.normal.xyz) <= OUTSIDE_MARGIN; + float t = (isCorrect1 && isCorrect2) ? min(hit.t, hit.t2): (isCorrect1 ? hit.t: hit.t2); + vec3 point = o+t*d; + + float edgeProj = -1; + bool isInside = true; + for (int j = 0; j < mirror.vertexCount; j++) { + vec3 u = vertices[mirror.offset + j].xyz; + vec3 v = vertices[mirror.offset + (j+1)%mirror.vertexCount].xyz; + + // Now the normal is given by the (v-u) x u, since our vertices lie on the sphere. + // We flip the normal to face the center of the face. + vec3 edgeNormal = normalize(cross(v-u, u)); + edgeNormal *= sign(dot(edgeNormal, mirror.center.xyz-u)); + + // Now we project with the vector from u to the hit point. + // Negative projections indicate we are outside the polygonal region! + // We ignore this mirror collision then. + float proj = dot(edgeNormal, point-u); + if (proj < 0) { + isInside = false; + break; + } + else if (proj < edgeProj || edgeProj < 0) { + edgeProj = proj; + } + } + + // We color if close and valid. + if (edgeProj < edgeThickness && (closestHit < 0 || t < closestHit) && isInside) { + closestProj = edgeProj; + closestHit = t; + } + } + + if (closestHit > 0) { + color = vec3(0, 1-closestProj/edgeThickness, 0); + } +} \ No newline at end of file diff --git a/Source/Ray3/Ray3.h b/Source/Ray3/Ray3.h index 6f32fe2..232f2ce 100644 --- a/Source/Ray3/Ray3.h +++ b/Source/Ray3/Ray3.h @@ -12,17 +12,23 @@ class Ray3 Ray3Program program; Model model; bool doRotate = true; - bool showMesh = true; + float rotY = 0; + float radius = 3; + + bool showMesh = false; + float sphereFocusPercent = 1.0f; + float numBouncesFl = 3; + public: Ray3() { camera.fovy = 45.0f; - camera.position = { 3, 2, 3 }; + camera.position = {}; camera.target = { 0, 0, 0 }; camera.up = { 0, 1, 0 }; camera.projection = CAMERA_PERSPECTIVE; - model = LoadModel("Models/Dodecahedron.obj"); + model = LoadModel("Models/Cube.obj"); scene.name = "Cube"; scene.AddMirrorModel(model, true); program.model = model; @@ -30,7 +36,9 @@ class Ray3 } void Draw() { - if (doRotate) UpdateCamera(&camera, CAMERA_ORBITAL); + float dt = GetFrameTime(); + program.sphereFocus = 1+powf(sphereFocusPercent, 8) * (SPHERE_FOCUS_INF-1); + program.numBounces = (int)numBouncesFl; BeginMode3D(camera); vector colors = { @@ -41,8 +49,9 @@ class Ray3 MAGENTA, GRAY }; - if (showMesh) scene.DrawMirrors(colors, 0.99, 0.95); program.Draw(camera); + if (showMesh) scene.DrawMirrors(colors, 0.99, 0.95); + // rlSetCullFace(RL_CULL_FACE_FRONT); // DrawSphereEx({}, 1.0f, 32, 32, Fade(RED, 0.3f)); // rlSetCullFace(RL_CULL_FACE_BACK); @@ -54,9 +63,16 @@ class Ray3 if (IsKeyDown(KEY_UP)) camera.position.y += spd; if (IsKeyDown(KEY_DOWN)) camera.position.y -= spd; if (IsKeyPressed(KEY_SPACE)) doRotate = !doRotate; + camera.position.x = radius * cosf(DEG2RAD*rotY); + camera.position.z = radius * sinf(DEG2RAD*rotY); + float scroll = GetMouseWheelMove(); + radius = Clamp(radius+scroll, 1, 10); + rotY += (float)(IsKeyDown(KEY_LEFT)-IsKeyDown(KEY_RIGHT)) * 45.0f*dt; if (GuiButton({ 700, 10, 80, 20 }, TextFormat(showMesh ? "Hide Mesh": "Show Mesh"))) showMesh = !showMesh; - GuiSlider({ 170, 10, 200, 10 }, "Edge Size", TextFormat("%.1f", program.edgeThickness), &program.edgeThickness, 0.1, 10.0); + GuiSlider({ 170, 10, 200, 10 }, "Edge Size", TextFormat("%.1f", program.edgeThickness), &program.edgeThickness, 0.1, 10.0); + GuiSlider({ 170, 30, 200, 10 }, "Sphere Focus", TextFormat("%.1f", program.sphereFocus), &sphereFocusPercent, 0, 1); + GuiSlider({ 170, 50, 200, 10 }, "Num Bounces", TextFormat("%d", program.numBounces), &numBouncesFl, 0, 20); } }; diff --git a/Source/Ray3/Ray3Program.cpp b/Source/Ray3/Ray3Program.cpp index d79dcc3..67e2cae 100644 --- a/Source/Ray3/Ray3Program.cpp +++ b/Source/Ray3/Ray3Program.cpp @@ -4,7 +4,7 @@ Ray3Program::Ray3Program() { - shader = LoadShader("Shaders/ray3.vert", "Shaders/ray3.frag"); + shader = LoadShader("Shaders/ray3.vert", "Shaders/ray3testing.frag"); } void Ray3Program::SetScene(Ray3Scene *_scene) { @@ -13,25 +13,11 @@ void Ray3Program::SetScene(Ray3Scene *_scene) // Retrieve the packed data from the scene and send it to the GPU. // This data does not change and hence RL_STATIC_READ. vector vertices = _scene->GetMirrorVerticesPacked(); - vector normals = _scene->GetMirrorNormalsPacked(); - vector sizes = _scene->GetMirrorSizesPacked(); - numMirrors = _scene->GetNumMirrors(); - printf("%d Mirrors\n", numMirrors); - - int offset = 0; - for (int i = 0; i < numMirrors; i++) { - Vector4 normal = normals[i]; - int size = sizes[i]; - printf("Face %d size %d normal (%1.1f %1.1f %1.1f)\n", i, size, normal.x, normal.y, normal.z); - for (int j = 0; j < size; j++) { - Vector4 vert = vertices[j+offset]; - printf("Vertex %d (%1.1f %1.1f %1.1f)\n", j, vert.x, vert.y, vert.z); - } - offset += size; - } + vector infos = _scene->GetMirrorInfosPacked(); + numMirrors = _scene->GetMirrorCount(); + mirrorVertexBuffer = rlLoadShaderBuffer(vertices.size()*sizeof(Vector4), vertices.data(), RL_STATIC_READ); - mirrorNormalBuffer = rlLoadShaderBuffer(normals.size()*sizeof(Vector4), normals.data(), RL_STATIC_READ); - mirrorSizeBuffer = rlLoadShaderBuffer(sizes.size()*sizeof(int), sizes.data(), RL_STATIC_READ); + mirrorBuffer = rlLoadShaderBuffer(infos.size()*sizeof(MirrorInfo), infos.data(), RL_STATIC_READ); } void Ray3Program::Draw(Camera3D camera) { @@ -40,10 +26,9 @@ void Ray3Program::Draw(Camera3D camera) // We only have to bind the buffers once. // They will persist for the entire drawing time. rlBindShaderBuffer(mirrorVertexBuffer, 0); - rlBindShaderBuffer(mirrorNormalBuffer, 1); - rlBindShaderBuffer(mirrorSizeBuffer, 2); + rlBindShaderBuffer(mirrorBuffer, 1); SetShaderValue(shader, 0, &numMirrors, SHADER_UNIFORM_INT); - float edgeThick = 0.001f*edgeThickness; + float edgeThick = 0.01f*edgeThickness; SetShaderValue(shader, 1, &edgeThick, SHADER_UNIFORM_FLOAT); // Precompute inverted matrix so the fragment shader does not have to. @@ -53,10 +38,15 @@ void Ray3Program::Draw(Camera3D camera) Matrix invMvp = MatrixInvert(mvp); SetShaderValueMatrix(shader, 2, invMvp); + SetShaderValue(shader, 3, &sphereFocus, SHADER_UNIFORM_FLOAT); + SetShaderValue(shader, 4, &numBounces, SHADER_UNIFORM_INT); + // The model uses the same shader, but we use the Raylib method // of drawing models since it is easiest. model.materials->shader = shader; rlSetCullFace(RL_CULL_FACE_FRONT); - DrawModel(model, {}, 1, WHITE); + BeginShaderMode(shader); + DrawSphere({}, 2.0f, WHITE); + EndShaderMode(); rlSetCullFace(RL_CULL_FACE_BACK); } \ No newline at end of file diff --git a/Source/Ray3/Ray3Program.h b/Source/Ray3/Ray3Program.h index 643a44d..07f35d6 100644 --- a/Source/Ray3/Ray3Program.h +++ b/Source/Ray3/Ray3Program.h @@ -4,6 +4,7 @@ #include #include "Ray3Scene.h" +#define SPHERE_FOCUS_INF 1000.0f typedef int ShaderBuffer; // This class represents the 3D mirror raytracer. @@ -15,10 +16,10 @@ class Ray3Program { public: int numMirrors = 0; - int numBounces = 0; + int numBounces = 5; Color color = GREEN; Model model; - float arcFocus = 1000.0f; + float sphereFocus = 1000.0f; float edgeThickness = 1.0f; Ray3Program(); @@ -28,8 +29,7 @@ class Ray3Program private: Shader shader; ShaderBuffer mirrorVertexBuffer; - ShaderBuffer mirrorNormalBuffer; - ShaderBuffer mirrorSizeBuffer; + ShaderBuffer mirrorBuffer; Ray3Scene *scene; Shader renderProgram = {}; }; diff --git a/Source/Ray3/Ray3Scene.cpp b/Source/Ray3/Ray3Scene.cpp index d35e77e..5a2d575 100644 --- a/Source/Ray3/Ray3Scene.cpp +++ b/Source/Ray3/Ray3Scene.cpp @@ -85,23 +85,22 @@ vector Ray3Scene::GetMirrorVerticesPacked() } return vertices; } -vector Ray3Scene::GetMirrorNormalsPacked() +vector Ray3Scene::GetMirrorInfosPacked() { - vector normals; + vector infos; + int offset = 0; for (Polygon poly: mirrors) { - normals.push_back({ poly.normal.x, poly.normal.y, poly.normal.z, 0 }); + MirrorInfo info = {}; + info.normal = { poly.normal.x, poly.normal.y, poly.normal.z, 0 }; + info.center = { poly.center.x, poly.center.y, poly.center.z, 0 }; + info.vertexOffset = offset; + info.vertexCount = poly.vertices.size(); + infos.push_back(info); + offset += info.vertexCount; } - return normals; + return infos; } -vector Ray3Scene::GetMirrorSizesPacked() -{ - vector sizes; - for (Polygon poly: mirrors) { - sizes.push_back(poly.vertices.size()); - } - return sizes; -} -int Ray3Scene::GetNumMirrors() +int Ray3Scene::GetMirrorCount() { return mirrors.size(); } \ No newline at end of file diff --git a/Source/Ray3/Ray3Scene.h b/Source/Ray3/Ray3Scene.h index 03158fc..c032cac 100644 --- a/Source/Ray3/Ray3Scene.h +++ b/Source/Ray3/Ray3Scene.h @@ -6,6 +6,17 @@ #include "Polygon.h" using namespace std; +// Face describes metadata of face. +// Must be tightly pack so we use pack(1) for 1-byte packing. +#pragma pack(1) +struct MirrorInfo { + Vector4 normal; + Vector4 center; + int vertexOffset; + int vertexCount; + float p1, p2; +}; + class Ray3Scene { private: @@ -20,9 +31,8 @@ class Ray3Scene // Packing functions. vector GetMirrorVerticesPacked(); - vector GetMirrorNormalsPacked(); - vector GetMirrorSizesPacked(); - int GetNumMirrors(); + vector GetMirrorInfosPacked(); + int GetMirrorCount(); }; #endif \ No newline at end of file