def Sample_f(self, woW: Vector3d, bsdfSample: BSDFSample, BxDFTypeFlags: int)->(Vector3d, float, int, Spectrum): pdf = 0.0 wiW = Vector3d() BxDFSampledTypeFlags = BxDFType.BSDF_NONE # Choose which _BxDF_ to sample matchingComps = self.NumComponents(BxDFTypeFlags) if matchingComps == 0: BxDFSampledTypeFlags = BxDFType.BSDF_NONE return wiW, pdf, BxDFSampledTypeFlags, Spectrum(0.0) # which = min(Floor2Int(bsdfSample.uComponent * matchingComps), matchingComps-1) which = min(int(bsdfSample.uComponent * matchingComps), matchingComps-1) bxdf = None count = which for b in self.bxdfs: if b.MatchesFlags(BxDFTypeFlags) and count == 0: bxdf = b break count -= 1 # Sample chosen _BxDF_ wo = self.WorldToLocal(woW) # if isinstance(bxdf, Microfacet): # print("dd") wi, pdf, f = bxdf.Sample_f(wo, bsdfSample.uDir) if pdf == 0.0: BxDFSampledTypeFlags = BxDFType.BSDF_NONE return wiW, pdf, BxDFSampledTypeFlags, Spectrum(0.0) if BxDFSampledTypeFlags != BxDFType.BSDF_NONE: BxDFSampledTypeFlags = bxdf.bxdf_types wiW.Set(self.LocalToWorld(wi)) # Compute overall PDF with all matching _BxDF_s if not (bxdf.bxdf_types & BxDFType.BSDF_SPECULAR) and matchingComps > 1: for b in self.bxdfs: if b != bxdf and b.MatchesFlags(BxDFTypeFlags): pdf += b.get_Pdf(wo, wi) if matchingComps > 1: pdf /= matchingComps # Compute value of BSDF for sampled direction if not bxdf.bxdf_types & BxDFType.BSDF_SPECULAR: f = Spectrum(0.0) if Vector3d.dot(wiW, self.ng) * Vector3d.dot(woW, self.ng) > 0: # ignore BTDFs flags = BxDFTypeFlags & ~BxDFType.BSDF_TRANSMISSION else: # ignore BRDFs flags = BxDFTypeFlags & ~BxDFType.BSDF_REFLECTION for i in range(len(self.bxdfs)): if self.bxdfs[i].MatchesFlags(flags): f += self.bxdfs[i].get_f(wo, wi) return wiW, pdf, BxDFSampledTypeFlags, f
def Pdf(self, wo: Vector3d, wi: Vector3d) -> float: from core.bxdf import AbsCosTheta wh = (wo + wi).get_normalized() costheta = AbsCosTheta(wh) # Compute PDF for $\wi$ from Blinn distribution blinn_pdf = ((self.exponent + 1.0) * math.pow(costheta, self.exponent)) / ( 2.0 * CONST_PI * 4.0 * Vector3d.dot(wo, wh)) if Vector3d.dot(wo, wh) <= 0.0: blinn_pdf = 0.0 return blinn_pdf
def f(self, woW: Vector3d, wiW: Vector3d, BxDFTypeFlags: int)->Spectrum: wi = self.WorldToLocal(wiW) wo = self.WorldToLocal(woW) if Vector3d.dot(wiW, self.ng) * Vector3d.dot(woW, self.ng) > 0: # ignore BTDFs flags = BxDFTypeFlags & ~BxDFType.BSDF_TRANSMISSION else: # ignore BRDFs flags = BxDFTypeFlags & ~BxDFType.BSDF_REFLECTION f = Spectrum(0.0) for i in range(len(self.bxdfs)): if self.bxdfs[i].MatchesFlags(flags): f += self.bxdfs[i].get_f(wo, wi) return f
def get_is_intersected(self, ray) -> bool: # ray from word_space_to_object_space ray_o = ray * self.worldToObject denominator = Vector3d.dot(self.normal, ray_o.direction) if math.fabs(denominator) < CONST_EPSILON: return False o = Vector3d.create_from_point3d(ray_o.origin) t = -(Vector3d.dot(self.normal, o) + self.distance) / denominator if ray_o.min_t <= t < ray_o.max_t: return True return False
def Sample2(self, p: Point3d, u: (float, float), Ns: Normal) -> Point3d: # Compute coordinate system for sphere sampling Pcenter = Point3d(0, 0, 0) * self.objectToWorld wc = (Pcenter - p).get_normalized() wcX, wcY = Transform.create_coordinateSystem(wc) # Sample uniformly on sphere if $\pt{}$ is inside it if (p - Pcenter).get_length_squared() - self.radius * self.radius < 1e-40: return self.Sample1(u, Ns) # Sample sphere uniformly inside subtended cone sinThetaMax2 = self.radius * self.radius / (p - Pcenter).get_length_squared() cosThetaMax = math.sqrt(max(0.0, 1.0 - sinThetaMax2)) dgSphere = DifferentialGeometry() thit = 1.0 r = Ray(p, UniformSampleCone2(u, cosThetaMax, wcX, wcY, wc), 1e-3) b, t = self.get_intersection(r, dgSphere) # if not b: #bug thit = Vector3d.dot(Pcenter - p, r.direction.get_normalized()) ps = r.get_at(thit) nn = (ps - Pcenter).get_normalized() Ns.Set(nn) # if (ReverseOrientation) *ns *= -1.f; return ps
def __init__(self, origin: Point3d=Point3d(0.0, 0.0, 0.0), direction:Vector3d=Vector3d.get_forward(), min_t: float=0.0, max_t: float=infinity_max_f, time: float=0.0, depth: int=0): self.origin = origin self.direction = direction self.depth = depth self.max_t = max_t self.min_t = min_t self.time = time
def get_intersection(self, ray: Ray, dg: DifferentialGeometry) -> (bool, float): # ray from word_space_to_object_space ray_o = ray * self.worldToObject denominator = Vector3d.dot(self.normal, ray_o.direction) if math.fabs(denominator) < CONST_EPSILON: return False, 0.0 o = Vector3d.create_from_point3d(ray_o.origin) t = -(Vector3d.dot(self.normal, o) + self.distance) / denominator if ray_o.min_t <= t < ray_o.max_t: dg.point = ray_o.get_at(t) * self.objectToWorld dg.normal = self.normal * self.objectToWorld dg.shape = self return True, t return False, 0.0
def EstimateDirect(scene: Scene, renderer: Renderer, light: Light, p: Point3d, n: Normal, wo: Vector3d, time: float, bsdf: BSDF, lightSample: LightSample, bsdfSample: BSDFSample, BxDFTypeFlag: int) -> Spectrum: Ld = Spectrum(0.0) visibility = VisibilityTester() wi, Li, lightPdf = light.Sample_L1(p, lightSample, time, visibility) if lightPdf > 0.0 and not Li.get_is_black(): f = bsdf.f(wo, wi, BxDFTypeFlag) if not f.get_is_black() and visibility.Unoccluded(scene): # Add light's contribution to reflected radiance #Li *= visibility.Transmittance(scene, renderer, None) inv_lightPdf = 1.0 / lightPdf if light.get_is_delta_light(): Ld += f * Li * math.fabs(Vector3d.dot(wi, n)) * inv_lightPdf else: bsdfPdf = bsdf.get_Pdf(wo, wi, BxDFTypeFlag) weight = PowerHeuristic(1, lightPdf, 1, bsdfPdf) Ld += f * Li * math.fabs(Vector3d.dot(wi, n)) * weight * inv_lightPdf # Sample BSDF with multiple importance sampling if not light.get_is_delta_light(): wi, bsdfPdf, sampledType, f = bsdf.Sample_f(wo, bsdfSample, BxDFTypeFlag) if not f.get_is_black() and bsdfPdf > 0.0: weight = 1.0 if not (BxDFTypeFlag & BxDFType.BSDF_SPECULAR): lightPdf = light.get_pdf(p, wi) if lightPdf == 0.0: return Ld weight = PowerHeuristic(1, bsdfPdf, 1, lightPdf) # Add light contribution from BSDF sampling lightIsect = Intersection() Li = Spectrum(0.0) ray = Ray(p, wi, 1e-3, infinity_max_f, time) if scene.get_intersection(ray, lightIsect): if lightIsect.primitive.GetAreaLight() == light: Li = lightIsect.Le(-wi) else: Li = light.Le(ray) if not Li.get_is_black(): Li *= 1.0 #todo renderer.Transmittance(scene, ray, None) Ld += f * Li * math.fabs(Vector3d.dot(wi, n)) * weight / bsdfPdf return Ld
def Area(self): # Get triangle vertices in _p1_, _p2_, and _p3_ p1_index = self.mesh.index[self.triangle_index*3 + 0] p2_index = self.mesh.index[self.triangle_index*3 + 2] p3_index = self.mesh.index[self.triangle_index*3 + 1] p1 = self.mesh.points[p1_index] p2 = self.mesh.points[p2_index] p3 = self.mesh.points[p3_index] return 0.5 * Vector3d.cross(p2-p1, p3-p1).get_length()
def Sample_f(self, wo: Vector3d, u: (float, float)) -> (Vector3d, float): from core.bxdf import SameHemisphere # Compute sampled half-angle vector $\wh$ for Blinn distribution costheta = math.pow(u[0], 1.0 / (self.exponent + 1)) sintheta = math.sqrt(max(0.0, 1.0 - costheta * costheta)) phi = u[1] * 2.0 * CONST_PI wh = SphericalDirection1(sintheta, costheta, phi) if not SameHemisphere(wo, wh): wh = -wh # Compute incident direction by reflecting about $\wh$ uu = wh * Vector3d.dot(wo, wh) wi = -wo + (uu * 2.0) # Compute PDF for $\wi$ from Blinn distribution blinn_pdf = ((self.exponent + 1.0) * math.pow(costheta, self.exponent)) / ( 2.0 * CONST_PI * 4.0 * Vector3d.dot(wo, wh)) if Vector3d.dot(wo, wh) <= 0.0: blinn_pdf = 0.0 return wi, blinn_pdf
def Sample_L2(self, scene: Scene, ls: LightSample, u: (float, float), n: Normal, ray: Ray, time: float) -> ( Spectrum, float): origin = self.shapeSet.Sample2(ls, n) direction = UniformSampleSphere(u) if Vector3d.dot(direction, n) < 0.0: direction *= -1.0 ray.Set(Ray(origin, direction, 1e-3, infinity_max_f, time)) Ls = self.L(origin, n, direction) pdf = self.shapeSet.Pdf1(origin) * CONST_INV_TWOPI return ray, Ls, pdf
def get_f(self, wo: Vector3d, wi: Vector3d)->Spectrum: cosThetaO = AbsCosTheta(wo) cosThetaI = AbsCosTheta(wi) if cosThetaI == 0.0 or cosThetaO == 0.0: return Spectrum(0.0) wh = wi + wo if wh.x == 0.0 and wh.y == 0.0 and wh.z == 0.0: return Spectrum(0.0) wh = wh.get_normalized() cosThetaH = Vector3d.dot(wi, wh) F = self.fresnel.Evaluate(cosThetaH) return self.r * self.distribution.D(wh) * self.G(wo, wi, wh) * F / (4.0 * cosThetaI * cosThetaO)
def test__look_at(self): from core.transform import Transform from maths.point3d import Point3d from maths.vector3d import Vector3d pos = Point3d(1.0, 0.0, 0.0) at = Point3d(1.0, 0.0, 1.0) up = Vector3d.get_up() t = Transform.create_look_at(pos, at, up) foo = Vector3d(1.0, 0.0, 0.0) self.assertEqual(foo * t, Vector3d(0.0, 0.0, 0.0))
def Pdf2(self, p: Point3d, wi: Vector3d)->float: # Intersect sample ray with area light geometry dgLight = DifferentialGeometry() ray = Ray(p, wi, 1e-3) ray.depth = -1 # temporary hack to ignore alpha mask b, thit =self.get_intersection(ray, dgLight) if not b: return 0.0 # Convert light sample weight to solid angle measure pdf = (p - ray.get_at(thit)).get_length_squared() / (math.fabs(Vector3d.dot(dgLight.normal, -wi) * self.Area())) if math.isinf(pdf): pdf = 0.0 return pdf
def internal_solve(self, ray_l: Ray, ray_w: Ray) -> (float, float): o = Vector3d.create_from_point3d(ray_l.origin) a = Vector3d.dot(ray_l.direction, ray_l.direction) b = 2.0 * Vector3d.dot(ray_l.direction, o) c = Vector3d.dot(o, o) - self.radius_squared # Solve quadratic equation for _t_ values t0, t1 = maths.tools.get_solve_quadratic(a, b, c) if t0 is None and t1 is None: return None, None # Compute intersection distance along ray if t0 > ray_w.max_t or t1 < ray_w.min_t: return None, None thit = t0 if t0 < ray_w.min_t: thit = t1 if thit > ray_w.max_t: return None, None return t0, t1
def Sample1(self, u: (float, float), n: Normal) -> Point3d: b1, b2 = UniformSampleTriangle(u) # Get triangle vertices in _p1_, _p2_, and _p3_ p1_index = self.mesh.index[self.triangle_index*3 + 0] p2_index = self.mesh.index[self.triangle_index*3 + 2] p3_index = self.mesh.index[self.triangle_index*3 + 1] p1 = self.mesh.points[p1_index] p2 = self.mesh.points[p2_index] p3 = self.mesh.points[p3_index] p = p1 * b1 + p2 * b2 + p3 * (1.0 - b1 - b2) n.Set(Normal.create_from_vector3d(Vector3d.cross(p2-p1, p3-p1)).get_normalized()) #todo if (ReverseOrientation) *Ns *= -1.f; return p
def get_map(self, dg: DifferentialGeometry): vec = dg.point - Point3d(0,0,0) s = self.ds + Vector3d.dot(vec, self.vs) t = self.dt + Vector3d.dot(vec, self.vt) dsdx = Vector3d.dot(dg.dpdx, self.vs) dtdx = Vector3d.dot(dg.dpdx, self.vt) dsdy = Vector3d.dot(dg.dpdy, self.vs) dtdy = Vector3d.dot(dg.dpdy, self.vt) return s,t ,dsdx, dtdx, dsdy, dtdy
def Li(self, scene: Scene, renderer: Renderer, ray: Ray, intersection: Intersection, sample: Sample)->Spectrum: occlusion = 0 intersection.ray_epsilon = infinity_max_f bsdf = intersection.get_bsdf(ray) p = bsdf.dgShading.point n = maths.tools.get_face_forward(intersection.differentialGeometry.normal, -ray.direction) for i in range(self.samples_count): w = maths.tools.get_uniform_sample_sphere(random(), random()) if Vector3d.dot(w, n) < 0.0: w = -w r = Ray(p, w, 0.01, self.max_distance) if scene.get_is_intersected(r): occlusion += 1 return Spectrum(1.0-(float(occlusion) / float(self.samples_count)))
def get_intersection(self, ray: Ray, dg: DifferentialGeometry) -> (bool, float): p1_index = self.mesh.index[self.triangle_index*3 + 0] p2_index = self.mesh.index[self.triangle_index*3 + 2] p3_index = self.mesh.index[self.triangle_index*3 + 1] p1 = self.mesh.points[p1_index] p2 = self.mesh.points[p2_index] p3 = self.mesh.points[p3_index] e1 = p2 - p1 e2 = p3 - p1 s1 = Vector3d.cross(ray.direction, e2) divisor = Vector3d.dot(s1, e1) if divisor == 0.0: return False, 0.0 invDivisor = 1.0 / divisor # Compute first barycentric coordinate d = ray.origin - p1 b1 = Vector3d.dot(d, s1) * invDivisor if b1 < 0.0 or b1 > 1.0: return False, 0.0 #Compute second barycentric coordinate s2 = Vector3d.cross(d, e1) b2 = Vector3d.dot(ray.direction, s2) * invDivisor if b2 < 0.0 or (b1 + b2) > 1.0: return False, 0.0 # Compute _t_ to intersection point t = Vector3d.dot(e2, s2) * invDivisor if t < ray.min_t or t > ray.max_t: return False, 0.0 dg.shape = self dg.point = ray.get_at(t) dg.normal = Normal.create_from_vector3d( Vector3d.cross(e1, e2).get_normalized()) return True, t
def get_is_intersected(self, ray) -> bool: p1_index = self.mesh.index[self.triangle_index*3 + 0] p2_index = self.mesh.index[self.triangle_index*3 + 2] p3_index = self.mesh.index[self.triangle_index*3 + 1] p1 = self.mesh.points[p1_index] p2 = self.mesh.points[p2_index] p3 = self.mesh.points[p3_index] e1 = p2 - p1 e2 = p3 - p1 s1 = Vector3d.cross(ray.direction, e2) divisor = Vector3d.dot(s1, e1) if divisor == 0.0: return False invDivisor = 1.0 / divisor # Compute first barycentric coordinate d = ray.origin - p1 b1 = Vector3d.dot(d, s1) * invDivisor if b1 < 0.0 or b1 > 1.0: return False #Compute second barycentric coordinate s2 = Vector3d.cross(d, e1) b2 = Vector3d.dot(ray.direction, s2) * invDivisor if b2 < 0.0 or (b1 + b2) > 1.0: return False # Compute _t_ to intersection point t = Vector3d.dot(e2, s2) * invDivisor if t < ray.min_t or t > ray.max_t: return False return True
def Li(self, scene, renderer: Renderer, r: Ray, intersection: Intersection, sample: Sample) -> Spectrum: # Declare common path integration variables pathThroughput = Spectrum(1.0) L = Spectrum(0.0) ray = deepcopy(r) specularBounce = False localIsect = Intersection() isectp = intersection bounces = 0 while True: # Possibly add emitted light at path vertex if bounces == 0 or specularBounce: L += pathThroughput * isectp.Le(-ray.direction) # Sample illumination from lights to find path contribution bsdf = isectp.get_bsdf(ray) p = bsdf.dgShading.point n = bsdf.dgShading.normal wo = -ray.direction if bounces < self.maxDepth: L += pathThroughput * UniformSampleOneLight(scene, renderer, p, n, wo, ray.time, bsdf, sample, self.lightSampleOffsets[bounces], self.bsdfSampleOffsets[bounces], self.lightNumOffset[bounces]) else: L += pathThroughput * UniformSampleOneLight(scene, renderer, p, n, wo, ray.time, bsdf, sample) # Sample BSDF to get new path direction # Get _outgoingBSDFSample_ for sampling new path direction if bounces < self.maxDepth: outgoingBSDFSample = BSDFSample.create_from_sample(sample, self.pathSampleOffsets[bounces], 0) else: outgoingBSDFSample = BSDFSample.create_from_random() wi, pdf, flags, f = bsdf.Sample_f(wo, outgoingBSDFSample, BxDFType.BSDF_ALL) if f.get_is_black() or pdf == 0.0: break specularBounce = (flags & BxDFType.BSDF_SPECULAR) != 0 pathThroughput *= f * math.fabs(Vector3d.dot(wi, n)) / pdf ray = Ray(p, wi, 0.01, ray.max_t) # Possibly terminate the path if bounces > 3: continueProbability = min(0.5, pathThroughput.y()) if random() > continueProbability: break pathThroughput /= continueProbability if bounces == self.maxDepth: break # Find next vertex of path if not scene.get_intersection(ray, localIsect): if specularBounce: for i in range(scene.lights.size()): L += pathThroughput * scene.lights[i].get_Le(ray) break # pathThroughput *= renderer->Transmittance(scene, ray, NULL, rng, arena); isectp = localIsect bounces += 1 return L
def WorldToLocal(self, v: Vector3d)->Vector3d: return Vector3d(Vector3d.dot(v, self.sn), Vector3d.dot(v, self.tn), Vector3d.dot(v, self.nn))
def G(self, wo: Vector3d, wi: Vector3d, wh: Vector3d): NdotWh = AbsCosTheta(wh) NdotWo = AbsCosTheta(wo) NdotWi = AbsCosTheta(wi) WOdotWh = abs(Vector3d.dot(wo, wh)) return min(1.0, min((2.0 * NdotWh * NdotWo / WOdotWh), 2.0 * NdotWh * NdotWi / WOdotWh))
def __init__(self, o2w, w2o): super().__init__(o2w, w2o) self.normal = Vector3d.get_up() self.distance = 0.0
def test_dot(self): foo1 = Vector3d(1.0, 2.0, 3.0) foo2 = Vector3d(10.0, 20.0, 30.0) foo3 = Vector3d.dot(foo1,foo2) self.assertEqual(foo3, foo1.x * foo2.x + foo1.y * foo2.y + foo1.z * foo2.z)
def L(self, p: Point3d, n: Normal, w: Vector3d) -> Spectrum: if Vector3d.dot(n, w) > 0.0: return self.Lemit return Spectrum(0.0)