JMonkeyEngine3——播放视频

概况

有时候我们需要在游戏中播放一些视频,比如恐怖游戏中玩家看到一台电视,我们希望在电视里播放一些片段;或者游戏过程显示一段CG之类的;无论如何,我们都有在游戏内播放视频的需求,JME3官方没有提供内置的功能,但是有一些人制作了相关库,参考:

SimpleMediaPlayer
VideoPlayer

后者需要你下载javafx库,并添加相关jar包,但是允许你直接播放mp4,avi等视频格式,不需要任何转换,非常强大。

SimpleMediaPlayer

这里我先简单介绍下如何使用SimpleMediaPlayer这个库(可以直接在JME3商店下载这个库),这个库与其说是播放视频,倒不如说是播放帧序列+音频轨迹,从而还原视频内容。

从商店打开下载这个库之后,实际上是有问题的,将库里的SimpleMediaPlayer.frag替换内容如下:

  1 #import "Common/ShaderLib/GLSLCompat.glsllib"
  2 
  3 #ifdef DISCARD_ALPHA
  4     uniform float m_AlphaDiscardThreshold;
  5 #endif
  6 uniform float m_Alpha;
  7 uniform vec4 m_Color;
  8 uniform sampler2D m_ColorMap;
  9 varying vec2 texCoord;
 10 varying vec4 vertColor;
 11  
 12 uniform float g_Time;
 13   uniform vec2 g_Resolution;
 14   
 15  uniform  bool m_EnabledVHS;
 16  uniform  bool m_EnabledLine;
 17  uniform  bool m_EnabledGrain;
 18  uniform  bool m_EnabledScanline;
 19  uniform  bool m_EnabledVignette;
 20  
 21  float lineHeight = 5.;
 22  float lineSpeed = 5.0;
 23  float lineOverflow = 1.4;
 24  float noise = .70;
 25  float pixelDensity = 450.;
 26     
 27  float rand(vec2 co){
 28     return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
 29 }
 30 
 31 
 32 float rand2(vec2 co){
 33     return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453) * 2.0 - 1.0;
 34 }
 35 
 36 float offset(float blocks, vec2 uv) {
 37     return rand2(vec2(g_Time, floor(uv.y * blocks)));
 38 }
 39  vec3 lum = vec3(0.299, 0.587, 0.114);
 40 void main(){
 41     vec4 color = vec4(1.0);
 42     vec2 uv = texCoord;
 43     #ifdef HAS_COLORMAP
 44         color = texture2D(m_ColorMap, texCoord);     
 45     #endif
 46   
 47     #ifdef HAS_COLOR
 48         color = m_Color;
 49     #endif
 50     
 51     
 52     //VHS dirt
 53      #if defined(HAS_COLORMAP) && defined(HAS_EFFECT_VHS)  
 54            vec2 pos=vec2(0.5+0.5*sin(g_Time),uv.y);
 55            vec3 col2=vec3(texture2D(m_ColorMap,pos))*0.2;
 56            color.rgb+=col2;
 57      #endif
 58      
 59      
 60       // Moving strip effect
 61      #if defined(HAS_COLORMAP) && defined(HAS_EFFECT_LINE)  
 62            float blurLine = clamp(sin(uv.y * lineHeight + g_Time * lineSpeed) + 1.22, 0., 1.);
 63             float line = clamp(floor(sin(uv.y * lineHeight + g_Time * lineSpeed) + 1.90), 0., lineOverflow);
 64             color.rgb = mix(color.rgb - noise * vec3(.08), color.rgb, line);
 65             color.rgb = mix(color.rgb - noise * vec3(.25), color.rgb, blurLine);
 66      #endif
 67      
 68        //Grain 
 69        #if defined(HAS_COLORMAP) && defined(HAS_EFFECT_GRAIN)  
 70             color.rgb *= vec3(clamp(rand(vec2(floor(uv.x * pixelDensity ), floor(uv.y * pixelDensity)) *g_Time / 1000.) + 1. - noise, 0., 1.));
 71        #endif
 72       
 73     //Scanlines
 74     #if defined(HAS_COLORMAP) && defined(HAS_EFFECT_SCANLINE)  
 75        float d = length(uv - vec2(0.5,0.5));
 76        float scanline = sin(uv.y* g_Resolution.y )*0.04;
 77        color  -= vec4(scanline);
 78      #endif
 79      
 80      // Vignette
 81      #if defined(HAS_COLORMAP) && defined(HAS_EFFECT_VIGNETTE)  
 82          color.rgb *= vec3(1.0 - pow(distance(uv, vec2(0.5, 0.5)), 3.0) * 3.0);
 83      #endif
 84      
 85      
 86       // LCD
 87      #if defined(HAS_COLORMAP) && defined(HAS_EFFECT_LCD)  
 88       float scanline2     = clamp( 0.95 + 0.05 * cos( 3.14 * ( uv.y + 0.008 * g_Time ) * 240.0 * 1.0 ), 0.0, 1.0 );
 89      float lins     = 0.85 + 0.15 * clamp( 1.5 * cos( 3.14 * uv.x * 640.0 * 1.0 ), 0.0, 1.0 );    
 90      color *= vec4(scanline2 * lins * 1.2);
 91      gl_FragColor = color*gl_FragColor ; 
 92       #endif
 93     
 94    // CRT
 95         #if defined(HAS_COLORMAP) && defined(HAS_EFFECT_CRT)  
 96         if (mod(gl_FragCoord.x, 3.0 ) <1.0) {
 97             color.rgb += vec3(color.r, 0, 0);
 98         } else if (mod(gl_FragCoord.x, 3.0 ) <2.0) {
 99             color.rgb += vec3(0, color.g, 0);
100         } else if (mod(gl_FragCoord.x, 3.0 ) <3.0) {
101             color.rgb += vec3(0, 0, color.b);
102         } else {
103              color.rgb +=  vec3(.1);
104         }
105         // 
106        if (  mod(gl_FragCoord.y, 3.0 ) < 1.0) {
107            color.rgb +=  vec3(.1);
108           }
109        #endif 
110      
111      #if defined(HAS_COLORMAP) && defined(HAS_EFFECT_GLITCH)  
112     color.r *= texture2D(m_ColorMap, uv + vec2(offset(16.0, uv)*0.1  , 0.0)).r;    
113     color.g *= texture2D(m_ColorMap, uv + vec2(offset(8.0, uv)*0.1 * 0.16666666, 0.0)).g;
114     color.b *= texture2D(m_ColorMap, uv + vec2(offset(8.0, uv)*0.1 , 0.0)).b;
115     #endif 
116     
117    #if defined(HAS_COLORMAP) && defined(HAS_EFFECT_BAW)  
118       color =   vec4(vec3( color.r * lum.r+color.g * lum.g+color.b * lum.b),1.0);
119     #endif 
120       
121    
122    
123     
124     #ifdef DISCARD_ALPHA
125         if(color.a < m_AlphaDiscardThreshold){
126            discard;
127         }
128     #endif
129     
130     #ifdef HAS_ALPHA
131         color.a = m_Alpha;
132      #endif
133   
134     gl_FragColor = color;
135 }

我猜作者没有做好兼容性测试就提交了代码。

然后我们将库中Media文件夹中的随便一个测试视频(比如960_540.mjpg),对应的音轨audio.ogg,还有库文件SimpleMediaPlayer.java以及相关的材质定义着色器文件复制到我们的工程测试,如下:

 注意必须将SimpleMediaPlayer.frag、vert、j3md放到MatDefs/SimpleMediaPlayer目录下,或者你修改SimpleMediaPlayer.java的加载以及j3md相对于frag、vert的读取位置,简单起见你就按照我的截图来放就行了

我们添加一个java文件,内容如下:

 1 import com.jme3.app.SimpleApplication;
 2 import de.lessvoid.nifty.Nifty;
 3 import de.lessvoid.nifty.NiftyEventSubscriber;
 4 import de.lessvoid.nifty.controls.ButtonClickedEvent;
 5 import de.lessvoid.nifty.screen.Screen;
 6 import de.lessvoid.nifty.screen.ScreenController;
 7 
 8 /**
 9  * 测试音视频播放
10  * @author JohnKkk 18402012144@163.com
11  */
12 public class HelloMedia extends SimpleApplication implements ScreenController{
13 
14     @Override
15     public void simpleInitApp() {
16     }
17     
18     @NiftyEventSubscriber(id = "Play")
19     public final void playMedia(final String id, ButtonClickedEvent buttonClickedEvent){
20         
21     }
22     
23     @NiftyEventSubscriber(id = "Pause")
24     public final void pauseMedia(final String id, ButtonClickedEvent buttonClickedEvent){
25         
26     }
27     
28     @NiftyEventSubscriber(id = "Reset")
29     public final void resetMedia(final String id, ButtonClickedEvent buttonClickedEvent){
30         
31     }
32 
33     @Override
34     public void bind(Nifty nifty, Screen screen) {
35     }
36 
37     @Override
38     public void onStartScreen() {
39     }
40 
41     @Override
42     public void onEndScreen() {
43     }
44     
45     public static void main(String[] args) {
46         HelloMedia helloMedia = new HelloMedia();
47         helloMedia.start();
48     }
49     
50 }

然后创建一个NiftyGui xml,在设计器上调整页面如下:

 切换到xml,内容如下:

 1 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
 2 <nifty xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://nifty-gui.lessvoid.com/nifty-gui" xsi:schemaLocation="https://gitee.com/JoyClm/nifty-gui/raw/1.4/nifty-core/src/main/resources/nifty.xsd https://gitee.com/JoyClm/nifty-gui/raw/1.4/nifty-core/src/main/resources/nifty.xsd">
 3     <useControls filename="nifty-default-controls.xml"/>
 4     <useStyles filename="nifty-default-styles.xml"/>
 5     <screen id="GScreen0" controller="mygame.HelloMedia">
 6         <layer id="GLayer0" childLayout="absolute">
 7             <control name="button" id="Play" childLayout="center" x="61" y="401" label="Play"/>
 8             <control name="button" id="Pause" childLayout="center" x="265" y="402" label="Pause"/>
 9             <control name="button" id="Reset" childLayout="center" x="454" y="401" label="Reset"/>
10         </layer>
11     </screen>
12 </nifty>

这里我们让三个按钮分别调用对应的EventBus执行方法,回到java代码,添加如下内容:

 1 public class HelloMedia extends SimpleApplication implements ScreenController{
 2     private Nifty m_Nifty;
 3 
 4     @Override
 5     public void simpleInitApp() {
 6         setDisplayStatView(false);
 7         // 初始化Nifty
 8         NiftyJmeDisplay niftyDisplay = NiftyJmeDisplay.newNiftyJmeDisplay(
 9                 assetManager,
10                 inputManager,
11                 audioRenderer,
12                 guiViewPort);
13         m_Nifty = niftyDisplay.getNifty();
14         // 将NiftyGUI显示对象添加到JME3中
15         guiViewPort.addProcessor(niftyDisplay);
16         
17         m_Nifty.fromXml("Interface/IntroInfo.xml", "GScreen0");
18         
19         // 禁用flyCam并显示鼠标
20         flyCam.setEnabled(false);
21         inputManager.setCursorVisible(true);
22     }
23     
24     @NiftyEventSubscriber(id = "Play")
25     public final void playMedia(final String id, ButtonClickedEvent buttonClickedEvent){
26         System.out.println("mygame.HelloMedia.playMedia()");
27     }
28     
29     @NiftyEventSubscriber(id = "Pause")
30     public final void pauseMedia(final String id, ButtonClickedEvent buttonClickedEvent){
31         System.out.println("mygame.HelloMedia.pauseMedia()");
32     }
33     
34     @NiftyEventSubscriber(id = "Reset")
35     public final void resetMedia(final String id, ButtonClickedEvent buttonClickedEvent){
36         System.out.println("mygame.HelloMedia.resetMedia()");
37     }
38 
39     @Override
40     public void bind(Nifty nifty, Screen screen) {
41     }
42 
43     @Override
44     public void onStartScreen() {
45     }
46 
47     @Override
48     public void onEndScreen() {
49     }
50     
51     public static void main(String[] args) {
52         HelloMedia helloMedia = new HelloMedia();
53         helloMedia.start();
54     }
55     
56 }

此时启动JME3程序,点击对应按钮可看到函数调用:

 接着我们编写播放视频的逻辑,我们首先需要初始化SimpleMediaPlayer对象,然后进行一些配置,内容如下:

 1 /**
 2      * 初始化mediaPlayer.<br/>
 3      */
 4     private void initMediaPlayer(){
 5         // Init player
 6         m_MediaPlayer = new SimpleMediaPlayer(this);
 7         // 原视频宽高比
 8         int movieWidth = 960;
 9         int movieHeight = 540;
10         // 是否保持宽高比
11         boolean keepAspect = true;
12         float scaleRatio = 1.0f;
13         int width = cam.getWidth();
14         int height = cam.getHeight();
15         if (keepAspect) {
16             scaleRatio = ((float) width) / ((float) movieWidth);
17             height = (int) (height * scaleRatio);
18             movieWidth = width;
19             movieHeight = height;
20         }
21         // Unique name
22         String screenName = "Intro";
23         // 播放器空闲时显示的图像。如果为空则使用屏幕颜色
24         String idleImageAssetPath = "Media/idleImageAssetPath.jpg";
25         // 播放器加载时显示的图像。如果为 Null,则使用 screenColor
26         String loadingImageAssetPath = "Media/loadingImageAssetPath.jpg";
27         // 播放器暂停时显示的图像。最后一帧为空
28         String pausedImageAssetPath = "Media/pausedImageAssetPath.jpg";
29         // 如果未提供以上图片则使用的颜色。
30         ColorRGBA screenColor = ColorRGBA.Black;
31         // 需要播放的视频
32         String videoAssetPath = "Media/960_540.mjpg";
33         // 需要播放的视频音轨(不需要则为null)
34         String audioAssetPath = "Media/audio.ogg";
35         // 源视频帧率。应与原始帧率一致。大多数情况下为 25 或 30
36         int framesPerSec = 30;
37         // 播放模式。播放一次或循环播放
38         int playBackMode = SimpleMediaPlayer.PB_MODE_ONCE;
39         // 屏幕透明度, 1用于intro, material and menu geometries. 小于1用于HUDgeometries
40         float alpha = 1f;
41 
42         Geometry menuGeometry1 = m_MediaPlayer.genGeometry(screenName, movieWidth, movieHeight, idleImageAssetPath, loadingImageAssetPath, pausedImageAssetPath, screenColor, videoAssetPath, audioAssetPath, framesPerSec, playBackMode, alpha);
43         // 保持宽高比的适合,需要调整位置
44         if(keepAspect){
45             menuGeometry1.setLocalTranslation(0, (cam.getHeight() - height) / 2  , 0);
46         }
47         // 加载视频和音轨资源
48         m_MediaPlayer.loadMedia();
49         // Add to gui 
50         guiNode.attachChild(menuGeometry1);
51     }

该说明的几乎都在注释里了,唯一需要注意的是,SimpleMediaPlayer提供了几个方式,第42行我们可以通过genGeometry()方法返回一个Quad(Geometry),然后将其添加到guiNode进行显示,我们也可以将Quad添加到3D场景中任何地方进行显示;我们还可以调用genState()方法返回一个BaseAppState对象,一旦stateManager.attach(state);方法,就会立即播放,通过stateManager.detach(state);方法移除可停止播放,该方式适合那些过程动画播放CG之类的需求(参考库代码IntroStateTest.java);我们还可以通过genMaterial()返回一个Material对象,然后将其设置给3D场景中的某个物体,这样就可以在某个物体上显示视频,比如恐怖游戏中的电视机(参考库代码ModelMaterialTest.java)。

第48行我们可以在其他地方加载资源,我们还可以调用loadAndPlayMedia()加载完就立即播放,但这个例子我们希望点击按钮进行视频播放、暂停和重置,继续完善EventBus执行函数,如下:

 1 @NiftyEventSubscriber(id = "Play")
 2     public final void playMedia(final String id, ButtonClickedEvent buttonClickedEvent){
 3         if(m_MediaPlayer.isLoaded()){
 4             if(!m_MediaPlayer.isPlaying()){
 5                 m_MediaPlayer.playMedia();
 6             }
 7             else if(m_MediaPlayer.isPaused()){
 8                 m_MediaPlayer.unpauseMedia();
 9             }
10         }
11     }
12     
13     @NiftyEventSubscriber(id = "Pause")
14     public final void pauseMedia(final String id, ButtonClickedEvent buttonClickedEvent){
15         if(m_MediaPlayer.isLoaded()){
16             if(!m_MediaPlayer.isPaused()){
17                 m_MediaPlayer.pauseMedia();
18             }
19             else{
20                 m_MediaPlayer.unpauseMedia();
21             }
22         }
23     }
24     
25     @NiftyEventSubscriber(id = "Reset")
26     public final void resetMedia(final String id, ButtonClickedEvent buttonClickedEvent){
27         if(m_MediaPlayer.isLoaded()){
28             m_MediaPlayer.stopMedia();
29             m_MediaPlayer.loadMedia();
30         }
31     }

这些代码也是不必多解释的,基本就是字面意思。启动JME3程序,点击play按钮,播放视频和音轨:

 库代码的演示案例截图:

视频和音轨转换制作

直接看文档吧,写的已经足够详细了,后续再补充。

posted @ 2024-07-23 15:12  JhonKkk  阅读(52)  评论(0)    收藏  举报