Cubemap not covering everything

Hey hey! Yet another issue arose and I need your help.

I’m generating a cubemap of my scene to then use the nonlinearimager to render it through a fisheye lens. My animated models now seem to vanish at the boundaries of the stitched field of views (see picture), since no camera seems to cover that part.
What’s weird is that the shadows are rendered correctly beneath the invisible fish…

Any idea how I can get around this issue?

# -*- coding: utf-8 -*-
"""
Created on Mon Mar 27 15:33:18 2023

@author: leco10

cylindrical lens VR
"""
import simplepbr

# Make the camera into a fisheye camera using the NonlinearImager.
from direct.showbase.ShowBase import ShowBase
from panda3d.core import loadPrcFile
loadPrcFile("VR_config.prc")

#load config manager and print config vars
#from panda3d.core import ConfigVariableManager
#ConfigVariableManager.getGlobalPtr().listVariables()

from panda3d.core import *

from panda3d.fx import NonlinearImager, ProjectionScreen, FisheyeLens

# import task, a task is a procedure that is called every frame
from direct.task import Task

from direct.particles.ParticleEffect import ParticleEffect

#import actor for animations
from direct.actor.Actor import Actor

import time

#------------------------------------------------------------------------------
#make dictionary to map keys press events to true/false
keyMap = {
    
    "up": False,
    "down": False,
    "left": False,
    "right": False,
    "rcon": False,
    "zero": False,
    "1": False,
    "2": False,
    '+': False,
    '-': False
    }

#callback update function

def updateKeyMap(key,state):
    keyMap[key] = state
    
start = time.time()
#------------------------------------------------------------------------------
class VR(ShowBase):
    def __init__(self, tex_res = 4096):
        super().__init__()
        
        self.win.setClearColorActive(False)
        self.win.setClearColor((1, 0, 0, 0))
        self.setBackgroundColor(1,0,0,0,self.win)
        
        
        #parameters
      
        color = (0.10, 0.215, 0.443, 1)
        self.scaling_factor =  0.01*12.96/2 # 12.96 cm (panda) = 2 cm (real)
        self.exp_decay = 3
        
        #variables
        self.base_speed = 0
        self.gain = 1
        self.velocity = 5
        self.T = [0,0]
        
        #set speed
        self.camera_speed = 1.5
        self.turning_speed = 1.5
        
        #load scene seafloor_pebbles
        self.room = self.loader.load_model("/e/leos data/VR environments/seafloor_pebbles_flat.gltf")
        self.room = NodePath(self.room)
        self.room.setScale(1,1,1)
        self.room.setHpr(0,0,0)
        self.room.reparentTo(self.render)
     
        #set lightning
        alight = AmbientLight('alight')
        alight.setColor((0.1, 0.2, 0.3, 1)) #for naturalistic environment
        alnp = self.render.attachNewNode(alight)
        self.render.setLight(alnp)
        
        slight = Spotlight('slight')
        slight.setColor((2, 2, 2, 0))
        lens = PerspectiveLens()
        slight.setLens(lens)
        slnp = self.render.attachNewNode(slight)
        slnp.setPos(0.15, 0, 130)
        slnp.setHpr(0,-90,0)
        self.render.setLight(slnp)
        
        # Use a 512x512 resolution shadow map
        slight.setShadowCaster(True, 1024, 1024)
        # Enable the shader generator for the receiving nodes
        self.render.setShaderAuto()

        #initialize gltf interpreter
        simplepbr.init(enable_fog=True, enable_shadows = True ,render_node=self.room)
        
        
        #load instances of fish
        fish_locations = [(-1,0,.6),(-0.8,0.4,.7),(-0.9,0.3,.7),(-0.7,0.3,.8),(-1.2,0.1,.6),(-0.8,0.8,.7),(-0.4,0.5,.9)]
        
        self.object = Actor("/e/leos data/VR environments/Dc_animation.egg")
        animation_names = self.object.get_anim_names()
        for i in range(len(animation_names)):
            if animation_names[i] == 'ArmatureAction.001':
                animation = animation_names[i]
        
        self.object.loop(animation)
        self.object.setScale(0.5)
        for i in range(0,7):
            placeholder = self.room.attachNewNode('fish' + str(i+1))
            placeholder.setPos(fish_locations[i])
            self.object.instanceTo(placeholder) # instanstiate geometry to empty nodepath
        
        
        #create subject
        self.subject = self.loader.load_model('smiley.egg')
        self.subject = NodePath(self.subject)
        self.subject.hide()
        self.subject.reparent_to(self.render)
        self.cam.reparentTo(self.subject)

         #set fog
         self.expfog = Fog("Scene-wide exponential Fog object")
         elf.expfog.setColor(color) 
         self.expfog.setExpDensity(0.4)
         self.render.setFog(self.expfog)
 
        #create dark room nodepath and define cube cams1
        screens = NodePath('dark_room')
        cubeCam = self.cam.attachNewNode('cubeCam')
        cubeForward = (1, 1, 1)
        #----------------------------------------------------------------------
        class CubeFace:
            def __init__(self, name, view, up, res):
        
                self.name = name
               
                # A camera, for viewing the world under render.
                self.camNode = Camera('cam' + self.name)
                self.camNode.setScene(render)
                self.cam = cubeCam.attachNewNode(self.camNode)
                self.cam.setHpr(0,0,0)
                
                # A projector, for projecting the generated image of the world
                # onto our screen.
                self.projNode = LensNode('proj' + self.name)
                self.proj = screens.attachNewNode(self.projNode)
        
                self.lens = PerspectiveLens()
                self.lens.setFov(92)
                self.lens.setNear(0.01)
                self.lens.setFar(1000)
                self.lens.setViewVector(view[0], view[1], view[2], up[0], up[1], up[2])
        
                self.camNode.setLens(self.lens)
                self.projNode.setLens(self.lens)
        
                # Now the projection screen itself, which is tied to the
                # projector.
                self.psNode = ProjectionScreen('ps' + self.name)
                self.ps = self.proj.attachNewNode(self.psNode)
                self.psNode.setProjector(self.proj)
                
                # Generate a flat, rectilinear mesh to project the image onto.
                self.psNode.regenerateScreen(self.proj, "screen", res[0], res[1], 10, 0.97)
        #----------------------------------------------------------------------    
              
        
        cubeCam.lookAt(cubeForward)
        m = Mat4()
        m.invertFrom(cubeCam.getMat())
        cubeCam.setMat(m)
         
        # Get the base display region.
        self.dr = self.camNode.getDisplayRegion(0)
        
        # Now make a fisheye lens to view the whole thing
        ccamNode = Camera('ccam')
        ccam = screens.attachNewNode(ccamNode)
        clens = FisheyeLens()#PerspectiveLens()#
        clens.setViewVector(cubeForward[0], cubeForward[1], cubeForward[2],  0, 0, 1)
        clens.setFov(180) 
        clens.setFilmSize(self.dr.getPixelWidth(), self.dr.getPixelHeight())
        ccamNode.setLens(clens)
        
        # Set the cylindrical lens
        self.dr.setCamera(ccam)
       
        # And create the NonlinearImager to do all the fancy stuff.
        nli = NonlinearImager()
        nli.addViewer(self.dr)

        # Define the six faces.
        cubeFaces = [
            CubeFace('Right', (1, 0, 0), (0, 0, 1), (10, 40)),
            CubeFace('Back', (0, -1, 0), (0, 0, 1), (40, 40)),
            CubeFace('Left', (-1, 0, 0), (0, 0, 1), (10, 40)),
            CubeFace('Front', (0, 1, 0), (0, 0, 1), (20, 20)),
            CubeFace('Up', (0, 0, 1), (0, -1, 0), (40, 10)),
            CubeFace('Down', (0, 0, -1), (0, 1, 0), (40, 10)),
            ]
        
        for face in cubeFaces:
            i = nli.addScreen(face.ps, face.name)
            nli.setTextureSize(i, tex_res, tex_res)
            nli.setSourceCamera(i, face.cam)
        
        
        
        #change camera view to look at the ground and shift downwards
        self.cam.setHpr(0,-90,0)
        self.cam.setPos(0,0,0.7) # for naturalistic ground
        self.camLens.setNearFar(0.1,50)
        #self.cam.setPos(0,0,-3.5) # for corridor         
        
        # activate keyboard control
        self.keyboard_control()
        
        # Add the move tasks procedure to the task manager.
        self.taskMgr.add(self.move_subject, "move_sub")
        #---------------------------------------------------------------------- 
    def keyboard_control(self):
        
        #create camera control for cam
        #use build-in keyboard support: self.accept. see doc for key (event) names
        self.accept("arrow_left",updateKeyMap,["left", True])
        self.accept("arrow_right",updateKeyMap,["right", True])
        self.accept("arrow_up",updateKeyMap,["up", True])
        self.accept("arrow_down",updateKeyMap,["down", True])
        self.accept("rcontrol",updateKeyMap,["rcon", True])
        self.accept("0",updateKeyMap,["zero", True])
        self.accept("1",updateKeyMap,["1", True])
        self.accept("2",updateKeyMap,["2", True])
        self.accept("+",updateKeyMap,["+", True])
        self.accept("-",updateKeyMap,["-", True])

        #key release events
        self.accept("arrow_left-up",updateKeyMap,["left", False])
        self.accept("arrow_right-up",updateKeyMap,["right", False])
        self.accept("arrow_up-up",updateKeyMap,["up", False])
        self.accept("arrow_down-up",updateKeyMap,["down", False])
        self.accept("rcontrol-up",updateKeyMap,["rcon", False])
        self.accept("0-up",updateKeyMap,["zero", False])
        self.accept("1-up",updateKeyMap,["1", False])
        self.accept("2-up",updateKeyMap,["2", False])
        self.accept("+-up",updateKeyMap,["+", False])
        self.accept("--up",updateKeyMap,["-", False])
    #--------------------------------------------------------------------------  

  
    def move_subject(self, task):
        
        #exp time
        t = time.time()-start
        
        #get delta t
        self.T.append(t)
        self.dt = self.T[-1]-self.T[-2]
        
        #get subject Hpr
        sub_hpr = self.subject.getHpr()
        sub_pos = self.subject.getPos()
        
        quat = self.subject.getQuat()
        forwardVec = quat.getForward()

        #relevant movement for omr 
        final_velocity = self.base_speed*self.scaling_factor*self.dt + self.velocity*self.scaling_factor*self.dt*self.gain
        
        #update camera based on key pressing
        if keyMap["up"]:
            self.subject.setPos(self.subject.getPos() + forwardVec*final_velocity)
            # self.expfog = Fog("Scene-wide exponential Fog object")
            # x = 0.1*(sub_pos.y)/10
            # color = (0.1, 0.1, x,1)
            # self.expfog.setColor(color)
            # self.render.setFog(self.expfog)
            
        if keyMap["down"]:
            self.subject.setPos(self.subject.getPos() + forwardVec*(-final_velocity))
           
        if keyMap["rcon"]:
            sub_hpr.x += self.turning_speed
                   
        if keyMap["zero"]:
            sub_hpr.x -= self.turning_speed
            
        if keyMap["1"]:
            sub_hpr.y += self.turning_speed
                   
        if keyMap["2"]:
            sub_hpr.y -= self.turning_speed    
            
        #teleport
        if sub_pos.y < -100:
            self.subject.setPos(Vec3(0,100,0) + forwardVec*final_velocity)
            
        if sub_pos.y >100:
            self.subject.setPos(Vec3(0,-100,0) + forwardVec*final_velocity)
            
        if sub_pos.x < -100:
            self.subject.setPos(Vec3(100,0,0) + forwardVec*final_velocity)
            
        if sub_pos.x > 100:
            self.subject.setPos(Vec3(-100,0,0) + forwardVec*final_velocity)
        
        
        #update camera position and Hpr
        #self.cam.setPos(cam_pos)
        self.subject.setHpr(sub_hpr)
        return Task.cont
    #--------------------------------------------------------------------------
VR_instance = VR() #read out variables with test_instance.variable
VR_instance.run()  

A cubic map is a six-sided one that can be properly cover by a 90-degree lens.

hm yes, maybe that’s not the issue then.

After some detective work, I can say that the issue is linked to the interplay between playing animations and generating the cube map. It only comes up when I play the animation (no matter how slow, it accumulates with every frame).
But I’m totally lost how to resolve the issue…

I suspect this is also the solution to my problem:

the bounding volume seems to be the culprit.

This is a known problem.

As a solution, you can set the bounds volume so that it covers the entire model.

https://docs.panda3d.org/1.10/python/programming/internal-structures/geometry-storage/boundingvolume#boundingvolume

1 Like

Yes, this solved my problem :slight_smile: