renderingbranch

This commit is contained in:
yggdrasil75
2026-03-07 11:17:51 -05:00
parent 3d96e569c8
commit 35c8a71102
3 changed files with 260 additions and 87 deletions

View File

@@ -34,7 +34,7 @@ endif
CXXFLAGS = $(BASE_CXXFLAGS) $(SIMD_CXXFLAGS)
# Source files
SRC := $(SRC_DIR)/ptest.cpp
SRC := $(SRC_DIR)/materialtest.cpp
#SRC := $(SRC_DIR)/g2chromatic2.cpp
SRC += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_tables.cpp $(IMGUI_DIR)/imgui_widgets.cpp
SRC += $(IMGUI_DIR)/backends/imgui_impl_glfw.cpp $(IMGUI_DIR)/backends/imgui_impl_opengl3.cpp

View File

@@ -10,6 +10,9 @@
#include "../util/grid/grid3eigen.hpp"
#include "../util/output/frame.hpp"
#include "../util/output/bmpwriter.hpp"
#include "../util/output/aviwriter.hpp"
#include "../util/timing_decorator.hpp"
#include "../util/timing_decorator.cpp"
// Helper function to create a solid volume of voxels with material properties
void createBox(Octree<int>& octree, const Eigen::Vector3f& center, const Eigen::Vector3f& size,
@@ -123,11 +126,16 @@ int main() {
octree.generateLODs();
octree.printStats();
// 3. Setup rendering loop
// 3. Setup video rendering
int width = 512;
int height = 512;
int samples = 400;
int bounces = 5;
// --- Video Animation Parameters ---
const float fps = 30.0f;
const float durationPerSegment = 10.0f; // Seconds to travel between each view
const int framesPerSegment = static_cast<int>(fps * durationPerSegment);
const int video_samples = 100; // Samples per pixel for each video frame
const int video_bounces = 5; // Ray bounces for each video frame
struct View {
std::string name;
@@ -135,61 +143,65 @@ int main() {
Eigen::Vector3f up;
};
// The walls are set perfectly at +/- 7.0 inner edges.
// Placing camera at +/- 6.8 will put it "just barely inside".
// Floor is at Z = -0.5, Wall top is at Z = 7.5
// Define the keyframe camera views for the animation
std::vector<View> views = {
{"+X", Eigen::Vector3f( 6.8f, 0.0f, 1.0f), Eigen::Vector3f(0.0f, 0.0f, 1.0f)},
{"-X", Eigen::Vector3f(-6.8f, 0.0f, 1.0f), Eigen::Vector3f(0.0f, 0.0f, 1.0f)},
{"+Y", Eigen::Vector3f( 0.0f, 6.8f, 1.0f), Eigen::Vector3f(0.0f, 0.0f, 1.0f)},
{"-Y", Eigen::Vector3f( 0.0f, -6.8f, 1.0f), Eigen::Vector3f(0.0f, 0.0f, 1.0f)},
{"+Z", Eigen::Vector3f( 0.0f, 0.0f, 7.3f), Eigen::Vector3f(0.0f, 1.0f, 0.0f)} // Looking down from just beneath wall top
{"+X", Eigen::Vector3f( 6.8f, 0.0f, 1.0f), Eigen::Vector3f(0.0f, 0.0f, 1.0f)},
{"+Y", Eigen::Vector3f( 0.0f, 6.8f, 1.0f), Eigen::Vector3f(0.0f, 0.0f, 1.0f)},
{"-X", Eigen::Vector3f(-6.8f, 0.0f, 1.0f), Eigen::Vector3f(0.0f, 0.0f, 1.0f)},
{"+Z", Eigen::Vector3f( 0.0f, 0.0f, 7.3f), Eigen::Vector3f(0.0f, 1.0f, 0.0f)} // Top-down view
};
Eigen::Vector3f target(0.0f, 0.0f, 0.5f);
Eigen::Vector3f target(0.0f, 0.0f, 0.5f); // The camera will always look at this point
for (const auto& view : views) {
std::cout << "\nRendering view from " << view.name << " direction (Fast Pass)..." << std::endl;
// --- Main Animation and Rendering Loop ---
std::vector<frame> videoFrames;
const int totalFrames = framesPerSegment * views.size();
videoFrames.reserve(totalFrames);
int frameCounter = 0;
Camera cam;
cam.origin = view.origin;
cam.direction = (target - view.origin).normalized();
cam.up = view.up;
std::cout << "\nStarting video render..." << std::endl;
std::cout << "Total frames to render: " << totalFrames << std::endl;
frame out = octree.fastRenderFrame(cam, height, width, frame::colormap::RGB);
for (size_t i = 0; i < views.size(); ++i) {
const View& startView = views[i];
const View& endView = views[(i + 1) % views.size()]; // Loop back to the first view at the end
std::string filename = "output/fast/render_" + view.name + ".bmp";
BMPWriter::saveBMP(filename, out);
std::cout << "\nAnimating segment: " << startView.name << " -> " << endView.name << std::endl;
for (int j = 0; j < framesPerSegment; ++j) {
frameCounter++;
float t = static_cast<float>(j) / static_cast<float>(framesPerSegment);
// Interpolate camera position (origin) linearly
Eigen::Vector3f currentOrigin = startView.origin * (1.0f - t) + endView.origin * t;
// Interpolate camera orientation (up vector) and normalize
Eigen::Vector3f currentUp = (startView.up * (1.0f - t) + endView.up * t).normalized();
Camera cam;
cam.origin = currentOrigin;
cam.up = currentUp;
cam.direction = (target - cam.origin).normalized();
std::cout << "Rendering video frame " << frameCounter << "/" << totalFrames << "..." << std::endl;
frame out = octree.renderFrame(cam, height, width, frame::colormap::RGB, video_samples, video_bounces, false, true);
videoFrames.push_back(std::move(out)); // Use std::move for efficiency
}
}
for (const auto& view : views) {
std::cout << "\nRendering view from " << view.name << " direction (Medium 60s Pass)..." << std::endl;
// --- Save the final video ---
std::cout << "\nAll frames rendered. Saving video file..." << std::endl;
std::string videoFilename = "output/material_test_video.avi";
Camera cam;
cam.origin = view.origin;
cam.direction = (target - view.origin).normalized();
cam.up = view.up;
frame out = octree.renderFrameTimed(cam, height, width, frame::colormap::RGB, 60, bounces, false, true);
std::string filename = "output/medium/render_" + view.name + ".bmp";
BMPWriter::saveBMP(filename, out);
if (AVIWriter::saveAVIFromCompressedFrames(videoFilename, std::move(videoFrames), width, height, fps)) {
std::cout << "Video saved successfully to " << videoFilename << std::endl;
} else {
std::cerr << "Error: Failed to save video!" << std::endl;
}
for (const auto& view : views) {
std::cout << "\nRendering view from " << view.name << " direction (Slow 400 Samples Pass)..." << std::endl;
Camera cam;
cam.origin = view.origin;
cam.direction = (target - view.origin).normalized();
cam.up = view.up;
frame out = octree.renderFrame(cam, height, width, frame::colormap::RGB, samples, bounces, false, true);
std::string filename = "output/slow/render_" + view.name + ".bmp";
BMPWriter::saveBMP(filename, out);
}
std::cout << "\nAll renders complete!" << std::endl;
std::cout << "\nRender complete!" << std::endl;
return 0;
}

View File

@@ -71,13 +71,38 @@ public:
bool active;
bool visible;
// --- NEW ACCUMULATION FIELDS ---
Eigen::Vector3f accumColorSum; // Sum of all samples currently in the "bucket"
float accumWeight; // Current number of samples in the "bucket"
int lastUpdateFrame; // Frame number this node was last hit
mutable std::mutex lightMutex; // Thread safety
// -------------------------------
NodeData(const T& data, const PointType& pos, bool visible, IndexType colorIdx, float size = 0.01f,
bool active = true, int objectId = -1, int subId = 0, IndexType materialIdx = 0)
: data(data), position(pos), objectId(objectId), subId(subId), size(size),
colorIdx(colorIdx), materialIdx(materialIdx), active(active), visible(visible) {}
colorIdx(colorIdx), materialIdx(materialIdx), active(active), visible(visible),
accumColorSum(Eigen::Vector3f::Zero()), accumWeight(0.0f), lastUpdateFrame(-1) {}
NodeData() : objectId(-1), subId(0), size(0.0f), colorIdx(0), materialIdx(0),
active(false), visible(false) {}
active(false), visible(false),
accumColorSum(Eigen::Vector3f::Zero()), accumWeight(0.0f), lastUpdateFrame(-1) {}
// Manual copy constructor needed because std::mutex is not copyable
NodeData(const NodeData& other) {
data = other.data;
position = other.position;
objectId = other.objectId;
subId = other.subId;
size = other.size;
colorIdx = other.colorIdx;
materialIdx = other.materialIdx;
active = other.active;
visible = other.visible;
accumColorSum = Eigen::Vector3f::Zero();
accumWeight = 0.0f;
lastUpdateFrame = -1;
}
PointType getHalfSize() const {
return PointType(size * 0.5f, size * 0.5f, size * 0.5f);
@@ -128,6 +153,11 @@ private:
Eigen::Vector3f skylight_ = {0.1f, 0.1f, 0.1f};
Eigen::Vector3f backgroundColor_ = {0.53f, 0.81f, 0.92f};
// Parallel Lightmap (Sun) settings
Eigen::Vector3f sunDirection_ = {0.0f, 1.0f, 0.0f}; // Default straight up
Eigen::Vector3f sunColor_ = {1.0f, 1.0f, 0.9f};
float sunIntensity_ = 1.0f; // 0.0 = disabled
// Addressable Maps
std::unique_ptr<std::mutex> mapMutex_;
std::vector<Eigen::Vector3f> colorMap_;
@@ -140,6 +170,9 @@ private:
std::set<std::pair<int, int>> dirtyMeshes_;
int nextSubIdGenerator_ = 1;
// Temporal Coherence
int frameCount_ = 0;
public:
inline IndexType getColorIndex(const Eigen::Vector3f& color) {
std::lock_guard<std::mutex> lock(*mapMutex_);
@@ -254,11 +287,6 @@ private:
uint8_t getOctant(const PointType& point, const PointType& center) const {
return (point[0] >= center[0]) | ((point[1] >= center[1]) << 1) | ((point[2] >= center[2]) << 2);
// uint8_t octant = 0;
// if (point[0] >= center[0]) octant |= 1;
// if (point[1] >= center[1]) octant |= 2;
// if (point[2] >= center[2]) octant |= 4;
// return octant;
}
BoundingBox createChildBounds(const OctreeNode* node, uint8_t octant) const {
@@ -677,26 +705,52 @@ private:
int maxBounces, bool globalIllumination, bool useLod) {
if (bounces > maxBounces) return globalIllumination ? skylight_ : Eigen::Vector3f::Zero();
auto hit = voxelTraverse(rayOrig, rayDir, std::numeric_limits<float>::max(), useLod);
PointType currOrig = rayOrig;
std::shared_ptr<NodeData> hit = nullptr;
PointType hitPoint;
PointType normal;
float t = 0.0f;
// Loop to skip internal boundaries of transmissive objects
int safety = 0;
while(safety++ < 5000) {
hit = voxelTraverse(currOrig, rayDir, std::numeric_limits<float>::max(), useLod);
if (!hit) break;
Ray ray(currOrig, rayDir);
rayCubeIntersect(ray, hit.get(), t, normal, hitPoint);
Material objMat = getMaterial(hit->materialIdx);
if (objMat.transmission > 0.0f && rayDir.dot(normal) > 0.0f) {
float coordMax = hitPoint.cwiseAbs().maxCoeff();
float rayOffset = std::max(1e-4f, 1e-5f * coordMax);
PointType checkPos = hitPoint + rayDir * (rayOffset * 2.0f);
auto nextNode = find(checkPos);
if (nextNode && nextNode->active && nextNode->materialIdx == hit->materialIdx) {
currOrig = checkPos;
continue;
}
}
break;
}
if (!hit) {
if (bounces == 0) return backgroundColor_;
return globalIllumination ? skylight_ : Eigen::Vector3f::Zero();
}
auto obj = hit;
PointType hitPoint;
PointType normal;
float t = 0.0f;
Ray ray(rayOrig, rayDir);
rayCubeIntersect(ray, obj.get(), t, normal, hitPoint);
Eigen::Vector3f calculatedColor = Eigen::Vector3f::Zero();
Eigen::Vector3f objColor = getColor(obj->colorIdx);
Material objMat = getMaterial(obj->materialIdx);
Eigen::Vector3f finalColor = globalIllumination ? skylight_ : Eigen::Vector3f::Zero();
Eigen::Vector3f lighting = globalIllumination ? skylight_ : Eigen::Vector3f::Zero();
if (objMat.emittance > 0.0f) {
finalColor += objColor * objMat.emittance;
lighting += objColor * objMat.emittance;
}
float roughness = std::clamp(objMat.roughness, 0.01f, 1.0f);
@@ -715,6 +769,36 @@ private:
Eigen::Vector3f F0 = Eigen::Vector3f::Constant(0.04f);
F0 = F0 * (1.0f - metallic) + objColor * metallic;
if (sunIntensity_ > 0.0f) {
PointType L = sunDirection_.normalized();
PointType shadowOrig = hitPoint + n_eff * rayOffset;
auto shadowHit = voxelTraverse(shadowOrig, L, std::numeric_limits<float>::max(), useLod);
if (!shadowHit) {
float NdotL = std::max(0.001f, n_eff.dot(L));
PointType H_sun = (V + L).normalized();
float VdotH_sun = std::max(0.001f, V.dot(H_sun));
float NdotH_sun = std::max(0.001f, n_eff.dot(H_sun));
Eigen::Vector3f F_sun = F0 + (Eigen::Vector3f::Constant(1.0f) - F0) * std::pow(std::max(0.0f, 1.0f - VdotH_sun), 5.0f);
float alpha2 = roughness * roughness * roughness * roughness;
float denom = (NdotH_sun * NdotH_sun * (alpha2 - 1.0f) + 1.0f);
float D = alpha2 / (M_PI * denom * denom);
float k = (roughness + 1.0f) * (roughness + 1.0f) / 8.0f;
float G = (NdotL / (NdotL * (1.0f - k) + k)) * (cosThetaI / (cosThetaI * (1.0f - k) + k));
Eigen::Vector3f spec = (F_sun * D * G) / (4.0f * NdotL * cosThetaI + EPSILON);
Eigen::Vector3f kD = (Eigen::Vector3f::Constant(1.0f) - F_sun) * (1.0f - metallic);
Eigen::Vector3f diff = kD.cwiseProduct(objColor) / M_PI;
lighting += (diff + spec).cwiseProduct(sunColor_) * sunIntensity_ * NdotL;
}
}
PointType H = sampleGGX(n_eff, roughness, rngState);
float VdotH = std::max(0.001f, V.dot(H));
@@ -780,22 +864,74 @@ private:
secondColor = W_second.cwiseProduct(traceRay(secondOrigin, secondDir, bounces + 1, rngState, maxBounces, globalIllumination, useLod));
}
return finalColor + specColor + secondColor;
calculatedColor = lighting + specColor + secondColor;
} else {
float totalLum = lumSpec + lumSecond;
if (totalLum < 0.0001f) return finalColor;
if (totalLum < 0.0001f) calculatedColor = lighting;
else {
float pSpec = lumSpec / totalLum;
float roll = float(rand_r(&rngState)) / float(RAND_MAX);
float pSpec = lumSpec / totalLum;
float roll = float(rand_r(&rngState)) / float(RAND_MAX);
if (roll < pSpec) {
Eigen::Vector3f sample = traceRay(hitPoint + n_eff * rayOffset, specDir, bounces + 1, rngState, maxBounces, globalIllumination, useLod);
return finalColor + (W_spec / std::max(EPSILON, pSpec)).cwiseProduct(sample);
} else {
Eigen::Vector3f sample = traceRay(secondOrigin, secondDir, bounces + 1, rngState, maxBounces, globalIllumination, useLod);
return finalColor + (W_second / std::max(EPSILON, 1.0f - pSpec)).cwiseProduct(sample);
if (roll < pSpec) {
Eigen::Vector3f sample = traceRay(hitPoint + n_eff * rayOffset, specDir, bounces + 1, rngState, maxBounces, globalIllumination, useLod);
calculatedColor = lighting + (W_spec / std::max(EPSILON, pSpec)).cwiseProduct(sample);
} else {
Eigen::Vector3f sample = traceRay(secondOrigin, secondDir, bounces + 1, rngState, maxBounces, globalIllumination, useLod);
calculatedColor = lighting + (W_second / std::max(EPSILON, 1.0f - pSpec)).cwiseProduct(sample);
}
}
}
std::lock_guard<std::mutex> lock(obj->lightMutex);
// 1. Calculate Capacity based on Material Properties
// How many frames do we keep in the "bucket" before removing the oldest?
float capacity = 60.0f; // Base capacity (e.g. Diffuse surfaces)
// REFLECTIONS: Fade out VERY fast (low capacity) to prevent ghosting on moving objects
// If it's metallic and smooth, we might only keep the last 2-5 frames.
float reflectionIntensity = metallic * (1.0f - roughness);
if (reflectionIntensity > 0.0f) {
// Reduces capacity down to a minimum of 2.0 frames for perfect mirrors
capacity = std::max(2.0f, capacity * (1.0f - reflectionIntensity));
}
// REFRACTIONS: Keep longer (high capacity) to stabilize noise
if (transmission > 0.0f) {
capacity += transmission * 40.0f;
}
// 2. Handle Time Gaps (Age Limit)
// If this node wasn't hit last frame, the object or camera moved.
// Decay aggressively.
if (obj->lastUpdateFrame != -1) {
int framesMissed = frameCount_ - obj->lastUpdateFrame;
if (framesMissed > 1) {
// Divide accumulated weight by 2 for every missed frame
float decay = std::pow(0.5f, (float)framesMissed);
obj->accumColorSum *= decay;
obj->accumWeight *= decay;
}
}
// 3. The "Leaky Bucket" Eviction
// If the bucket is full (weight >= capacity), we "pour out" the average old value
// before adding the new one.
if (obj->accumWeight >= capacity) {
// Scale down the sum and weight so that adding 1.0 brings it back to capacity
float keepRatio = (capacity - 1.0f) / obj->accumWeight;
obj->accumColorSum *= keepRatio;
obj->accumWeight *= keepRatio;
}
// 4. Accumulate
obj->accumColorSum += calculatedColor;
obj->accumWeight += 1.0f;
obj->lastUpdateFrame = frameCount_;
// 5. Return the Average
// This average represents the last 'capacity' frames moving average
return obj->accumColorSum / obj->accumWeight;
}
void clearNode(OctreeNode* node) {
@@ -923,6 +1059,7 @@ private:
writeVal(out, pt->size);
writeVal(out, pt->colorIdx);
writeVal(out, pt->materialIdx);
// accumulatedLight is transient cache, do not serialize
}
if (!node->isLeaf) {
@@ -964,6 +1101,8 @@ private:
readVal(in, pt->size);
readVal(in, pt->colorIdx);
readVal(in, pt->materialIdx);
pt->accumulatedLight = Eigen::Vector3f::Zero(); // Initialize clean
pt->lastUpdateFrame = -1;
node->points.push_back(pt);
}
@@ -1244,7 +1383,7 @@ private:
}
}
if (totalWeight > 1e-6) {
if (totalWeight > EPSILON) {
return {density, accumulatedColor / totalWeight};
}
@@ -1311,6 +1450,12 @@ public:
return backgroundColor_;
}
void setSun(const Eigen::Vector3f& direction, const Eigen::Vector3f& color, float intensity) {
sunDirection_ = direction.normalized();
sunColor_ = color;
sunIntensity_ = intensity;
}
void setLODFalloff(float rate) { lodFalloffRate_ = rate; }
void setLODMinDistance(float dist) { lodMinDistance_ = dist; }
void setMaxDistance(float dist) { maxDistance_ = dist; }
@@ -1353,6 +1498,10 @@ public:
writeVec3(out, skylight_);
writeVec3(out, backgroundColor_);
writeVec3(out, sunDirection_);
writeVec3(out, sunColor_);
writeVal(out, sunIntensity_);
{
std::lock_guard<std::mutex> lock(*mapMutex_);
size_t cMapSize = colorMap_.size();
@@ -1400,6 +1549,10 @@ public:
readVec3(in, skylight_);
readVec3(in, backgroundColor_);
readVec3(in, sunDirection_);
readVec3(in, sunColor_);
readVal(in, sunIntensity_);
{
std::lock_guard<std::mutex> lock(*mapMutex_);
colorMap_.clear();
@@ -1659,6 +1812,7 @@ public:
frame renderFrame(const Camera& cam, int height, int width, frame::colormap colorformat = frame::colormap::RGB, int samplesPerPixel = 2,
int maxBounces = 4, bool globalIllumination = false, bool useLod = true) {
frameCount_++; // Advance time
generateLODs();
PointType origin = cam.origin;
PointType dir = cam.direction.normalized();
@@ -1680,7 +1834,7 @@ public:
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
int pidx = (y * width + x);
uint32_t seed = pidx * 1973 + 9277;
uint32_t seed = pidx * 1973 + 9277 + frameCount_ * 12345;
int idx = pidx * channels;
float px = (2.0f * (x + 0.5f) / width - 1.0f) * tanfovx;
@@ -1724,6 +1878,7 @@ public:
frame fastRenderFrame(const Camera& cam, int height, int width, frame::colormap colorformat = frame::colormap::RGB) {
//TIME_FUNCTION;
frameCount_++;
generateLODs();
PointType origin = cam.origin;
PointType dir = cam.direction.normalized();
@@ -1740,7 +1895,7 @@ public:
const float tanfovy = tanHalfFov;
const float tanfovx = tanHalfFov * aspect;
const PointType globalLightDir = (-cam.direction * 0.2f).normalized();
const PointType globalLightDir = sunDirection_.normalized();
const float fogStart = 1000.0f;
const float minVisibility = 0.2f;
@@ -1772,9 +1927,12 @@ public:
if (objMat.emittance > 0.0f) {
color = color * objMat.emittance;
} else {
// Use Sun Direction for quick shading
float diffuse = std::max(0.0f, normal.dot(globalLightDir));
float ambient = 0.35f;
float intensity = std::min(1.0f, ambient + diffuse * 0.65f);
if (sunIntensity_ > 0.0f) intensity = std::min(1.0f, ambient + diffuse * sunIntensity_);
color = color * intensity;
}
@@ -1798,6 +1956,7 @@ public:
frame renderFrameTimed(const Camera& cam, int height, int width, frame::colormap colorformat = frame::colormap::RGB,
double maxTimeSeconds = 0.16, int maxBounces = 4, bool globalIllumination = false, bool useLod = true) {
auto startTime = std::chrono::high_resolution_clock::now();
frameCount_++;
generateLODs();
PointType origin = cam.origin;
@@ -1816,7 +1975,7 @@ public:
const float tanfovy = tanHalfFov;
const float tanfovx = tanHalfFov * aspect;
const PointType globalLightDir = (-cam.direction * 0.2f).normalized();
const PointType globalLightDir = sunDirection_.normalized();
const float fogStart = 1000.0f;
const float minVisibility = 0.2f;
@@ -1850,6 +2009,7 @@ public:
float diffuse = std::max(0.0f, normal.dot(globalLightDir));
float ambient = 0.35f;
float intensity = std::min(1.0f, ambient + diffuse * 0.65f);
if (sunIntensity_ > 0.0f) intensity = std::min(1.0f, ambient + diffuse * sunIntensity_);
color = color * intensity;
}
@@ -1910,7 +2070,7 @@ public:
rayDir.normalize();
uint32_t pass = currentOffset / totalPixels;
uint32_t seed = pidx * 1973 + pass * 12345 + localSeed;
uint32_t seed = pidx * 1973 + pass * 12345 + localSeed + frameCount_ * 777;
Eigen::Vector3f pbrColor = traceRay(origin, rayDir, 0, seed, maxBounces, globalIllumination, useLod);
@@ -2279,6 +2439,7 @@ public:
}
size = 0;
frameCount_ = 0;
}
};