• 粒子系统和Ogre 3D扩展 -----OGRE 3D 1.7 Beginner‘s Guide中文版 第十章(终章)


    文章来源:http://www.cnblogs.com/oneDouble/articles/2913160.html

    这是这本书的最后一章,我们将会接触到我们未曾涉及的一章——粒子系统。在此之后,这章将会呈现一些Ogre 3D的扩展和特效手法,这在未来对于你来说会是很重要的,但是不是对所有的程序都必要。

     

    在这一章,我们将会:

     

       1. 学习什么是粒子系统和怎样使用它

     

       2. 创建几个不同的粒子系统

     

       3. 了解Ogre 3D扩展

     

       4. 自豪的读完这本书

     

    那么让我们开始吧!

     

    【 增加一个粒子系统 】

      我们将绑定一个烟雾特效粒子系统到sinbad上,这有助于我们观察它所在的位置

      我们将使用之前最后一个例子的代码:

    1. 创建一个预先定义粒子脚本的粒子系统。增加这个粒子系统绑定到和sinbad实体的相同结点上: 

     

    Ogre::ParticleSystem* partSystem = _sceneManager->createParticleSy stem("Smoke","Examples/Smoke");
    _SinbadNode->attachObject(partSystem);

     

    2. 编译运行程序。将会看到Sinbad产生大量的烟雾。

     刚刚发生了什么我们已经定义过例子脚本来创建一个粒子系统。这样,这样粒子系统跟随着我们的实体一起运动。 

     

    什么是粒子系统:

      在我们创建自己的粒子系统而不是载入预先定义好的,我们需要讨论一下到底什么是粒子系统。我们已经看见了粒子系统创建带来的效果——在我们的实例中,是一个烟雾锥形体;但是它是怎么被创建的?

      一个例子系统由两到三个不同的部分组成——发射器,粒子和效应器(可选择的)。这三部分最重要的是粒子本身,正如它的名字粒子系统一样。一个粒子通过一个四边形显示一个颜色或者纹理,或者显卡的点渲染能力。当粒子使用一个四边形,这个四边形总是经旋转后正对着摄像机。每个粒子拥有一组参数,包括生存时间,方向和速率。虽然还有很多其它的参数,但是这三个是粒子系统概念里最重要的三个参数。参数生存时间控制着一个粒子的生存和死亡。一般,一个粒子在它被销毁前是存活不了几秒种的。这个效果可以在我们之前的烟雾例子中看到。将会有一个粒子消失点。对于这些粒子,生存计时器得到0并且这些粒子被销毁。

      方向和速率描述粒子的移动行为。在这个烟雾例子中,我们的方向是向上。发射器创建一个预先定义每秒钟产生的粒子数量,可以看做是粒子的来源。效应器,从另一方面来说,不创建粒子但改变它们的一些参数。在这个场景中我们并没有看到任何的效应器,但之后将会应用到。一个效应器能够改变粒子方向,速率,或者发射器创建的粒子颜色。

         现在我们已经知道了一些基础概念,那么让我们自己手动创建一些粒子系统吧。

     

    【 创建一个简单的粒子系统 】

      创建一个粒子系统,我们需要定义整个系统的行为,尤其是发射器行为。

    1. 粒子系统在.particle文件中定义。在mendia/particle文档中创建一个。

    2. 定义这个系统并命名为MySmoke1:

    particle_system MySmoke1
    {

    3. 每个粒子应当使用Example/Smoke材质并且10个单位长10个单位宽:

    material          Examples/Smoke
    particle_width    10
    particle_height   10

    4.我们想要一个最大值为500粒子同时每个粒子都应总是正对着摄像机的点:

     

    quota             500
    billboard_type    point

     

    5.我们想要一个发射器,它从一个单独点上以每秒3个粒子的速率发射粒子

     

    emitter Point
      {    
    emission_rate 3

     

    6.粒子应当在(1,0,0)方向上以20单元每秒的速度发射:

     

    direction 1 0 0
    velocity 20

     

    7. 脚本代码就这么多。闭合括号:

      }
    
    }

    8.在createScene()函数中,改变代码:

     

    Ogre::ParticleSystem* partSystem = _sceneManager->createParticleSy
    stem("Smoke","Examples/Smoke");
    to:
    Ogre::ParticleSystem* partSystem = _sceneManager->createParticleSy
    stem("Smoke","MySmoke1");

     

    9.编译和运行。你应当看到一个sinbad和尾随着一条烟雾尾巴。

    刚刚发生了什么

      我们创建了第一个粒子系统。对于它,我们需要一个.particle文件来存储脚本代码。在这个脚本代码中,我们以关键词particle_system开始来定义粒子系统,然后是我们想要的命名名称,就像其它脚本那样。在第3步中,我们定义了粒子材质。我们使用了SDK自带的材质。这个材质仅绑定一个纹理并结合这个纹理到顶点颜色上而忽略其它灯光。下面是完整的材质脚本:

     

    material Examples/Smoke
    {
      technique
      {
        pass
        {
          lighting off
          scene_blend alpha_blend
          depth_write off
          diffuse vertexcolor
          texture_unit
          {
            texture smoke.png
            tex_address_mode clamp
          }
        }
      }
    }

     

      我们给予每个粒子10单元的长度和宽度。第4步,定义了我们想要任意给定的时刻生存在粒子系统中的粒子最大数量;这个数量有助于阻止定义一个错误的粒子系统而导致我们整个程序的运行缓慢。如果达到了这个数量,发射器将不被允许创建新的粒子。这一步也定义了我们想要作为粒子的点总是面向摄像机。第5步增加了一个从确定的点中每秒发射3个粒子的发射器。第6步设置了粒子移动的方向和速率。然后我们改变程序使用新的粒子系统,最后观察它。

     

     

    【 一些更多的参数 】

      现在我们有个一个可测试的粒子系统,让我们尝试一些别的参数。 

      1. 用下面三个新参数来增加一个点发射器:

    angle 30
    
    time_to_live 10
    
    color 1 0 0 1

      2. 编译运行。你应当看到不同方向上飞行的红色粒子:

     

    刚刚发生了什么:

      我们增加了三个改变粒子系统行为的参数。现在粒子是红色的在不同方向上飞。参数angle定义了每创建一个粒子需要多少角度才能够从给定的方向上区别开来。Ogre 3D使用了一个随机生成器生成在一定范围内的方向。因为这个方向可以最大到30度,其中的一些粒子能够飞进地面。

      参数time_to_live设置了每个粒子的生命周期,在我们的例子中,为10s。默认值为5s。通过这个,我们增加了每个粒子一倍的生命周期,所以我们可以更长久地观察他们的行为了。

      参数color设置了粒子的顶点颜色为给定的颜色向量,在我们的例子中,为红色。

     

    【 实践时刻 —— 生存时间和颜色范围 】

     

    1. 改变time_to_live为一个最大和最小值范围: 

    time_to_live_min 1
    
    time_to_live_max 10

     2. 改变color的值用相同的方法:

    color_range_start 1 0 0
    
    color_range_end 0 0 1

    3.调整程序代码;然后编译运行。你将看到不同颜色粒子和一些将消失在另一些前面的粒子。

    刚刚发生了什么:

      我们使用了描述一个范围值的参数来代替使用单独的参数,然后让ogre 3D选择确切的值。这增加了我们的粒子系统的多样性并可用于模型更实际的自然效果,因为实际上,很少有东西在时间和空间上有一个相同的外观。 

     

     

    【 添加粒子系统的间隔时间 】

      现在我们将看到存在的一些参数它不影响粒子的外观,而只影响它们的发射方式。

     1.  移除在point emitter里增加的参数,只保留emission_rate, direction, 和 velocity:

    emitter Point
    {    
      emission_rate 30
      direction 1 0 0
      velocity 20

    2.  然后增加发射一个粒子所需时间的参数和重新开始之前需要等待多久的参数。

    duration 1
    
      repeat_delay 1
    
    }

    3.  编译运行。你应当看到一股白色颗粒,它产生的效果是通过每次打断一下发射器发射而产生的。

     

    刚刚发生了什么:我们增加了参数duration,它定义了在发射结束之前发射器发射粒子多长时间。Repeat_delay设置了发射器重新发射粒子之前需要等待多长时间。通过这两参数,我们已经定义了一个发射1s等待1s后开始发射的发射器。

     

     

    【 添加效应器(affector)

      我们已经当我们创建和使用发射器的时候改变粒子的行为和外观。现在我们将使用效应器,它将在整个粒子生存周期里改变外观和行为。 

    1. 为了展示效应器,我们需要一个简单的point发射器,它每秒以20单元速度发射30个粒子并且生存周期为100s

     

    emitter Point
    {    
      emission_rate 30
      direction 1 0 0
      velocity 20   
      time_to_live 100
    }

     

    2.  在粒子的整个生存周期里,我们想要它每秒增长五倍的大小。为了做到这点,我们增加一个Scaler效应器:

     

    affector Scaler
    {
      rate 5
    }

     

    3.  重新编译运行。你应看到生存时间中每秒变大的粒子。

    刚刚发生了什么:我们增加了一个效应器来改变粒子的大小。Scaler效应器通过给定的值来缩放粒子。在我们的例子中,每个粒子的大小通过一个每秒5倍的因数来缩放。

     

     

    【 改变颜色 】

    1.替换scaler,增加一个每一个颜色通道每秒减去0.25的ColorFader效应器

     

    color channel per second:
    affector ColorFader
    {
      red -0.25
      green -0.25
      blue -0.25
    }

     

    2. 编译运行。你应当看到粒子在它的生存周期里怎样慢慢的从白变黑。

     

    刚刚发生了什么 : 我们增加了一个改变每种颜色通道的效应器,使用了一个预先定义的值。

     

    【 让英雄动起来 —— 改变颜色为红色 】

      改变colorfader代码,让粒子从白褪到红色。结果应该看起来如下图

     

     

     

    【依赖于生存时间的粒子的颜色改变 】

      我们已经改变一种颜色到另一种,但是有时我们想要这个改变依赖于粒子的生存周期。当构建火和烟雾模型时候这是很重要的。 

        我们现在将通过粒子效应器来介绍更多的颜色。

     

    1. 对于这个例子,我们不想我们的粒子存活100s,所以改变生存周期为4s 

    emitter Point
    
    {    
    
      emission_rate 30
    
      direction 1 0 0
    
      velocity 20
    
      time_to_live 4  
    
    }

    2.  因为我们想要一个稍微不同的行为,所以我们将使用第二个可用的colorfader。它应当每秒每个颜色通道褪色一个单元的颜色: 

    affector ColorFader2
    
        {     
    
            red1 -1
    
            green1 -1
    
            blue1 -1

    3.  现在,当粒子只有2s存活时间,代替减法的颜色通道,增加相同的值

      state_change 2   
    
      red2 +1
    
      green2 +1
    
      blue2 +1
    
    }

    4.  重新编译运行程序

     

    刚刚发生了什么:我们使用了colorfader2效应器。这个效应器首先通过给予red1green1blue1的值来改变,当粒子只有state_change参数的生存时间的时候。如red2green2blue2的这些值被修改直到粒子死亡。在这个例子中,我们使用了这个效应器来先改变粒子的颜色从白到黑,然后当距离死亡2s时,我们改变它从黑到白。

     

     

    【使用更加复杂的颜色操作】

      再一次,我们使用粒子颜色并影响它们。  

    1.  我们将使用一个新的效应器命名为ColorInterpolator:

    affector ColorInterpolator
    
    {

    2.  当它产生的时候我们确定像素应当使用哪种颜色。我们将使用白色:

    color0 1 1 1

    3.  当粒子存活到它生存周期的四分之一的时候,它应当为红色:

    time1 0.25
    
    color1 1 0 0

    4.  在四分之一周期到二分之一生存周期里,我们想要它为绿色:

    time2 0.5
    
    color2 0 1 0

    5. 在二分之一到四分之三周期,它应当为蓝色,最后它又变回白色:

    time3 0.75
    
    color3 0 0 1
    
    time4 1
    
    color4 1 1 1

    6.  编译运行。你应当看到粒子流,它们应当从白到红到绿到蓝最后变回白色。

     

    刚刚发生了什么:我们使用了另一个效应器来创建一个更复杂的颜色操作。ColorInterpolator操控这所有粒子颜色。我们通过关键词timeXcolorX来定义这个操作,X必须是05之间。time00表示我们在粒子被创建的时候想要效应器的颜色color0time10.25意味着我们想要效应器使用color1当粒子存活到它生命周期的四分之一。在这两个时间点之间,效应器应会插值来达到渐变的效果。我们的例子定义五个点,并且每个点有一个不同的颜色。第一个和最后一个使用白色,第二个点使用红色,第三个使用绿色,第四个使用蓝色。每个点都是生存周期的四分之一,所以在整个生存周期,每种颜色都使用了相同的时间段。 

     

     

    【 增加随机性 】

      为了创建一个更漂亮的效果,可以通过增加一点随机性给我们的粒子系统,这样它就会看起来比较自然。

       增加随机性能够提到一个场景的视觉效果,所以让我们来实现它。

     1. 移除ColorInterpolator效应器。

     2.  增加一个不同的效应器命名为DirectionRandomiser:

    affector DirectionRandomiser 
    
    {

    3.  首先我们定义效应器在每个粒子的轴上的影响值: 

    randomness 100

    4.  然后我们描述每次效应器被应用的时候,有多少粒子应当被影响。1.0代表100%0代表0%。然后我们确定一下是否我们想要我们的粒子保持它们速率或者是否速度也应当被改变: 

      scope 1
    
      keep_velocity true
    
    }

    5.  编译运行。这次,我们不应当看到一个单独的粒子流,而是,很多的粒子飞行在不确定的方向上,但是大概在一个方向上。 

     

    刚刚发生了什么:DirectionRandomiser效应器通过给粒子的不同值,从而改变了粒子的方向。通过这个效应器,它能够给粒子的运动增加一个随机组件。 

     

     

    【 使用偏移平面 】

      为了能够让我们的粒子能在平面反弹起作用,这将是我们在这里将要做的。

    1.  代替随机性发射器,我们使用DeflectorPlane效应器 

    affector DeflectorPlane  
    
    {

    2.  这个平面定义使用了一个在空间中的点和这个平面的法线:  

    plane_point 0 20 0
    
    plane_normal 0 -1 0

    3.  最后定义当粒子撞击这个平面时,粒子应当受到什么影响。我们想要它们保持它们原先的速度,所以我们选择1.0作为值:

      bounce 1.0
    
    }

    4.  为了观察偏移平面的效果,我们需要我们的粒子朝稍微不同的方向上移动。所以更改发射器,使粒子的发射方向朝30度以内的方向发射。此外,由于平面悬浮于空中,我们的粒子应使用向上的方向向量来初始化粒子方向。

    emitter Point
    
    {    
    
      emission_rate 30
    
      direction 0 1 0
    
      velocity 20
    
      time_to_live 4
    
      angle 30
    
    }

    5. 编译运行。粒子在空中的隐形平面反弹。

     

    刚刚发生了什么 : 我们增加了一个悬浮于空间的平面,并同时保持粒子的速度。

    让我们做更多:创建一个新的程序,让第二个平面位于(0,0,0)使得它们的粒子受到第一个平面的影响。同时增加ColorInterpolator效应器。效果应当看起来和下图一样:

     

     

     

    【 使用一个盒发射器 】

      我们已经使用了一个点发射器,但是,当然,还有各种不同的发射器类型供我们使用。

      从一个点上发射粒子是很单调的,使用一个box来发射会更有趣。

     1.  改变发射器类型从发射点变为发射盒 

    emitter Box
    
    {

    2.  定义一个用于创建粒子的box:  

    height 50
    
    width 50
    
    depth 50

    3.  让发射器创建每秒10粒子并且它们的移动速度为20: 

      emission_rate 10
    
      direction 0 1 0
    
      velocity 20   
    
    }

    4. 使用新的粒子系统,编译运行。你应当看到粒子被创建在sinbad的周围,飞向空中。

     

     刚刚发生了什么 :我们使用了另一个发射器类型,在这个例子中,盒发射器(Box emitter)。我们定义了一个box,并且这个发射器使用了随机点在它的box内部作为起始位置来创建粒子。这个发射器能够用于创建不从准确点发射的粒子系统,而是从一个范围上发射。如果我们需要一个用于粒子发射的平面或一条线,我们只需要相应地设置box的参数即可。

     

     

    【 使用环形发射 】

     

       除了box类型,还有其它发射器类型,如环。

      代替一个点或者盒,我们可以使用一个环作为发射器。 

     

    1.  改变发射器类型为Ring

     

    emitter Ring
    {

    2.  通过widthheight来定义Ring的长宽

     

    height 50
    
    
    width 50

    3.  现在,为了创建一个环而不是一个圈,我们需要定义其内部部分不发射粒子。这儿我们使用百分比: 

     

    inner_height 0.9
    
    
    inner_width 0.9

    4.  余下部分没有改动,如下:

     

      emission_rate 50
    
    
      direction 0 1 0
    
    
      velocity 20   
    
    
    }

    5.  编译运行。移动摄像机到模型的顶部,你应当看到发射粒子的环。

     

    刚刚发生了什么我们在一个定义好的环形内使用了环发射器发射粒子。为了定义这个环形,我们使用了长宽,而不是一个点和半径。长宽描述了椭圆长宽的最大值。这里,下图展示了环是怎样定义的。通过inner_widthinner_height,我们定义环形内部多少是不发射粒子的。这里我们不使用空间单位,而是百分比。

     

    【  添加一些烟火 】

     

      在最后,我们想要一些烟火 

     

        这将是本书的最后一个粒子,所以放点烟火是较好的表示。 

      在一件特别的事件后来场烟花是非常赞的。 

    1. 创建一个在任意方向上以稳定时间间隔爆炸不同颜色的粒子的粒子系统:

    particle_system Firework
    
    {
    
      material          Examples/Smoke
    
      particle_width    10
    
      particle_height   10
    
      quota             5000
    
      billboard_type    point   
    
      emitter Point
    
      {  
    
      emission_rate 100
    
        direction 0 1 0
    
        velocity 50
    
        angle 360
    
        duration 0.1
    
        repeat_delay 1     
    
        color_range_start 0 0 0
    
        color_range_end 1 1 1
    
      }
    
    }

    2.  创建5个这种粒子系统实例:

     

    Ogre::ParticleSystem* partSystem1 = _sceneManager->createParticleS
    
    ystem("Firework1","Firework");
    
    Ogre::ParticleSystem* partSystem2 = _sceneManager->createParticleS
    
    ystem("Firework2","Firework");
    
    Ogre::ParticleSystem* partSystem3 = _sceneManager->createParticleS
    
    ystem("Firework3","Firework");
    
    Ogre::ParticleSystem* partSystem4 = _sceneManager->createParticleS
    
    ystem("Firework4","Firework");
    
    Ogre::ParticleSystem* partSystem5 = _sceneManager->createParticleS
    
    ystem("Firework5","Firework");

    3. 然后创建五个在空中不同位置的结点:

     

    Ogre::SceneNode* node1 = _sceneManager->getRootSceneNode()->create
    
    ChildSceneNode(Ogre::Vector3(0,10,0));
    
    Ogre::SceneNode* node2 = _sceneManager->getRootSceneNode()->create
    
    ChildSceneNode(Ogre::Vector3(10,11,0));
    
    Ogre::SceneNode* node3 = _sceneManager->getRootSceneNode()->create
    
    ChildSceneNode(Ogre::Vector3(20,9,0));
    
    Ogre::SceneNode* node4 = _sceneManager->getRootSceneNode()->create
    
    ChildSceneNode(Ogre::Vector3(-10,11,0));
    
    Ogre::SceneNode* node5 = _sceneManager->getRootSceneNode()->create
    
    ChildSceneNode(Ogre::Vector3(-20,19,0));

    4.  最后,绑定粒子系统到结点上:  

    node1->attachObject(partSystem1);
    
    node2->attachObject(partSystem2);
    
    node3->attachObject(partSystem3);
    
    node4->attachObject(partSystem4);
    
    node5->attachObject(partSystem5);

    5.  编译运行,享受一下。

     

    刚刚发生了什么我们创建了一个激情四射烟火的粒子系统并复制了它,让它看起来像是有好多烟火在空中。

     

    【最后】

       我们已经看到许多Ogre 3D提供的不同功能,Ogre 3D也很易于扩充它新的功能。所以有很多不同的库,用来增加一些新的功能给Ogre 3D。我们将讨论这些库,描述下有什么附加功能。一个完整库的列表,可以在维基百科获得:www.Ogre3D.org/tikiwiki/OGRE+Libraries 

     

    Speedtree

      Speedtree是一个用以渲染很多漂亮树和草的商业解决方案。它被广泛地应用几个商业游戏和Ogre 3D的创立者Sinbad提供给Ogre 3D一个版本。SpeedtreeOgre 3D版本须购买,不可以免费使用的。更多的信息,可以发现在http://www.ogre3d.org/tikiwiki/OgreSpeedtree 。

     

    Hydrax

       Hydrax增加是一个附加的功能,它可以为Ogre 3D渲染漂亮的水场景。通过这个附加功能,水能够被增加到场景上并且可以进行很多不同的设置,例如水的深度设置、增加泡沫、水下光线的影响,等等。附加功能参见:http://www.ogre3d.org/tikiwiki/Hydrax .

    Caelum

      Caelum是另一个附加扩展,主要介绍Ogre 3D天空昼夜循环渲染。它使太阳和月亮根据一个日期和时间来地渲染。它也使天气效果比如雪或雨,一个复杂的云仿真让天空看起来像真实越好。更多参见维基百科:http://www.ogre3d.org/tikiwiki/Caelum

     

    Particle Universe

      另一个商业附加库是Particle Universe。Particle Universe增加一个新的粒子系统给ogre 3D,它提供比ogre 3D粒子系统更多允许的粒子特效。同时,它带有一个粒子编辑器,允许美工在一个单独的程序中创建粒子,然后程序员可以载入这个创建好的粒子脚本。这个插件参见于:http://www.ogre3d.org/tikiwiki/Particle+Universe+plugin .

     

    GUIs

      有很多不同的GUI库可供Ogre 3D使用,每一种都有它存在的理由,但目前并没有一个GUI库每个人都使用。最好的办法是尝试了其中的一些,然后再做决定选择我们需要的库。 

     

    CEGUI

      CEGUI可能是第一个被整合到Ogre 3D中的库。它提供了一个GUI系统应有的函数甚至更多。它提供了一个于在代码之外GUI编辑器用来创建您的图形用户界面和为你的GUI定制不同的皮肤。更多的信息参见:http://www.cegui.org.uk/wiki/index.php/Main_Page .

     

    BetaGUI

       BetaGUI是极其微小的库,它是在一个头和一个cpp的文件。唯一的依赖是Ogre 3D,并能提供基本的功能如同创建窗口,按钮,文本域,和静态文本。这不是一个完整的图形用户界面,但是它提供了基本的功能无效其它任何依赖库,因此可以用在一个简单而快速的解决方案。在http://www.ogre3d.org/tikiwiki/BetaGUI 都能找到。

    QuickGUI

      QuickGUI是比BetaGUI一种更复杂和强大的方案。 虽然QuickGui提供更多的窗体小部件,它也让一体化进程变得更为困难。QuickGUI是一个成熟的GUI解决方案,可用于各种不同的项目,定期更新。维基百科网站能找到http://www.ogre3d.org/tikiwiki/QuickGUI 

    Berkelium

      Berkelium不是一个GUI库等,因为它没有任何部件或类似的情况。相反,它使Ogre 3D渲染使用谷歌Chromium库。在这个库的帮助下,使得建立一个游戏浏览器变为有可能。更多参见:http://www.ogre3d.org/tikiwiki/Berkelium 。

     

    【 本章概要 】

    我们这章学习了很多东西。

    特别,我们涉及了:

    Ø 怎样通过不同类型的发射器创建粒子系统

    Ø 效应器是怎样影响发射器的

    Ø Ogre 3D的一些附加扩展库

     

     

    【结束 】

      这是这本书的结束,我想向您表示祝贺。我知道阅读一本完整的编程的书是需要做大量工作的,并且做完所有的例子,去理解一个新话题,但它也非常有益而且新知识永远属于你。我希望你会喜欢这本书,它教给你足够的能创建自己的交互式的3D应用程序的方法,因为,在我看来,这是程序和计算机科学领域中最有趣并且高速更新的一个领域。

  • 相关阅读:
    JSP脚本及指令
    Servlet处理请求业务
    JSP隐式对象
    JavaWeb HttpSession应用
    【刷题】排队接水(水题妙解)
    【刷题】【cf】B. Card Constructions
    【刷题】【cf】D1. Game on Sum (Easy Version)
    【刷题】【stl】最大(连续)子序列之和
    【笔记】STL的七种武器(一)顺序容器
    【刷题】【stl】扑克游戏
  • 原文地址:https://www.cnblogs.com/SunkingYang/p/11049198.html
Copyright © 2020-2023  润新知