void Mesh::addChild(NoriObject *obj) { switch (obj->getClassType()) { case EBSDF: if (m_bsdf) throw NoriException("Mesh: tried to register multiple BSDF instances!"); m_bsdf = static_cast<BSDF *>(obj); break; default: throw NoriException(QString("Mesh::addChild(<%1>) is not supported!").arg( classTypeName(obj->getClassType()))); } }
void PerlinTexture::addChild(NoriObject *obj) { switch (obj->getClassType()) { case ESampler: if (m_sampler) throw NoriException("There can only be one sampler per scene!"); m_sampler = static_cast<Sampler *>(obj); createTexture(); break; default: throw NoriException("Scene::addChild(<%s>) is not supported!", classTypeName(obj->getClassType())); } }
HeterogeneousMedium(const PropertyList &propList) { // Denotes the scattering albedo m_albedo = propList.getColor("albedo"); // An (optional) transformation that converts between medium and world coordinates m_worldToMedium = propList.getTransform("toWorld", Transform()).inverse(); // Optional multiplicative factor that will be applied to all density values in the file m_densityMultiplier = propList.getFloat("densityMultiplier", 1.0f); m_filename = propList.getString("filename"); QByteArray filename = m_filename.toLocal8Bit(); QFile file(m_filename); if (!file.exists()) throw NoriException(QString("The file \"%1\" does not exist!").arg(m_filename)); /* Parse the file header */ file.open(QIODevice::ReadOnly); QDataStream stream(&file); stream.setByteOrder(QDataStream::LittleEndian); qint8 header[3], version; qint32 type; stream >> header[0] >> header[1] >> header[2] >> version >> type; if (memcmp(header, "VOL", 3) != 0 || version != 3) throw NoriException("This is not a valid volume data file!"); stream >> m_resolution.x() >> m_resolution.y() >> m_resolution.z(); file.close(); cout << "Mapping \"" << filename.data() << "\" (" << m_resolution.x() << "x" << m_resolution.y() << "x" << m_resolution.z() << ") into memory .." << endl; m_fileSize = (size_t) file.size(); #if defined(PLATFORM_LINUX) || defined(PLATFORM_MACOS) int fd = open(filename.data(), O_RDONLY); if (fd == -1) throw NoriException(QString("Could not open \"%1\"!").arg(m_filename)); m_data = (float *) mmap(NULL, m_fileSize, PROT_READ, MAP_SHARED, fd, 0); if (m_data == NULL) throw NoriException("mmap(): failed."); if (close(fd) != 0) throw NoriException("close(): unable to close file descriptor!"); #elif defined(PLATFORM_WINDOWS) m_file = CreateFileA(filename.data(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (m_file == INVALID_HANDLE_VALUE) throw NoriException(QString("Could not open \"%1\"!").arg(m_filename)); m_fileMapping = CreateFileMapping(m_file, NULL, PAGE_READONLY, 0, 0, NULL); if (m_fileMapping == NULL) throw NoriException("CreateFileMapping(): failed."); m_data = (float *) MapViewOfFile(m_fileMapping, FILE_MAP_READ, 0, 0, 0); if (m_data == NULL) throw NoriException("MapViewOfFile(): failed."); #endif m_data += 12; // Shift past the header }
NORI_NAMESPACE_BEGIN void NoriObject::addChild(NoriObject *) { throw NoriException(QString("NoriObject::addChild() is not " "implemented for objects of type '%1'!").arg( classTypeName(getClassType()))); }
NORI_NAMESPACE_BEGIN void NoriObject::addChild(NoriObject *) { throw NoriException( "NoriObject::addChild() is not implemented for objects of type '%s'!", classTypeName(getClassType())); }
WavefrontOBJ(const PropertyList &propList) { typedef boost::unordered_map<OBJVertex, uint32_t, OBJVertexHash> VertexMap; /* Process the OBJ-file line by line */ QString filename = propList.getString("filename"); QFile input(filename); if (!input.open(QIODevice::ReadOnly | QIODevice::Text)) throw NoriException(QString("Cannot open \"%1\"").arg(filename)); Transform trafo = propList.getTransform("toWorld", Transform()); cout << "Loading \"" << qPrintable(filename) << "\" .." << endl; m_name = filename; QTextStream stream(&input); QTextStream line; QString temp, prefix; std::vector<Point3f> positions; std::vector<Point2f> texcoords; std::vector<Normal3f> normals; std::vector<uint32_t> indices; std::vector<OBJVertex> vertices; VertexMap vertexMap; while (!(temp = stream.readLine()).isNull()) { line.setString(&temp); line >> prefix; if (prefix == "v") { Point3f p; line >> p.x() >> p.y() >> p.z(); p = trafo * p; positions.push_back(p); } else if (prefix == "vt") {
void Mesh::addChild(NoriObject *obj) { switch (obj->getClassType()) { case EBSDF: if (m_bsdf) throw NoriException( "Mesh: tried to register multiple BSDF instances!"); m_bsdf = static_cast<BSDF *>(obj); break; case ETEXTURE: if (m_texture) throw NoriException( "Mesh: tried to register multiple texture instances!"); m_texture = static_cast<Texture *>(obj); break; case EBUMPMAP: if (m_bumpmap) throw NoriException( "Mesh: tried to register multiple bumpmap instances!"); m_bumpmap = static_cast<BumpTexture *>(obj); break; case EMIXTEXTURE: if (m_texture) throw NoriException( "Mesh: tried to register multiple texture instances!"); m_texture = static_cast<Texture *>(obj); break; case EMIXBUMPMAP: if (m_bumpmap) throw NoriException( "Mesh: tried to register multiple bumpmap instances!"); m_bumpmap = static_cast<BumpTexture *>(obj); break; case EEmitter: { Emitter *emitter = static_cast<Emitter *>(obj); if (m_emitter) throw NoriException( "Mesh: tried to register multiple Emitter instances!"); m_emitter = emitter; } break; case EMedium: { Medium *media = static_cast<Medium *>(obj); if (m_medium) throw NoriException( "Mesh: tried to register multiple medium instances!"); m_medium = media; } break; default: throw NoriException("Mesh::addChild(<%s>) is not supported!", classTypeName(obj->getClassType())); } }
virtual ~HeterogeneousMedium() { if (m_data) { m_data -= 12; cout << "Unmapping \"" << qPrintable(m_filename) << "\" from memory.." << endl; #if defined(PLATFORM_LINUX) || defined(PLATFORM_MACOS) int retval = munmap(m_data, m_fileSize); if (retval != 0) throw NoriException("munmap(): unable to unmap memory!"); #elif defined(PLATFORM_WINDOWS) if (!UnmapViewOfFile(m_data)) throw NoriException("UnmapViewOfFile(): unable to unmap memory region"); if (!CloseHandle(m_fileMapping)) throw NoriException("CloseHandle(): unable to close file mapping!"); if (!CloseHandle(m_file)) throw NoriException("CloseHandle(): unable to close file"); #endif } }
/** * \brief Directly sample the lights, providing a sample weighted by 1/pdf * where pdf is the probability of sampling that given sample * * \param scene * the scene to work with * * \param lRec * the luminaire information storage * * \param _sample * the 2d uniform sample * * \return the sampled light radiance including its geometric, visibility and pdf weights */ inline Color3f sampleLights(const Scene *scene, LuminaireQueryRecord &lRec, const Point2f &_sample) const { Point2f sample(_sample); const std::vector<Luminaire *> &luminaires = scene->getLuminaires(); if (luminaires.size() == 0) throw NoriException("LightIntegrator::sampleLights(): No luminaires were defined!"); // TODO Implement the following steps // and take care of using the good G, V terms to work with the Li method below // 1. Choose one luminaire at random lRec.luminaire = luminaires[rand() % (luminaires.size())]; // Tirage pas tout a fait uniforme (mais ca suffit je pense) // 2. Sample the position on the luminaire mesh // using Mesh::samplePosition(const Point2d &sample, Point3f &p, Normal3f &n) const Mesh* lum_mesh=getMesh(lRec.luminaire); lum_mesh->samplePosition(sample, lRec.p, lRec.n); lRec.d = lRec.p - lRec.ref; // Vecteur vers la source de lumiere lRec.dist = lRec.d.norm(); // Distance lRec.d /= lRec.dist; // Direction // 3. Compute geometry term G and visibility term on the luminaire's side (no cos(w) of the mesh side) // as well as the pdf of that point being found // use Mesh::pdf to get the probability of choosing the point in Mesh::samplePosition // Pdf lRec.pdf = lum_mesh->pdf(); // Visibility Intersection its; Ray3f ray(lRec.ref, lRec.d); float V = 1.0f; // N'arrive jamais a priori if(!scene->rayIntersect(ray, its)) V = 0.0f; // Permet de se debarrasser de la comparaison de float. // Marche si les sources de lumieres sont convexes (ce qui est le cas ^^) grace au test de direction ci-apres (dans G). else if (its.mesh != lum_mesh) V = 0.0f; // Geometry float G = std::max(0.0f, -(lRec.d).dot(lRec.n)/(lRec.dist*lRec.dist)); // max(0, ...) pour annuler un eclairage dans la mauvaise direction // 4. Return radiance emitted from luminaire multiplied by the appropriate terms G, V ... return lRec.luminaire->getColor()*V*G/lRec.pdf; // Il manque juste le cos(theta') et la BRDF }
NORI_NAMESPACE_BEGIN Bitmap::Bitmap(const std::string &filename) { Imf::InputFile file(filename.c_str()); const Imf::Header &header = file.header(); const Imf::ChannelList &channels = header.channels(); Imath::Box2i dw = file.header().dataWindow(); resize(dw.max.y - dw.min.y + 1, dw.max.x - dw.min.x + 1); cout << "Reading a " << cols() << "x" << rows() << " OpenEXR file from \"" << filename << "\"" << endl; const char *ch_r = nullptr, *ch_g = nullptr, *ch_b = nullptr; for (Imf::ChannelList::ConstIterator it = channels.begin(); it != channels.end(); ++it) { std::string name = toLower(it.name()); if (it.channel().xSampling != 1 || it.channel().ySampling != 1) { /* Sub-sampled layers are not supported */ continue; } if (!ch_r && (name == "r" || name == "red" || endsWith(name, ".r") || endsWith(name, ".red"))) { ch_r = it.name(); } else if (!ch_g && (name == "g" || name == "green" || endsWith(name, ".g") || endsWith(name, ".green"))) { ch_g = it.name(); } else if (!ch_b && (name == "b" || name == "blue" || endsWith(name, ".b") || endsWith(name, ".blue"))) { ch_b = it.name(); } } if (!ch_r || !ch_g || !ch_b) throw NoriException("This is not a standard RGB OpenEXR file!"); size_t compStride = sizeof(float), pixelStride = 3 * compStride, rowStride = pixelStride * cols(); char *ptr = reinterpret_cast<char *>(data()); Imf::FrameBuffer frameBuffer; frameBuffer.insert(ch_r, Imf::Slice(Imf::FLOAT, ptr, pixelStride, rowStride)); ptr += compStride; frameBuffer.insert(ch_g, Imf::Slice(Imf::FLOAT, ptr, pixelStride, rowStride)); ptr += compStride; frameBuffer.insert(ch_b, Imf::Slice(Imf::FLOAT, ptr, pixelStride, rowStride)); file.setFrameBuffer(frameBuffer); file.readPixels(dw.min.y, dw.max.y); m_totalLuminance = getTotalLuminace(); }
void Mesh::addChild(NoriObject *obj) { switch (obj->getClassType()) { case EBSDF: if (m_bsdf) throw NoriException("Mesh: tried to register multiple BSDF instances!"); m_bsdf = static_cast<BSDF *>(obj); break; case ELuminaire: if (m_luminaire) throw NoriException("Mesh: tried to register multiple luminaire instances!"); m_luminaire = static_cast<Luminaire*>(obj); m_luminaire -> setMesh(this); break; case ETEXTURE: if (m_texture) throw NoriException("Mesh: tried to register multiple texture instances!"); m_texture = static_cast<Texture*>(obj); break; default: throw NoriException(QString("Mesh::addChild(<%1>) is not supported!").arg( classTypeName(obj->getClassType()))); } }
void addChild(NoriObject *obj) { switch (obj->getClassType()) { case EBSDF: m_bsdfs.push_back(static_cast<BSDF *>(obj)); break; case EScene: m_scenes.push_back(static_cast<Scene *>(obj)); break; default: throw NoriException("StudentsTTest::addChild(<%s>) is not supported!", classTypeName(obj->getClassType())); } }
Color3f evalTransmittance(const Ray3f &_ray, Sampler *sampler) const { /* Transform the ray into the local coordinate system */ Ray3f ray = m_worldToMedium * _ray; throw NoriException("HeterogeneousMedium::evalTransmittance(): not implemented!"); }
/// Return the mesh corresponding to a given luminaire inline const Mesh *getMesh(const Luminaire *lum) const { const Mesh *mesh = dynamic_cast<const Mesh *> (lum->getParent()); if (!mesh) throw NoriException("Unhandled type of luminaire!"); return mesh; }
NORI_NAMESPACE_BEGIN NoriObject *loadFromXML(const std::string &filename) { /* Load the XML file using 'pugi' (a tiny self-contained XML parser implemented in C++) */ pugi::xml_document doc; pugi::xml_parse_result result = doc.load_file(filename.c_str()); /* Helper function: map a position offset in bytes to a more readable row/column value */ auto offset = [&](ptrdiff_t pos) -> std::string { std::fstream is(filename); char buffer[1024]; int line = 0, linestart = 0, offset = 0; while (is.good()) { is.read(buffer, sizeof(buffer)); for (int i = 0; i < is.gcount(); ++i) { if (buffer[i] == '\n') { if (offset + i >= pos) return tfm::format("row %i, col %i", line + 1, pos - linestart); ++line; linestart = offset + i; } } offset += (int) is.gcount(); } return "byte offset " + std::to_string(pos); }; if (!result) /* There was a parser / file IO error */ throw NoriException("Error while parsing \"%s\": %s (at %s)", filename, result.description(), offset(result.offset)); /* Set of supported XML tags */ enum ETag { /* Object classes */ EScene = NoriObject::EScene, EMesh = NoriObject::EMesh, EBSDF = NoriObject::EBSDF, ETEXTURE = NoriObject::ETEXTURE, EPERLIN = NoriObject::EPERLIN, EMIXTEXTURE = NoriObject::EMIXTEXTURE, EMIXBUMPMAP = NoriObject::EMIXBUMPMAP, EBUMPMAP = NoriObject::EBUMPMAP, EPhaseFunction = NoriObject::EPhaseFunction, EEmitter = NoriObject::EEmitter, EMedium = NoriObject::EMedium, EVolume = NoriObject::EVolume, ECamera = NoriObject::ECamera, EIntegrator = NoriObject::EIntegrator, ESampler = NoriObject::ESampler, ETest = NoriObject::ETest, EReconstructionFilter = NoriObject::EReconstructionFilter, /* Properties */ EBoolean = NoriObject::EClassTypeCount, EInteger, EFloat, EString, EPoint, EVector, EColor, ETransform, ETranslate, EMatrix, ERotate, EScale, ELookAt, EInvalid }; /* Create a mapping from tag names to tag IDs */ std::map<std::string, ETag> tags; tags["scene"] = EScene; tags["mesh"] = EMesh; tags["bsdf"] = EBSDF; tags["texture"] = ETEXTURE; tags["bumpmap"] = EBUMPMAP; tags["perlin"] = EPERLIN; tags["mixTexture"] = EMIXTEXTURE; tags["mixBumpmap"] = EMIXBUMPMAP; tags["bumpmap"] = EBUMPMAP; tags["emitter"] = EEmitter; tags["camera"] = ECamera; tags["medium"] = EMedium; tags["volume"] = EVolume; tags["phase"] = EPhaseFunction; tags["integrator"] = EIntegrator; tags["sampler"] = ESampler; tags["rfilter"] = EReconstructionFilter; tags["test"] = ETest; tags["boolean"] = EBoolean; tags["integer"] = EInteger; tags["float"] = EFloat; tags["string"] = EString; tags["point"] = EPoint; tags["vector"] = EVector; tags["color"] = EColor; tags["transform"] = ETransform; tags["translate"] = ETranslate; tags["matrix"] = EMatrix; tags["rotate"] = ERotate; tags["scale"] = EScale; tags["lookat"] = ELookAt; /* Helper function to check if attributes are fully specified */ auto check_attributes = [&](const pugi::xml_node &node, std::set<std::string> attrs) { for (auto attr : node.attributes()) { auto it = attrs.find(attr.name()); if (it == attrs.end()) throw NoriException("Error while parsing \"%s\": unexpected attribute \"%s\" in \"%s\" at %s", filename, attr.name(), node.name(), offset(node.offset_debug())); attrs.erase(it); } if (!attrs.empty()) throw NoriException("Error while parsing \"%s\": missing attribute \"%s\" in \"%s\" at %s", filename, *attrs.begin(), node.name(), offset(node.offset_debug())); }; Eigen::Affine3f transform; /* Helper function to parse a Nori XML node (recursive) */ std::function<NoriObject *(pugi::xml_node &, PropertyList &, int)> parseTag = [&]( pugi::xml_node &node, PropertyList &list, int parentTag) -> NoriObject * { /* Skip over comments */ if (node.type() == pugi::node_comment || node.type() == pugi::node_declaration) return nullptr; if (node.type() != pugi::node_element) throw NoriException( "Error while parsing \"%s\": unexpected content at %s", filename, offset(node.offset_debug())); /* Look up the name of the current element */ auto it = tags.find(node.name()); if (it == tags.end()) throw NoriException("Error while parsing \"%s\": unexpected tag \"%s\" at %s", filename, node.name(), offset(node.offset_debug())); int tag = it->second; /* Perform some safety checks to make sure that the XML tree really makes sense */ bool hasParent = parentTag != EInvalid; bool parentIsObject = hasParent && parentTag < NoriObject::EClassTypeCount; bool currentIsObject = tag < NoriObject::EClassTypeCount; bool parentIsTransform = parentTag == ETransform; bool currentIsTransformOp = tag == ETranslate || tag == ERotate || tag == EScale || tag == ELookAt || tag == EMatrix; if (!hasParent && !currentIsObject) throw NoriException("Error while parsing \"%s\": root element \"%s\" must be a Nori object (at %s)", filename, node.name(), offset(node.offset_debug())); if (parentIsTransform != currentIsTransformOp) throw NoriException("Error while parsing \"%s\": transform nodes " "can only contain transform operations (at %s)", filename, offset(node.offset_debug())); if (hasParent && !parentIsObject && !(parentIsTransform && currentIsTransformOp)) throw NoriException("Error while parsing \"%s\": node \"%s\" requires a Nori object as parent (at %s)", filename, node.name(), offset(node.offset_debug())); if (tag == EScene) node.append_attribute("type") = "scene"; else if (tag == ETransform) transform.setIdentity(); PropertyList propList; std::vector<NoriObject *> children; for (pugi::xml_node &ch: node.children()) { NoriObject *child = parseTag(ch, propList, tag); if (child) children.push_back(child); } NoriObject *result = nullptr; try { if (currentIsObject) { check_attributes(node, { "type" }); /* This is an object, first instantiate it */ result = NoriObjectFactory::createInstance( node.attribute("type").value(), propList ); if (result->getClassType() != (int) tag) { throw NoriException( "Unexpectedly constructed an object " "of type <%s> (expected type <%s>): %s", NoriObject::classTypeName(result->getClassType()), NoriObject::classTypeName((NoriObject::EClassType) tag), result->toString()); } /* Add all children */ for (auto ch: children) { result->addChild(ch); ch->setParent(result); } /* Activate / configure the object */ result->activate(); } else { /* This is a property */ switch (tag) { case EString: { check_attributes(node, { "name", "value" }); list.setString(node.attribute("name").value(), node.attribute("value").value()); } break; case EFloat: { check_attributes(node, { "name", "value" }); list.setFloat(node.attribute("name").value(), toFloat(node.attribute("value").value())); } break; case EInteger: { check_attributes(node, { "name", "value" }); list.setInteger(node.attribute("name").value(), toInt(node.attribute("value").value())); } break; case EBoolean: { check_attributes(node, { "name", "value" }); list.setBoolean(node.attribute("name").value(), toBool(node.attribute("value").value())); } break; case EPoint: { check_attributes(node, { "name", "value" }); list.setPoint(node.attribute("name").value(), Point3f(toVector3f(node.attribute("value").value()))); } break; case EVector: { check_attributes(node, { "name", "value" }); list.setVector(node.attribute("name").value(), Vector3f(toVector3f(node.attribute("value").value()))); } break; case EColor: { check_attributes(node, { "name", "value" }); list.setColor(node.attribute("name").value(), Color3f(toVector3f(node.attribute("value").value()).array())); } break; case ETransform: { check_attributes(node, { "name" }); list.setTransform(node.attribute("name").value(), transform.matrix()); } break; case ETranslate: { check_attributes(node, { "value" }); Eigen::Vector3f v = toVector3f(node.attribute("value").value()); transform = Eigen::Translation<float, 3>(v.x(), v.y(), v.z()) * transform; } break; case EMatrix: { check_attributes(node, { "value" }); std::vector<std::string> tokens = tokenize(node.attribute("value").value()); if (tokens.size() != 16) throw NoriException("Expected 16 values"); Eigen::Matrix4f matrix; for (int i=0; i<4; ++i) for (int j=0; j<4; ++j) matrix(i, j) = toFloat(tokens[i*4+j]); transform = Eigen::Affine3f(matrix) * transform; } break; case EScale: { check_attributes(node, { "value" }); Eigen::Vector3f v = toVector3f(node.attribute("value").value()); transform = Eigen::DiagonalMatrix<float, 3>(v) * transform; } break; case ERotate: { check_attributes(node, { "angle", "axis" }); float angle = degToRad(toFloat(node.attribute("angle").value())); Eigen::Vector3f axis = toVector3f(node.attribute("axis").value()); transform = Eigen::AngleAxis<float>(angle, axis) * transform; } break; case ELookAt: { check_attributes(node, { "origin", "target", "up" }); Eigen::Vector3f origin = toVector3f(node.attribute("origin").value()); Eigen::Vector3f target = toVector3f(node.attribute("target").value()); Eigen::Vector3f up = toVector3f(node.attribute("up").value()); Vector3f dir = (target - origin).normalized(); Vector3f left = up.normalized().cross(dir); Vector3f newUp = dir.cross(left); Eigen::Matrix4f trafo; trafo << left, newUp, dir, origin, 0, 0, 0, 1; transform = Eigen::Affine3f(trafo) * transform; } break; default: throw NoriException("Unhandled element \"%s\"", node.name()); }; } } catch (const NoriException &e) { throw NoriException("Error while parsing \"%s\": %s (at %s)", filename, e.what(), offset(node.offset_debug())); } return result; }; PropertyList list; return parseTag(*doc.begin(), list, EInvalid); }
/// Sample the BRDF Color3f sample(BSDFQueryRecord &bRec, const Point2f &sample) const { throw NoriException("Uh oh -- Microfacet::sample() is not implemented!"); }
/// Evaluate the sampling density of \ref sample() wrt. solid angles float pdf(const BSDFQueryRecord &bRec) const { throw NoriException("Uh oh -- Microfacet::pdf() is not implemented!"); }
/// Evaluate the BRDF for the given pair of directions Color3f eval(const BSDFQueryRecord &bRec) const { throw NoriException("Uh oh -- Microfacet::eval() is not implemented!"); }
bool sampleDistance(const Ray3f &_ray, Sampler *sampler, float &t, Color3f &weight) const { /* Transform the ray into the local coordinate system */ Ray3f ray = m_worldToMedium * _ray; throw NoriException("HeterogeneousMedium::sampleDistance(): not implemented!"); }
/// Invoke a series of t-tests on the provided input void activate() { int total = 0, passed = 0; pcg32 random; if (!m_bsdfs.empty()) { if (m_references.size() * m_bsdfs.size() != m_angles.size()) throw NoriException("Specified a different number of angles and reference values!"); if (!m_scenes.empty()) throw NoriException("Cannot test BSDFs and scenes at the same time!"); /* Test each registered BSDF */ int ctr = 0; for (auto bsdf : m_bsdfs) { for (size_t i=0; i<m_references.size(); ++i) { float angle = m_angles[i], reference = m_references[ctr++]; cout << "------------------------------------------------------" << endl; cout << "Testing (angle=" << angle << "): " << bsdf->toString() << endl; ++total; BSDFQueryRecord bRec(sphericalDirection(degToRad(angle), 0)); cout << "Drawing " << m_sampleCount << " samples .. " << endl; double mean=0, variance = 0; for (int k=0; k<m_sampleCount; ++k) { Point2f sample(random.nextFloat(), random.nextFloat()); double result = (double) bsdf->sample(bRec, sample).getLuminance(); /* Numerically robust online variance estimation using an algorithm proposed by Donald Knuth (TAOCP vol.2, 3rd ed., p.232) */ double delta = result - mean; mean += delta / (double) (k+1); variance += delta * (result - mean); } variance /= m_sampleCount - 1; std::pair<bool, std::string> result = hypothesis::students_t_test(mean, variance, reference, m_sampleCount, m_significanceLevel, (int) m_references.size()); if (result.first) ++passed; cout << result.second << endl; } } } else { if (m_references.size() != m_scenes.size()) throw NoriException("Specified a different number of scenes and reference values!"); Sampler *sampler = static_cast<Sampler *>( NoriObjectFactory::createInstance("independent", PropertyList())); int ctr = 0; for (auto scene : m_scenes) { const Integrator *integrator = scene->getIntegrator(); const Camera *camera = scene->getCamera(); float reference = m_references[ctr++]; cout << "------------------------------------------------------" << endl; cout << "Testing scene: " << scene->toString() << endl; ++total; cout << "Generating " << m_sampleCount << " paths.. " << endl; double mean = 0, variance = 0; for (int k=0; k<m_sampleCount; ++k) { /* Sample a ray from the camera */ Ray3f ray; Point2f pixelSample = (sampler->next2D().array() * camera->getOutputSize().cast<float>().array()).matrix(); Color3f value = camera->sampleRay(ray, pixelSample, sampler->next2D()); /* Compute the incident radiance */ value *= integrator->Li(scene, sampler, ray); /* Numerically robust online variance estimation using an algorithm proposed by Donald Knuth (TAOCP vol.2, 3rd ed., p.232) */ double result = (double) value.getLuminance(); double delta = result - mean; mean += delta / (double) (k+1); variance += delta * (result - mean); } variance /= m_sampleCount - 1; std::pair<bool, std::string> result = hypothesis::students_t_test(mean, variance, reference, m_sampleCount, m_significanceLevel, (int) m_references.size()); if (result.first) ++passed; cout << result.second << endl; } } cout << "Passed " << passed << "/" << total << " tests." << endl; }