OpenSceneGraph程序设计 www.osgChina .org 第二章:OSG基础 版本,argv[o]为空串(")。argv[1]为在DOs命令行中执行程序名后的第一个字符串;argv[2]为执行程序名后的 第二个字符串;:argv[agc为NULL。因此我们完全可以通过读取参数列表到OSG程序当中,来利用参数 列表来寻找用户传入的模型,在OSG当中,有一个类名为osg:ArgumentParser,该类是专门用来管理main 中的一些参数的,里面有一些方法来提取和控制mai里面的参数,在本章后会有类方法说明列表。下面我 们来看一下最终可以通过参数来读取模型什么的代码: 示例四:最好的Hel loWor Id //by Free South www.osgChina .orgieysx@163.com #indude <os gDB/ReadFile> #indude <osgUtil/Optimizer> #indude <osg/Coordina teSys temNode> #indude <osg/Switch> #indude <osgText/Text> #indude <osgViewer/Viewer> #indude <osgViewe r/ViewerEventHandlers> #indude <osgGA/Tra ckballManipulator> #indude <osgGA/FlightManipulator> #indude <osgGA/Drive Manipula tor> #indude <osgGA/KeySwi tch MatrixManipulator> #indude <osgGA/State SetManipula tor> #indude <osgGA/Animation Pa th Manipulator> #indude <osgGA/Terrain Manipulator> #indude <iostream> int main(intargc,char**argv) osg::ArgumentPa rser a rguments(&argc a rgv); arguments ge tApplica tionUsage()->setApplica tion Name(a rguments.getApplicationNa me()); 3. arguments getApplica tionUsage()->setDes cription(a rguments.getApplica tion Name()+"is the s tandard OpenScene Graph 4.example which loads and visualises 3d models."); arguments ge tApplicationUsage()->setCommandLine Usage(a rguments.getApplica tion Name()+"[options]filename..."); 6. arguments getApplicationUsage()->addCommandLine Option("-image <filename>""Load animage and renderitona quad"); 7. arguments getApplicationUsage(->addCommandLineOption("--dem <filename>","Load animage/DEMand renderit on a HeightField"); 8. arguments getApplicationUsage()->addCommandLineOption("-h or-help""Displaycommand line parameters"); 9 arguments ge tApplica tionUsage()->addCommandLine Option("-help-env","Displayenvironmental variables available"); 10. arguments getApplicationUsage()->addCommandLineOption("--help-keys","Display keyboard&mouse bindings available"); 11. arguments getApplicationUsage()->addCommandLineOption("--help-all","Displayall command line,env vars and keyboard mouse bindings."); 12. arguments getApplicationUsage()->addCommandLine Option("--SingleThreaded","Select Single Threaded threading model 28
OpenSceneGraph 程序设计 www.osgChina.org 第二章:OSG 基础 28 版本,argv[0]为空串("")。argv[1]为在 DOS 命令行中执行程序名后的第一个字符串; argv[2]为执行程序名后的 第二个字符串; ... argv[argc]为 NULL。因此我们完全可以通过读取参数列表到 OSG 程序当中,来利用参数 列表来寻找用户传入的模型,在 OSG 当中,有一个类名为 osg::ArgumentParser ,该类是专 门用来管理 main 中的一些参数的,里面有一些方法来提取和控制 main 里面的参数,在本章后会有类方法说明列表。下面我 们来看一下最终可以通过参数来读取模型什么的代码: 示例四:最好的 HelloWorld //by FreeSouth www.osgChina.org ieysx@163.com #include <osgDB/ReadFile> #include <osgUtil/Optimizer> #include <osg/CoordinateSystemNode> #include <osg/Switch> #include <osgText/Text> #include <osgViewer/Viewer> #include <osgViewer/ViewerEventHandlers> #include <osgGA/TrackballManipulator> #include <osgGA/FlightManipulator> #include <osgGA/DriveManipulator> #include <osgGA/KeySwitchMatrixManipulator> #include <osgGA/StateSetManipulator> #include <osgGA/AnimationPathManipulator> #include <osgGA/TerrainManipulator> #include <iostream> int main(int argc, char** argv) { 1. osg::ArgumentParser arguments(&argc,argv); 2. arguments.getApplicationUsage()->setApplicationName(arguments.getApplicationName()); 3. arguments.getApplicationUsage()->setDescription(arguments.getApplicationName()+" is the standard OpenSceneGraph 4.example which loads and visualises 3d models."); 5. arguments.getApplicationUsage()->setCommandLineUsage(arguments.getApplicationName()+" [options] filename ..."); 6. arguments.getApplicationUsage()->addCommandLineOption("--image <filename>","Load an image and render it on a quad"); 7. arguments.getApplicationUsage()->addCommandLineOption("--dem <filename>","Load an image/DEM and render it on a HeightField"); 8. arguments.getApplicationUsage()->addCommandLineOption("-h or --help","Display command line parameters"); 9. arguments.getApplicationUsage()->addCommandLineOption("--help-env","Display environmental variables available"); 10. arguments.getApplicationUsage()->addCommandLineOption("--help-keys","Display keyboard & mouse bindings available"); 11. arguments.getApplicationUsage()->addCommandLineOption("--help-all","Display all command line, env vars and keyboard & mouse bindings."); 12. arguments.getApplicationUsage()->addCommandLineOption("--SingleThreaded","Select SingleThreaded threading model
OpenScene Graph程序设计 www.osgChina .org 第二章:OsG基础 for viewer."); 13.arguments getApplicationUsage()->addCommand Line Option("--CullDrawThreadPerContext","Select CullDrawThreadPerContext threading model for viewer."); 14. arguments getApplicationUsage()->addCommandLine Option("--DrawThreadPerContext","Select DrawThreadPerContext threa ding model for viewer."); 15. arguments getApplicationUsage()->addCommand LineOption("--Cull ThreadPerCame ra DrawThreadPerContext","Select CullThread PerCa me raDra wThread PerContext threading model for vie wer."); 16. bool helpAll =arguments.read("-help-all"); 17. unsignedint helpType =((helpAll II arguments.read("-h")II arguments.read("--help"))? osg::Application Usage::COMMAND_LINE_OPTION 0)I ((helpAll I arguments.read("-help-env"))? osg::Application Usage::ENVIRONMENTAL VARIABLE:0) ((helpAll I argume nts.read("-help-keys"))? osg::Applica tion Usage::KEYBOARD_MOUSE_BINDING 0); 18.if(helpType) 19. a rguments ge tApplicationUsage()->write(std:cout,helpType); 20. retum 1; 21. os gViewer::Viewer viewer(arguments); 22. if (a rguments .errors()) 23. 24. arguments write ErrorMessages(s td::cout); 25. retum 1; 26. } 27. if (a rguments .argc()<=1) 28. 29. arguments getApplicationUsage()->write(std:cout,osg::ApplicationUsage::COMMAND_UNE_OPTON); 30. retum 1; 31. } 32. 33 34. osg::ref_ptr<osgGA::KeySwitch Ma trixMa nipula tor>keys witch Manipulator new osgGA::KeySwitch Ma trixManipula tor; 35. keyswitch Manipulator->addMatrixMa nipulator('1',"Trackball",new osgGA::TrackballManipulator()); 36. keyswitchManipula tor->add MatrixMa nipulator('2',"Flight",new osgGA::FlightMa nipulator()); 37. keyswitch Manipula tor->add MatrixMa nipula tor('3',"Drive",new osgGA::Drive Manipula tor()); 38. keyswitchManipula tor->addMatrixMa nipula tor('4',"Terrain",new osgGA::Terrain Ma nipulator()); 39. std:string pathfile; 29
OpenSceneGraph 程序设计 www.osgChina.org 第二章:OSG 基础 29 for viewer."); 13. arguments.getApplicationUsage()->addCommandLineOption("--CullDrawThreadPerContext","Select CullDrawThreadPerContext threading model for viewer."); 14. arguments.getApplicationUsage()->addCommandLineOption("--DrawThreadPerContext","Select DrawThreadPerContext threading model for viewer."); 15. arguments.getApplicationUsage()->addCommandLineOption("--CullThreadPerCameraDrawThreadPerContext","Select CullThreadPerCameraDrawThreadPerContext threading model for viewer."); 16. bool helpAll = arguments.read("--help-all"); 17. unsigned int helpType = ((helpAll || arguments.read("-h") || arguments.read("--help"))? osg::ApplicationUsage::COMMAND_LINE_OPTION : 0 ) | ((helpAll || arguments.read("--help-env"))? osg::ApplicationUsage::ENVIRONMENTAL_VARIABLE : 0 ) | ((helpAll || arguments.read("--help-keys"))? osg::ApplicationUsage::KEYBOARD_MOUSE_BINDING : 0 ); 18. if (helpType) { 19. arguments.getApplicationUsage()->write(std::cout, helpType); 20. return 1; } 21. osgViewer::Viewer viewer(arguments); 22. if (arguments.errors()) 23. { 24. arguments.writeErrorMessages(std::cout); 25. return 1; 26. } 27. if (arguments.argc()<=1) 28. { 29. arguments.getApplicationUsage()->write(std::cout,osg::ApplicationUsage::COMMAND_LINE_OPTION); 30. return 1; 31. } 32. 33. { 34. osg::ref_ptr<osgGA::KeySwitchMatrixManipulator> keyswitchManipulator = new osgGA::KeySwitchMatrixManipulator; 35. keyswitchManipulator->addMatrixManipulator( '1', "Trackball", new osgGA::TrackballManipulator() ); 36. keyswitchManipulator->addMatrixManipulator( '2', "Flight", new osgGA::FlightManipulator() ); 37. keyswitchManipulator->addMatrixManipulator( '3', "Drive", new osgGA::DriveManipulator() ); 38. keyswitchManipulator->addMatrixManipulator( '4', "Terrain", new osgGA::TerrainManipulator() ); 39. std::string pathfile;
OpenScene Graph程序设计 www.osgChina .org 第二章:OsG基础 40. char keyForAnimationPath ='5'; 41. while (a rguments.read("p",pa thfile)) 42. 43. os gGA::Anima tionPathManipulator*apm =new osgGA::Anima tionPa thManipulator(pa thfile): 44. if (apm lapm->valid()) 45. 46. unsigned int num keys witch Manipulator->getNumMatrixManipulators(); 47. keyswitch Manipula tor->addMa trixManipula tor(keyForAnima tion Path,"Pa th",apm ) 48. keyswitch Manipula tor->selectMa trixManipula tor(num); 49. ++keyForAnimation Pa th; 50. 51 52 viewersetCa me ra Manipula tor(keys witch Manipulator.get()); 54 vieweraddEventHandler(new osgGA::Sta teSetManipulator(viewer.getCamera()->getOrCreateStateSet()); 55 vieweradd EventHandler(new osgViewer::ThreadingHandler); 56 vieweraddEventHandler(new osgViewer::WindowSizeHandler); 57 vieweraddEventHandler(new osgViewer::StatsHandler); 吧 vieweraddEventHandler(new os gViewer::HelpHandle rarguments.getApplicationUsage())); 59 vie weraddEventHandler(new osgViewer::Record Ca me ra PathHandler); 60 osg::ref_ptr<osg::Node>loadedModel osgDB::readNodeFiles(arguments); 61 if(lloadedModel) 62 63 std::cout <<arguments.getApplication Name()<<":No da ta loaded"<<std:endl; 64 retum 1; 65 } 66 a rguments.reportRe mainingOptions AsUnrecognized(); 0 if (a rguments.errors() 68 9 a rguments.write ErrorMessages(s td:cout); 70 retum 1; 71 72 osgUtil::Optimizer optimizer; 13 optimizer.optimize(loaded Model.get()); 74 viewersetSceneData(loadedModel.get()); 75 viewer.realize(); 76 viewer.run(); } 这回不得不费时使劲解释一下里面的代码了,他运行后定位到他的文件夹,在命令行中输入:4.最好的 HelloWorId.exe Cessna..osg这样它就可以读取你指定的cessna模型。 代码解释如下: 第1行:申请一个ArgumentParser类,它是总的来负责MAIN里的参数的,它可以判断里面的参数是否合 30
OpenSceneGraph 程序设计 www.osgChina.org 第二章:OSG 基础 30 40. char keyForAnimationPath = '5'; 41. while (arguments.read("-p",pathfile)) 42. { 43. osgGA::AnimationPathManipulator* apm = new osgGA::AnimationPathManipulator(pathfile); 44. if (apm || !apm->valid()) 45. { 46. unsigned int num = keyswitchManipulator->getNumMatrixManipulators(); 47. keyswitchManipulator->addMatrixManipulator( keyForAnimationPath, "Path", apm ); 48. keyswitchManipulator->selectMatrixManipulator(num); 49. ++keyForAnimationPath; 50. } 51 } 52 viewer.setCameraManipulator( keyswitchManipulator.get() ); 53 } 54 viewer.addEventHandler( new osgGA::StateSetManipulator(viewer.getCamera()->getOrCreateStateSet()) ); 55 viewer.addEventHandler(new osgViewer::ThreadingHandler); 56 viewer.addEventHandler(new osgViewer::WindowSizeHandler); 57 viewer.addEventHandler(new osgViewer::StatsHandler); 58 viewer.addEventHandler(new osgViewer::HelpHandler(arguments.getApplicationUsage())); 59 viewer.addEventHandler(new osgViewer::RecordCameraPathHandler); 60 osg::ref_ptr<osg::Node> loadedModel = osgDB::readNodeFiles(arguments); 61 if (!loadedModel) 62 { 63 std::cout << arguments.getApplicationName() <<": No data loaded" << std::endl; 64 return 1; 65 } 66 arguments.reportRemainingOptionsAsUnrecognized(); 67 if (arguments.errors()) 68 { 69 arguments.writeErrorMessages(std::cout); 70 return 1; 71 } 72 osgUtil::Optimizer optimizer; 73 optimizer.optimize(loadedModel.get()); 74 viewer.setSceneData( loadedModel.get() ); 75 viewer.realize(); 76 viewer.run(); } 这回不得不费时使劲解释一下里面的代码了,他运行后定位到他的文件夹,在命令行中输入:4.最好的 HelloWorld.exe Cessna.osg 这样它就可以读取你指定的 cessna 模型。 代码解释如下: 第 1 行:申请一个 ArgumentParser 类,它是总的来负责 MAIN 里的参数的,它可以判断里面的参数是否合
OpenSceneGraph程序设计 www.osgChina .org 第二章:OSG基础 法,也可以读取里面的参数给OSG程序来用。也可以写一些帮助信息到程序里显示出来给客户来看。 第2行:设置应用程序的名称,一般而言程序的名称是你的项目名字。因为在main的第一个命令行中,是 程序的路径全名,比如会是"E\oSG大全1第二章OsG基础4.最好的Hello World小de bug\4.最好的 HelloWorld.exe“这样ArgumentParser就可以从这个路当中获得程序的名称,然后传到ArgumentParser 当中去。 第3~4行:设置一下对这个程序的描述,描述的内容是后面的字串。 第5行:命令行的格式说明,注意这里只是说明,并没有实际意义,即程序后[arguments..getApplicationName(】 加操作+[options]加文件名[[filename."]。 第6~l5行:对osgviewer的一些命令行功能进行一些说明,比如-image filename为展示一张图片,下面雷 同,它主要的功能是查看模型。 第16~20行:为输出帮助信息,里面有帮助信息的类型,其中一help-all为最全的帮助信息,但是显然没有 必要查看这么全,因为行数太多翻着忒累。 第21行:申请一个viewer但是这时可以使用类对象arguments做为参数来读取main中输入的参数。 第2226行:用来判断参数是否合法,比如你乱输一气,那就是不合法,程序会直接结束并返回。 第2731行:当程序的命令行参数只有一个时,会输出一些命令提示。只有一个代表用户没有输入模型文 件,因为这一个命令行参数是该程序的全路径名,所以如果只有一个,代表用户并未输入模型,程序 无法演示。会输出可执行命令行,然后退出。 第3338行:这个非常熟悉了,是将要添加的操作器。 第3951行:这里的功能是:看是否输入了路径文件,如果输入路径文件就按路径文件走。里面具体类的 用法,会在别章讲到。 第54~59行:添加一些事件响应,在前面有所涉及。 第6065行:从参数中读取模型,以前我们是直接读取的模型,这一次我们会从外部参数中来读。如果找 不到模型就会输出错误信息,然后程序退出。 第66~71行:报到无法识别的错误参数。如果有错,这些错将会被报告出来,且程序退出。 第72~73行:优化读到的模型,为什么模型需要优化呢,主要是关于模型状态的优化,OSG对模型的状态 渲染进行了重组,以便达到最佳的性能与最少的GPU损耗。 第74~76行:渲染最终的模型。 到这里来讲,读者会对OSG程序有了一个大概的了解,可能不会太深,但是所谓熟能生巧,多敲一些代码, 慢慢的进去了。其实前面的程序在osg的源代码中就有,就是Application中的osgViewer程序,osgViewer 程序功能非常强大,用的也非常非常多,我们平时都用它来查看一些模型。 在这里我需要讲一下关于osgViewer的异常,有些时候读取模型的时候可能会很慢,这可能是模型的原因。 有些时候没有纹理,因为模型的启动文件有时候与纹理是分开存放的,可能会找不到纹理,这可能是因为 纹理的路径错误了。 在开始->运行->cmd输入osgViewer glider..osg也会得到和上面一样的结果,输出一个小飞机。下面来简单的 看一下OSG安装包中的那四个非常非常重要的程序。 2.2官方的四个0SG程序 第一个先来看看刚才我们编的osgViewer
OpenSceneGraph 程序设计 www.osgChina.org 第二章:OSG 基础 31 法,也可以读取里面的参数给 OSG 程序来用。也可以写一些帮助信息到程序里显示出来给客户来看。 第 2 行:设置应用程序的名称,一般而言程序的名称是你的项目名字。因为在 main 的第一个命令行中,是 程序的路径全名,比如会是”E:\OSG 大全\第二章 OSG 基础\4.最好的 HelloWorld\debug\4.最好的 HelloWorld.exe“这样 ArgumentParser 就可以从这个路当中获得程序的名称,然后传到 ArgumentParser 当中去。 第 3~4 行:设置一下对这个程序的描述,描述的内容是后面的字串。 第 5 行:命令行的格式说明,注意这里只是说明,并没有实际意义,即程序后[arguments.getApplicationName()] 加操作[+[options]]加文件名[filename ..."]。 第 6~15 行:对 osgviewer 的一些命令行功能进行一些说明,比如-image filename 为展示一张图片,下面雷 同,它主要的功能是查看模型。 第 16~20 行:为输出帮助信息,里面有帮助信息的类型,其中—help-all 为最全的帮助信息,但是显然没有 必要查看这么全,因为行数太多翻着忒累。 第 21 行:申请一个 viewer 但是这时可以使用类对象 arguments 做为参数来读取 main 中输入的参数。 第 22~26 行:用来判断参数是否合法,比如你乱输一气,那就是不合法,程序会直接结束并返回。 第 27~31 行:当程序的命令行参数只有一个时,会输出一些命令提示。只有一个代表用户没有输入模型文 件,因为这一个命令行参数是该程序的全路径名,所以如果只有一个,代表用户并未输入模型,程序 无法演示。会输出可执行命令行,然后退出。 第 33~38 行:这个非常熟悉了,是将要添加的操作器。 第 39~51 行:这里的功能是:看是否输入了路径文件,如果输入路径文件就按路径文件走。里面具体类的 用法,会在别章讲到。 第 54~59 行:添加一些事件响应,在前面有所涉及。 第 60~65 行:从参数中读取模型,以前我们是直接读取的模型,这一次我们会从外部参数中来读。如果找 不到模型就会输出错误信息,然后程序退出。 第 66~71 行:报到无法识别的错误参数。如果有错,这些错将会被报告出来,且程序退出。 第 72~73 行:优化读到的模型,为什么模型需要优化呢,主要是关于模型状态的优化,OSG 对模型的状态 渲染进行了重组,以便达到最佳的性能与最少的 GPU 损耗。 第 74~76 行:渲染最终的模型。 到这里来讲,读者会对 OSG 程序有了一个大概的了解,可能不会太深,但是所谓熟能生巧,多敲一些代码, 慢慢的进去了。其实前面的程序在 osg 的源代码中就有,就是 Application 中的 osgViewer 程序,osgViewer 程序功能非常强大,用的也非常非常多,我们平时都用它来查看一些模型。 在这里我需要讲一下关于 osgViewer 的异常,有些时候读取模型的时候可能会很慢,这可能是模型的原因。 有些时候没有纹理,因为模型的启动文件有时候与纹理是分开存放的,可能会找不到纹理,这可能是因为 纹理的路径错误了。 在开始->运行->cmd 输入 osgViewer glider.osg 也会得到和上面一样的结果,输出一个小飞机。下面来简单的 看一下 OSG 安装包中的那四个非常非常重要的程序。 2.2 官方的四个 OSG 程序 第一个先来看看刚才我们编的 osgViewer
OpenSceneGraph程序设计 www.osgChina .org 第二章:OSG基础 2.2.1 osgViewer模型查看工具 osgViewer是这四个程序当中用的最多的OSG程序,代码也非常入门与浅显易懂,在开始->运行->CMD中就 可以直接启动osgViewer,因为path中添加了它的路径。 下面来看一下osgViewer的功能,如表2.1: 表2.1 osgViewer的功能表 命令:注意有的双杠 功能 -image <filename> 读取纹理文件,比如:osgViewer-image Images/skymap.jpg -dem <filename> 以高程图的形式渲染一个镜像/DEM比如osg Viewer-dema.img h或-help 命令行参数功能帮助 -help-env 所有可用的环境变量帮助 -help-keys 所有可用键帮助 -help-all 展示所有帮助信息 -SingleThreaded 为viewer选择单线程模式 -CullDrawThreadPerContext 为viewer选择CullDrawThreadPerContext线程模式 -DrawThreadPerContext 为viewer选择DrawThreadPerContext线程模式 -CullThreadPerCameraDrawThr 为viewer选择-CullThreadPerCameraDrawThreadPerContext线程模式 eadPerContext 除此外,当osgViewer在渲染的过程当中,也会有一些基本的操作,同样列表如表2.2。 表2.2 按键 功能 选择TrackBall操作器 2 选择Flight操作器 3 选择Driver操作器 4 选择Terrain操作器 < 在窗口模式下减少分辨率 > 在窗口模式下增大分辨率 Driver:Down 在Driver操作器下,光标向下移代表向下看 Driver:Space 空格代表重置视口,回到原点 Driver:Up 在Driver操作器下,光标向上移代表向上看 Driver:a 用鼠标中键和右键加速 Driver:q 用鼠标Y键控制速度 输出状态到控制台 Z 切换播放与否视口录像 b 切换背面锡除与否 e 切换是否限制帧速与否,一般60帧就够了,最少30也差不多 f 在全屏与不全屏之间切换 h 输出帮助信息 在打开与关闭灯光之间切换 32
OpenSceneGraph 程序设计 www.osgChina.org 第二章:OSG 基础 32 2.2.1 osgViewer 模型查看工具 osgViewer 是这四个程序当中用的最多的 OSG 程序,代码也非常入门与浅显易懂,在开始->运行->CMD 中就 可以直接启动 osgViewer,因为 path 中添加了它的路径。 下面来看一下 osgViewer 的功能,如表 2.1: 表 2.1 osgViewer 的功能表 命令:注意有的双杠 功能 --image <filename> 读取纹理文件,比如:osgViewer --image Images/skymap.jpg --dem <filename> 以高程图的形式渲染一个镜像/DEM 比如 osgViewer --dem a.img -h 或--help 命令行参数功能帮助 --help-env 所有可用的环境变量帮助 --help-keys 所有可用键帮助 --help-all 展示所有帮助信息 --SingleThreaded 为 viewer 选择单线程模式 --CullDrawThreadPerContext 为 viewer 选择 CullDrawThreadPerContext 线程模式 --DrawThreadPerContext 为 viewer 选择 DrawThreadPerContext 线程模式 --CullThreadPerCameraDrawThr eadPerContext 为 viewer 选择—CullThreadPerCameraDrawThreadPerContext 线程模式 除此外,当 osgViewer 在渲染的过程当中,也会有一些基本的操作,同样列表如表 2.2。 表 2.2 按键 功能 1 选择 TrackBall 操作器 2 选择 Flight 操作器 3 选择 Driver 操作器 4 选择 Terrain 操作器 < 在窗口模式下减少分辨率 > 在窗口模式下增大分辨率 Driver:Down 在 Driver 操作器下,光标向下移代表向下看 Driver:Space 空格代表重置视口,回到原点 Driver:Up 在 Driver 操作器下,光标向上移代表向上看 Driver:a 用鼠标中键和右键加速 Driver:q 用鼠标 Y 键控制速度 S 输出状态到控制台 Z 切换播放与否视口录像 b 切换背面锡除与否 e 切换是否限制帧速与否,一般 60 帧就够了,最少 30 也差不多 f 在全屏与不全屏之间切换 h 输出帮助信息 l 在打开与关闭灯光之间切换