diff options
| author | hachem <im@hachem.wtf> | 2025-08-17 00:01:03 +0200 |
|---|---|---|
| committer | hachem <im@hachem.wtf> | 2025-08-17 00:01:03 +0200 |
| commit | d4ed53752cb3d078e0b79444a3e9a6015c6ff679 (patch) | |
| tree | d9f9fa94aea2ad781a534175d81ce2daed40b0c7 /docs/ray-tracing-implementation.md | |
| parent | df98427825234b24cfa87ad9f9d8a0d738b3ea13 (diff) | |
[doc]: Add explanation and documentation
Diffstat (limited to 'docs/ray-tracing-implementation.md')
| -rw-r--r-- | docs/ray-tracing-implementation.md | 258 |
1 files changed, 258 insertions, 0 deletions
diff --git a/docs/ray-tracing-implementation.md b/docs/ray-tracing-implementation.md new file mode 100644 index 0000000..89778b7 --- /dev/null +++ b/docs/ray-tracing-implementation.md @@ -0,0 +1,258 @@ +# Ray Tracing in Curved Spacetime: A Detailed Overview + +This ray tracer simulates how light behaves near a black hole, including the effects of curved spacetime and interactions with objects like stars and an accretion disk. The implementation follows a step-by-step approach, which includes initializing rays, tracing their paths through spacetime, detecting intersections with objects, and finally rendering the scene. + +## 1. Ray Initialization + +The first step in ray tracing is to create rays originating from the camera that will eventually traverse through spacetime. Each ray represents a possible path of light. + +### Camera Setup + +The camera is positioned in three-dimensional space and is defined by its orientation and field of view. It has the following parameters: + +```glsl +layout(std140, binding = 1) uniform Camera +{ + vec3 camPos; float _pad0; + vec3 camRight; float _pad1; + vec3 camUp; float _pad2; + vec3 camForward; float _pad3; + float tanHalfFov; // Field of view + float aspect; // Aspect ratio + bool moving; // Whether the camera is moving + int _pad4; +} cam; +``` + +- `camPos`: The 3D position of the camera. +- `camRight`, `camUp`, `camForward`: Orthonormal vectors defining the camera's orientation. +- `tanHalfFov` and `aspect`: Determine the camera’s field of view and the shape of the image plane. + +### Ray Generation per Pixel + +For each pixel on the image, we calculate the corresponding direction in world space and generate a ray pointing in that direction: + +```glsl +float u = (2.0 * (pix.x + 0.5) / WIDTH - 1.0) * cam.aspect * cam.tanHalfFov; +float v = (1.0 - 2.0 * (pix.y + 0.5) / HEIGHT) * cam.tanHalfFov; +vec3 dir = normalize(u * cam.camRight - v * cam.camUp + cam.camForward); +Ray ray = InitRay(cam.camPos, dir); +``` + +Here, `u` and `v` are normalized coordinates on the image plane. The `InitRay` function takes the camera position and the computed direction to create a ray in both Cartesian and spherical coordinates. + +### Ray Structure + +Each ray contains not only the standard 3D Cartesian position but also spherical coordinates and velocity components, which are necessary for simulating curved spacetime: + +```glsl +struct Ray +{ + float x, y, z; // Cartesian coordinates + float r, theta, phi; // Spherical coordinates + float dr, dtheta, dphi; // Radial and angular velocities + float E, L; // Conserved quantities in Schwarzschild geometry +}; +``` + +### Converting Cartesian to Spherical Coordinates + +The `InitRay` function converts the position and direction of the ray into spherical coordinates and computes initial velocities: + +```glsl +Ray InitRay(vec3 pos, vec3 dir) +{ + Ray ray; + ray.x = pos.x; + ray.y = pos.y; + ray.z = pos.z; + + // Spherical coordinates + ray.r = length(pos); + ray.theta = acos(pos.z / ray.r); + ray.phi = atan(pos.y, pos.x); + + // Convert direction vector to spherical velocities + float dx = dir.x; + float dy = dir.y; + float dz = dir.z; + + ray.dr = sin(ray.theta)*cos(ray.phi)*dx + sin(ray.theta)*sin(ray.phi)*dy + cos(ray.theta)*dz; + ray.dtheta = (cos(ray.theta)*cos(ray.phi)*dx + cos(ray.theta)*sin(ray.phi)*dy - sin(ray.theta)*dz) / ray.r; + ray.dphi = (-sin(ray.phi)*dx + cos(ray.phi)*dy) / (ray.r * sin(ray.theta)); + + // Calculate conserved quantities (energy E and angular momentum L) + ray.L = ray.r * ray.r * sin(ray.theta) * ray.dphi; + float f = 1.0 - SagA_rs / ray.r; + float dt_dL = sqrt((ray.dr*ray.dr)/f + ray.r*ray.r*(ray.dtheta*ray.dtheta + sin(ray.theta)*sin(ray.theta)*ray.dphi*ray.dphi)); + ray.E = f * dt_dL; + + return ray; +} +``` + +## 2. Geodesic Integration + +Once the ray is initialized, we trace its path through curved spacetime using the Schwarzschild metric. This requires solving the geodesic equations for the ray. + +### Integration Loop + +The ray is advanced step by step using a numerical integrator (Runge-Kutta 4th order). The loop continues until the ray either escapes the scene, falls into the black hole, or intersects an object. + +```glsl +for (int i = 0; i < maxSteps; ++i) +{ + if (ray.r > exitDistance) break; + if (ray.r > ESCAPE_R) break; + if (Intercept(ray, SagA_rs)) { hitBlackHole = true; break; } + + currentStepSize = CalculateAdaptiveStepSize(ray, D_LAMBDA); + RK4Step(ray, currentStepSize); + lambda += currentStepSize; + + vec3 newPos = vec3(ray.x, ray.y, ray.z); + if (CrossesEquatorialPlane(prevPos, newPos)) { hitDisk = true; break; } + if (i % objectCheckInterval == 0 && InterceptObject(ray)) { hitObject = true; break; } + + prevPos = newPos; +} +``` + +### Runge-Kutta 4 (RK4) + +The RK4 integrator provides high accuracy by computing intermediate slopes and averaging them to advance the ray: + +```glsl +void RK4Step(inout Ray ray, float dL) +{ + vec3 k1a, k1b; + GeodesicRHS(ray, k1a, k1b); + + ray.r += dL * k1a.x; + ray.theta += dL * k1a.y; + ray.phi += dL * k1a.z; + ray.dr += dL * k1b.x; + ray.dtheta += dL * k1b.y; + ray.dphi += dL * k1b.z; + + // Update Cartesian coordinates + ray.x = ray.r * sin(ray.theta) * cos(ray.phi); + ray.y = ray.r * sin(ray.theta) * sin(ray.phi); + ray.z = ray.r * cos(ray.theta); +} +``` + +### Geodesic Derivatives + +The function `GeodesicRHS` computes the derivatives needed for integration: + +```glsl +void GeodesicRHS(Ray ray, out vec3 d1, out vec3 d2) +{ + float r = ray.r; + float theta = ray.theta; + float dr = ray.dr; + float dtheta = ray.dtheta; + float dphi = ray.dphi; + float f = 1.0 - SagA_rs / r; + float dt_dL = ray.E / f; + + d1 = vec3(dr, dtheta, dphi); + + d2.x = - (SagA_rs / (2.0 * r*r)) * f * dt_dL * dt_dL + + (SagA_rs / (2.0 * r*r * f)) * dr * dr + + r * (dtheta*dtheta + sin(theta)*sin(theta)*dphi*dphi); + d2.y = -2.0*dr*dtheta/r + sin(theta)*cos(theta)*dphi*dphi; + d2.z = -2.0*dr*dphi/r - 2.0*cos(theta)/(sin(theta)) * dtheta * dphi; +} +``` + +## 3. Intersection Testing + +Rays can intersect three types of entities: the black hole, spherical objects, and the accretion disk. + +### Black Hole Intersection + +A ray hitting the event horizon is considered absorbed: + +```glsl +bool Intercept(Ray ray, float rs) +{ + return ray.r <= rs; +} +``` + +### Object Intersection + +The scene can contain multiple spherical objects such as stars or planets: + +```glsl +bool InterceptObject(Ray ray) +{ + vec3 P = vec3(ray.x, ray.y, ray.z); + + for (int i = 0; i < numObjects; ++i) + { + vec3 center = objPosRadius[i].xyz; + float radius = objPosRadius[i].w; + + float distSq = dot(P - center, P - center); + if (distSq > radius * radius * 4.0) continue; + if (distSq <= radius * radius) + { + objectColor = objColor[i]; + hitCenter = center; + hitRadius = radius; + return true; + } + } + return false; +} +``` + +### Accretion Disk Intersection + +The accretion disk lies in the equatorial plane and is checked by detecting if the ray crosses this plane: + +```glsl +bool CrossesEquatorialPlane(vec3 oldPos, vec3 newPos) +{ + bool crossed = (oldPos.y * newPos.y <0.0); + if (crossed) { diskIntersection = newPos; } + return crossed; +} +``` + +## 4. Shading and Color Computation + +Once an intersection is found, we compute the color of the pixel based on the object hit and relativistic effects such as gravitational redshift and Doppler shift. + +```glsl +vec3 ComputeColor(Ray ray) +{ + if (hitBlackHole) return vec3(0.0); // Black hole is black + if (hitDisk) return SampleDiskTexture(diskIntersection); + if (hitObject) return ApplyLighting(ray, objectColor, hitCenter, hitRadius); + + return SampleBackground(ray); // Background stars, etc. +} +```` + +This ensures that each pixel reflects both the geometrical position and relativistic effects along the ray. + +## 5. Rendering Loop + +Finally, the main rendering loop iterates over every pixel on the screen, traces a ray, and stores the computed color: + +```glsl +for (int y = 0; y < HEIGHT; ++y) +{ + for (int x = 0; x < WIDTH; ++x) + { + Ray ray = InitRayForPixel(x, y); + TraceRay(ray); + vec3 color = ComputeColor(ray); + framebuffer[y*WIDTH + x] = vec4(color, 1.0); + } +} +```
\ No newline at end of file |
