OpenGL/GLUT实践:实现反弹运动的三角形动画与键盘控制(电子科技大学信软图形与动画Ⅱ实验)

源码见GitHub:A-UESTCer-s-Code

文章目录

    • 1 运行效果
    • 2 实验过程
      • 2.1 环境配置
      • 2.2 绘制三角形
        • 2.2.1 渲染函数
        • 2.2.2 主函数
        • 2.2.3 运行结果
      • 2.3 调整窗口大小
      • 2.4 简单动画与按键控制
        • 2.4.1 简单旋转
        • 2.4.2 键盘控制
      • 2.5 窗口反弹动画
        • 2.5.1 处理窗口大小变化
        • 2.5.2 渲染函数
        • 2.5.3 定时器
        • 2.5.4 控制速度

1 运行效果

我们运行程序,得到一个运动的等腰三角形,当其触碰到边框时会反弹,并且可以通过键盘上的F1、F2、F3来控制颜色,wasd来控制速度。

recording

2 实验过程

2.1 环境配置

OpenGL作为图形显示库,在图形建模、游戏开发和科学可视化等领域有着广泛的应用,而GLUT作为其补充库则提供了方便的窗口管理和用户交互功能,简化了OpenGL程序的开发过程。

步骤:

  1. 下载Glut的依赖库:

    • 访问OpenGL官网或直接从https://www.opengl.org/resources/libraries/glut/glut_downloads.php#windows下载Glut
    • 解压下载文件,得到glut.dllglut32.dllglut.libglut32.libglut.h5个文件
  2. 配置OpenGL环境:

    • glut.h文件复制到 Visual Studio 安装目录下的 include 文件夹内的一个新建名为 GL 的子文件夹中

      image-20240330085410414
    • glut32.lib文件复制到 Visual Studio 安装目录下的lib文件夹中的x86文件夹内

      image-20240330085440530
    • glut.lib文件复制到 Visual Studio 安装目录下的lib文件夹中的x64文件夹内

      image-20240330085509954
    • glut.dllglut32.dll文件复制到系统文件夹 C:\Windows\SysWOW64

      image-20240330085332324
  3. 创建OpenGL项目:

    • 在Microsoft Visual Studio中创建一个控制台应用程序项目

    • 添加源文件(如main.cpp)到项目中

    • 在源文件中引入OpenGL和GLUT库:#include <GL/glut.h>

  4. 编写测试程序:

    • 编写一个简单的OpenGL程序,例如,绘制一个正方形
    • 使用OpenGL提供的函数进行图形绘制
    • 设置窗口的显示模式、大小、位置等参数
    • 编译并运行程序,检查环境配置是否成功

    运行结果:

    image-20240330091416015

2.2 绘制三角形

具体步骤:

2.2.1 渲染函数
void renderScene() {glClear(GL_COLOR_BUFFER_BIT); // 清除颜色缓冲区// 绘制三角形glBegin(GL_TRIANGLES);glColor3f(181./225, 206./225, 163./255); // 设置颜色为指定的RGB值glVertex2f(-0.88, -0.5); // 左下角glVertex2f(0.64, -0.5); // 右下角glVertex2f(0.43, 0.7); // 顶点glEnd();glFlush(); // 刷新绘图命令
}

作用:renderScene() 函数是一个回调函数,在窗口需要被绘制时被调用,用于绘制图形。

细节讲解:

  • glClear(GL_COLOR_BUFFER_BIT);:清除颜色缓冲区,确保每次绘制前都有一个干净的画布

  • glBegin(GL_TRIANGLES);glEnd();:开始和结束绘制三角形的过程

  • glColor3f(181./225, 206./225, 163./255);:设置绘制图形的颜色为指定的RGB值。需要注意的是,这里的颜色值是使用范围在0到1之间的小数来表示的,所以将RGB值除以255来进行归一化处理

  • glVertex2f():指定三角形的顶点坐标,这里使用的是二维坐标

    坐标是以窗口为中心, x x x y y y 轴范围从-11,三角形只需要设置三个顶点即可:

    glVertex2f(-0.88, -0.5); // 左下角
    glVertex2f(0.64, -0.5); // 右下角
    glVertex2f(0.43, 0.7); // 顶点
    
    image-20240330092749677
2.2.2 主函数
int main(int argc, char** argv) {glutInit(&argc, argv); // 初始化GLUT库glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); // 设置显示模式为单缓冲和RGB颜色模式glutInitWindowSize(400, 400); // 设置窗口大小glutInitWindowPosition(100, 100); // 设置窗口位置glutCreateWindow("Simple Triangle Test"); // 创建窗口,并设置标题glutDisplayFunc(renderScene); // 设置绘图函数glutMainLoop(); // 进入主循环,等待事件return 0;
}

作用: main() 函数是程序的入口,主要负责初始化OpenGL环境和GLUT库,并进入主循环等待事件。

细节讲解:

  • glutInit(&argc, argv);:初始化GLUT库,接受程序启动时的命令行参数
  • glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);:设置显示模式为单缓冲和RGB颜色模式,这意味着窗口使用单缓冲区,并且颜色采用RGB模式
  • glutInitWindowSize(400, 400);:设置窗口的大小为400x400像素
  • glutInitWindowPosition(100, 100);:设置窗口的位置为屏幕左上角起始点向右下移动100像素的位置
  • glutCreateWindow("Simple Triangle Test");:创建窗口,并设置窗口标题为 “Simple Triangle Test”
  • glutDisplayFunc(renderScene);:注册回调函数 renderScene(),当窗口需要被重绘时会调用该函数进行绘制
  • glutMainLoop();:进入主循环,等待事件的发生,例如窗口的关闭、键盘输入等
2.2.3 运行结果

通过运行得到一个颜色为黄绿色(RGB为181, 206, 163),顶点为(0.43, 0.7)(0.64, -0.5)(-0.88, -0.5)的普通三角形

image-20240330093833522

2.3 调整窗口大小

当我们调整窗口时,图像出现了明显的变形,窗口大小改变会影响OpenGL的视口和投影矩阵,而默认情况下OpenGL使用的是透视投影矩阵,因此窗口大小改变会导致图像的变形。为了解决这个问题,我们需要重新计算投影矩阵,并将其与窗口的新大小相匹配。

image-20240330094129019

在OpenGL中,我们可以使用 glutReshapeFunc() 函数来注册一个回调函数,以便在窗口大小改变时进行处理。这个函数的原型如下:

void glutReshapeFunc(void (*func)(int width, int height));

这个函数将一个自定义的函数与窗口大小改变事件绑定,当窗口大小改变时,该函数就会被调用。

接下来,我们需要编写一个自定义的函数来处理窗口大小改变事件。这个函数应该重新计算投影矩阵,并将其与新的窗口大小相匹配。下面是一个示例函数:

void changeSize(int width, int height) {// 避免除以0if (height == 0) height = 1;// 设置视口大小glViewport(0, 0, width, height);// 设置投影矩阵glMatrixMode(GL_PROJECTION);glLoadIdentity();gluPerspective(45.0f, (GLfloat)width / (GLfloat)height, 0.1f, 100.0f);// 切换回模型观察矩阵glMatrixMode(GL_MODELVIEW);glLoadIdentity();
}

方法:

为了处理窗口大小的改变,我们使用了OpenGL提供的glutReshapeFunc()函数来注册一个回调函数,该函数会在窗口大小改变时被调用。在这个回调函数中,我们重新计算投影矩阵,并根据新的窗口大小设置视口和透视投影。

具体而言,我们的处理过程包括以下步骤:

  1. 检查窗口的高度是否为0,如果是则将其设置为1,以避免除以0的错误
  2. 计算窗口的宽高比,以便在设置透视投影时使用
  3. 将当前矩阵模式设置为投影矩阵模式,并重置矩阵
  4. 设置OpenGL的视口大小为新的改变后的窗口大小
  5. 使用gluPerspective()函数设置透视投影,其中包括视角大小、宽高比、近裁剪面和远裁剪面的距离
  6. 将当前矩阵模式设置回模型视图矩阵模式,并重置矩阵
  7. 使用gluLookAt()函数设置观察者的视点和方向,以模拟相机在场景中的位置和方向

最后,在 main() 函数中,我们需要将这个自定义的函数与窗口大小改变事件绑定起来,如下所示:

glutReshapeFunc(changeSize);

注意:这个函数要在主循环 glutMainLoop();,否则无法生效(因为程序一直在主循环运行,运行不到这个函数)

通过这样的设置,当我们调整窗口大小时,OpenGL会自动调用 changeSize() 函数来重新计算投影矩阵,从而保证图像的正确显示

执行结果:

recording

2.4 简单动画与按键控制

2.4.1 简单旋转

目的:

本部分旨在实现一个简单的OpenGL动画,让一个三角形在窗口中绕固定轴匀速旋转。

方法:

为了实现动画效果,我们需要做以下几个步骤:

  1. main()函数中,设置双缓冲区模式(GLUT_DOUBLE)以及RGB颜色模式(GLUT_RGB),这样可以避免画面闪烁并且产生平滑的动画效果

    双缓冲区通过在后一个缓冲区里绘画,并不停交换前后缓冲区(可见缓冲区),来产生平滑的动画。使用双缓冲区可以预防闪烁。

  2. 使用glutSwapBuffers()函数在每一帧绘制完毕后切换前后缓冲区,将后台缓冲区的内容绘制到屏幕上

  3. 使用glutTimerFunc()函数设置一个定时器,以一定的时间间隔调用指定的函数,在这个函数中更新动画的状态

  4. 在定时器函数中更新三角形的旋转角度,并重新注册定时器,实现连续的动画效果

    • GLfloat angle = 0;:定义了一个全局变量angle,用于表示旋转角度,初始值为0
    • timer()函数:是一个定时器函数,在每次被调用时更新旋转角度,并请求重绘窗口。这里使用了glutPostRedisplay()函数请求重绘,以触发renderScene()函数的执行。同时,通过glutTimerFunc()函数设置了一个16毫秒的定时器,用于每隔一定时间调用一次timer()函数,从而实现连续的动画效果

    定时器函数

    GLfloat angle = 0; // 设置初始旋转角度
    // 定时器函数,每16ms调用一次,用于旋转三角形
    void timer(int value)
    {angle += 1; // 旋转角度glutPostRedisplay(); // 重绘glutTimerFunc(16, timer, 0); // 设置定时器
    } 
    
  5. 在渲染函数renderScene()中,使用更新后的旋转角度绘制旋转的三角形

    renderScene()函数:在这里,首先清除颜色缓冲区,然后将当前矩阵推入堆栈,接着根据当前的旋转角度对场景进行旋转,绘制一个黄绿色的三角形。最后,通过glutSwapBuffers()函数进行双缓冲区的切换,将后台缓冲区的内容显示到屏幕上。

    // 渲染函数,绘制一个单色的三角形
    void renderScene()
    {glClear(GL_COLOR_BUFFER_BIT); // 清除颜色缓冲区glPushMatrix();glRotatef(angle, 1.0, 0.0, 0.0); // 绕x旋转// 绘制三角形// ...glPopMatrix(); glutSwapBuffers(); // 设置双缓冲
    }
    

结果:
recording

通过以上步骤,我们可以实现一个简单的OpenGL动画,让一个三角形在窗口中不断旋转。动画效果平滑流畅。

2.4.2 键盘控制

普通按键处理:

使用 glutKeyboardFunc 函数来注册处理普通按键事件的回调函数。

  • 普通按键是指字母数字和其他可以用 ASCII 代码表示的键

  • 我们可以在普通按键回调函数中执行相应的操作,例如根据按下的键执行不同的操作,或者响应程序的退出等

    void processNormalKeys(unsigned char key, int x, int y)
    {switch (key){case 27: // ESC键exit(0);break;
    }
    

特殊按键处理:

使用 glutSpecialFunc 函数来注册处理特殊按键事件的回调函数。

  • 特殊按键的标识符通常由预定义的常量表示,如 GLUT_KEY_F1GLUT_KEY_LEFT

  • 我们可以在特殊按键回调函数中根据按下的特殊键执行相应的操作,例如改变颜色、移动物体等

    记得将颜色值设为全局变量,渲染时,使用全局变量对颜色进行设置

    void processSpecialKeys(int key, int x, int y)
    {switch (key){case GLUT_KEY_F1:red = 181. / 225;green = 206. / 225;blue = 163. / 255;break;case GLUT_KEY_F2:red = 163. / 255;green = 163. / 255;blue = 163. / 255;break;case GLUT_KEY_F3:red = 30. / 255;green = 60. / 255;blue = 153. / 255;break;}
    

最后在main函数里面设置这两个回调函数即可

int main(int argc, char** argv)
{// ...glutSpecialFunc(processSpecialKeys); // 设置特殊键回调函数glutKeyboardFunc(processNormalKeys); // 设置普通键回调函数// ...
}

运行结果:

recording
  • processSpecialKeys 函数,根据按下的特殊键设置不同的颜色值。具体地,当按下F1键时,设置颜色为黄绿色;按下F2键时,设置颜色为灰色;按下F3键时,设置颜色为蓝色
  • processNormalKeys 函数,增加对ESC键的处理。当按下ESC键时,退出程序

2.5 窗口反弹动画

这一部分,我们要实现可以反弹运动的三角形动画,键盘控制三角形移动速度、颜色。

先引入几个全局变量:

GLfloat x = 0, y = 0, rsize = 50; // 设置初始位置和大小
GLfloat dx = 1.5, dy = 1.5; // 设置初始速度
GLfloat windowWidth, windowHeight; // 窗口大小
GLfloat red = 181. / 225, green = 206. / 225, blue = 163. / 255; // 设置初始颜色为黄绿色
2.5.1 处理窗口大小变化
  • 函数调用 glViewport 函数设置新的视口大小

    glViewport(0, 0, w, h); // 设置视口大小
    
  • 函数计算新的宽高比,并根据宽高比的大小调整窗口的宽度和高度。如果宽度小于或等于高度,那么窗口的宽度被设置为100,高度则根据宽高比进行调整。否则,窗口的高度被设置为100,宽度则根据宽高比进行调整

    aspectRatio = (GLfloat)w / (GLfloat)h; // 计算窗口的宽高比
    if (w <= h)
    {windowWidth = 100;windowHeight = 100 / aspectRatio;
    }
    else
    {windowWidth = 100 * aspectRatio;windowHeight = 100;
    }
    

    引入了 windowWidthwindowHeight 全局变量,用于保存窗口的宽度和高度

  • 使用 glOrtho 函数设置正交投影矩阵,将场景限定在一个固定大小的区域内,以保持图形的比例不变

     glOrtho(-windowWidth, windowWidth, -windowHeight, windowHeight, 1.0, -1.0);
    
2.5.2 渲染函数

渲染函数修改:

  • 在绘制三角形时,顶点坐标根据 xy 的值确定,以及矩形大小 rsize 的一半,顶点定义如下:

    glVertex2f(x, y);     
    glVertex2f(x + rsize, y);
    glVertex2f(x + rsize / 2, y + rsize);
    
2.5.3 定时器

修改了定时器函数 timer

  • 先更新位置 xy

    x += dx;
    y += dy;
    
  • 然后检测是否与窗口边界发生碰撞,如果碰撞,则将对应的速度分量 dxdy 取反,实现反弹效果,并稍微调整位置以避免卡在边界上

     // 检测碰撞,如果碰到边界就反弹if (x + rsize > windowWidth || x < -windowWidth){dx = -dx;x += dx; // 稍微调整位置,防止卡在边界上}if (y + rsize > windowHeight || y < -windowHeight){dy = -dy;y += dy; // 稍微调整位置,防止卡在边界上}
    
  • 使用 glutPostRedisplay() 触发重绘,以更新窗口中的图形

  • 设置定时器每16ms触发一次 glutTimerFunc(16, timer, 1);,以实现动画效果

2.5.4 控制速度

控制速度的实现:

我们使用了 processNormalKeys 函数来处理普通按键事件

  1. 当按下 ESC 键时,程序退出
  2. 当按下 ‘w’ 键时,增加图形在y方向上的速度
  3. 当按下 ‘s’ 键时,减少图形在y方向上的速度,但保证速度不小于0
  4. 当按下 ‘a’ 键时,减少图形在x方向上的速度,但保证速度不小于0
  5. 当按下 ‘d’ 键时,增加图形在x方向上的速度
switch (key)
{
case 27: // ESC键exit(0);break;
case 'w': // 增加y方向的速度dy += 0.1;break;
case 's': // 减少y方向的速度,但不小于0if (dy > 0){dy -= 0.1;            }break;
case 'a': // 减少x方向的速度,但不小于0if (dx > 0){dx -= 0.1;            }break;
case 'd': // 增加x方向的速度dx += 0.1;break;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/417406.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

c++修炼之路之C++11

目录 一&#xff1a;使用列表初始化 二&#xff1a;decltype和nullptr 三&#xff1a;右值引用和移动语义 四&#xff1a;新的类功能 五&#xff1a;可变参数模板 六&#xff1a;lambda表达式 七&#xff1a;包装器 1.function包装器 2.bind包装器 接下来的日子会顺…

Linux CentOS 部署Docker

1. yum 配置 &#xff08;1&#xff09;更新yum yum update -y 如果不升级更新yum 可能在后续docker部署后再更新容器会出现oci runtime error等 &#xff08;2&#xff09;安装yum工具类准备 yum install -y yum-utils device-mapper-persistent-data lvm2 &#xff08;3&…

【操作系统存储篇】Linux文件基本操作

目录 一、Linux目录 二、Linux文件的常用操作 三、Linux文件类型 一、Linux目录 Linux有很多目录&#xff0c;Linux一切皆是文件&#xff0c;包括进程、设备等。 相对路径&#xff1a;相对于当前的操作目录&#xff0c;文件位于哪个目录。 绝对路径 &#xff1a;从根目录开…

Golang | Leetcode Golang题解之第387题字符串中的第一个唯一字符

题目&#xff1a; 题解&#xff1a; type pair struct {ch bytepos int }func firstUniqChar(s string) int {n : len(s)pos : [26]int{}for i : range pos[:] {pos[i] n}q : []pair{}for i : range s {ch : s[i] - aif pos[ch] n {pos[ch] iq append(q, pair{ch, i})} e…

数仓工具—Hive语法之URL 函数

hive—语法—URL 函数 业务需求中,我们经常需要对用户的访问、用户的来源进行分析,用于支持运营和决策。例如我们经常对用户访问的页面进行统计分析,分析热门受访页面的Top10,观察大部分用户最喜欢的访问最多的页面等: 又或者我们需要分析不同搜索平台的用户来源分析,统…

jmeter同步定时器、固定定时器、统一随机定时器、常数吞吐量定时器详解

一、同步定时器 可以让多个线程同时向服务器发送请求&#xff0c;实现瞬间并发(相当于现实中同步秒杀商品)类似于集合点 例如&#xff1a;10个人约定去旅游&#xff0c;出发前提前会在某一个地方等到10个人同时都到了约定地点之后再一同排队上车 在任意接口下添加同步定时器模…

区域智慧农业解决方案

### 1. 项目概述 《区域智慧农业整体解决方案》由左岸芯慧提出&#xff0c;旨在通过数字化手段打造新型农业经营管理方式&#xff0c;实现产销一体化。 ### 2. 农业监管与需求分析 方案针对监管部门、消费者和生产服务企业的需求进行分析&#xff0c;指出农业监管包括数据收…

【SpringBoot】使用Redis

目录 0. 安装Redis 1. 导入依赖 2. 配置Redis 3. idea连接Redis 4. 使用Redis简单实现记录访问次数 1. 配置拦截器 2. 定义拦截器 3. 控制器类 0. 安装Redis 我使用的是本地Redis服务器&#xff0c;安装过程。安装完成后启动Redis服务。 1. 导入依赖 <!-- red…

负载均衡调度器--LVS

文章目录 集群和分布式集群分布式 LVS介绍LVS特点LVS工作原理LVS集群架构 LVS集群中的术语CIPVIPRSDIPRIP LVS集群的工作模式NAT模式DR模式DR模式的特点: TUN模式 LVS调度算法LVS相关软件ipvsadm 命令管理集群服务&#xff1a;增、改、删管理集群上的RS:增、改、删 创建集群 LV…

如何修复软件中的BUG

笔者上一篇博文《如何开发出一款优秀的软件》主要讲了如何开发一款优秀的软件及相应的必要条件。但对一个已上线&#xff0c;已经成型的产品&#xff0c;该如何解决存在的bug呢&#xff1f;这是本文要阐述的内容。 在这里&#xff0c;首先说一下bug的种类及bug严重程度分类&…

微信小程序认证和备案

小程序备案的流程一般包括以下步骤‌&#xff1a; 准备备案所需材料‌&#xff1a;通常需要提供‌营业执照、法人的‌身份证、两个‌手机号和一个邮箱等资料。 ‌1 ‌登录‌微信公众平台‌&#xff1a;作为第一次开发微信小程序的服务商&#xff0c;需要通过微信公众平台申请…

AAC高级音频编码技术

一、什么是AAC AAC的中文名称是高级音频编码技术&#xff0c;它是基于MPEG-2的一种全新的音频编码技术。随着时代的发展&#xff0c;目前AAC的技术升级到MPEG-4表准。AAC广泛的应用在网络传输、高清录制等领域&#xff0c;而AAC技术的出现就是为了取代之前的MP3格式。 二、为什…

酒店网站管理系统前后台完整源码

酒店网站管理系统前后台完整源码&#xff0c;架构是javasshmysql。 系统基于SSH框架&#xff0c;数据库用的mysql。系统分为前台网站部分&#xff0c;和后台管理部分。 注意&#xff0c;不是php代码&#xff0c;是java代码&#xff0c;使用者需要懂java才行。仅供懂java的人学…

如何提升网站的收录率?

要提升网站的收录率&#xff0c;其中一个特别有效的工具就是GPC爬虫池&#xff0c;这个工具通过深度研究谷歌SEO算法&#xff0c;吸引谷歌爬虫。 GPC爬虫池的基本原理是构建一个庞大的站群系统&#xff0c;并创建复杂的内链和外链结构&#xff0c;以吸引并留住谷歌蜘蛛 使用GP…

跟着李沐学ai

01 课程安排【动手学深度学习v2】-跟李沐学AI-【完结】动手学深度学习 PyTorch版-哔哩哔哩视频 (bilibili.com)https://www.bilibili.com/list/1567748478?sid358497&spm_id_from333.999.0.0&desc1&oid714717789&bvidBV1oX4y137bC 目标 介绍深度学习经典和最…

大白话说什么是“MLLM”多模态大语言模型

1. 什么是MLLM多模态大语言模型 1.1 先来思考一个问题 如果上传了一张图片&#xff0c;并向大模型提问。“图片中绿色框框中的人是谁&#xff1f;” 大模型回答&#xff1a;“那是波多野吉衣老师” 请问&#xff0c;大模型是怎么做到的&#xff1f; 我们用常规的思路来想一…

使用 MongoDB 构建 AI:Patronus 如何自动进行大语言模型评估来增强对生成式 AI 的信心

大语言模型可能不可靠&#xff0c;这几乎算不上头条新闻。对于某些用例&#xff0c;这可能会带来不便。而对于其他行业&#xff0c;尤其是受监管行业&#xff0c;后果则要严重得多。于是&#xff0c;业内首个大语言模型自动评估平台 Patronus AI 应运而生。 Patronus AI 由 Met…

SPIRNGBOOT+VUE实现浏览器播放音频流并合成音频

一、语音合成支持流式返回&#xff0c;通过WS可以实时拿到音频流&#xff0c;那么我们如何在VUE项目中实现合成功能呢。语音合成应用非常广泛&#xff0c;如商家广告合成、驾校声音合成、新闻播报、在线听书等等场景都会用到语音合成。 二、VUE下实现合成并使用浏览器播放代码…

山东大学机试试题合集

&#x1f370;&#x1f370;&#x1f370;高分篇已经涵盖了绝大多数的机试考点&#xff0c;由于临近预推免&#xff0c;各校的机试蜂拥而至&#xff0c;我们接下来先更一些各高校机试题合集&#xff0c;算是对前边学习成果的深入学习&#xff0c;也是对我们代码能力的锻炼。加油…

Faceware面部动作捕捉系统,为虚拟角色赋予真实可信的面部动画

在数字娱乐产业日新月异的今天&#xff0c;虚拟角色的真实性和可信度成为了衡量作品质量的重要标准之一。Faceware面部动作捕捉系统是这一领域中的佼佼者&#xff0c;它以先进的技术和卓越的性能&#xff0c;为虚拟角色创造了生动、自然的面部表情&#xff0c;极大地提升了观众…