Spectrum FresnelSpecular::Sample_f(const Vector3f &wo, Vector3f *wi, const Point2f &u, Float *pdf, BxDFType *sampledType) const { Float F = FrDielectric(CosTheta(wo), etaA, etaB); if (u[0] < F) { // Compute specular reflection for _FresnelSpecular_ // Compute perfect specular reflection direction *wi = Vector3f(-wo.x, -wo.y, wo.z); if (sampledType) *sampledType = BxDFType(BSDF_SPECULAR | BSDF_REFLECTION); *pdf = F; return F * R / AbsCosTheta(*wi); } else { // Compute specular transmission for _FresnelSpecular_ // Figure out which $\eta$ is incident and which is transmitted bool entering = CosTheta(wo) > 0; Float etaI = entering ? etaA : etaB; Float etaT = entering ? etaB : etaA; // Compute ray direction for specular transmission if (!Refract(wo, Faceforward(Normal3f(0, 0, 1), wo), etaI / etaT, wi)) return 0; Spectrum ft = T * (1 - F); // Account for non-symmetry with transmission to different medium if (mode == TransportMode::Radiance) ft *= (etaI * etaI) / (etaT * etaT); if (sampledType) *sampledType = BxDFType(BSDF_SPECULAR | BSDF_TRANSMISSION); *pdf = 1 - F; return ft / AbsCosTheta(*wi); } }
// SurfaceInteraction Method Definitions SurfaceInteraction::SurfaceInteraction( const Point3f &p, const Vector3f &pError, const Point2f &uv, const Vector3f &wo, const Vector3f &dpdu, const Vector3f &dpdv, const Normal3f &dndu, const Normal3f &dndv, Float time, const Shape *shape) : Interaction(p, Normal3f(Normalize(Cross(dpdu, dpdv))), pError, wo, time, nullptr), uv(uv), dpdu(dpdu), dpdv(dpdv), dndu(dndu), dndv(dndv), shape(shape) { // Initialize shading geometry from true geometry shading.n = n; shading.dpdu = dpdu; shading.dpdv = dpdv; shading.dndu = dndu; shading.dndv = dndv; // Adjust normal based on orientation and handedness if (shape && (shape->reverseOrientation ^ shape->transformSwapsHandedness)) { n *= -1; shading.n *= -1; } }
void ParamSetProxy::AddNormal3f(const std::string& name, std::vector<Float> values) { std::vector<Normal3f> v; for (size_t i = 0; i < values.size() - 2; i+=3) { v.push_back(Normal3f(values[i], values[i + 1], values[i + 2])); } ArrayView<Normal3f>* arrayView = new ArrayView<Normal3f>(v); paramSet->AddNormal3f(name, std::move(arrayView->v), arrayView->size); delete arrayView; }
bool Sphere::Sample(const Point2f &sample, Interaction *it) const { Point3f pObj = Point3f(0, 0, 0) + radius * UniformSampleSphere(sample); it->n = Normalize((*ObjectToWorld)(Normal3f(pObj.x, pObj.y, pObj.z))); if (ReverseOrientation) it->n *= -1.f; Vector3f pObjError = 16.f * MachineEpsilon * Vector3f(radius, radius, radius); it->p = (*ObjectToWorld)(pObj, pObjError, &it->pError); return true; }
bool Disk::Sample(const Point2f &sample, Interaction *it) const { Point2f pd = ConcentricSampleDisk(sample); Point3f pObj(pd.x * radius, pd.y * radius, height); it->n = Normalize((*ObjectToWorld)(Normal3f(0, 0, 1))); if (ReverseOrientation) it->n *= -1.f; Vector3f pObjError(0.f, 0.f, MachineEpsilon * height); it->p = (*ObjectToWorld)(pObj, pObjError, &it->pError); return true; }
Interaction Sphere::Sample(const Point2f &u) const { Interaction it; Point3f pObj = Point3f(0, 0, 0) + radius * UniformSampleSphere(u); it.n = Normalize((*ObjectToWorld)(Normal3f(pObj.x, pObj.y, pObj.z))); if (reverseOrientation) it.n *= -1.f; // Reproject _pObj_ to sphere surface and compute _pObjError_ pObj *= radius / Distance(pObj, Point3f(0, 0, 0)); Vector3f pObjError = gamma(5) * Abs((Vector3f)pObj); it.p = (*ObjectToWorld)(pObj, pObjError, &it.pError); return it; }
Interaction Cylinder::Sample(const Point2f &u) const { Interaction it; Float z = Lerp(u[0], zMin, zMax); Float t = u[1] * phiMax; Point3f pObj = Point3f(radius * std::cos(t), radius * std::sin(t), z); it.n = Normalize((*ObjectToWorld)(Normal3f(pObj.x, pObj.y, 0))); if (reverseOrientation) it.n *= -1.f; // Reproject _pObj_ to cylinder surface and compute _pObjError_ Float hitRad = std::sqrt(pObj.x * pObj.x + pObj.y * pObj.y); pObj.x *= radius / hitRad; pObj.y *= radius / hitRad; Vector3f pObjError = gamma(3) * Abs(Vector3f(pObj.x, pObj.y, 0)); it.p = (*ObjectToWorld)(pObj, pObjError, &it.pError); return it; }
Spectrum SpecularTransmission::Sample_f(const Vector3f &wo, Vector3f *wi, const Point2f &sample, Float *pdf, BxDFType *sampledType) const { // Figure out which $\eta$ is incident and which is transmitted bool entering = CosTheta(wo) > 0; Float etaI = entering ? etaA : etaB; Float etaT = entering ? etaB : etaA; // Compute ray direction for specular transmission if (!Refract(wo, Faceforward(Normal3f(0, 0, 1), wo), etaI / etaT, wi)) return 0; *pdf = 1; Spectrum ft = T * (Spectrum(1.) - fresnel.Evaluate(CosTheta(*wi))); // Account for non-symmetry with transmission to different medium if (mode == TransportMode::Radiance) ft *= (etaI * etaI) / (etaT * etaT); return ft / AbsCosTheta(*wi); }
Interaction Sphere::Sample(const Interaction &ref, const Point2f &u) const { // Compute coordinate system for sphere sampling Point3f pCenter = (*ObjectToWorld)(Point3f(0, 0, 0)); Vector3f wc = Normalize(pCenter - ref.p); Vector3f wcX, wcY; CoordinateSystem(wc, &wcX, &wcY); // Sample uniformly on sphere if $\pt{}$ is inside it Point3f pOrigin = OffsetRayOrigin(ref.p, ref.pError, ref.n, pCenter - ref.p); if (DistanceSquared(pOrigin, pCenter) <= radius * radius) return Sample(u); // Sample sphere uniformly inside subtended cone // Compute $\theta$ and $\phi$ values for sample in cone Float sinThetaMax2 = radius * radius / DistanceSquared(ref.p, pCenter); Float cosThetaMax = std::sqrt(std::max((Float)0, 1 - sinThetaMax2)); Float cosTheta = (1 - u[0]) + u[0] * cosThetaMax; Float sinTheta = std::sqrt(1 - cosTheta * cosTheta); Float phi = u[1] * 2 * Pi; // Compute angle $\alpha$ from center of sphere to sampled point on surface Float dc = Distance(ref.p, pCenter); Float ds = dc * cosTheta - std::sqrt(std::max( (Float)0, radius * radius - dc * dc * sinTheta * sinTheta)); Float cosAlpha = (dc * dc + radius * radius - ds * ds) / (2 * dc * radius); Float sinAlpha = std::sqrt(std::max((Float)0, 1 - cosAlpha * cosAlpha)); // Compute surface normal and sampled point on sphere Vector3f nObj = SphericalDirection(sinAlpha, cosAlpha, phi, -wcX, -wcY, -wc); Point3f pObj = radius * Point3f(nObj.x, nObj.y, nObj.z); // Return _Interaction_ for sampled point on sphere Interaction it; // Reproject _pObj_ to sphere surface and compute _pObjError_ pObj *= radius / Distance(pObj, Point3f(0, 0, 0)); Vector3f pObjError = gamma(5) * Abs((Vector3f)pObj); it.p = (*ObjectToWorld)(pObj, pObjError, &it.pError); it.n = (*ObjectToWorld)(Normal3f(nObj)); if (reverseOrientation) it.n *= -1.f; return it; }
ArbitraryMeshVertex MD2Vertex_construct( const md2Header_t* pHeader, const md2Frame_t* pFrame, const md2XyzNormal_t* xyz, const md2St_t* st ){ return ArbitraryMeshVertex( Vertex3f( xyz->v[0] * pFrame->scale[0] + pFrame->translate[0], xyz->v[1] * pFrame->scale[1] + pFrame->translate[1], xyz->v[2] * pFrame->scale[2] + pFrame->translate[2] ), Normal3f( g_mdl_normals[xyz->lightnormalindex][0], g_mdl_normals[xyz->lightnormalindex][1], g_mdl_normals[xyz->lightnormalindex][2] ), TexCoord2f( (float)st->s / pHeader->skinwidth, (float)st->t / pHeader->skinheight ) ); }
inline ArbitraryMeshVertex MDLVertex_construct( const mdlHeader_t& header, const mdlXyzNormal_t& xyz, const mdlSt_t& st, bool facesfront ){ return ArbitraryMeshVertex( Vertex3f( xyz.v[0] * header.scale[0] + header.scale_origin[0], xyz.v[1] * header.scale[1] + header.scale_origin[1], xyz.v[2] * header.scale[2] + header.scale_origin[2] ), Normal3f( g_mdl_normals[xyz.lightnormalindex][0], g_mdl_normals[xyz.lightnormalindex][1], g_mdl_normals[xyz.lightnormalindex][2] ), TexCoord2f( ( (float)st.s / header.skinwidth ) + ( ( st.onseam == MDL_ONSEAM && !facesfront ) ? 0.5f : 0.0f ), (float)st.t / header.skinheight ) ); }
bool RealisticCamera::IntersectSphericalElement(Float radius, Float zCenter, const Ray &ray, Float *t, Normal3f *n) { // Compute _t0_ and _t1_ for ray--element intersection Point3f o = ray.o - Vector3f(0, 0, zCenter); Float A = ray.d.x * ray.d.x + ray.d.y * ray.d.y + ray.d.z * ray.d.z; Float B = 2 * (ray.d.x * o.x + ray.d.y * o.y + ray.d.z * o.z); Float C = o.x * o.x + o.y * o.y + o.z * o.z - radius * radius; Float t0, t1; if (!Quadratic(A, B, C, &t0, &t1)) return false; // Select intersection $t$ based on ray direction and element curvature bool useCloserT = (ray.d.z > 0) ^ (radius < 0); *t = useCloserT ? std::min(t0, t1) : std::max(t0, t1); if (*t < 0) return false; // Compute surface normal of element at ray intersection point *n = Normal3f(Vector3f(o + *t * ray.d)); *n = Faceforward(Normalize(*n), -ray.d); return true; }
void normalquantisation_draw() { glPointSize(1); glBegin(GL_POINTS); for(std::size_t i = 0; i <= c_quantise_normal; ++i) { for(std::size_t j = 0; j <= c_quantise_normal; ++j) { Normal3f vertex(normal3f_normalised(Normal3f( static_cast<float>(c_quantise_normal - j - i), static_cast<float>(i), static_cast<float>(j) ))); VectorScale(normal3f_to_array(vertex), 64.f, normal3f_to_array(vertex)); glVertex3fv(normal3f_to_array(vertex)); vertex.x = -vertex.x; glVertex3fv(normal3f_to_array(vertex)); } } glEnd(); }
void MD5Surface::buildVertexNormals() { for (Indices::iterator j = _indices.begin(); j != _indices.end(); j += 3) { ArbitraryMeshVertex& a = _vertices[*(j + 0)]; ArbitraryMeshVertex& b = _vertices[*(j + 1)]; ArbitraryMeshVertex& c = _vertices[*(j + 2)]; Vector3 weightedNormal((c.vertex - a.vertex).crossProduct(b.vertex - a.vertex)); a.normal += weightedNormal; b.normal += weightedNormal; c.normal += weightedNormal; } // Normalise all normal vectors for (Vertices::iterator j = _vertices.begin(); j != _vertices.end(); ++j) { j->normal = Normal3f(j->normal.getNormalised()); } }
void MD5Surface::updateToSkeleton(const MD5Skeleton& skeleton) { // Ensure we have all vertices allocated if (_vertices.size() != _mesh->vertices.size()) { _vertices.resize(_mesh->vertices.size()); } // Deform vertices to fit the skeleton for (std::size_t j = 0; j < _mesh->vertices.size(); ++j) { MD5Vert& vert = _mesh->vertices[j]; Vector3 skinned(0, 0, 0); for (std::size_t k = 0; k != vert.weight_count; ++k) { MD5Weight& weight = _mesh->weights[vert.weight_index + k]; const IMD5Anim::Key& key = skeleton.getKey(weight.joint); //const Joint& joint = skeleton.getJoint(weight.joint); Vector3 rotatedPoint = key.orientation.transformPoint(weight.v); skinned += (rotatedPoint + key.origin) * weight.t; } _vertices[j].vertex = skinned; _vertices[j].texcoord = TexCoord2f(vert.u, vert.v); _vertices[j].normal = Normal3f(0,0,0); } // Ensure the index array is ok if (_indices.empty()) { buildIndexArray(); } buildVertexNormals(); updateGeometry(); }
Interaction Triangle::Sample(const Point2f &u) const { Point2f b = UniformSampleTriangle(u); // Get triangle vertices in _p0_, _p1_, and _p2_ const Point3f &p0 = mesh->p[v[0]]; const Point3f &p1 = mesh->p[v[1]]; const Point3f &p2 = mesh->p[v[2]]; Interaction it; it.p = b[0] * p0 + b[1] * p1 + (1 - b[0] - b[1]) * p2; // Compute surface normal for sampled point on triangle if (mesh->n) it.n = Normalize(b[0] * mesh->n[v[0]] + b[1] * mesh->n[v[1]] + (1 - b[0] - b[1]) * mesh->n[v[2]]); else it.n = Normalize(Normal3f(Cross(p1 - p0, p2 - p0))); if (reverseOrientation) it.n *= -1; // Compute error bounds for sampled point on triangle Point3f pAbsSum = Abs(b[0] * p0) + Abs(b[1] * p1) + Abs((1 - b[0] - b[1]) * p2); it.pError = gamma(6) * Vector3f(pAbsSum.x, pAbsSum.y, pAbsSum.z); return it; }
Spectrum PerspectiveCamera::Sample_Wi(const Interaction &ref, const Point2f &u, Vector3f *wi, Float *pdf, Point2f *pRaster, VisibilityTester *vis) const { // Uniformly sample a lens interaction _lensIntr_ Point2f pLens = lensRadius * ConcentricSampleDisk(u); Point3f pLensWorld = CameraToWorld(ref.time, Point3f(pLens.x, pLens.y, 0)); Interaction lensIntr(pLensWorld, ref.time, medium); lensIntr.n = Normal3f(CameraToWorld(ref.time, Vector3f(0, 0, 1))); // Populate arguments and compute the importance value *vis = VisibilityTester(ref, lensIntr); *wi = lensIntr.p - ref.p; Float dist = wi->Length(); *wi /= dist; // Compute PDF for importance arriving at _ref_ // Compute lens area of perspective camera Float lensArea = lensRadius != 0 ? (Pi * lensRadius * lensRadius) : 1; *pdf = (dist * dist) / (AbsDot(lensIntr.n, *wi) * lensArea); return We(lensIntr, -*wi, pRaster); }
void MD5Surface::updateToDefaultPose(const MD5Joints& joints) { if (_vertices.size() != _mesh->vertices.size()) { _vertices.resize(_mesh->vertices.size()); } for (std::size_t j = 0; j < _mesh->vertices.size(); ++j) { MD5Vert& vert = _mesh->vertices[j]; Vector3 skinned(0, 0, 0); for (std::size_t k = 0; k != vert.weight_count; ++k) { MD5Weight& weight = _mesh->weights[vert.weight_index + k]; const MD5Joint& joint = joints[weight.joint]; Vector3 rotatedPoint = joint.rotation.transformPoint(weight.v); skinned += (rotatedPoint + joint.position) * weight.t; } _vertices[j].vertex = skinned; _vertices[j].texcoord = TexCoord2f(vert.u, vert.v); _vertices[j].normal = Normal3f(0,0,0); } // Ensure the index array is ok if (_indices.empty()) { buildIndexArray(); } buildVertexNormals(); updateGeometry(); }
// Constructor. Copy the provided picoSurface_t structure into this object RenderablePicoSurface::RenderablePicoSurface (picoSurface_t* surf) : _originalShaderName(""), _mappedShaderName("") { // Get the shader from the picomodel struct. picoShader_t* shader = PicoGetSurfaceShader(surf); if (shader != 0) { _originalShaderName = PicoGetShaderName(shader); } // Capture the shader _shader = GlobalShaderCache().capture(_originalShaderName); if (_shader) _mappedShaderName = _originalShaderName; // no skin at this time // Get the number of vertices and indices, and reserve capacity in our vectors in advance // by populating them with empty structs. const int nVerts = PicoGetSurfaceNumVertexes(surf); _nIndices = PicoGetSurfaceNumIndexes(surf); _vertices.resize(nVerts); _indices.resize(_nIndices); // Stream in the vertex data from the raw struct, expanding the local AABB to include // each vertex. for (int vNum = 0; vNum < nVerts; ++vNum) { Vertex3f vertex(PicoGetSurfaceXYZ(surf, vNum)); _localAABB.includePoint(vertex); _vertices[vNum].vertex = vertex; _vertices[vNum].normal = Normal3f(PicoGetSurfaceNormal(surf, vNum)); _vertices[vNum].texcoord = TexCoord2f(PicoGetSurfaceST(surf, 0, vNum)); } // Stream in the index data picoIndex_t* ind = PicoGetSurfaceIndexes(surf, 0); for (unsigned int i = 0; i < _nIndices; i++) _indices[i] = ind[i]; }
bool Cylinder::Intersect(const Ray &r, Float *tHit, SurfaceInteraction *isect, bool testAlphaTexture) const { Float phi; Point3f pHit; // Transform _Ray_ to object space Vector3f oErr, dErr; Ray ray = (*WorldToObject)(r, &oErr, &dErr); // Compute quadratic cylinder coefficients // Initialize _EFloat_ ray coordinate values EFloat ox(ray.o.x, oErr.x), oy(ray.o.y, oErr.y), oz(ray.o.z, oErr.z); EFloat dx(ray.d.x, dErr.x), dy(ray.d.y, dErr.y), dz(ray.d.z, dErr.z); EFloat a = dx * dx + dy * dy; EFloat b = 2 * (dx * ox + dy * oy); EFloat c = ox * ox + oy * oy - EFloat(radius) * EFloat(radius); // Solve quadratic equation for _t_ values EFloat t0, t1; if (!Quadratic(a, b, c, &t0, &t1)) return false; // Check quadric shape _t0_ and _t1_ for nearest intersection if (t0.UpperBound() > ray.tMax || t1.LowerBound() <= 0) return false; EFloat tShapeHit = t0; if (tShapeHit.LowerBound() <= 0) { tShapeHit = t1; if (tShapeHit.UpperBound() > ray.tMax) return false; } // Compute cylinder hit point and $\phi$ pHit = ray((Float)tShapeHit); // Refine cylinder intersection point Float hitRad = std::sqrt(pHit.x * pHit.x + pHit.y * pHit.y); pHit.x *= radius / hitRad; pHit.y *= radius / hitRad; phi = std::atan2(pHit.y, pHit.x); if (phi < 0) phi += 2 * Pi; // Test cylinder intersection against clipping parameters if (pHit.z < zMin || pHit.z > zMax || phi > phiMax) { if (tShapeHit == t1) return false; tShapeHit = t1; if (t1.UpperBound() > ray.tMax) return false; // Compute cylinder hit point and $\phi$ pHit = ray((Float)tShapeHit); // Refine cylinder intersection point Float hitRad = std::sqrt(pHit.x * pHit.x + pHit.y * pHit.y); pHit.x *= radius / hitRad; pHit.y *= radius / hitRad; phi = std::atan2(pHit.y, pHit.x); if (phi < 0) phi += 2 * Pi; if (pHit.z < zMin || pHit.z > zMax || phi > phiMax) return false; } // Find parametric representation of cylinder hit Float u = phi / phiMax; Float v = (pHit.z - zMin) / (zMax - zMin); // Compute cylinder $\dpdu$ and $\dpdv$ Vector3f dpdu(-phiMax * pHit.y, phiMax * pHit.x, 0); Vector3f dpdv(0, 0, zMax - zMin); // Compute cylinder $\dndu$ and $\dndv$ Vector3f d2Pduu = -phiMax * phiMax * Vector3f(pHit.x, pHit.y, 0); Vector3f d2Pduv(0, 0, 0), d2Pdvv(0, 0, 0); // Compute coefficients for fundamental forms Float E = Dot(dpdu, dpdu); Float F = Dot(dpdu, dpdv); Float G = Dot(dpdv, dpdv); Vector3f N = Normalize(Cross(dpdu, dpdv)); Float e = Dot(N, d2Pduu); Float f = Dot(N, d2Pduv); Float g = Dot(N, d2Pdvv); // Compute $\dndu$ and $\dndv$ from fundamental form coefficients Float invEGF2 = 1 / (E * G - F * F); Normal3f dndu = Normal3f((f * F - e * G) * invEGF2 * dpdu + (e * F - f * E) * invEGF2 * dpdv); Normal3f dndv = Normal3f((g * F - f * G) * invEGF2 * dpdu + (f * F - g * E) * invEGF2 * dpdv); // Compute error bounds for cylinder intersection Vector3f pError = gamma(3) * Abs(Vector3f(pHit.x, pHit.y, 0)); // Initialize _SurfaceInteraction_ from parametric information *isect = (*ObjectToWorld)(SurfaceInteraction(pHit, pError, Point2f(u, v), -ray.d, dpdu, dpdv, dndu, dndv, ray.time, this)); // Update _tHit_ for quadric intersection *tHit = (Float)tShapeHit; return true; }
// Constructor. Copy the provided picoSurface_t structure into this object RenderablePicoSurface::RenderablePicoSurface(picoSurface_t* surf, const std::string& fExt) : _shaderName(""), _dlRegular(0), _dlProgramVcol(0), _dlProgramNoVCol(0) { // Get the shader from the picomodel struct. If this is a LWO model, use // the material name to select the shader, while for an ASE model the // bitmap path should be used. picoShader_t* shader = PicoGetSurfaceShader(surf); std::string rawName = ""; if (shader != 0) { if (fExt == "lwo") { _shaderName = PicoGetShaderName(shader); } else if (fExt == "ase") { rawName = PicoGetShaderName(shader); std::string rawMapName = PicoGetShaderMapName(shader); _shaderName = cleanupShaderName(rawMapName); } } // If shader not found, fallback to alternative if available // _shaderName is empty if the ase material has no BITMAP // materialIsValid is false if _shaderName is not an existing shader if ((_shaderName.empty() || !GlobalMaterialManager().materialExists(_shaderName)) && !rawName.empty()) { _shaderName = cleanupShaderName(rawName); } // Capturing the shader happens later on when we have a RenderSystem reference // Get the number of vertices and indices, and reserve capacity in our // vectors in advance by populating them with empty structs. int nVerts = PicoGetSurfaceNumVertexes(surf); _nIndices = PicoGetSurfaceNumIndexes(surf); _vertices.resize(nVerts); _indices.resize(_nIndices); // Stream in the vertex data from the raw struct, expanding the local AABB // to include each vertex. for (int vNum = 0; vNum < nVerts; ++vNum) { // Get the vertex position and colour Vertex3f vertex(PicoGetSurfaceXYZ(surf, vNum)); // Expand the AABB to include this new vertex _localAABB.includePoint(vertex); _vertices[vNum].vertex = vertex; _vertices[vNum].normal = Normal3f(PicoGetSurfaceNormal(surf, vNum)); _vertices[vNum].texcoord = TexCoord2f(PicoGetSurfaceST(surf, 0, vNum)); _vertices[vNum].colour = getColourVector(PicoGetSurfaceColor(surf, 0, vNum)); } // Stream in the index data picoIndex_t* ind = PicoGetSurfaceIndexes(surf, 0); for (unsigned int i = 0; i < _nIndices; i++) _indices[i] = ind[i]; // Calculate the tangent and bitangent vectors calculateTangents(); // Construct the DLs createDisplayLists(); }
bool Curve::recursiveIntersect(const Ray &ray, Float *tHit, SurfaceInteraction *isect, const Point3f cp[4], const Transform &rayToObject, Float u0, Float u1, int depth) const { // Try to cull curve segment versus ray // Compute bounding box of curve segment, _curveBounds_ Bounds3f curveBounds = Union(Bounds3f(cp[0], cp[1]), Bounds3f(cp[2], cp[3])); Float maxWidth = std::max(Lerp(u0, common->width[0], common->width[1]), Lerp(u1, common->width[0], common->width[1])); curveBounds = Expand(curveBounds, 0.5 * maxWidth); // Compute bounding box of ray, _rayBounds_ Float rayLength = ray.d.Length(); Float zMax = rayLength * ray.tMax; Bounds3f rayBounds(Point3f(0, 0, 0), Point3f(0, 0, zMax)); if (Overlaps(curveBounds, rayBounds) == false) return false; if (depth > 0) { // Split curve segment into sub-segments and test for intersection Float uMid = 0.5f * (u0 + u1); Point3f cpSplit[7]; SubdivideBezier(cp, cpSplit); return (recursiveIntersect(ray, tHit, isect, &cpSplit[0], rayToObject, u0, uMid, depth - 1) || recursiveIntersect(ray, tHit, isect, &cpSplit[3], rayToObject, uMid, u1, depth - 1)); } else { // Intersect ray with curve segment // Test ray against segment endpoint boundaries Vector2f segmentDirection = Point2f(cp[3]) - Point2f(cp[0]); // Test sample point against tangent perpendicular at curve start Float edge = (cp[1].y - cp[0].y) * -cp[0].y + cp[0].x * (cp[0].x - cp[1].x); if (edge < 0) return false; // Test sample point against tangent perpendicular at curve end // TODO: update to match the starting test. Vector2f endTangent = Point2f(cp[2]) - Point2f(cp[3]); if (Dot(segmentDirection, endTangent) < 0) endTangent = -endTangent; if (Dot(endTangent, Vector2f(cp[3])) < 0) return false; // Compute line $w$ that gives minimum distance to sample point Float denom = Dot(segmentDirection, segmentDirection); if (denom == 0) return false; Float w = Dot(-Vector2f(cp[0]), segmentDirection) / denom; // Compute $u$ coordinate of curve intersection point and _hitWidth_ Float u = Clamp(Lerp(w, u0, u1), u0, u1); Float hitWidth = Lerp(u, common->width[0], common->width[1]); Normal3f nHit; if (common->type == CurveType::Ribbon) { // Scale _hitWidth_ based on ribbon orientation Float sin0 = std::sin((1 - u) * common->normalAngle) * common->invSinNormalAngle; Float sin1 = std::sin(u * common->normalAngle) * common->invSinNormalAngle; nHit = sin0 * common->n[0] + sin1 * common->n[1]; hitWidth *= AbsDot(nHit, -ray.d / rayLength); } // Test intersection point against curve width Vector3f dpcdw; Point3f pc = EvalBezier(cp, Clamp(w, 0, 1), &dpcdw); Float ptCurveDist2 = pc.x * pc.x + pc.y * pc.y; if (ptCurveDist2 > hitWidth * hitWidth * .25) return false; if (pc.z < 0 || pc.z > zMax) return false; // Compute $v$ coordinate of curve intersection point Float ptCurveDist = std::sqrt(ptCurveDist2); Float edgeFunc = dpcdw.x * -pc.y + pc.x * dpcdw.y; Float v = (edgeFunc > 0.) ? 0.5f + ptCurveDist / hitWidth : 0.5f - ptCurveDist / hitWidth; // Compute hit _t_ and partial derivatives for curve intersection if (tHit != nullptr) { // FIXME: this tHit isn't quite right for ribbons... *tHit = pc.z / rayLength; // Compute error bounds for curve intersection Vector3f pError(2 * hitWidth, 2 * hitWidth, 2 * hitWidth); // Compute $\dpdu$ and $\dpdv$ for curve intersection Vector3f dpdu, dpdv; EvalBezier(common->cpObj, u, &dpdu); if (common->type == CurveType::Ribbon) dpdv = Normalize(Cross(nHit, dpdu)) * hitWidth; else { // Compute curve $\dpdv$ for flat and cylinder curves Vector3f dpduPlane = (Inverse(rayToObject))(dpdu); Vector3f dpdvPlane = Normalize(Vector3f(-dpduPlane.y, dpduPlane.x, 0)) * hitWidth; if (common->type == CurveType::Cylinder) { // Rotate _dpdvPlane_ to give cylindrical appearance Float theta = Lerp(v, -90., 90.); Transform rot = Rotate(-theta, dpduPlane); dpdvPlane = rot(dpdvPlane); } dpdv = rayToObject(dpdvPlane); } *isect = (*ObjectToWorld)(SurfaceInteraction( ray(pc.z), pError, Point2f(u, v), -ray.d, dpdu, dpdv, Normal3f(0, 0, 0), Normal3f(0, 0, 0), ray.time, this)); } ++nHits; return true; } }
bool Sphere::Intersect(const Ray &r, Float *tHit, SurfaceInteraction *isect, bool testAlphaTexture) const { Float phi; Point3f pHit; // Transform _Ray_ to object space Vector3f oErr, dErr; Ray ray = (*WorldToObject)(r, &oErr, &dErr); // Compute quadratic sphere coefficients // Initialize _EFloat_ ray coordinate values EFloat ox(ray.o.x, oErr.x), oy(ray.o.y, oErr.y), oz(ray.o.z, oErr.z); EFloat dx(ray.d.x, dErr.x), dy(ray.d.y, dErr.y), dz(ray.d.z, dErr.z); EFloat a = dx * dx + dy * dy + dz * dz; EFloat b = 2 * (dx * ox + dy * oy + dz * oz); EFloat c = ox * ox + oy * oy + oz * oz - EFloat(radius) * EFloat(radius); // Solve quadratic equation for _t_ values EFloat t0, t1; if (!Quadratic(a, b, c, &t0, &t1)) return false; // Check quadric shape _t0_ and _t1_ for nearest intersection if (t0.UpperBound() > ray.tMax || t1.LowerBound() <= 0) return false; EFloat tShapeHit = t0; if (tShapeHit.LowerBound() <= 0) { tShapeHit = t1; if (tShapeHit.UpperBound() > ray.tMax) return false; } // Compute sphere hit position and $\phi$ pHit = ray((Float)tShapeHit); // Refine sphere intersection point pHit *= radius / Distance(pHit, Point3f(0, 0, 0)); if (pHit.x == 0 && pHit.y == 0) pHit.x = 1e-5f * radius; phi = std::atan2(pHit.y, pHit.x); if (phi < 0) phi += 2 * Pi; // Test sphere intersection against clipping parameters if ((zMin > -radius && pHit.z < zMin) || (zMax < radius && pHit.z > zMax) || phi > phiMax) { if (tShapeHit == t1) return false; if (t1.UpperBound() > ray.tMax) return false; tShapeHit = t1; // Compute sphere hit position and $\phi$ pHit = ray((Float)tShapeHit); // Refine sphere intersection point pHit *= radius / Distance(pHit, Point3f(0, 0, 0)); if (pHit.x == 0 && pHit.y == 0) pHit.x = 1e-5f * radius; phi = std::atan2(pHit.y, pHit.x); if (phi < 0) phi += 2 * Pi; if ((zMin > -radius && pHit.z < zMin) || (zMax < radius && pHit.z > zMax) || phi > phiMax) return false; } // Find parametric representation of sphere hit Float u = phi / phiMax; Float theta = std::acos(Clamp(pHit.z / radius, -1, 1)); Float v = (theta - thetaMin) / (thetaMax - thetaMin); // Compute sphere $\dpdu$ and $\dpdv$ Float zRadius = std::sqrt(pHit.x * pHit.x + pHit.y * pHit.y); Float invZRadius = 1 / zRadius; Float cosPhi = pHit.x * invZRadius; Float sinPhi = pHit.y * invZRadius; Vector3f dpdu(-phiMax * pHit.y, phiMax * pHit.x, 0); Vector3f dpdv = (thetaMax - thetaMin) * Vector3f(pHit.z * cosPhi, pHit.z * sinPhi, -radius * std::sin(theta)); // Compute sphere $\dndu$ and $\dndv$ Vector3f d2Pduu = -phiMax * phiMax * Vector3f(pHit.x, pHit.y, 0); Vector3f d2Pduv = (thetaMax - thetaMin) * pHit.z * phiMax * Vector3f(-sinPhi, cosPhi, 0.); Vector3f d2Pdvv = -(thetaMax - thetaMin) * (thetaMax - thetaMin) * Vector3f(pHit.x, pHit.y, pHit.z); // Compute coefficients for fundamental forms Float E = Dot(dpdu, dpdu); Float F = Dot(dpdu, dpdv); Float G = Dot(dpdv, dpdv); Vector3f N = Normalize(Cross(dpdu, dpdv)); Float e = Dot(N, d2Pduu); Float f = Dot(N, d2Pduv); Float g = Dot(N, d2Pdvv); // Compute $\dndu$ and $\dndv$ from fundamental form coefficients Float invEGF2 = 1 / (E * G - F * F); Normal3f dndu = Normal3f((f * F - e * G) * invEGF2 * dpdu + (e * F - f * E) * invEGF2 * dpdv); Normal3f dndv = Normal3f((g * F - f * G) * invEGF2 * dpdu + (f * F - g * E) * invEGF2 * dpdv); // Compute error bounds for sphere intersection Vector3f pError = gamma(5) * Abs((Vector3f)pHit); // Initialize _SurfaceInteraction_ from parametric information *isect = (*ObjectToWorld)(SurfaceInteraction(pHit, pError, Point2f(u, v), -ray.d, dpdu, dpdv, dndu, dndv, ray.time, this)); // Update _tHit_ for quadric intersection *tHit = (Float)tShapeHit; return true; }
static Normal3f normalise(float x, float y, float z) { return normal3f_normalised(Normal3f(x, y, z)); }
bool Hyperboloid::Intersect(const Ray &r, Float *tHit, SurfaceInteraction *isect) const { Float phi, v; Point3f pHit; // Transform _Ray_ to object space Vector3f oErr, dErr; Ray ray = (*WorldToObject)(r, &oErr, &dErr); // Compute quadratic hyperboloid coefficients // Initialize _EFloat_ ray coordinate values EFloat ox(ray.o.x, oErr.x), oy(ray.o.y, oErr.y), oz(ray.o.z, oErr.z); EFloat dx(ray.d.x, dErr.x), dy(ray.d.y, dErr.y), dz(ray.d.z, dErr.z); EFloat a = ah * dx * dx + ah * dy * dy - ch * dz * dz; EFloat b = 2.f * (ah * dx * ox + ah * dy * oy - ch * dz * oz); EFloat c = ah * ox * ox + ah * oy * oy - ch * oz * oz - 1.f; // Solve quadratic equation for _t_ values EFloat t0, t1; if (!Quadratic(a, b, c, &t0, &t1)) return false; // Check quadric shape _t0_ and _t1_ for nearest intersection if (t0.UpperBound() > ray.tMax || t1.LowerBound() <= 0) return false; EFloat tShapeHit = t0; if (t0.LowerBound() <= 0) { tShapeHit = t1; if (tShapeHit.UpperBound() > ray.tMax) return false; } // Compute hyperboloid inverse mapping pHit = ray((Float)tShapeHit); v = (pHit.z - p1.z) / (p2.z - p1.z); Point3f pr = (1 - v) * p1 + v * p2; phi = std::atan2(pr.x * pHit.y - pHit.x * pr.y, pHit.x * pr.x + pHit.y * pr.y); if (phi < 0) phi += 2 * Pi; // Test hyperboloid intersection against clipping parameters if (pHit.z < zMin || pHit.z > zMax || phi > phiMax) { if (tShapeHit == t1) return false; tShapeHit = t1; if (t1.UpperBound() > ray.tMax) return false; // Compute hyperboloid inverse mapping pHit = ray((Float)tShapeHit); v = (pHit.z - p1.z) / (p2.z - p1.z); Point3f pr = (1 - v) * p1 + v * p2; phi = std::atan2(pr.x * pHit.y - pHit.x * pr.y, pHit.x * pr.x + pHit.y * pr.y); if (phi < 0) phi += 2 * Pi; if (pHit.z < zMin || pHit.z > zMax || phi > phiMax) return false; } // Compute parametric representation of hyperboloid hit Float u = phi / phiMax; // Compute hyperboloid $\dpdu$ and $\dpdv$ Float cosPhi = std::cos(phi), sinPhi = std::sin(phi); Vector3f dpdu(-phiMax * pHit.y, phiMax * pHit.x, 0.); Vector3f dpdv((p2.x - p1.x) * cosPhi - (p2.y - p1.y) * sinPhi, (p2.x - p1.x) * sinPhi + (p2.y - p1.y) * cosPhi, p2.z - p1.z); // Compute hyperboloid $\dndu$ and $\dndv$ Vector3f d2Pduu = -phiMax * phiMax * Vector3f(pHit.x, pHit.y, 0); Vector3f d2Pduv = phiMax * Vector3f(-dpdv.y, dpdv.x, 0.); Vector3f d2Pdvv(0, 0, 0); // Compute coefficients for fundamental forms Float E = Dot(dpdu, dpdu); Float F = Dot(dpdu, dpdv); Float G = Dot(dpdv, dpdv); Vector3f N = Normalize(Cross(dpdu, dpdv)); Float e = Dot(N, d2Pduu); Float f = Dot(N, d2Pduv); Float g = Dot(N, d2Pdvv); // Compute $\dndu$ and $\dndv$ from fundamental form coefficients Float invEGF2 = 1 / (E * G - F * F); Normal3f dndu = Normal3f((f * F - e * G) * invEGF2 * dpdu + (e * F - f * E) * invEGF2 * dpdv); Normal3f dndv = Normal3f((g * F - f * G) * invEGF2 * dpdu + (f * F - g * E) * invEGF2 * dpdv); // Compute error bounds for hyperboloid intersection // Compute error bounds for intersection computed with ray equation EFloat px = ox + tShapeHit * dx; EFloat py = oy + tShapeHit * dy; EFloat pz = oz + tShapeHit * dz; Vector3f pError = Vector3f(px.GetAbsoluteError(), py.GetAbsoluteError(), pz.GetAbsoluteError()); // Initialize _SurfaceInteraction_ from parametric information *isect = (*ObjectToWorld)(SurfaceInteraction(pHit, pError, Point2f(u, v), -ray.d, dpdu, dpdv, dndu, dndv, ray.time, this)); *tHit = (Float)tShapeHit; return true; }
bool Paraboloid::Intersect(const Ray &r, Float *tHit, SurfaceInteraction *isect) const { Float phi; Point3f pHit; // Transform _Ray_ to object space Vector3f oErr, dErr; Ray ray = (*WorldToObject)(r, &oErr, &dErr); // Compute quadratic paraboloid coefficients // Initialize _EFloat_ ray coordinate values EFloat ox(ray.o.x, oErr.x), oy(ray.o.y, oErr.y), oz(ray.o.z, oErr.z); EFloat dx(ray.d.x, dErr.x), dy(ray.d.y, dErr.y), dz(ray.d.z, dErr.z); EFloat k = EFloat(zMax) / (EFloat(radius) * EFloat(radius)); EFloat a = k * (dx * dx + dy * dy); EFloat b = 2.f * k * (dx * ox + dy * oy) - dz; EFloat c = k * (ox * ox + oy * oy) - oz; // Solve quadratic equation for _t_ values EFloat t0, t1; if (!Quadratic(a, b, c, &t0, &t1)) return false; // Check quadric shape _t0_ and _t1_ for nearest intersection if (t0.UpperBound() > ray.tMax || t1.LowerBound() <= 0) return false; EFloat tShapeHit = t0; if (tShapeHit.LowerBound() <= 0) { tShapeHit = t1; if (tShapeHit.UpperBound() > ray.tMax) return false; } // Compute paraboloid inverse mapping pHit = ray((Float)tShapeHit); phi = std::atan2(pHit.y, pHit.x); if (phi < 0.) phi += 2 * Pi; // Test paraboloid intersection against clipping parameters if (pHit.z < zMin || pHit.z > zMax || phi > phiMax) { if (tShapeHit == t1) return false; tShapeHit = t1; if (t1.UpperBound() > ray.tMax) return false; // Compute paraboloid inverse mapping pHit = ray((Float)tShapeHit); phi = std::atan2(pHit.y, pHit.x); if (phi < 0.) phi += 2 * Pi; if (pHit.z < zMin || pHit.z > zMax || phi > phiMax) return false; } // Find parametric representation of paraboloid hit Float u = phi / phiMax; Float v = (pHit.z - zMin) / (zMax - zMin); // Compute paraboloid $\dpdu$ and $\dpdv$ Vector3f dpdu(-phiMax * pHit.y, phiMax * pHit.x, 0.); Vector3f dpdv = (zMax - zMin) * Vector3f(pHit.x / (2 * pHit.z), pHit.y / (2 * pHit.z), 1.); // Compute paraboloid $\dndu$ and $\dndv$ Vector3f d2Pduu = -phiMax * phiMax * Vector3f(pHit.x, pHit.y, 0); Vector3f d2Pduv = (zMax - zMin) * phiMax * Vector3f(-pHit.y / (2 * pHit.z), pHit.x / (2 * pHit.z), 0); Vector3f d2Pdvv = -(zMax - zMin) * (zMax - zMin) * Vector3f(pHit.x / (4 * pHit.z * pHit.z), pHit.y / (4 * pHit.z * pHit.z), 0.); // Compute coefficients for fundamental forms Float E = Dot(dpdu, dpdu); Float F = Dot(dpdu, dpdv); Float G = Dot(dpdv, dpdv); Vector3f N = Normalize(Cross(dpdu, dpdv)); Float e = Dot(N, d2Pduu); Float f = Dot(N, d2Pduv); Float g = Dot(N, d2Pdvv); // Compute $\dndu$ and $\dndv$ from fundamental form coefficients Float invEGF2 = 1 / (E * G - F * F); Normal3f dndu = Normal3f((f * F - e * G) * invEGF2 * dpdu + (e * F - f * E) * invEGF2 * dpdv); Normal3f dndv = Normal3f((g * F - f * G) * invEGF2 * dpdu + (f * F - g * E) * invEGF2 * dpdv); // Compute error bounds for paraboloid intersection // Compute error bounds for intersection computed with ray equation EFloat px = ox + tShapeHit * dx; EFloat py = oy + tShapeHit * dy; EFloat pz = oz + tShapeHit * dz; Vector3f pError = Vector3f(px.GetAbsoluteError(), py.GetAbsoluteError(), pz.GetAbsoluteError()); // Initialize _SurfaceInteraction_ from parametric information *isect = (*ObjectToWorld)(SurfaceInteraction(pHit, pError, Point2f(u, v), -ray.d, dpdu, dpdv, dndu, dndv, ray.time, this)); *tHit = (Float)tShapeHit; return true; }
Color3f Li(const Scene *scene, Sampler *sampler, const Ray3f &ray) const { /* Find the surface that is visible in the requested direction */ Intersection its; //check if the ray intersects the scene if (!scene->rayIntersect(ray, its)) { //check if a distant disk light is set const Emitter* distantsDisk = scene->getDistantEmitter(); if(distantsDisk == nullptr ) return Color3f(0.0f); //sample the distant disk light Vector3f d = ray.d; return distantsDisk->sampleL(d); } //get the Number of lights from the scene const std::vector<Emitter *> lights = scene->getEmitters(); uint32_t nLights = lights.size(); Color3f tp(1.0f, 1.0f, 1.0f); Color3f L(0.0f, 0.0f, 0.0f); Ray3f pathRay(ray.o, ray.d); bool deltaFlag = true; while(true) { if (its.mesh->isEmitter() && deltaFlag) { const Emitter* areaLightEM = its.mesh->getEmitter(); const areaLight* aEM = static_cast<const areaLight *> (areaLightEM); L += tp * aEM->sampleL(-pathRay.d, its.shFrame.n, its); } //Light sampling //randomly select a lightsource uint32_t var = uint32_t(std::min(sampler->next1D()*nLights, float(nLights) - 1.0f)); //init the light color Color3f Li(0.0f, 0.0f, 0.0f); Color3f Ld(1.0f, 1.0f, 1.0f); //create a sample for the light const BSDF* curBSDF = its.mesh->getBSDF(); const Point2f lightSample = sampler->next2D(); VisibilityTester vis; Vector3f wo; float lightpdf; float bsdfpdf; Normal3f n = its.shFrame.n; deltaFlag = curBSDF->isDeltaBSDF(); //sample the light { Li = lights[var]->sampleL(its.p, Epsilon, lightSample , &wo, &lightpdf, &vis); lightpdf /= float(nLights); //check if the pdf of the sample is greater than 0 and if the color is not black if(lightpdf > 0 && Li.maxCoeff() != 0.0f) { //calculate the cosine term wi in my case the vector to the light float cosTerm = std::abs(n.dot(wo)); const BSDFQueryRecord queryEM = BSDFQueryRecord(its.toLocal(- pathRay.d), its.toLocal(wo), EMeasure::ESolidAngle, sampler); Color3f f = curBSDF->eval(queryEM); if(f.maxCoeff() > 0.0f && f.minCoeff() >= 0.0f && vis.Unoccluded(scene)) { bsdfpdf = curBSDF->pdf(queryEM); float weight = BalanceHeuristic(float(1), lightpdf, float(1), bsdfpdf); if(curBSDF->isDeltaBSDF()) weight = 1.0f; if(bsdfpdf > 0.0f) { Ld = (weight * f * Li * cosTerm) / lightpdf; L += tp * Ld; } else { //cout << "bsdfpdf = " << bsdfpdf << endl; //cout << "f = " << f << endl; } } } } //Material part BSDFQueryRecord queryMats = BSDFQueryRecord(its.toLocal(-pathRay.d), Vector3f(0.0f), EMeasure::ESolidAngle, sampler); Color3f fi = curBSDF->sample(queryMats, sampler->next2D()); bsdfpdf = curBSDF->pdf(queryMats); lightpdf = 0.0f; if(fi.maxCoeff() > 0.0f && fi.minCoeff() >= 0.0f) { if(bsdfpdf > 0.0f) { Ray3f shadowRay(its.p, its.toWorld(queryMats.wo)); Intersection lightIsect; if (scene->rayIntersect(shadowRay, lightIsect)) { if(lightIsect.mesh->isEmitter()){ const Emitter* areaLightEMcur = lightIsect.mesh->getEmitter(); const areaLight* aEMcur = static_cast<const areaLight *> (areaLightEMcur); Li = aEMcur->sampleL(-shadowRay.d, lightIsect.shFrame.n, lightIsect); lightpdf = aEMcur->pdf(its.p, (lightIsect.p - its.p).normalized(), lightIsect.p, Normal3f(lightIsect.shFrame.n)); } } else { const Emitter* distantsDisk = scene->getDistantEmitter(); if(distantsDisk != nullptr ) { //check if THIS is right! Li = distantsDisk->sampleL(lightIsect.toWorld(queryMats.wo)); lightpdf = distantsDisk->pdf(Point3f(0.0f), wo, Point3f(0.0f), Normal3f(0.0f)); } } lightpdf /= float(nLights); //calculate the weights float weight = BalanceHeuristic(float(1), bsdfpdf, float(1), lightpdf); //check if the lightcolor is not black if(Li.maxCoeff() > 0.0f && lightpdf > 0.0f ) { //wo in my case the vector to the light Ld = weight * Li * fi; L += tp * Ld; } } tp *= fi; } else { break; } wo = its.toWorld(queryMats.wo); pathRay = Ray3f(its.p, wo); if (!scene->rayIntersect(pathRay, its)) { const Emitter* distantsDisk = scene->getDistantEmitter(); if(distantsDisk != nullptr ) { //sample the distant disk light Vector3f d = pathRay.d; L += tp * distantsDisk->sampleL(d); } break; } float maxCoeff = tp.maxCoeff(); float q = std::min(0.99f, maxCoeff); if(q < sampler->next1D()){ break; } tp /= q; } return L; }
std::vector<std::shared_ptr<Shape>> CreateNURBS(const Transform *o2w, const Transform *w2o, bool reverseOrientation, const ParamSet ¶ms) { int nu = params.FindOneInt("nu", -1); if (nu == -1) { Error("Must provide number of control points \"nu\" with NURBS shape."); return std::vector<std::shared_ptr<Shape>>(); } int uorder = params.FindOneInt("uorder", -1); if (uorder == -1) { Error("Must provide u order \"uorder\" with NURBS shape."); return std::vector<std::shared_ptr<Shape>>(); } int nuknots, nvknots; const Float *uknots = params.FindFloat("uknots", &nuknots); if (uknots == nullptr) { Error("Must provide u knot vector \"uknots\" with NURBS shape."); return std::vector<std::shared_ptr<Shape>>(); } if (nuknots != nu + uorder) { Error( "Number of knots in u knot vector %d doesn't match sum of " "number of u control points %d and u order %d.", nuknots, nu, uorder); return std::vector<std::shared_ptr<Shape>>(); } Float u0 = params.FindOneFloat("u0", uknots[uorder - 1]); Float u1 = params.FindOneFloat("u1", uknots[nu]); int nv = params.FindOneInt("nv", -1); if (nv == -1) { Error("Must provide number of control points \"nv\" with NURBS shape."); return std::vector<std::shared_ptr<Shape>>(); } int vorder = params.FindOneInt("vorder", -1); if (vorder == -1) { Error("Must provide v order \"vorder\" with NURBS shape."); return std::vector<std::shared_ptr<Shape>>(); } const Float *vknots = params.FindFloat("vknots", &nvknots); if (vknots == nullptr) { Error("Must provide v knot vector \"vknots\" with NURBS shape."); return std::vector<std::shared_ptr<Shape>>(); } if (nvknots != nv + vorder) { Error( "Number of knots in v knot vector %d doesn't match sum of " "number of v control points %d and v order %d.", nvknots, nv, vorder); return std::vector<std::shared_ptr<Shape>>(); } Float v0 = params.FindOneFloat("v0", vknots[vorder - 1]); Float v1 = params.FindOneFloat("v1", vknots[nv]); bool isHomogeneous = false; int npts; const Float *P = (const Float *)params.FindPoint3f("P", &npts); if (!P) { P = params.FindFloat("Pw", &npts); if (!P) { Error( "Must provide control points via \"P\" or \"Pw\" parameter to " "NURBS shape."); return std::vector<std::shared_ptr<Shape>>(); } if ((npts % 4) != 0) { Error( "Number of \"Pw\" control points provided to NURBS shape must " "be " "multiple of four"); return std::vector<std::shared_ptr<Shape>>(); } npts /= 4; isHomogeneous = true; } if (npts != nu * nv) { Error("NURBS shape was expecting %dx%d=%d control points, was given %d", nu, nv, nu * nv, npts); return std::vector<std::shared_ptr<Shape>>(); } // Compute NURBS dicing rates int diceu = 30, dicev = 30; std::unique_ptr<Float[]> ueval(new Float[diceu]); std::unique_ptr<Float[]> veval(new Float[dicev]); std::unique_ptr<Point3f[]> evalPs(new Point3f[diceu * dicev]); std::unique_ptr<Normal3f[]> evalNs(new Normal3f[diceu * dicev]); int i; for (i = 0; i < diceu; ++i) ueval[i] = Lerp((float)i / (float)(diceu - 1), u0, u1); for (i = 0; i < dicev; ++i) veval[i] = Lerp((float)i / (float)(dicev - 1), v0, v1); // Evaluate NURBS over grid of points memset(evalPs.get(), 0, diceu * dicev * sizeof(Point3f)); memset(evalNs.get(), 0, diceu * dicev * sizeof(Point3f)); std::unique_ptr<Point2f[]> uvs(new Point2f[diceu * dicev]); // Turn NURBS into triangles std::unique_ptr<Homogeneous3[]> Pw(new Homogeneous3[nu * nv]); if (isHomogeneous) { for (int i = 0; i < nu * nv; ++i) { Pw[i].x = P[4 * i]; Pw[i].y = P[4 * i + 1]; Pw[i].z = P[4 * i + 2]; Pw[i].w = P[4 * i + 3]; } } else { for (int i = 0; i < nu * nv; ++i) { Pw[i].x = P[3 * i]; Pw[i].y = P[3 * i + 1]; Pw[i].z = P[3 * i + 2]; Pw[i].w = 1.; } } for (int v = 0; v < dicev; ++v) { for (int u = 0; u < diceu; ++u) { uvs[(v * diceu + u)].x = ueval[u]; uvs[(v * diceu + u)].y = veval[v]; Vector3f dpdu, dpdv; Point3f pt = NURBSEvaluateSurface(uorder, uknots, nu, ueval[u], vorder, vknots, nv, veval[v], Pw.get(), &dpdu, &dpdv); evalPs[v * diceu + u].x = pt.x; evalPs[v * diceu + u].y = pt.y; evalPs[v * diceu + u].z = pt.z; evalNs[v * diceu + u] = Normal3f(Normalize(Cross(dpdu, dpdv))); } } // Generate points-polygons mesh int nTris = 2 * (diceu - 1) * (dicev - 1); std::unique_ptr<int[]> vertices(new int[3 * nTris]); int *vertp = vertices.get(); // Compute the vertex offset numbers for the triangles for (int v = 0; v < dicev - 1; ++v) { for (int u = 0; u < diceu - 1; ++u) { #define VN(u, v) ((v)*diceu + (u)) *vertp++ = VN(u, v); *vertp++ = VN(u + 1, v); *vertp++ = VN(u + 1, v + 1); *vertp++ = VN(u, v); *vertp++ = VN(u + 1, v + 1); *vertp++ = VN(u, v + 1); #undef VN } } int nVerts = diceu * dicev; return CreateTriangleMesh(o2w, w2o, reverseOrientation, nTris, vertices.get(), nVerts, evalPs.get(), nullptr, evalNs.get(), uvs.get(), nullptr); }
const Distribution1D *SpatialLightDistribution::Lookup(const Point3f &p) const { ProfilePhase _(Prof::LightDistribLookup); ++nLookups; // Compute integer voxel coordinates for the given point |p| with // respect to the overall voxel grid. Vector3f offset = scene.WorldBound().Offset(p); // offset in [0,1]. Point3i pi; for (int i = 0; i < 3; ++i) pi[i] = int(offset[i] * nVoxels[i]); // Create the per-thread cache of sampling distributions if needed. LocalBucketHash *localVoxelDistribution = localVoxelDistributions[ThreadIndex].get(); if (!localVoxelDistribution) { LOG(INFO) << "Created per-thread SpatialLightDistribution for thread" << ThreadIndex; localVoxelDistribution = new LocalBucketHash; localVoxelDistributions[ThreadIndex].reset(localVoxelDistribution); } else { // Otherwise see if we have a sampling distribution for the voxel // that |p| is in already available in the local cache. auto iter = localVoxelDistribution->find(pi); if (iter != localVoxelDistribution->end()) return iter->second; } // Now we need to either get the distribution from the shared hash // table (if another thread has already created it), or create it // ourselves and add it to the shared table. ProfilePhase __(Prof::LightDistribLookupL2); // First, compute a hash into the first-level hash table. size_t hash = std::hash<int>{}(pi[0] + nVoxels[0] * pi[1] + nVoxels[0] * nVoxels[1] * pi[2]); hash &= (nBuckets - 1); // Acquire the lock for the corresponding second-level hash table. std::lock_guard<std::mutex> lock(mutexes[hash]); // See if we can find it. auto iter = voxelDistribution[hash].find(pi); if (iter != voxelDistribution[hash].end()) { // Success. Add the pointer to the thread-specific hash table so // that we can look up this distribution more efficiently in the // future. (*localVoxelDistribution)[pi] = iter->second.get(); return iter->second.get(); } // We need to compute a new sampling distriibution for this voxel. Note // that we're holding the lock for the first-level hash table bucket // throughout the following; in general, we'd like to do the following // quickly so that other threads don't get held up waiting for that // lock (for this or other voxels that share it). ProfilePhase ___(Prof::LightDistribCreation); ++nCreated; ++nDistributions; // Compute the world-space bounding box of the voxel. Point3f p0(Float(pi[0]) / Float(nVoxels[0]), Float(pi[1]) / Float(nVoxels[1]), Float(pi[2]) / Float(nVoxels[2])); Point3f p1(Float(pi[0] + 1) / Float(nVoxels[0]), Float(pi[1] + 1) / Float(nVoxels[1]), Float(pi[2] + 1) / Float(nVoxels[2])); Bounds3f voxelBounds(scene.WorldBound().Lerp(p0), scene.WorldBound().Lerp(p1)); // Compute the sampling distribution. Sample a number of points inside // voxelBounds using a 3D Halton sequence; at each one, sample each // light source and compute a weight based on Li/pdf for the light's // sample (ignoring visibility between the point in the voxel and the // point on the light source) as an approximation to how much the light // is likely to contribute to illumination in the voxel. int nSamples = 128; std::vector<Float> lightContrib(scene.lights.size(), Float(0)); for (int i = 0; i < nSamples; ++i) { Point3f po = voxelBounds.Lerp(Point3f( RadicalInverse(0, i), RadicalInverse(1, i), RadicalInverse(2, i))); Interaction intr(po, Normal3f(), Vector3f(), Vector3f(1, 0, 0), 0 /* time */, MediumInterface()); // Use the next two Halton dimensions to sample a point on the // light source. Point2f u(RadicalInverse(3, i), RadicalInverse(4, i)); for (size_t j = 0; j < scene.lights.size(); ++j) { Float pdf; Vector3f wi; VisibilityTester vis; Spectrum Li = scene.lights[j]->Sample_Li(intr, u, &wi, &pdf, &vis); if (pdf > 0) { // TODO: look at tracing shadow rays / computing beam // transmittance. Probably shouldn't give those full weight // but instead e.g. have an occluded shadow ray scale down // the contribution by 10 or something. lightContrib[j] += Li.y() / pdf; } } } // We don't want to leave any lights with a zero probability; it's // possible that a light contributes to points in the voxel even though // we didn't find such a point when sampling above. Therefore, compute // a minimum (small) weight and ensure that all lights are given at // least the corresponding probability. Float sumContrib = std::accumulate(lightContrib.begin(), lightContrib.end(), Float(0)); Float avgContrib = sumContrib / (nSamples * lightContrib.size()); Float minContrib = (avgContrib > 0) ? .001 * avgContrib : 1; for (size_t i = 0; i < lightContrib.size(); ++i) { VLOG(2) << "Voxel pi = " << pi << ", light " << i << " contrib = " << lightContrib[i]; lightContrib[i] = std::max(lightContrib[i], minContrib); } LOG(INFO) << "Initialized light distribution in voxel pi= " << pi << ", avgContrib = " << avgContrib; // Compute a sampling distribution from the accumulated // contributions. std::unique_ptr<Distribution1D> distrib( new Distribution1D(&lightContrib[0], lightContrib.size())); // Store a pointer to it in the per-thread cache for the future. (*localVoxelDistribution)[pi] = distrib.get(); // Store the canonical unique_ptr for it in the global hash table so // other threads can use it. voxelDistribution[hash][pi] = std::move(distrib); return (*localVoxelDistribution)[pi]; }
Distribution1D * SpatialLightDistribution::ComputeDistribution(Point3i pi) const { ProfilePhase _(Prof::LightDistribCreation); ++nCreated; ++nDistributions; // Compute the world-space bounding box of the voxel corresponding to // |pi|. Point3f p0(Float(pi[0]) / Float(nVoxels[0]), Float(pi[1]) / Float(nVoxels[1]), Float(pi[2]) / Float(nVoxels[2])); Point3f p1(Float(pi[0] + 1) / Float(nVoxels[0]), Float(pi[1] + 1) / Float(nVoxels[1]), Float(pi[2] + 1) / Float(nVoxels[2])); Bounds3f voxelBounds(scene.WorldBound().Lerp(p0), scene.WorldBound().Lerp(p1)); // Compute the sampling distribution. Sample a number of points inside // voxelBounds using a 3D Halton sequence; at each one, sample each // light source and compute a weight based on Li/pdf for the light's // sample (ignoring visibility between the point in the voxel and the // point on the light source) as an approximation to how much the light // is likely to contribute to illumination in the voxel. int nSamples = 128; std::vector<Float> lightContrib(scene.lights.size(), Float(0)); for (int i = 0; i < nSamples; ++i) { Point3f po = voxelBounds.Lerp(Point3f( RadicalInverse(0, i), RadicalInverse(1, i), RadicalInverse(2, i))); Interaction intr(po, Normal3f(), Vector3f(), Vector3f(1, 0, 0), 0 /* time */, MediumInterface()); // Use the next two Halton dimensions to sample a point on the // light source. Point2f u(RadicalInverse(3, i), RadicalInverse(4, i)); for (size_t j = 0; j < scene.lights.size(); ++j) { Float pdf; Vector3f wi; VisibilityTester vis; Spectrum Li = scene.lights[j]->Sample_Li(intr, u, &wi, &pdf, &vis); if (pdf > 0) { // TODO: look at tracing shadow rays / computing beam // transmittance. Probably shouldn't give those full weight // but instead e.g. have an occluded shadow ray scale down // the contribution by 10 or something. lightContrib[j] += Li.y() / pdf; } } } // We don't want to leave any lights with a zero probability; it's // possible that a light contributes to points in the voxel even though // we didn't find such a point when sampling above. Therefore, compute // a minimum (small) weight and ensure that all lights are given at // least the corresponding probability. Float sumContrib = std::accumulate(lightContrib.begin(), lightContrib.end(), Float(0)); Float avgContrib = sumContrib / (nSamples * lightContrib.size()); Float minContrib = (avgContrib > 0) ? .001 * avgContrib : 1; for (size_t i = 0; i < lightContrib.size(); ++i) { VLOG(2) << "Voxel pi = " << pi << ", light " << i << " contrib = " << lightContrib[i]; lightContrib[i] = std::max(lightContrib[i], minContrib); } LOG(INFO) << "Initialized light distribution in voxel pi= " << pi << ", avgContrib = " << avgContrib; // Compute a sampling distribution from the accumulated contributions. return new Distribution1D(&lightContrib[0], lightContrib.size()); }