Panda3d 相机控制
Panda3d 相机控制
文章目录
- Panda3d 相机控制
- Panda3d中的透视镜头和垂直镜头
- 透视镜头
- 垂直镜头
- Panda3d 中用代码控制相机的移动
- 用键盘控制相机的移动
- 用鼠标控制相机的移动
Panda3d 把相机也当做是一个 PandaNode,因此可以向操作其他节点对其进行操作。
真正的相机是在ShowBase类中的一个叫做base.cam的NodePath,在这个上面还有一个更简单的叫做base.camera的NodePath,一般对相机进行控制的话,是在代码中进行控制。
默认情况下,panda运行一个task使我们可以通过鼠标来移动相机。用户自己写的移动相机的代码将和这个task产生冲突。该task根据鼠标当前的每一帧输入来更新相机的位置。这意味着直接控制相机的代码将不能工作,因为它会跟默认的相机控制任务相冲突。为了处理这种冲突,那么用户可以通过调用以下函数进行:
base.disableMouse()
ShowBase类为用户准备了一些控制相机的方法。useDrive()命令打开键盘和鼠标控制,这两种控制系统都只能在X和Y轴上移动,不能在Z轴上移动。
键盘系统使用方向键,“上”向前移动相机,“下”向后移动。“左”“右”键左右移动相机镜头。
鼠标系统对按下的任何一个键都有反应。若光标向屏幕上方移动,相机向前;若光标向屏幕下方移动,相机向后。如果光标在屏幕两边,相机向那个方向进行旋转。相机的移动速度取决于光标距离中心的远近。另外,Panda还提供一个命令允许使用跟踪球(trackball)鼠标:
base.useDrive()
base.useTrackball()
ShowBase还提供了oobe()方法,当你的代码在移动相机节点(base.camera)时,你可以用鼠标/跟踪球来控制基相机节点(base.cam)。这对debug非常有用。oobe代表“out-of-body experience”(灵魂出窍),即开发时你可以对程序进行全方位的观察(God’s-eye view)。该方法是一个开关,你要打开oobe模式时调用它,然后再调用一次关闭它:
base.oobe()
oobeCull()是oobe()的一个变形,它们的作用相近,oobeCull()的不同在于,当从新的相机位置重绘场景时,场景仍然会以原先的相机位置进行剔除(cull)。因此,你可以从你“灵魂飞出”地方来观察场景,你可以四处游走,看到物体进入和弹出视区,就像你的视棱台(view frustum)也在移动一样。
Panda3d中的透视镜头和垂直镜头
透视镜头
每个相机都有一个镜头,决定它的成像参数。对于简单的应用程序,你无需考虑镜头问题,默认的镜头参数已经很好了。但是,有时候你想调整一些镜头的参数,如视域。根据对镜头的不同需求,我们提供了几种接口来修改参数。
Panda3d 启动时,它自动为你创建了一个默认的相机和镜头。这个默认相机对象保持在base.cam(从方便的角度,我们应该使用base.camera来移动相机),默认镜头是base.camLens。
默认镜头几乎总是一个透视镜头——即 PerspectiveLens 类的一个实例——除非你换成另一种镜头。到目前为止,透视镜头是一种使用最广泛的镜头,它就像一台真实的相机的镜头,功能与人眼晶状体相同。
上图展示一个常规透镜相机的成像。相机只能看到黑线框里面的物体,这个区域被称为镜头棱台(frustum)。
图中可以看到镜头拍摄的图像(图像是颠倒的,跟真实的物理镜头成像一样)。颠倒的图像只是起到说明作用,它并不是Panda3D相机的一部分,它帮助我们理解Panda3D镜头和真实镜头的关系。
PerspectiveLens有很多参数可以设置,这些参数不都是独立的,设置某些参数将改变另外一些参数的值。
垂直镜头
前面介绍了PerspectiveLens类,以及透视镜头,3D渲染常用的另外一种镜头就是垂直镜头,它没有视域的概念, 如下图所示:
在垂直镜头里没有透视——穿过镜头的平行光不汇聚,而是保持平行。透视镜头模拟了真实的物理镜头,但现实中不存在垂直镜头。它主要用于特殊效果,比如非自然的景观、模拟即时战略游戏的2.5D场景,或绘制不需要透视的2D物体。事实上,对于 render2d scene graph,默认的相机就是一个OrthographicLens,用于绘制屏幕GUI。
既然垂直镜头没有视域角度,lens.setFov()方法就不起作用。为了调节垂直镜头的范围,你需要调整它的胶片规格。与PerspectiveLens不同,对OrthographicLens来说胶片规格的单位不是任意的,应该用空间单位,与场景建模时使用的单位一致。例如,上图OrthographicLens的胶片规格被设为lens.setFilmSize(20, 15), 20 英尺 x 15 英尺 ——因为场景建模以英尺为单位,一只大熊猫大概有 12 英尺 高。
垂直镜头的另一个方便的参数是近距离,它的值不必一定是正数。实际上可以是负数——可以把近平面放在相机平面之后,也就是说相机可以看到它身后的物体。为render2d准备的OrthographicLens被设成setNearFar(-1000, 1000),将绘制所有Z值在-1000到1000之间的物体。(当然,在render2d中,几乎全部物体的Z值都为0,因此不会有什么问题)
如果需要,你可以把默认的相机换成一个垂直镜头:
lens = OrthographicLens()
lens.setFilmSize(20, 15) # 根据你的场景来选取适当的值
base.cam.node().setLens(lens)
注意,使用垂直镜头可能让人失去空间感——比如,物体不因为你靠近它而变大,也不因为你远离它而变小——因此你可能不知道相机在移动。
Panda3d 中用代码控制相机的移动
下面两个代码就需要禁用Panda3d中默认的鼠标控制相机任务,否则不能达到用户预期的目的。
用键盘控制相机的移动
最终的代码如下所示:
from direct.actor.Actor import Actor
from panda3d.core import loadPrcFileData
from direct.showbase.ShowBase import ShowBase #基本显示模块# 画面显示配置,设置窗口大小,窗口名称、显示帧率
confVar = """
win-size 1280 720
window-title example
show-frame-rate-meter True
"""loadPrcFileData("", confVar)class MyApp(ShowBase):def __init__(self):#场景初始化super(MyApp, self).__init__()base.disableMouse()self.person = base.loader.loadModel('smiley')self.person.reparentTo(self.render)# 循环一个动作# self.person.loop('run')self.cam.setPos(0, -10, 0)self.keyMap = {'up':False,'down':False,'left':False,'right':False,'go':False,'back':False,'rotate':False,}self.speed = 4self.angle = 0# self.accept(<event-name>,<function name>)# self.accept(<event-name>,<function name>, <parameters-list>)self.accept('arrow_up', self.updateKeyMap, ['up', True])self.accept('arrow_up-up', self.updateKeyMap, ['up', False])self.accept('arrow_down', self.updateKeyMap, ['down', True])self.accept('arrow_down-up', self.updateKeyMap, ['down', False])self.accept('arrow_left', self.updateKeyMap, ['left', True])self.accept('arrow_left-up', self.updateKeyMap, ['left', False])self.accept('arrow_right', self.updateKeyMap, ['right', True])self.accept('arrow_right-up', self.updateKeyMap, ['right', False])self.accept('w', self.updateKeyMap, ['go', True])self.accept('w-up', self.updateKeyMap, ['go', False])self.accept('s', self.updateKeyMap, ['back', True])self.accept('s-up', self.updateKeyMap, ['back', False])self.accept('space-up', self.updateKeyMap, ['rotate', True])self.accept('apace-up-up', self.updateKeyMap, ['rotate', False])self.taskMgr.add(self.update,"update")def updateKeyMap(self, key, state):self.keyMap[key] = statedef update(self, task):# 或者这个函数的运行时间,或者说是更新的帧率dt = globalClock.getDt()pos = self.person.getPos()if self.keyMap['up']:pos.z += self.speed * dtif self.keyMap['down']:pos.z -= self.speed * dtif self.keyMap['left']:pos.x -= self.speed * dtif self.keyMap['right']:pos.x += self.speed * dtif self.keyMap['go']:pos.y -= self.speed * dtif self.keyMap['back']:pos.y += self.speed * dtif self.keyMap['rotate']:self.angle += 1if self.angle == 360:self.angle = 0self.person.setH(self.angle)self.person.setPos(pos)return task.contapp = MyApp()
app.run()
run()
用鼠标控制相机的移动
Panda3d 中默认的鼠标操作定义如下:
- 鼠标左键:按住左键再移动光标可以控制画面左右旋摆。
- 鼠标右键:按住右键键再移动光标可以控制画面的远近。
- 鼠标滚轮:按住滚轮键键再移动光标可以控制角度上下左右的角度旋转(盘旋)。
- 鼠标右键+滚轮:按住右键+滚轮键键再移动光标绕垂直电脑屏幕的轴旋转。
下面代码中的鼠标操作定义如下:
-
鼠标左键:按住左键再移动光标可以控制画面左右上下移动。
-
鼠标右键:按住右键键再移动光标可以控制画面的左右上下旋转。
-
鼠标滚轮:按住滚轮键可以控制画面的前后移动,也就是画面的放大和缩小。
- mouse1 是鼠标左键
- mouse2 是鼠标中键
- mouse3 是鼠标右键
代码如下:
from direct.actor.Actor import Actor
from panda3d.core import loadPrcFileData
from direct.showbase.ShowBase import ShowBase #基本显示模块
from direct.showbase.ShowBase import (Filename, LVecBase3f, NodePath, Task)
from direct.interval.IntervalGlobal import LerpPosInterval
from IPython import embed# 画面显示配置,设置窗口大小,窗口名称、显示帧率
confVar = """
win-size 1280 720
window-title example
show-frame-rate-meter True
"""loadPrcFileData("", confVar)class MyApp(ShowBase):def __init__(self):#场景初始化super(MyApp, self).__init__()base.disableMouse()self.person = base.loader.loadModel('smiley')self.person.reparentTo(self.render)# 循环一个动作# self.person.loop('run')self.cam.setPos(0, -10, 0)self.keyMap = {'up':False,'down':False,'left':False,'right':False,'go':False,'back':False,'rotate':False,}self.speed = 4self.angle = 0self.mouse_map = {}self.Cursor2D_X = 0.0self.Cursor2D_Y = 0.0self.Cursor2D_X_pre = 0.0self.Cursor2D_Y_pre = 0.0self.Cursor2D_X_direction = 0.0self.Cursor2D_Y_direction = 0.0# 可以取消原来默认的鼠标点击事件,可以用户自定义鼠标控制事件self.accept("mouse1", self.SetMouse, ["mouse1_event", True])self.accept("mouse1-up", self.SetMouse, ["mouse1_event", False])# mouse2 是鼠标中键# 鼠标的右键self.accept("mouse3", self.SetMouse, ["mouse3_event", True])self.accept("mouse3-up", self.SetMouse, ["mouse3_event", False])# self.mouseWatcherNode.set_modifier_buttons(ModifierButtons())# self.buttonThrowers[0].node().set_modifier_buttons(ModifierButtons())self.accept("mouse3-up_mouse1-up", self.SetMouse, ["mouse31_event", False])# 鼠标中键向上滚动self.accept("wheel_up", self.cameraZoom,[-1])# 鼠标中键向下滚动self.accept("wheel_down", self.cameraZoom,[1])self.taskMgr.add(self.UpdateMouseCameraTask, "UpdateMouseCameraTask")def SetMouse(self, mouse, val):self.mouse_map[mouse] = valdef UpdateMouseCameraTask(self, task):# time since last framedt = globalClock.getDt()step = 90if base.mouseWatcherNode.hasMouse():# 两条指令值等价的,都是得到当前鼠标的位置self.Cursor2D_X = base.mouseWatcherNode.getMouseX()self.Cursor2D_Y = base.mouseWatcherNode.getMouseY()if(self.Cursor2D_X - self.Cursor2D_X_pre > 1e-4):self.Cursor2D_X_direction = 0.1elif (self.Cursor2D_X - self.Cursor2D_X_pre < -1e-4):self.Cursor2D_X_direction = -0.1else:self.Cursor2D_X_direction = 0.0if(self.Cursor2D_Y - self.Cursor2D_Y_pre > 1e-4):self.Cursor2D_Y_direction = 0.1elif (self.Cursor2D_Y - self.Cursor2D_Y_pre < -1e-4):self.Cursor2D_Y_direction = -0.1else:self.Cursor2D_Y_direction = 0.0if self.mouse_map.get("mouse1_event") == True and (self.mouse_map.get("mouse3_event") == False or self.mouse_map.get("mouse3_event") == None):self.camera.setPos(LVecBase3f(self.camera.getX()-(2*self.Cursor2D_X_direction), self.camera.getY(), self.camera.getZ()-2*self.Cursor2D_Y_direction))if self.mouse_map.get("mouse3_event") == True and (self.mouse_map.get("mouse1_event") == False or self.mouse_map.get("mouse1_event") == None):self.camera.setHpr(self.camera.getH()+(2*self.Cursor2D_X_direction), self.camera.getP()+(2*self.Cursor2D_Y_direction), self.camera.getR())# # 以下两个指令用来获取得到当前窗口的大小# print(base.win.getProperties().getXSize())# print(base.win.getProperties().getYSize())self.Cursor2D_X_pre = self.Cursor2D_Xself.Cursor2D_Y_pre = self.Cursor2D_Yreturn Task.cont# 进行相机视野的放大和缩小def cameraZoom(self,dir):self.camera.setPos(LVecBase3f(self.camera.getX(), self.camera.getY()-(2*dir), self.camera.getZ()+(2*dir)))# self.camZoom = LerpPosInterval(self.camera, self.speed, LVecBase3f(self.camera.getX(), self.camera.getY()-(2*dir), self.camera.getZ()+(2*dir)))# self.camZoom.start()app = MyApp()
app.run()
run()
上述UpdateMouseCameraTask 主要是通过不断的比较当前鼠标光标的位置来调整相机的位置,按下鼠标的左键主要是控制相机的位置移动, 而按下右键主要是控制相机的姿态旋转。
相关文章:

Panda3d 相机控制
Panda3d 相机控制 文章目录 Panda3d 相机控制Panda3d中的透视镜头和垂直镜头透视镜头垂直镜头 Panda3d 中用代码控制相机的移动用键盘控制相机的移动用鼠标控制相机的移动 Panda3d 把相机也当做是一个 PandaNode,因此可以向操作其他节点对其进行操作。 真正的相机是…...

Linux(CentOS)安装MySQL教程
主要参考链接 教程 1. 准备工作 1.1 安装CentOS虚拟机 教程点击 1.2 将CentOS虚拟机设置为静态IP,否则你每次重启虚拟机后连接数据库都要重新查IP 教程点击 1.3 如果有安装过MySQL,请先卸载MySQL 教程点击 1.4 虚拟机执行命令su切换到root账号(输…...
使用 OpenSSL 工具撰写 Bash 脚本进行密码明文的加密与解密
使用 OpenSSL 工具进行密码明文的加密与解密 Written By: Xinyao Tian 简介 本文档描述了使用 OpenSSL 工具在 Bash 脚本中对密码进行加密和解密的简单方式。 BASE64 的加密与解密脚本 使用 Base64 算法进行密码的加密 脚本名称为 encryptPasswd.sh, 脚本内容如下: #!/b…...

uniapp之actionsheet 自定义组件
uniapp本身自带的actionsheet太丑,不够美观。闲着也是闲着,自己实现了一个类似的选择器。 支持功能: 1、左对齐 2、右对齐 3、居中 4、可加图标 下面贴出使用教程: <template><view><action-sheet alignment&…...
在nodejs中使用Mongoose和MongoDB实现curd操作
在nodejs中使用Mongoose和MongoDB实现curd操作 在Node.js中,数据库被用来存储和检索Web应用程序的数据。它们是构建动态和可伸缩应用程序的重要组成部分。Node.js提供了各种模块和包,可以与数据库一起工作,如MySQL、PostgreSQL、MongoDB等。它们允许开发人员使用各…...
10.28 校招 实习 内推 面经
绿*泡*泡: neituijunsir 交流裙 ,内推/实习/校招汇总表格 1、校招|理想汽车2024校园招聘算法类岗位特辑(内推) 校招|理想汽车2024校园招聘算法类岗位特辑(内推) 2、校招 | 国网信…...

Azure 机器学习 - 使用无代码 AutoML 训练分类模型
了解如何在 Azure 机器学习工作室中使用 Azure 机器学习自动化 ML,通过无代码 AutoML 来训练分类模型。 此分类模型预测某个金融机构的客户是否会认购定期存款产品。 关注TechLead,分享AI全维度知识。作者拥有10年互联网服务架构、AI产品研发经验、团队管…...
【调试技术】用户态查看PEB和TEB
概述:用户态查看进程 PEB 和 TEB(通过windbg附加或启动调试的exe) 0x01 用户态查看 TEB 和 PEB 在双机调试的时候,可以直接使用 !PEB PID 和 !TEB TID 获取进程和线程的相关信息,在用户态这两个命令就会失效。原因就是用户态不支持大写的 !T…...
如何搭建一个Spring MVC和Vue3的应用程序
要搭建一个基于Spring MVC框架和Vue3框架的前端应用程序,可以按照以下步骤进行: 创建Java项目并添加Spring MVC依赖 使用Maven或Gradle等构建工具创建一个Java项目,并在项目的pom.xml或build.gradle文件中添加Spring MVC依赖。例如…...

CSS3设计动画样式
CSS3动画包括过渡动画和关键帧动画,它们主要通过改变CSS属性值来模拟实现。我将详细介绍Transform、Transitions和Animations 3大功能模块,其中Transform实现对网页对象的变形操作,Transitions实现CSS属性过渡变化,Animations实现…...
AtCoder abc 144
D - Water Bottle x先除以a,得到面积。体积和面积是等同考虑的。 分两种情况,一种是水比一半面积少,一种是水比一半面积多。 # -*- coding: utf-8 -*- # time : 2023/6/2 13:30 # author : yhdutongwoo.cn # desc : # file : …...
【开题报告】基于SpringBoot的医美在线预约系统的设计与实现
1.研究背景 医美行业是指结合医学和美容技术,为人们提供外貌改善和整容手术等服务的领域。随着社会经济的发展和人们审美观念的变化,医美行业得到了快速的发展,并受到越来越多人的关注和需求。 传统的医美预约方式主要依赖于电话预约或现场…...

AutoGen agent使用;调用本地LLM
参考: https://microsoft.github.io/autogen 安装: pip install pyautogen 代码 本地LLM部署可以用fastchat、vllm等框架部署openai接口: from autogen import AssistantAgent, UserProxyAgent, oai ## 调用本地模型对外的openai接口 conf…...

Docker安装matomo
Docker安装matomo 文章目录 Docker安装matomo1.安装Docker2.matomo安装 1.安装Docker curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun2.matomo安装 #拉取matomo镜像 docker pull matomo#启动matomo容器 docker run -d --name matomo -p 8093:80 -v /do…...

DSP开发例程(4): logbuf_print_to_uart
目录 DSP开发例程: logbuf_print_to_uart新建工程源码编辑app.cfgos.cmain.c 调试说明 DSP开发例程: logbuf_print_to_uart SYS/BIOS 提供了 xdc.runtime.Log, xdc.runtime.LoggerBuf 和 xdc.runtime.LoggerSys 这几个模块用于日志记录. 日志信息在 应用程序调试和状态监控中非…...

计算机毕业设计选题推荐-超市售货微信小程序/安卓APP-项目实战
✨作者主页:IT研究室✨ 个人简介:曾从事计算机专业培训教学,擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…...

Azure 机器学习 - 使用 Visual Studio Code训练图像分类 TensorFlow 模型
了解如何使用 TensorFlow 和 Azure 机器学习 Visual Studio Code 扩展训练图像分类模型来识别手写数字。 关注TechLead,分享AI全维度知识。作者拥有10年互联网服务架构、AI产品研发经验、团队管理经验,同济本复旦硕,复旦机器人智能实验室成员…...
Vue 创建自定义 ref 函数
Vue 创建自定义 ref 函数 customRef customRef 用于:创建一个自定义的 ref 函数,并对其依赖项跟踪和更新触发进行显式控制。 使用 customRef 创建自定义 ref 函数 // 创建自定义 ref 函数 function myRef(value) {return customRef((track, trigger) &…...

[2016-2018]phpstudy的exp制作
[2016-2018]phpstudy的exp制作 用python的requests模块进行编写 修改请求数据包进行远程代码执行 import requests import base64 def remove_code_execute():try:url input("请输入要测试的网址:")cmd input("想要执行的命令:")cmd f"system({…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...

练习(含atoi的模拟实现,自定义类型等练习)
一、结构体大小的计算及位段 (结构体大小计算及位段 详解请看:自定义类型:结构体进阶-CSDN博客) 1.在32位系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是多少? #pragma pack(4)st…...

python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...
Python爬虫实战:研究feedparser库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...
MVC 数据库
MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...

Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...

Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...

04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...

安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)
船舶制造装配管理现状:装配工作依赖人工经验,装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书,但在实际执行中,工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...

VM虚拟机网络配置(ubuntu24桥接模式):配置静态IP
编辑-虚拟网络编辑器-更改设置 选择桥接模式,然后找到相应的网卡(可以查看自己本机的网络连接) windows连接的网络点击查看属性 编辑虚拟机设置更改网络配置,选择刚才配置的桥接模式 静态ip设置: 我用的ubuntu24桌…...