def __init__(self, diffuseColor=Vector(0.18, 0.18, 0.18), diffuseWeight=1.0, reflectionColor=Vector(1, 1, 1), reflectionWeight=0, reflectionRoughness=0, refractionColor=Vector(1, 1, 1), refractionWeight=0, refractionIndex=1.5, emissionColor=Vector(1, 1, 1), emissionAmount=0): # Default material values self.diffuseColor = diffuseColor self.diffuseWeight = diffuseWeight self.reflectionColor = reflectionColor self.reflectionWeight = reflectionWeight self.reflectionRoughness = reflectionRoughness self.refractionColor = refractionColor self.refractionWeight = refractionWeight self.refractionIndex = refractionIndex self.emissionColor = emissionColor self.emissionAmount = emissionAmount
def getMirrorReflectionColor(self, currObj, prevHitPos, hitResult, indirectDepth, reflectDepth, reflectDist): reflectionCol = Vector(0, 0, 0) reflectDepth += 1 # If this object's material is perfect mirror, and maybe the reflected ray hits mirror again incomingVec = (prevHitPos - hitResult[1]).normalized() reflectRayDir = incomingVec.rot("A", math.radians(180), hitResult[2]) biasedOrigin = hitResult[1] + reflectRayDir * self.bias reflectionRay = Ray(biasedOrigin, reflectRayDir) reflectHitResult = [] reflectionHitBool = self.scene.getClosestIntersection( reflectionRay, reflectHitResult) if reflectionHitBool: reflectDist = reflectDist + ( reflectHitResult[1] - hitResult[1] ).length() # accumulation of distance of reflection prevHitPos = hitResult[1] hitResult = reflectHitResult if reflectDepth < self.reflectionMaxDepth: reflectionCol = reflectionCol + self.getColor( hitResult, prevHitPos, indirectDepth=indirectDepth, reflectDepth=reflectDepth, reflectDist=reflectDist).colorMult( currObj.material.reflectionColor) else: reflectionCol = reflectionCol + self.getHitPointColor( hitResult).colorMult(currObj.material.reflectionColor) return reflectionCol
def getHitPointColor(self, hitResult): litColor = Vector(0, 0, 0) #color accumulated after being lit #iterate through all the lights using shadow ray, check if object is in shadow for eachLight in self.scene.lights: for i in range(eachLight.samples): if "AreaLight" in eachLight.type: shadowRayDir = eachLight.getRandomSample() - hitResult[1] if eachLight.isDoubleSided == False: #single side area light hitLightBack = shadowRayDir.normalized().dot( eachLight.normal) if hitLightBack > 0: break #this is a cheat, only works for disk of planeLight #continue else: shadowRayDir = eachLight.pos - hitResult[1] #lambert is the cosine lambert = hitResult[2].dot(shadowRayDir.normalized()) facingCam = hitResult[2].dot( (self.cam.pos - hitResult[1]).normalized()) if lambert < 0 and facingCam < 0: #poly is not facing the light or cam lambert = abs(lambert) if lambert > 0: #poly is facing the light offsetOrigin = hitResult[1] + shadowRayDir.normalized( ) * self.bias #slightly offset the ray start point because the origin itself is a root shadowRay = Ray(offsetOrigin, shadowRayDir) temp_t = shadowRayDir.length( ) #length form hit point to light shadowRayResult = [temp_t] inShadow = self.scene.getClosestIntersection( shadowRay, shadowRayResult, eachLight) if not inShadow: litColor = litColor + eachLight.color * ( eachLight.intensity * lambert / (4 * math.pi * math.pow(temp_t, 2))) litColorAvg = litColor / eachLight.samples * eachLight.area matColor = self.scene.getObjectById(hitResult[3]).material.diffuseColor return Vector(matColor.x * litColorAvg.x, matColor.y * litColorAvg.y, matColor.z * litColorAvg.z)
def __init__(self, pos, radius, intensity=6, color=Vector(1, 1, 1), samples=8, normal=Vector(0, -1, 0), isDoubleSided=False, visible=True): Disk.__init__(self, pos, radius, normal) Light.__init__(self, pos, intensity, color) self.type = "AreaLight_Disk" self.radius = radius self.samples = samples self.normal = normal self.isDoubleSided = isDoubleSided self.visible = visible self.area = math.pi * math.pow(radius, 2)
def __init__(self, pos, radius, intensity=10, color=Vector(1, 1, 1), samples=8): super().__init__(pos, intensity, color) self.type = "AreaLight_Rectangle" self.radius = radius self.samples = samples
def getRandomSample(self): theta = random.random() * 2 * math.pi # range [0,2pi) u = random.random() + random.random() # Better sampling method if u > 1: multiplier = 2 - u else: multiplier = u return self.pos + Vector(math.cos(theta) * self.radius * multiplier, 0, math.sin(theta) * self.radius * multiplier)
def getRandomSample(self): #generate a sample point on the disk # samplePtLi = [] # for i in range(self.samples): theta = random.random() * math.pi #range [0,2pi) multiplier = random.random() #range [0,1) randPointOnDisk = self.pos + Vector( math.cos(theta) * self.radius * multiplier, 0, math.sin(theta) * self.radius * multiplier) #samplePtLi.append(randPointOnDisk) return randPointOnDisk
def getRandomPointOnLens(self): theta = random.random() * 2 * math.pi # [0,2pi) u = random.random() + random.random() # Better sampling method if u > 1: multiplier = 2 - u else: multiplier = u randPointOnLens = self.pos + Vector(math.cos(theta) * self.pupilDiameter * 0.5 * multiplier, math.sin(theta) * self.pupilDiameter * 0.5 * multiplier, 0) return randPointOnLens
def run(self): #bucket rendered color data is stored in an array bucketArray = numpy.ndarray(shape=(self.bucketSize,self.bucketSize,3),dtype = numpy.float) bucketArray.fill(0) #Very import need to set default color #shoot multiple rays each pixel for anti-aliasing #--deconstruct self.AAsamples--------- AAxSubstep = int(math.floor(math.sqrt(self.AAsamples))) AAxSubstepLen = 1.0 / AAxSubstep AAySubstep = int(self.AAsamples / AAxSubstep) AAySubstepLen = 1.0 / AAySubstep AAsampleGrid = [] for AAy in range(0,AAySubstep): for AAx in range(0,AAxSubstep): AAsingleOffset = [AAx * AAxSubstepLen + AAxSubstepLen/2,AAy * AAySubstepLen + AAySubstepLen/2] AAsampleGrid.append(AAsingleOffset) timerStart = datetime.now() bucketResult = [] #-------Process keeps getting new bucket------------------------- while self.getNextBucket(bucketResult): bucketX = bucketResult[0] bucketY = bucketResult[1] thisAAoffset = bucketResult[2] bucketResult.clear() #-------------shoot rays--------------------------------------- #----Each bucket level-------------- for j in range(bucketY,bucketY + self.bucketSize): #----Each line of pixels level-------------- for i in range(bucketX,bucketX + self.bucketSize): #--------Each pixel level-------------------- col = Vector(0,0,0) rayDir = Vector(i + AAsampleGrid[thisAAoffset][0] - self.width/2, -j - AAsampleGrid[thisAAoffset][1] + self.height/2, -0.5*self.width/math.tan(math.radians(self.cam.angle/2))) #Warning!!!!! Convert to radian!!!!!!! camRay = Ray(self.cam.pos,rayDir) #hitResult is a list storing calculated data [hit_t, hit_pos,hit_normal,objectId] hitResult = [] hitBool = self.scene.getClosestIntersection(camRay,hitResult) if hitBool: prevHitPos = self.cam.pos col = col + self.getColor(hitResult,prevHitPos) bucketArray[j%self.bucketSize,i%self.bucketSize] = [col.x,col.y,col.z] returnData = [bucketX,bucketY,bucketArray,thisAAoffset+1,self.AAsamples] print("bucket" + str(bucketX) + ":" + str(bucketY) + " Rendered by " + multiprocessing.current_process().name) #----get the next bucket-------- self.outputQ.put(returnData) self.outputQ.put("Done") timerEnd = datetime.now() processRenderTime = timerEnd - timerStart print("Process Finished - " + multiprocessing.current_process().name + " Render time: " + str(processRenderTime)) sys.stdout.flush()
def getColor(self,hitResult,prevHitPos,indirectDepth=0,reflectDepth=0,refractDepth=0,reflectDist=0): #Important, when calculating refraction/mirror reflection, pass indirectDepthLimit to getColor to avoid infinite loop currObj = self.scene.getObjectById(hitResult[3]) hitPointColor = Vector(0,0,0) if "AreaLight" in currObj.type: #if this object is AreaLight, return lightColor * lightIntensity if currObj.visible: #if hit a light, return color is attenuated. And will be clipped in renderThread if indirectDepth == 0: #camera ray, no attenuation litColor = currObj.color * currObj.intensity else: #indirect ray, return attenuated value litColor = currObj.color * currObj.intensity / (4*math.pi*math.pow(reflectDist,2)) hitPointColor = hitPointColor + litColor return hitPointColor if currObj.material.refractionWeight == 1 and currObj.material.reflectionWeight == 1: #if this object is glass hitPointColor = hitPointColor + self.getRefractionColor(currObj,prevHitPos,hitResult,indirectDepth,refractDepth,reflectDepth,reflectDist) elif currObj.material.refractionWeight != 1 and currObj.material.reflectionWeight == 1: #If this object is perfect mirror hitPointColor = hitPointColor + self.getMirrorReflectionColor(currObj,prevHitPos,hitResult,indirectDepth,reflectDepth,reflectDist) else: #Diffuse material hitPointColor = hitPointColor + self.getHitPointColor(hitResult) #Recurvsive path tracing, only for Diffuse material-------------------- if indirectDepth < self.indirectDepthLimit: indirectDepth += 1 incomingRayDir = hitResult[1] - prevHitPos tangentAxis = incomingRayDir.cross(hitResult[2]).normalized() biTangentAxis = hitResult[2].cross(tangentAxis).normalized() indirectColor = Vector(0,0,0) currentObj = self.scene.getObjectById(hitResult[3]) for i in range(self.indirectSamples): tangentRotAmount = random.random()*0.5*math.pi #range: [0,0.5pi) biTrangentRotAmount = random.random()*2*math.pi #range: [0,pi) indirectRayDir = hitResult[2].rot("A",tangentRotAmount,tangentAxis).rot("A",biTrangentRotAmount,hitResult[2]) biasedOrigin = hitResult[1] + indirectRayDir * self.bias indirectRay = Ray(biasedOrigin,indirectRayDir) indirectHitResult = [] indirectHitBool = self.scene.getClosestIntersection(indirectRay,indirectHitResult) if indirectHitBool: indirectPointDist = (indirectHitResult[1] - hitResult[1]).length() reflectDist = reflectDist + indirectPointDist indirectHitPColor = self.getColor(indirectHitResult,hitResult[1],indirectDepth,0,0,reflectDist) #get the indirect color lambert = hitResult[2].dot(indirectRayDir) indirectLitColor = indirectHitPColor * lambert #/ (2*math.pi*math.pow(indirectPointDist,2)) indirectColor = indirectColor + indirectLitColor indirectColor = indirectColor / self.indirectSamples * 2 * math.pi matColor = currentObj.material.diffuseColor hitPointColor = hitPointColor + indirectColor.colorMult(matColor) * 0.3 #arbitrary contribution multiplier return hitPointColor
def getRefractionColor(self,currObj,prevHitPos,hitResult,indirectDepth,refractDepth,reflectDepth,reflectDist): refractDepth += 1 refractionCol = Vector(0,0,0) reflectionCol = Vector(0,0,0) fresnelCol = Vector(0,0,0) fresnelRefract = 1 fresnelReflect = 0 incomingVec = (prevHitPos - hitResult[1]).normalized() #It's actually the inverse direction of incoming ray incomingCos = incomingVec.dot(hitResult[2]) ior = self.scene.getObjectById(hitResult[3]).material.refractionIndex rotAxis = hitResult[2].cross(incomingVec).normalized() if incomingCos >= 0 and incomingCos < 1: #When ray is entering another medium refractAngle = math.asin(math.sqrt(1-math.pow(incomingCos,2))/ior) refractRayDir = (hitResult[2]*(-1)).rot("A",refractAngle,rotAxis) refractCos = math.cos(refractAngle) elif incomingCos > -1 and incomingCos < 0: #When ray is leaving the medium refractAngleMultIor = math.sqrt(1-math.pow(incomingCos,2))*ior if refractAngleMultIor > 1: #Critical angle, total internal reflection refractRayDir = incomingVec.rot("A",math.radians(180),hitResult[2]) refractCos = 0 else: refractAngle = math.asin(refractAngleMultIor) refractRayDir = hitResult[2].rot("A",-refractAngle,rotAxis) refractCos = math.cos(refractAngle) else: #incoming ray is perpendicular to the surface refractCos = 1 refractRayDir = incomingVec * (-1) biasedOrigin = hitResult[1] + refractRayDir * self.bias refractionRay = Ray(biasedOrigin,refractRayDir) refractHitResult = [] refractionHitBool = self.scene.getClosestIntersection(refractionRay,refractHitResult) if refractDepth == 1: #Calculate fresnel, but only the first refraction fresnelS = math.pow((incomingCos - ior*refractCos) / (incomingCos + ior*refractCos),2) fresnelP = math.pow((refractCos - ior*incomingCos) / (ior*incomingCos + refractCos),2) fresnelReflect = 0.5 * (fresnelS + fresnelP) fresnelRefract = 1 - fresnelReflect #Get the reflection color reflectionCol = reflectionCol + self.getMirrorReflectionColor(currObj,prevHitPos,hitResult,indirectDepth,reflectDepth,reflectDist) if refractionHitBool: reflectDist = reflectDist + (refractHitResult[1] - hitResult[1]).length() #accumulation of distance of reflection prevHitPos = hitResult[1] hitResult = refractHitResult if refractDepth < self.refractionMaxDepth: refractionCol = refractionCol + self.getColor(hitResult,prevHitPos,indirectDepth=indirectDepth,refractDepth=refractDepth,reflectDist=reflectDist) else: refractionCol = refractionCol + self.getHitPointColor(hitResult) fresnelCol = fresnelCol + refractionCol * fresnelRefract + reflectionCol*fresnelReflect return fresnelCol
def __init__(self, pos, intensity=5000, color=Vector(1, 1, 1)): super().__init__(pos, intensity, color) self.type = "Point" + self.type self.samples = 1
def main(): renderApp = QApplication(sys.argv) renderView = RenderWindow() redLambert = Material(diffuseColor=Vector(0.9, 0.1, 0.1)) blueLambert = Material(diffuseColor=Vector(0, 0, 0.9)) greenLambert = Material(diffuseColor=Vector(0.1, 0.9, 0.1)) whiteLambert = Material(diffuseColor=Vector(0.9, 0.9, 0.9)) yellowLambert = Material(diffuseColor=Vector(0.95, 0.4, 0.0)) lightBlueLambert = Material(diffuseColor=Vector(0.1, 0.5, 0.9)) mirror = Material(reflectionColor=Vector(1, 1, 1), reflectionWeight=1) redMirror = Material(reflectionColor=Vector(0.9, 0, 0), reflectionWeight=1) emissive = Material(emissionAmount=500) glass = Material(refractionWeight=1, reflectionWeight=1) sphere02 = Sphere(Vector(10, -20, -146), 30, material=mirror) sphere03 = Sphere(Vector(-25, -35, -115), 15, material=redMirror) sphere04 = Sphere(Vector(25, -35, -100), 15, material=glass) plane01 = Plane(Vector(0, -50, -136), Vector(0, 1, 0), material=whiteLambert) # bottom wall plane02 = Plane(Vector(-50, 0, -136), Vector(1, 0, 0), material=yellowLambert) # left wall plane03 = Plane(Vector(0, 0, -186), Vector(0, 0, 1), material=whiteLambert) # back wall plane04 = Plane(Vector(50, 0, -136), Vector(-1, 0, 0), material=lightBlueLambert) # right wall plane05 = Plane(Vector(0, 50, -136), Vector(0, -1, 0), material=emissive) # top wall light01 = DiskLight(Vector(0, 48, -136), 30, normal=Vector(0, -1, 0), samples=1, isDoubleSided=True, visible=True) newScene = Scene({ "geometry": [ plane01, plane02, plane03, plane04, plane05, sphere02, sphere03, sphere04 ], "light": [light01] }) teleCam = Camera(Vector(0, 0, 130), Vector(0, 0, 1), 80, aperture=1.4, focusDist=243, filmFit="Horizontal") renderView.startRender(newScene, teleCam) sys.exit(renderApp.exec_())
def main(): renderApp = QApplication(sys.argv) renderView = RenderWindow() #All setting loaded from json file #--------------------Scene Modeling------------------------------- #Materials-------------------------------------------- redLambert = Material(diffuseColor=Vector(0.9,0.1,0.1)) blueLambert = Material(diffuseColor=Vector(0,0,0.9)) greenLambert = Material(diffuseColor=Vector(0.1,0.9,0.1)) whiteLambert = Material(diffuseColor=Vector(0.9,0.9,0.9)) mirror = Material(reflectionColor=Vector(1,1,1),reflectionWeight=1) redMirror = Material(reflectionColor=Vector(0.9,0,0),reflectionWeight=1) emissive = Material(emissionAmount=500) glass = Material(refractionWeight=1,reflectionWeight=1) #Geometries---------------------------------------------- #important! This is a right handed coordinate system! sphere01 = Sphere(Vector(-15,-30,-136),20,material=whiteLambert) sphere02 = Sphere(Vector(10,-20,-146),30,material=mirror) sphere03 = Sphere(Vector(-25,-35,-115),15,material=glass) sphere04 = Sphere(Vector(25,-35,-100),15,material=whiteLambert) #plane01 = Plane(Vector(0,-50,-136),Vector(0,1,0),material=whiteLambert) #bottom wall #plane02 = Plane(Vector(-50,0,-136),Vector(1,0,0),material=redLambert) #left wall #plane03 = Plane(Vector(0,0,-186),Vector(0,0,1),material=whiteLambert) #back wall #plane04 = Plane(Vector(50,0,-136),Vector(-1,0,0),material=greenLambert) #right wall #plane05 = Plane(Vector(0,50,-136),Vector(0,-1,0),material=whiteLambert) #top wall tri01 = Triangle(Vector(30,40,-136),Vector(-10,20,-136),Vector(50,20,-156),material=glass) tri02 = Triangle(Vector(30,40,-146),Vector(50,20,-166),Vector(-10,20,-146),material=glass) disk01 = Disk(Vector(-30,30,-136),15,Vector(1,0,0),material=blueLambert) quad01 = Quad(Vector(-50,-50,-186),Vector(-50,-50,-76),Vector(50,-50,-76),Vector(50,-50,-186),material=whiteLambert) #bottom wall quad02 = Quad(Vector(-50,50,-76),Vector(-50,-50,-76),Vector(-50,-50,-186),Vector(-50,50,-186),material=redLambert) #left wall quad03 = Quad(Vector(-50,50,-186),Vector(-50,-50,-186),Vector(50,-50,-186),Vector(50,50,-186),material=whiteLambert) #back wall quad04 = Quad(Vector(50,50,-186),Vector(50,-50,-186),Vector(50,-50,-76),Vector(50,50,-76),material=greenLambert) #right wall quad05 = Quad(Vector(-50,50,-76),Vector(-50,50,-186),Vector(50,50,-186),Vector(50,50,-76),material=emissive) #top wall quad06 = Quad(Vector(-50,20,-76),Vector(-50,20,-186),Vector(30,20,-186),Vector(30,20,-76),material=whiteLambert) #top matte #Lights------------------------------------------------------- light01 = DiskLight(Vector(0,48,-136),30,normal=Vector(0,-1,0),samples=8,isDoubleSided=True,visible=True) #light source on the top light02 = PointLight(Vector(-20,40,-120)) light03 = PointLight(Vector(20,30,-90)) newScene = Scene({"geometry":[quad01,quad02,quad03,quad04,quad05,sphere02,sphere03,sphere04],"light":[light01]}) cam = Camera(Vector(0,0,0),Vector(0,0,1),60) renderView.startRender(newScene,cam) sys.exit(renderApp.exec_())