def observe(self): obj_node = self.car_env._car_local_node cam_node = self.camera_node bounds = BoundingBox(*obj_node.getTightBounds()) corners_XYZ = np.array(list(itertools.product(*zip(bounds.getMin(), bounds.getMax())))) obj_to_cam_T = obj_node.getParent().getMat(cam_node) corners_XYZ = np.array([obj_to_cam_T.xform(Point3(*corner_XYZ)) for corner_XYZ in corners_XYZ])[:, :3] obs = super(Bbox3dSimpleQuadPanda3dEnv, self).observe() obs['points'] = corners_XYZ return obs
def NormalizedSquarePatchAABB(radius, x0, y0, x1, y1, offset=None, x_inverted=False, y_inverted=False, xy_swap=False): a = NormalizedSquarePatchPoint(radius, 0, 0, x0, y0, x1, y1, offset, x_inverted, y_inverted, xy_swap) b = NormalizedSquarePatchPoint(radius, 1, 0, x0, y0, x1, y1, offset, x_inverted, y_inverted, xy_swap) c = NormalizedSquarePatchPoint(radius, 1, 1, x0, y0, x1, y1, offset, x_inverted, y_inverted, xy_swap) d = NormalizedSquarePatchPoint(radius, 0, 1, x0, y0, x1, y1, offset, x_inverted, y_inverted, xy_swap) m = NormalizedSquarePatchPoint(radius, 0.5, 0.5, x0, y0, x1, y1, offset, x_inverted, y_inverted, xy_swap) x_min = min(a[0], b[0], c[0], d[0], m[0]) y_min = min(a[1], b[1], c[1], d[1], m[1]) z_min = min(a[2], b[2], c[2], d[2], m[2]) x_max = max(a[0], b[0], c[0], d[0], m[0]) y_max = max(a[1], b[1], c[1], d[1], m[1]) z_max = max(a[2], b[2], c[2], d[2], m[2]) return BoundingBox(LPoint3(x_min, y_min, z_min), LPoint3(x_max, y_max, z_max))
def recalcBoundingBox(self): if not self.np: return # Don't have the picker box or selection visualization contribute to the # calculation of the bounding box. if self.collNp: self.collNp.stash() self.hideBoundingBox() # Calculate a bounding box relative to ourself mins, maxs = self.getBounds(self.np) invalid, mins, maxs = self.fixBounds(mins, maxs) if invalid: mins = Point3(-8) maxs = Point3(8) self.boundingBox = BoundingBox(mins, maxs) self.boundsBox.setMinMax(mins, maxs) if self.selected: self.showBoundingBox() if self.collNp: self.collNp.unstash() self.collNp.node().clearSolids() self.collNp.node().addSolid(CollisionBox(mins, maxs)) self.collNp.hide(~VIEWPORT_3D_MASK) self.send('mapObjectBoundsChanged', [self])
def __init__(self, id): MapObjectInit.start() MapWritable.__init__(self, base.document) self.temporary = False self.id = id self.selected = False self.classname = "" self.parent = None self.children = {} self.boundingBox = BoundingBox(Vec3(-0.5, -0.5, -0.5), Vec3(0.5, 0.5, 0.5)) self.boundsBox = Box() self.boundsBox.addView(GeomView.Lines, VIEWPORT_3D_MASK, state = BoundsBox3DState) self.boundsBox.addView(GeomView.Lines, VIEWPORT_2D_MASK, state = BoundsBox2DState) self.boundsBox.generateGeometry() self.collNp = None self.group = None self.properties = {} # All MapObjects have transform self.addProperty(OriginProperty(self)) self.addProperty(AnglesProperty(self)) self.addProperty(ScaleProperty(self)) self.addProperty(ShearProperty(self)) self.np = NodePath(ModelNode(self.ObjectName + ".%i" % self.id)) self.np.setPythonTag("mapobject", self) self.applyCollideMask() # Test bounding volume at this node and but nothing below it. self.np.node().setFinal(True) MapObjectInit.stop()
def is_in_view(cam_node, obj_node, scale_size=None, crop_size=None): """ Returns the intersection flag between the camera's frustrum and the object's tight bounding box. https://www.panda3d.org/forums/viewtopic.php?t=11704 Intersection flags are defined in here: https://github.com/panda3d/panda3d/blob/master/panda/src/mathutil/boundingVolume.h """ lens_bounds = make_bounds(cam_node.node().getLens(), scale_size=scale_size, crop_size=crop_size) bounds = BoundingBox(*obj_node.getTightBounds()) bounds.xform(obj_node.getParent().getMat(cam_node)) return lens_bounds.contains(bounds)
def halfSphereAABB(height, positive, offset): if positive: min_points = LPoint3(-1, 0 - offset, -1) max_points = LPoint3(1, 1 - offset, 1) else: min_points = LPoint3(-1, offset - 1, -1) max_points = LPoint3(1, offset, 1) return BoundingBox(min_points, max_points)
def UVPatchAABB(radius, x0, y0, x1, y1, offset): a = UVPatchPoint(radius, 0, 0, x0, y0, x1, y1, offset) b = UVPatchPoint(radius, 1, 0, x0, y0, x1, y1, offset) c = UVPatchPoint(radius, 1, 1, x0, y0, x1, y1, offset) d = UVPatchPoint(radius, 0, 1, x0, y0, x1, y1, offset) m = UVPatchPoint(radius, 0.5, 0.5, x0, y0, x1, y1, offset) x_min = min(a[0], b[0], c[0], d[0], m[0]) y_min = min(a[1], b[1], c[1], d[1], m[1]) z_min = min(a[2], b[2], c[2], d[2], m[2]) x_max = max(a[0], b[0], c[0], d[0], m[0]) y_max = max(a[1], b[1], c[1], d[1], m[1]) z_max = max(a[2], b[2], c[2], d[2], m[2]) return BoundingBox(LPoint3(x_min, y_min, z_min), LPoint3(x_max, y_max, z_max))
def __init__(self, pipeline): DebugObject.__init__(self, "GlobalIllumnination") self.pipeline = pipeline self.qualityLevel = self.pipeline.settings.giQualityLevel if self.qualityLevel not in self.QualityLevels: self.fatal("Unsupported gi quality level:" + self.qualityLevel) self.qualityLevelIndex = self.QualityLevels.index(self.qualityLevel) # Grid size in world space units self.voxelGridSize = self.pipeline.settings.giVoxelGridSize # Grid resolution in pixels self.voxelGridResolution = [32, 64, 128, 192][self.qualityLevelIndex] # Has to be a multiple of 2 self.distributionSteps = [16, 30, 60, 90][self.qualityLevelIndex] self.slideCount = int(self.voxelGridResolution / 8) self.slideVertCount = self.voxelGridResolution / self.slideCount self.bounds = BoundingBox() self.renderCount = 0 # Create the task manager self.taskManager = DistributedTaskManager() self.gridPosLive = PTALVecBase3f.emptyArray(1) self.gridPosTemp = PTALVecBase3f.emptyArray(1) # Store ready state self.readyStateFlag = PTAFloat.emptyArray(1) self.readyStateFlag[0] = 0 self.frameIndex = 0 self.steps = []
def test_nodepath_user_data(): from panda3d.core import NodePath, BoundingBox # Randomly chose this for testing. data = BoundingBox() path = NodePath("node") assert not path.has_user_data() path.set_user_data(data) assert path.has_user_data() assert path.get_user_data() == data path.clear_user_data() assert not path.has_user_data()
def calcBoundingBox(self): self.minX = None self.minY = None self.minZ = None self.maxX = None self.maxY = None self.maxZ = None mins = Point3(99999999) maxs = Point3(-99999999) for point in self.points: if point.x < mins.x: mins.x = point.x if point.y < mins.y: mins.y = point.y if point.z < mins.z: mins.z = point.z if point.x > maxs.x: maxs.x = point.x if point.y > maxs.y: maxs.y = point.y if point.z > maxs.z: maxs.z = point.z if self.minX is None or point.x < self.minX.x: self.minX = point if self.minY is None or point.y < self.minY.y: self.minY = point if self.minZ is None or point.z < self.minZ.z: self.minZ = point if self.maxX is None or point.x > self.maxX.x: self.maxX = point if self.maxY is None or point.y > self.maxY.y: self.maxY = point if self.maxZ is None or point.z > self.maxZ.z: self.maxZ = point self.mins = mins self.maxs = maxs self.boundingBox = BoundingBox(self.mins, self.maxs) self.extents = [ self.minX, self.minY, self.minZ, self.maxX, self.maxY, self.maxZ ]
def __init__(self, point_cloud_size): format = GeomVertexFormat.getV3c4t2() vdata = GeomVertexData('point', format, Geom.UHDynamic) self._pos_writer = GeomVertexWriter(vdata, 'vertex') self._color_writer = GeomVertexWriter(vdata, 'color') self._tex_writer = GeomVertexWriter(vdata, 'texcoord') self._point_cloud_size = point_cloud_size self._prev_point_cloud_size = 0 assert point_cloud_size > 0 vdata.setNumRows(point_cloud_size * 6) self._tex_writer.set_row(0) set_texture(self._tex_writer, point_cloud_size) pnts = GeomTriangles(Geom.UHStatic) pnts.addConsecutiveVertices(0, 3 * 2 * point_cloud_size) pnts.closePrimitive() points_geom = Geom(vdata) points_geom.addPrimitive(pnts) snode = GeomNode('points') snode.addGeom(points_geom) dir_name = osp.dirname(__file__) # print(osp.join(dir_name, 'pnts_vs.glsl')) vs_shader = osp.join(dir_name, 'pnts_vs.glsl') fs_shader = osp.join(dir_name, 'pnts_fs.glsl') myShader = Shader.load( Shader.SL_GLSL, vertex=Filename.fromOsSpecific(vs_shader).getFullpath(), fragment=Filename.fromOsSpecific(fs_shader).getFullpath()) assert myShader is not None self.points_node = base.render.attachNewNode(snode) self.points_node.setPos(0., 0., 0.) self.points_node.set_shader(myShader) self.points_node.set_shader_input( "view_size", (base.win.getXSize(), base.win.getYSize())) self.points_node.node().setBounds( BoundingBox((-1000., -1000., -1000.), (1000., 1000., 1000.))) self.points_node.setTransparency(TransparencyAttrib.MAlpha)
def __init__(self, world, x1, y1, x2, y2, z): self.world = world logging.info(('setting up water plane at z=' + str(z))) # Water surface maker = CardMaker('water') maker.setFrame(x1, x2, y1, y2) self.waterNP = render.attachNewNode(maker.generate()) self.waterNP.setHpr(0, -90, 0) self.waterNP.setPos(0, 0, z) self.waterNP.setTransparency(TransparencyAttrib.MAlpha) self.waterNP.setShader(loader.loadShader('shaders/water.sha')) self.waterNP.setShaderInput('wateranim', Vec4(0.03, -0.015, 64.0, 0)) # vx, vy, scale, skip # offset, strength, refraction factor (0=perfect mirror, 1=total refraction), refractivity self.waterNP.setShaderInput('waterdistort', Vec4(0.4, 4.0, 0.25, 0.45)) self.waterNP.setShaderInput('time', 0) # Reflection plane self.waterPlane = Plane(Vec3(0, 0, z + 1), Point3(0, 0, z)) planeNode = PlaneNode('waterPlane') planeNode.setPlane(self.waterPlane) # Buffer and reflection camera buffer = base.win.makeTextureBuffer('waterBuffer', 512, 512) buffer.setClearColor(Vec4(0, 0, 0, 1)) cfa = CullFaceAttrib.makeReverse() rs = RenderState.make(cfa) self.watercamNP = base.makeCamera(buffer) self.watercamNP.reparentTo(render) #sa = ShaderAttrib.make() #sa = sa.setShader(loader.loadShader('shaders/splut3Clipped.sha') ) cam = self.watercamNP.node() cam.getLens().setFov(base.camLens.getFov()) cam.getLens().setNear(1) cam.getLens().setFar(5000) cam.setInitialState(rs) cam.setTagStateKey('Clipped') #cam.setTagState('True', RenderState.make(sa)) # ---- water textures --------------------------------------------- # reflection texture, created in realtime by the 'water camera' tex0 = buffer.getTexture() tex0.setWrapU(Texture.WMClamp) tex0.setWrapV(Texture.WMClamp) ts0 = TextureStage('reflection') self.waterNP.setTexture(ts0, tex0) # distortion texture tex1 = loader.loadTexture('textures/water.png') ts1 = TextureStage('distortion') self.waterNP.setTexture(ts1, tex1) # ---- Fog --- broken min = Point3(x1, y1, -999.0) max = Point3(x2, y2, z) boundry = BoundingBox(min, max) self.waterFog = Fog('waterFog') self.waterFog.setBounds(boundry) colour = (0.2, 0.5, 0.8) self.waterFog.setColor(*colour) self.waterFog.setExpDensity(0.05) render.attachNewNode(self.waterFog) #render.setFog(world.waterFog) taskMgr.add(self.update, "waterTask")
class GlobalIllumination(DebugObject): """ This class handles the global illumination processing. To process the global illumination, the scene is first rasterized from 3 directions, and a 3D voxel grid is created. After that, the mipmaps of the voxel grid are generated. The final shader then performs voxel cone tracing to compute an ambient, diffuse and specular term. The gi is split over several frames to reduce the computation cost. Currently there are 5 different steps, split over 4 frames: Frame 1: - Rasterize the scene from the x-axis Frame 2: - Rasterize the scene from the y-axis Frame 3: - Rasterize the scene from the z-axis Frame 4: - Copy the generated temporary voxel grid into a stable voxel grid - Generate the mipmaps for that stable voxel grid using a gaussian filter In the final pass the stable voxel grid is sampled. The voxel tracing selects the mipmap depending on the cone size. This enables small scale details as well as blurry reflections and low-frequency ao / diffuse. For performance reasons, the final pass is executed at half window resolution and then bilateral upscaled. """ QualityLevels = ["Low", "Medium", "High", "Ultra"] def __init__(self, pipeline): DebugObject.__init__(self, "GlobalIllumnination") self.pipeline = pipeline self.qualityLevel = self.pipeline.settings.giQualityLevel if self.qualityLevel not in self.QualityLevels: self.fatal("Unsupported gi quality level:" + self.qualityLevel) self.qualityLevelIndex = self.QualityLevels.index(self.qualityLevel) # Grid size in world space units self.voxelGridSize = self.pipeline.settings.giVoxelGridSize # Grid resolution in pixels self.voxelGridResolution = [32, 64, 128, 192][self.qualityLevelIndex] # Has to be a multiple of 2 self.distributionSteps = [16, 30, 60, 90][self.qualityLevelIndex] self.slideCount = int(self.voxelGridResolution / 8) self.slideVertCount = self.voxelGridResolution / self.slideCount self.bounds = BoundingBox() self.renderCount = 0 # Create the task manager self.taskManager = DistributedTaskManager() self.gridPosLive = PTALVecBase3f.emptyArray(1) self.gridPosTemp = PTALVecBase3f.emptyArray(1) # Store ready state self.readyStateFlag = PTAFloat.emptyArray(1) self.readyStateFlag[0] = 0 self.frameIndex = 0 self.steps = [] def _createDebugTexts(self): """ Creates a debug overlay to show GI status """ self.debugText = None self.buildingText = None if self.pipeline.settings.displayDebugStats: self.debugText = FastText(pos=Vec2( Globals.base.getAspectRatio() - 0.1, 0.88), rightAligned=True, color=Vec3(1, 1, 0), size=0.03) self.buildingText = FastText(pos=Vec2(-0.3, 0), rightAligned=False, color=Vec3(1, 1, 0), size=0.03) self.buildingText.setText("PREPARING GI, PLEASE BE PATIENT ....") def stepVoxelize(self, idx): # If we are at the beginning of the frame, compute the new grid position if idx == 0: self.gridPosTemp[0] = self._computeGridPos() # Clear voxel grid at the beginning # for tex in self.generationTextures: # tex.clearImage() self.clearGridTarget.setActive(True) if self.debugText is not None: self.debugText.setText("GI Grid Center: " + ", ".join( str(round(i, 2)) for i in self.gridPosTemp[0]) + " / GI Frame " + str(self.renderCount)) self.renderCount += 1 if self.renderCount == 3: self.readyStateFlag[0] = 1.0 if self.buildingText: self.buildingText.remove() self.buildingText = None self.voxelizePass.voxelizeSceneFromDirection(self.gridPosTemp[0], "xyz"[idx]) def stepDistribute(self, idx): if idx == 0: skyBegin = 142.0 skyInGrid = (skyBegin - self.gridPosTemp[0].z) / (2.0 * self.voxelGridSize) skyInGrid = int(skyInGrid * self.voxelGridResolution) self.convertGridTarget.setShaderInput("skyStartZ", skyInGrid) self.convertGridTarget.setActive(True) self.distributeTarget.setActive(True) swap = idx % 2 == 0 sources = self.pingDataTextures if swap else self.pongDataTextures dests = self.pongDataTextures if swap else self.pingDataTextures if idx == self.distributionSteps - 1: self.publishGrid() dests = self.dataTextures for i, direction in enumerate(self.directions): self.distributeTarget.setShaderInput("src" + direction, sources[i]) self.distributeTarget.setShaderInput("dst" + direction, dests[i]) # Only do the last blur-step on high+ quality, leads to artifacts otherwise # due to the low grid resolution if self.qualityLevel in ["High", "Ultra"]: self.distributeTarget.setShaderInput( "isLastStep", idx >= self.distributionSteps - 1) self.distributeTarget.setShaderInput("writeSolidness", idx >= self.distributionSteps - 1) def publishGrid(self): """ This function gets called when the grid is ready to be used, and updates the live grid data """ self.gridPosLive[0] = self.gridPosTemp[0] self.bounds.setMinMax(self.gridPosLive[0] - Vec3(self.voxelGridSize), self.gridPosLive[0] + Vec3(self.voxelGridSize)) def getBounds(self): """ Returns the bounds of the gi grid """ return self.bounds def update(self): """ Processes the gi, this method is called every frame """ # Disable all buffers here before starting the rendering self.disableTargets() # for target in self.mipmapTargets: # target.setActive(False) self.taskManager.process() def disableTargets(self): """ Disables all active targets """ self.voxelizePass.setActive(False) self.convertGridTarget.setActive(False) self.clearGridTarget.setActive(False) self.distributeTarget.setActive(False) def setup(self): """ Setups everything for the GI to work """ assert (self.distributionSteps % 2 == 0) self._createDebugTexts() self.pipeline.getRenderPassManager().registerDefine( "USE_GLOBAL_ILLUMINATION", 1) self.pipeline.getRenderPassManager().registerDefine( "GI_SLIDE_COUNT", self.slideCount) self.pipeline.getRenderPassManager().registerDefine( "GI_QUALITY_LEVEL", self.qualityLevelIndex) # make the grid resolution a constant self.pipeline.getRenderPassManager().registerDefine( "GI_GRID_RESOLUTION", self.voxelGridResolution) self.taskManager.addTask(3, self.stepVoxelize) self.taskManager.addTask(self.distributionSteps, self.stepDistribute) # Create the voxelize pass which is used to voxelize the scene from # several directions self.voxelizePass = VoxelizePass(self.pipeline) self.voxelizePass.setVoxelGridResolution(self.voxelGridResolution) self.voxelizePass.setVoxelGridSize(self.voxelGridSize) self.voxelizePass.setGridResolutionMultiplier(1) self.pipeline.getRenderPassManager().registerPass(self.voxelizePass) self.generationTextures = [] # Create the buffers used to create the voxel grid for color in "rgb": tex = Texture("VoxelGeneration-" + color) tex.setup3dTexture(self.voxelGridResolution, self.voxelGridResolution, self.voxelGridResolution, Texture.TInt, Texture.FR32) tex.setClearColor(Vec4(0)) self.generationTextures.append(tex) Globals.render.setShaderInput("voxelGenDest" + color.upper(), tex) MemoryMonitor.addTexture("VoxelGenerationTex-" + color.upper(), tex) self.bindTo(Globals.render, "giData") self.convertGridTarget = RenderTarget("ConvertGIGrid") self.convertGridTarget.setSize( self.voxelGridResolution * self.slideCount, self.voxelGridResolution * self.slideVertCount) if self.pipeline.settings.useDebugAttachments: self.convertGridTarget.addColorTexture() self.convertGridTarget.prepareOffscreenBuffer() # Set a near-filter to the texture if self.pipeline.settings.useDebugAttachments: self.convertGridTarget.getColorTexture().setMinfilter( Texture.FTNearest) self.convertGridTarget.getColorTexture().setMagfilter( Texture.FTNearest) self.clearGridTarget = RenderTarget("ClearGIGrid") self.clearGridTarget.setSize( self.voxelGridResolution * self.slideCount, self.voxelGridResolution * self.slideVertCount) if self.pipeline.settings.useDebugAttachments: self.clearGridTarget.addColorTexture() self.clearGridTarget.prepareOffscreenBuffer() for idx, color in enumerate("rgb"): self.convertGridTarget.setShaderInput( "voxelGenSrc" + color.upper(), self.generationTextures[idx]) self.clearGridTarget.setShaderInput("voxelGenTex" + color.upper(), self.generationTextures[idx]) # Create the data textures self.dataTextures = [] self.directions = ["PosX", "NegX", "PosY", "NegY", "PosZ", "NegZ"] for i, direction in enumerate(self.directions): tex = Texture("GIDataTex" + direction) tex.setup3dTexture(self.voxelGridResolution, self.voxelGridResolution, self.voxelGridResolution, Texture.TFloat, Texture.FR11G11B10) MemoryMonitor.addTexture("VoxelDataTex-" + direction, tex) self.dataTextures.append(tex) self.pipeline.getRenderPassManager().registerStaticVariable( "giVoxelData" + direction, tex) # Create ping / pong textures self.pingDataTextures = [] self.pongDataTextures = [] for i, direction in enumerate(self.directions): texPing = Texture("GIPingDataTex" + direction) texPing.setup3dTexture(self.voxelGridResolution, self.voxelGridResolution, self.voxelGridResolution, Texture.TFloat, Texture.FR11G11B10) MemoryMonitor.addTexture("VoxelPingDataTex-" + direction, texPing) self.pingDataTextures.append(texPing) texPong = Texture("GIPongDataTex" + direction) texPong.setup3dTexture(self.voxelGridResolution, self.voxelGridResolution, self.voxelGridResolution, Texture.TFloat, Texture.FR11G11B10) MemoryMonitor.addTexture("VoxelPongDataTex-" + direction, texPong) self.pongDataTextures.append(texPong) self.convertGridTarget.setShaderInput("voxelDataDest" + direction, self.pingDataTextures[i]) # self.clearGridTarget.setShaderInput("voxelDataDest" + str(i), self.pongDataTextures[i]) # Set texture wrap modes for tex in self.pingDataTextures + self.pongDataTextures + self.dataTextures + self.generationTextures: tex.setMinfilter(Texture.FTLinear) tex.setMagfilter(Texture.FTLinear) tex.setWrapU(Texture.WMBorderColor) tex.setWrapV(Texture.WMBorderColor) tex.setWrapW(Texture.WMBorderColor) tex.setAnisotropicDegree(0) tex.setBorderColor(Vec4(0)) for tex in self.dataTextures: tex.setMinfilter(Texture.FTLinear) tex.setMagfilter(Texture.FTLinear) self.distributeTarget = RenderTarget("DistributeVoxels") self.distributeTarget.setSize( self.voxelGridResolution * self.slideCount, self.voxelGridResolution * self.slideVertCount) if self.pipeline.settings.useDebugAttachments: self.distributeTarget.addColorTexture() self.distributeTarget.prepareOffscreenBuffer() # Set a near-filter to the texture if self.pipeline.settings.useDebugAttachments: self.distributeTarget.getColorTexture().setMinfilter( Texture.FTNearest) self.distributeTarget.getColorTexture().setMagfilter( Texture.FTNearest) self.distributeTarget.setShaderInput("isLastStep", False) # Create solidness texture self.voxelSolidTex = Texture("GIDataSolidTex") self.voxelSolidTex.setup3dTexture(self.voxelGridResolution, self.voxelGridResolution, self.voxelGridResolution, Texture.TFloat, Texture.FR16) self.convertGridTarget.setShaderInput("voxelSolidDest", self.voxelSolidTex) self.distributeTarget.setShaderInput("voxelSolidTex", self.voxelSolidTex) MemoryMonitor.addTexture("VoxelSolidTex", self.voxelSolidTex) self.voxelSolidStableTex = Texture("GIDataSolidStableTex") self.voxelSolidStableTex.setup3dTexture(self.voxelGridResolution, self.voxelGridResolution, self.voxelGridResolution, Texture.TFloat, Texture.FR16) self.distributeTarget.setShaderInput("voxelSolidWriteTex", self.voxelSolidStableTex) self.pipeline.getRenderPassManager().registerStaticVariable( "giVoxelSolidTex", self.voxelSolidStableTex) # Create the final gi pass self.finalPass = GlobalIlluminationPass() self.pipeline.getRenderPassManager().registerPass(self.finalPass) self.pipeline.getRenderPassManager().registerDynamicVariable( "giData", self.bindTo) self.pipeline.getRenderPassManager().registerStaticVariable( "giReadyState", self.readyStateFlag) # Visualize voxels if False: self.voxelCube = loader.loadModel("Box") self.voxelCube.reparentTo(render) # self.voxelCube.setTwoSided(True) self.voxelCube.node().setFinal(True) self.voxelCube.node().setBounds(OmniBoundingVolume()) self.voxelCube.setInstanceCount(self.voxelGridResolution**3) # self.voxelCube.hide() self.bindTo(self.voxelCube, "giData") for i in xrange(5): self.voxelCube.setShaderInput("giDataTex" + str(i), self.pingDataTextures[i]) self.disableTargets() def _createConvertShader(self): """ Loads the shader for converting the voxel grid """ shader = Shader.load(Shader.SLGLSL, "Shader/DefaultPostProcess.vertex", "Shader/GI/ConvertGrid.fragment") self.convertGridTarget.setShader(shader) def _createClearShader(self): """ Loads the shader for converting the voxel grid """ shader = Shader.load(Shader.SLGLSL, "Shader/DefaultPostProcess.vertex", "Shader/GI/ClearGrid.fragment") self.clearGridTarget.setShader(shader) def _createGenerateMipmapsShader(self): """ Loads the shader for generating the voxel grid mipmaps """ computeSize = self.voxelGridResolution for child in self.mipmapTargets: computeSize /= 2 shader = Shader.load( Shader.SLGLSL, "Shader/DefaultPostProcess.vertex", "Shader/GI/GenerateMipmaps/" + str(computeSize) + ".fragment") child.setShader(shader) def _createDistributionShader(self): """ Creates the photon distribution shader """ shader = Shader.load(Shader.SLGLSL, "Shader/DefaultPostProcess.vertex", "Shader/GI/Distribute.fragment") self.distributeTarget.setShader(shader) def _createBlurShader(self): """ Creates the photon distribution shader """ shader = Shader.load(Shader.SLGLSL, "Shader/DefaultPostProcess.vertex", "Shader/GI/BlurPhotonGrid.fragment") self.blurBuffer.setShader(shader) def reloadShader(self): """ Reloads all shaders and updates the voxelization camera state aswell """ self.debug("Reloading shaders") self._createConvertShader() self._createClearShader() self._createDistributionShader() # self._createGenerateMipmapsShader() # self._createPhotonBoxShader() # self._createBlurShader() if hasattr(self, "voxelCube"): self.pipeline.setEffect(self.voxelCube, "Effects/DisplayVoxels.effect", { "normalMapping": False, "castShadows": False, "castGI": False }) def _createPhotonBoxShader(self): """ Loads the shader to visualize the photons """ shader = Shader.load(Shader.SLGLSL, "Shader/DefaultShaders/Photon/vertex.glsl", "Shader/DefaultShaders/Photon/fragment.glsl") # self.photonBox.setShader(shader, 100) def _computeGridPos(self): """ Computes the new center of the voxel grid. The center pos is also snapped, to avoid flickering. """ # It is important that the grid is snapped, otherwise it will flicker # while the camera moves. When using a snap of 32, everything until # the log2(32) = 5th mipmap is stable. snap = 1.0 stepSizeX = float(self.voxelGridSize * 2.0) / float( self.voxelGridResolution) * snap stepSizeY = float(self.voxelGridSize * 2.0) / float( self.voxelGridResolution) * snap stepSizeZ = float(self.voxelGridSize * 2.0) / float( self.voxelGridResolution) * snap gridPos = Globals.base.camera.getPos(Globals.base.render) gridPos.x -= gridPos.x % stepSizeX gridPos.y -= gridPos.y % stepSizeY gridPos.z -= gridPos.z % stepSizeZ return gridPos def bindTo(self, node, prefix): """ Binds all required shader inputs to a target to compute / display the global illumination """ node.setShaderInput(prefix + ".positionGeneration", self.gridPosTemp) node.setShaderInput(prefix + ".position", self.gridPosLive) node.setShaderInput(prefix + ".size", self.voxelGridSize) node.setShaderInput(prefix + ".resolution", self.voxelGridResolution)
class GlobalIllumination(DebugObject): """ This class handles the global illumination processing. To process the global illumination, the scene is first rasterized from 3 directions, and a 3D voxel grid is created. After that, the mipmaps of the voxel grid are generated. The final shader then performs voxel cone tracing to compute an ambient, diffuse and specular term. The gi is split over several frames to reduce the computation cost. Currently there are 5 different steps, split over 4 frames: Frame 1: - Rasterize the scene from the x-axis Frame 2: - Rasterize the scene from the y-axis Frame 3: - Rasterize the scene from the z-axis Frame 4: - Copy the generated temporary voxel grid into a stable voxel grid - Generate the mipmaps for that stable voxel grid using a gaussian filter In the final pass the stable voxel grid is sampled. The voxel tracing selects the mipmap depending on the cone size. This enables small scale details as well as blurry reflections and low-frequency ao / diffuse. For performance reasons, the final pass is executed at half window resolution and then bilateral upscaled. """ QualityLevels = ["Low", "Medium", "High", "Ultra"] def __init__(self, pipeline): DebugObject.__init__(self, "GlobalIllumnination") self.pipeline = pipeline self.qualityLevel = self.pipeline.settings.giQualityLevel if self.qualityLevel not in self.QualityLevels: self.fatal("Unsupported gi quality level:" + self.qualityLevel) self.qualityLevelIndex = self.QualityLevels.index(self.qualityLevel) # Grid size in world space units self.voxelGridSize = self.pipeline.settings.giVoxelGridSize # Grid resolution in pixels self.voxelGridResolution = [32, 64, 128, 192][self.qualityLevelIndex] # Has to be a multiple of 2 self.distributionSteps = [16, 30, 60, 90][self.qualityLevelIndex] self.slideCount = int(self.voxelGridResolution / 8) self.slideVertCount = self.voxelGridResolution / self.slideCount self.bounds = BoundingBox() self.renderCount = 0 # Create the task manager self.taskManager = DistributedTaskManager() self.gridPosLive = PTALVecBase3f.emptyArray(1) self.gridPosTemp = PTALVecBase3f.emptyArray(1) # Store ready state self.readyStateFlag = PTAFloat.emptyArray(1) self.readyStateFlag[0] = 0 self.frameIndex = 0 self.steps = [] def _createDebugTexts(self): """ Creates a debug overlay to show GI status """ self.debugText = None self.buildingText = None if self.pipeline.settings.displayDebugStats: self.debugText = FastText(pos=Vec2( Globals.base.getAspectRatio() - 0.1, 0.88), rightAligned=True, color=Vec3(1, 1, 0), size=0.03) self.buildingText = FastText(pos=Vec2(-0.3, 0), rightAligned=False, color=Vec3(1, 1, 0), size=0.03) self.buildingText.setText("PREPARING GI, PLEASE BE PATIENT ....") def stepVoxelize(self, idx): # If we are at the beginning of the frame, compute the new grid position if idx == 0: self.gridPosTemp[0] = self._computeGridPos() # Clear voxel grid at the beginning # for tex in self.generationTextures: # tex.clearImage() self.clearGridTarget.setActive(True) if self.debugText is not None: self.debugText.setText("GI Grid Center: " + ", ".join(str(round(i, 2)) for i in self.gridPosTemp[0]) + " / GI Frame " + str(self.renderCount) ) self.renderCount += 1 if self.renderCount == 3: self.readyStateFlag[0] = 1.0 if self.buildingText: self.buildingText.remove() self.buildingText = None self.voxelizePass.voxelizeSceneFromDirection(self.gridPosTemp[0], "xyz"[idx]) def stepDistribute(self, idx): if idx == 0: skyBegin = 142.0 skyInGrid = (skyBegin - self.gridPosTemp[0].z) / (2.0 * self.voxelGridSize) skyInGrid = int(skyInGrid * self.voxelGridResolution) self.convertGridTarget.setShaderInput("skyStartZ", skyInGrid) self.convertGridTarget.setActive(True) self.distributeTarget.setActive(True) swap = idx % 2 == 0 sources = self.pingDataTextures if swap else self.pongDataTextures dests = self.pongDataTextures if swap else self.pingDataTextures if idx == self.distributionSteps - 1: self.publishGrid() dests = self.dataTextures for i, direction in enumerate(self.directions): self.distributeTarget.setShaderInput("src" + direction, sources[i]) self.distributeTarget.setShaderInput("dst" + direction, dests[i]) # Only do the last blur-step on high+ quality, leads to artifacts otherwise # due to the low grid resolution if self.qualityLevel in ["High", "Ultra"]: self.distributeTarget.setShaderInput("isLastStep", idx >= self.distributionSteps-1) self.distributeTarget.setShaderInput("writeSolidness", idx >= self.distributionSteps-1) def publishGrid(self): """ This function gets called when the grid is ready to be used, and updates the live grid data """ self.gridPosLive[0] = self.gridPosTemp[0] self.bounds.setMinMax(self.gridPosLive[0]-Vec3(self.voxelGridSize), self.gridPosLive[0]+Vec3(self.voxelGridSize)) def getBounds(self): """ Returns the bounds of the gi grid """ return self.bounds def update(self): """ Processes the gi, this method is called every frame """ # Disable all buffers here before starting the rendering self.disableTargets() # for target in self.mipmapTargets: # target.setActive(False) self.taskManager.process() def disableTargets(self): """ Disables all active targets """ self.voxelizePass.setActive(False) self.convertGridTarget.setActive(False) self.clearGridTarget.setActive(False) self.distributeTarget.setActive(False) def setup(self): """ Setups everything for the GI to work """ assert(self.distributionSteps % 2 == 0) self._createDebugTexts() self.pipeline.getRenderPassManager().registerDefine("USE_GLOBAL_ILLUMINATION", 1) self.pipeline.getRenderPassManager().registerDefine("GI_SLIDE_COUNT", self.slideCount) self.pipeline.getRenderPassManager().registerDefine("GI_QUALITY_LEVEL", self.qualityLevelIndex) # make the grid resolution a constant self.pipeline.getRenderPassManager().registerDefine("GI_GRID_RESOLUTION", self.voxelGridResolution) self.taskManager.addTask(3, self.stepVoxelize) self.taskManager.addTask(self.distributionSteps, self.stepDistribute) # Create the voxelize pass which is used to voxelize the scene from # several directions self.voxelizePass = VoxelizePass(self.pipeline) self.voxelizePass.setVoxelGridResolution(self.voxelGridResolution) self.voxelizePass.setVoxelGridSize(self.voxelGridSize) self.voxelizePass.setGridResolutionMultiplier(1) self.pipeline.getRenderPassManager().registerPass(self.voxelizePass) self.generationTextures = [] # Create the buffers used to create the voxel grid for color in "rgb": tex = Texture("VoxelGeneration-" + color) tex.setup3dTexture(self.voxelGridResolution, self.voxelGridResolution, self.voxelGridResolution, Texture.TInt, Texture.FR32) tex.setClearColor(Vec4(0)) self.generationTextures.append(tex) Globals.render.setShaderInput("voxelGenDest" + color.upper(), tex) MemoryMonitor.addTexture("VoxelGenerationTex-" + color.upper(), tex) self.bindTo(Globals.render, "giData") self.convertGridTarget = RenderTarget("ConvertGIGrid") self.convertGridTarget.setSize(self.voxelGridResolution * self.slideCount, self.voxelGridResolution * self.slideVertCount) if self.pipeline.settings.useDebugAttachments: self.convertGridTarget.addColorTexture() self.convertGridTarget.prepareOffscreenBuffer() # Set a near-filter to the texture if self.pipeline.settings.useDebugAttachments: self.convertGridTarget.getColorTexture().setMinfilter(Texture.FTNearest) self.convertGridTarget.getColorTexture().setMagfilter(Texture.FTNearest) self.clearGridTarget = RenderTarget("ClearGIGrid") self.clearGridTarget.setSize(self.voxelGridResolution * self.slideCount, self.voxelGridResolution * self.slideVertCount) if self.pipeline.settings.useDebugAttachments: self.clearGridTarget.addColorTexture() self.clearGridTarget.prepareOffscreenBuffer() for idx, color in enumerate("rgb"): self.convertGridTarget.setShaderInput("voxelGenSrc" + color.upper(), self.generationTextures[idx]) self.clearGridTarget.setShaderInput("voxelGenTex" + color.upper(), self.generationTextures[idx]) # Create the data textures self.dataTextures = [] self.directions = ["PosX", "NegX", "PosY", "NegY", "PosZ", "NegZ"] for i, direction in enumerate(self.directions): tex = Texture("GIDataTex" + direction) tex.setup3dTexture(self.voxelGridResolution, self.voxelGridResolution, self.voxelGridResolution, Texture.TFloat, Texture.FR11G11B10) MemoryMonitor.addTexture("VoxelDataTex-" + direction, tex) self.dataTextures.append(tex) self.pipeline.getRenderPassManager().registerStaticVariable("giVoxelData" + direction, tex) # Create ping / pong textures self.pingDataTextures = [] self.pongDataTextures = [] for i, direction in enumerate(self.directions): texPing = Texture("GIPingDataTex" + direction) texPing.setup3dTexture(self.voxelGridResolution, self.voxelGridResolution, self.voxelGridResolution, Texture.TFloat, Texture.FR11G11B10) MemoryMonitor.addTexture("VoxelPingDataTex-" + direction, texPing) self.pingDataTextures.append(texPing) texPong = Texture("GIPongDataTex" + direction) texPong.setup3dTexture(self.voxelGridResolution, self.voxelGridResolution, self.voxelGridResolution, Texture.TFloat, Texture.FR11G11B10) MemoryMonitor.addTexture("VoxelPongDataTex-" + direction, texPong) self.pongDataTextures.append(texPong) self.convertGridTarget.setShaderInput("voxelDataDest"+direction, self.pingDataTextures[i]) # self.clearGridTarget.setShaderInput("voxelDataDest" + str(i), self.pongDataTextures[i]) # Set texture wrap modes for tex in self.pingDataTextures + self.pongDataTextures + self.dataTextures + self.generationTextures: tex.setMinfilter(Texture.FTLinear) tex.setMagfilter(Texture.FTLinear) tex.setWrapU(Texture.WMBorderColor) tex.setWrapV(Texture.WMBorderColor) tex.setWrapW(Texture.WMBorderColor) tex.setAnisotropicDegree(0) tex.setBorderColor(Vec4(0)) for tex in self.dataTextures: tex.setMinfilter(Texture.FTLinear) tex.setMagfilter(Texture.FTLinear) self.distributeTarget = RenderTarget("DistributeVoxels") self.distributeTarget.setSize(self.voxelGridResolution * self.slideCount, self.voxelGridResolution * self.slideVertCount) if self.pipeline.settings.useDebugAttachments: self.distributeTarget.addColorTexture() self.distributeTarget.prepareOffscreenBuffer() # Set a near-filter to the texture if self.pipeline.settings.useDebugAttachments: self.distributeTarget.getColorTexture().setMinfilter(Texture.FTNearest) self.distributeTarget.getColorTexture().setMagfilter(Texture.FTNearest) self.distributeTarget.setShaderInput("isLastStep", False) # Create solidness texture self.voxelSolidTex = Texture("GIDataSolidTex") self.voxelSolidTex.setup3dTexture(self.voxelGridResolution, self.voxelGridResolution, self.voxelGridResolution, Texture.TFloat, Texture.FR16) self.convertGridTarget.setShaderInput("voxelSolidDest", self.voxelSolidTex) self.distributeTarget.setShaderInput("voxelSolidTex", self.voxelSolidTex) MemoryMonitor.addTexture("VoxelSolidTex", self.voxelSolidTex) self.voxelSolidStableTex = Texture("GIDataSolidStableTex") self.voxelSolidStableTex.setup3dTexture(self.voxelGridResolution, self.voxelGridResolution, self.voxelGridResolution, Texture.TFloat, Texture.FR16) self.distributeTarget.setShaderInput("voxelSolidWriteTex", self.voxelSolidStableTex) self.pipeline.getRenderPassManager().registerStaticVariable("giVoxelSolidTex", self.voxelSolidStableTex) # Create the final gi pass self.finalPass = GlobalIlluminationPass() self.pipeline.getRenderPassManager().registerPass(self.finalPass) self.pipeline.getRenderPassManager().registerDynamicVariable("giData", self.bindTo) self.pipeline.getRenderPassManager().registerStaticVariable("giReadyState", self.readyStateFlag) # Visualize voxels if False: self.voxelCube = loader.loadModel("Box") self.voxelCube.reparentTo(render) # self.voxelCube.setTwoSided(True) self.voxelCube.node().setFinal(True) self.voxelCube.node().setBounds(OmniBoundingVolume()) self.voxelCube.setInstanceCount(self.voxelGridResolution**3) # self.voxelCube.hide() self.bindTo(self.voxelCube, "giData") for i in xrange(5): self.voxelCube.setShaderInput("giDataTex" + str(i), self.pingDataTextures[i]) self.disableTargets() def _createConvertShader(self): """ Loads the shader for converting the voxel grid """ shader = Shader.load(Shader.SLGLSL, "Shader/DefaultPostProcess.vertex", "Shader/GI/ConvertGrid.fragment") self.convertGridTarget.setShader(shader) def _createClearShader(self): """ Loads the shader for converting the voxel grid """ shader = Shader.load(Shader.SLGLSL, "Shader/DefaultPostProcess.vertex", "Shader/GI/ClearGrid.fragment") self.clearGridTarget.setShader(shader) def _createGenerateMipmapsShader(self): """ Loads the shader for generating the voxel grid mipmaps """ computeSize = self.voxelGridResolution for child in self.mipmapTargets: computeSize /= 2 shader = Shader.load(Shader.SLGLSL, "Shader/DefaultPostProcess.vertex", "Shader/GI/GenerateMipmaps/" + str(computeSize) + ".fragment") child.setShader(shader) def _createDistributionShader(self): """ Creates the photon distribution shader """ shader = Shader.load(Shader.SLGLSL, "Shader/DefaultPostProcess.vertex", "Shader/GI/Distribute.fragment") self.distributeTarget.setShader(shader) def _createBlurShader(self): """ Creates the photon distribution shader """ shader = Shader.load(Shader.SLGLSL, "Shader/DefaultPostProcess.vertex", "Shader/GI/BlurPhotonGrid.fragment") self.blurBuffer.setShader(shader) def reloadShader(self): """ Reloads all shaders and updates the voxelization camera state aswell """ self.debug("Reloading shaders") self._createConvertShader() self._createClearShader() self._createDistributionShader() # self._createGenerateMipmapsShader() # self._createPhotonBoxShader() # self._createBlurShader() if hasattr(self, "voxelCube"): self.pipeline.setEffect(self.voxelCube, "Effects/DisplayVoxels.effect", { "normalMapping": False, "castShadows": False, "castGI": False }) def _createPhotonBoxShader(self): """ Loads the shader to visualize the photons """ shader = Shader.load(Shader.SLGLSL, "Shader/DefaultShaders/Photon/vertex.glsl", "Shader/DefaultShaders/Photon/fragment.glsl") # self.photonBox.setShader(shader, 100) def _computeGridPos(self): """ Computes the new center of the voxel grid. The center pos is also snapped, to avoid flickering. """ # It is important that the grid is snapped, otherwise it will flicker # while the camera moves. When using a snap of 32, everything until # the log2(32) = 5th mipmap is stable. snap = 1.0 stepSizeX = float(self.voxelGridSize * 2.0) / float(self.voxelGridResolution) * snap stepSizeY = float(self.voxelGridSize * 2.0) / float(self.voxelGridResolution) * snap stepSizeZ = float(self.voxelGridSize * 2.0) / float(self.voxelGridResolution) * snap gridPos = Globals.base.camera.getPos(Globals.base.render) gridPos.x -= gridPos.x % stepSizeX gridPos.y -= gridPos.y % stepSizeY gridPos.z -= gridPos.z % stepSizeZ return gridPos def bindTo(self, node, prefix): """ Binds all required shader inputs to a target to compute / display the global illumination """ node.setShaderInput(prefix + ".positionGeneration", self.gridPosTemp) node.setShaderInput(prefix + ".position", self.gridPosLive) node.setShaderInput(prefix + ".size", self.voxelGridSize) node.setShaderInput(prefix + ".resolution", self.voxelGridResolution)
class WaterNode(NodePath): Nothing = 0 Touching = 2 Submerged = 4 def __init__(self, size, pos, depth, mask, spec = WaterSpec()): NodePath.__init__(self, 'waterNode') self.setPos(pos) self.spec = spec self.pos = pos self.depth = depth self.size = size self.mask = mask self.height = pos[2] normal = (0, 0, 1) tangent = (normal[0], normal[2], -normal[1]) binormal = (normal[2], normal[1], -normal[0]) # Build array for new format. array = GeomVertexArrayFormat() array.addColumn(InternalName.make('vertex'), 3, Geom.NTFloat32, Geom.CPoint) array.addColumn(InternalName.make('texcoord'), 2, Geom.NTFloat32, Geom.CTexcoord) array.addColumn(InternalName.make('normal'), 3, Geom.NTFloat32, Geom.CVector) array.addColumn(InternalName.make('binormal'), 3, Geom.NTFloat32, Geom.CVector) array.addColumn(InternalName.make('tangent'), 3, Geom.NTFloat32, Geom.CVector) # Create and register format. format = GeomVertexFormat() format.addArray(array) format = GeomVertexFormat.registerFormat(format) vdata = GeomVertexData('waterPlanes', format, Geom.UHStatic) vdata.setNumRows(4) vtxWriter = GeomVertexWriter(vdata, 'vertex') tcWriter = GeomVertexWriter(vdata, 'texcoord') tnWriter = GeomVertexWriter(vdata, 'tangent') bnWriter = GeomVertexWriter(vdata, 'binormal') normWriter = GeomVertexWriter(vdata, 'normal') # top left corner vtxWriter.addData3f(size[0], size[3], 0) tcWriter.addData2f(0, 1) normWriter.addData3f(*normal) tnWriter.addData3f(*tangent) bnWriter.addData3f(*binormal) # bottom left corner vtxWriter.addData3f(size[0], size[2], 0) tcWriter.addData2f(0, 0) normWriter.addData3f(*normal) tnWriter.addData3f(*tangent) bnWriter.addData3f(*binormal) # top right corner vtxWriter.addData3f(size[1], size[3], 0) tcWriter.addData2f(1, 1) normWriter.addData3f(*normal) tnWriter.addData3f(*tangent) bnWriter.addData3f(*binormal) # bottom right corner vtxWriter.addData3f(size[1], size[2], 0) tcWriter.addData2f(1, 0) normWriter.addData3f(*normal) tnWriter.addData3f(*tangent) bnWriter.addData3f(*binormal) topTris = GeomTriangles(Geom.UHStatic) topTris.addVertices(0, 1, 2) topTris.addVertices(3, 2, 1) topGeom = Geom(vdata) topGeom.addPrimitive(topTris) self.topNP = self.attachNewNode(GeomNode('waterTop')) self.topNP.node().addGeom(topGeom) # Reverse the winding for the bottom water plane botTris = GeomTriangles(Geom.UHStatic) botTris.addVertices(2, 1, 0) botTris.addVertices(1, 2, 3) botGeom = Geom(vdata) botGeom.addPrimitive(botTris) self.botNP = self.attachNewNode(GeomNode('waterBot')) self.botNP.node().addGeom(botGeom) # Create an AABB which defines the volume of this water. self.aabb = BoundingBox(Point3(size[0], size[2], -depth), Point3(size[1], size[3], 0)) self.aabb.xform(self.getMat(render)) self.cubemap = base.bspLoader.getClosestCubemapTexture(self.getPos(render)) self.dudvFrame = 0 def setup(self): self.reparentTo(render) self.hide(CIGlobals.ReflectionCameraBitmask | CIGlobals.RefractionCameraBitmask) #self.setLightOff(1) self.setMaterialOff(1) self.setTransparency(1) def disableEffects(self): self.topNP.clearShader() self.botNP.clearShader() def enableFakeEffect(self): self.disableEffects() staticTex = loader.loadTexture(self.spec.staticTex) self.topNP.setTexture(staticTex, 1) self.botNP.setTexture(staticTex, 1) def disableFakeEffect(self): self.topNP.clearTexture() self.botNP.clearTexture() def enableEffects(self, reflScene, refrScene, underwaterRefrScene): self.disableFakeEffect() static = loader.loadTexture(self.spec.staticTex) if not self.spec.cheap: self.topNP.setShader(Shader.load(Shader.SL_GLSL, "shaders/water_v.glsl", "shaders/water_f.glsl"), 2) self.topNP.setShaderInput("dudv", static) self.topNP.setShaderInput("dudv_tile", self.spec.dudvTile) self.topNP.setShaderInput("dudv_strength", self.spec.dudvStrength) self.topNP.setShaderInput("move_factor", self.spec.moveFactor) self.topNP.setShaderInput("near", CIGlobals.DefaultCameraNear) self.topNP.setShaderInput("far", CIGlobals.DefaultCameraFar) self.topNP.setShaderInput("normal_map", static) self.topNP.setShaderInput("fog_density", self.spec.fog.density) self.topNP.setShaderInput("fog_color", self.spec.fog.color) self.topNP.setShaderInput("water_tint", self.spec.tint) self.topNP.setShaderInput("reflect_factor", self.spec.reflectFactor) self.topNP.setShaderInput("static_depth", self.depth) self.botNP.setShader(Shader.load(Shader.SL_GLSL, "shaders/water_bottom_v.glsl", "shaders/water_bottom_f.glsl"), 2) self.botNP.setShaderInput("dudv", static) self.botNP.setShaderInput("dudv_tile", self.spec.dudvTile) self.botNP.setShaderInput("dudv_strength", self.spec.dudvStrength) self.botNP.setShaderInput("move_factor", self.spec.moveFactor) self.botNP.setShaderInput("water_tint", self.spec.tint) else: # We are doing cheap water self.topNP.setShader(Shader.load(Shader.SL_GLSL, "shaders/water_cheap_v.glsl", "shaders/water_cheap_f.glsl"), 2) self.topNP.setShaderInput("tex_scale", self.spec.dudvTile) self.topNP.setShaderInput("cube_map", self.cubemap) self.topNP.setShaderInput("normal_map", static) self.topNP.setShaderInput("base_map", static) self.topNP.setShaderInput("reflectivity", self.spec.reflectivity) self.topNP.setShaderInput("_exposureAdjustment", base.shaderGenerator.getExposureAdjustment()) self.topNP.setShaderInput("water_tint", self.spec.tint) self.setTextureInputs(reflScene, refrScene, underwaterRefrScene) def setTextureInputs(self, reflScene, refrScene, underwaterRefrScene): if not self.spec.cheap: self.topNP.setShaderInput("refl", reflScene.texture) self.topNP.setShaderInput("refr", refrScene.texture) self.topNP.setShaderInput("refr_depth", refrScene.depthTex) self.botNP.setShaderInput("refr", underwaterRefrScene.texture) def isInWater(self, bottom, top): return self.aabb.contains(bottom, top) def isTouchingWater(self, position): return self.aabb.contains(position) != BoundingBox.IFNoIntersection def removeNode(self): self.topNP.removeNode() del self.topNP self.botNP.removeNode() del self.botNP del self.aabb del self.height del self.pos del self.size del self.depth NodePath.removeNode(self)
def __init__(self, size, pos, depth, mask, spec = WaterSpec()): NodePath.__init__(self, 'waterNode') self.setPos(pos) self.spec = spec self.pos = pos self.depth = depth self.size = size self.mask = mask self.height = pos[2] normal = (0, 0, 1) tangent = (normal[0], normal[2], -normal[1]) binormal = (normal[2], normal[1], -normal[0]) # Build array for new format. array = GeomVertexArrayFormat() array.addColumn(InternalName.make('vertex'), 3, Geom.NTFloat32, Geom.CPoint) array.addColumn(InternalName.make('texcoord'), 2, Geom.NTFloat32, Geom.CTexcoord) array.addColumn(InternalName.make('normal'), 3, Geom.NTFloat32, Geom.CVector) array.addColumn(InternalName.make('binormal'), 3, Geom.NTFloat32, Geom.CVector) array.addColumn(InternalName.make('tangent'), 3, Geom.NTFloat32, Geom.CVector) # Create and register format. format = GeomVertexFormat() format.addArray(array) format = GeomVertexFormat.registerFormat(format) vdata = GeomVertexData('waterPlanes', format, Geom.UHStatic) vdata.setNumRows(4) vtxWriter = GeomVertexWriter(vdata, 'vertex') tcWriter = GeomVertexWriter(vdata, 'texcoord') tnWriter = GeomVertexWriter(vdata, 'tangent') bnWriter = GeomVertexWriter(vdata, 'binormal') normWriter = GeomVertexWriter(vdata, 'normal') # top left corner vtxWriter.addData3f(size[0], size[3], 0) tcWriter.addData2f(0, 1) normWriter.addData3f(*normal) tnWriter.addData3f(*tangent) bnWriter.addData3f(*binormal) # bottom left corner vtxWriter.addData3f(size[0], size[2], 0) tcWriter.addData2f(0, 0) normWriter.addData3f(*normal) tnWriter.addData3f(*tangent) bnWriter.addData3f(*binormal) # top right corner vtxWriter.addData3f(size[1], size[3], 0) tcWriter.addData2f(1, 1) normWriter.addData3f(*normal) tnWriter.addData3f(*tangent) bnWriter.addData3f(*binormal) # bottom right corner vtxWriter.addData3f(size[1], size[2], 0) tcWriter.addData2f(1, 0) normWriter.addData3f(*normal) tnWriter.addData3f(*tangent) bnWriter.addData3f(*binormal) topTris = GeomTriangles(Geom.UHStatic) topTris.addVertices(0, 1, 2) topTris.addVertices(3, 2, 1) topGeom = Geom(vdata) topGeom.addPrimitive(topTris) self.topNP = self.attachNewNode(GeomNode('waterTop')) self.topNP.node().addGeom(topGeom) # Reverse the winding for the bottom water plane botTris = GeomTriangles(Geom.UHStatic) botTris.addVertices(2, 1, 0) botTris.addVertices(1, 2, 3) botGeom = Geom(vdata) botGeom.addPrimitive(botTris) self.botNP = self.attachNewNode(GeomNode('waterBot')) self.botNP.node().addGeom(botGeom) # Create an AABB which defines the volume of this water. self.aabb = BoundingBox(Point3(size[0], size[2], -depth), Point3(size[1], size[3], 0)) self.aabb.xform(self.getMat(render)) self.cubemap = base.bspLoader.getClosestCubemapTexture(self.getPos(render)) self.dudvFrame = 0