如何通过JavaScript的WebGL进行3D图形渲染,以及它如何与着色器语言协作处理图形管线?

WebGL是低级3D图形API,需通过JavaScript操作GPU完成渲染。首先创建canvas并获取WebGL上下文,接着将顶点数据传入GPU缓冲区。然后编写GLSL着色器:顶点着色器处理顶点变换,片段着色器计算像素颜色。编译链接着色器后,通过attribute和uniform连接数据与着色器。最后调用gl.drawArrays()执行绘制。相比Three.js等高级库,WebGL控制更精细但学习曲线陡峭,适合需要定制化或极致性能的场景。GLSL作为GPU执行语言,核心包括attribute(每顶点输入)、uniform(全局参数)、varying(顶点到片段传递)及内置变量如gl_Position和gl_FragColor。性能优化关键在于减少绘制调用、合并几何体、避免频繁状态切换、合理使用缓冲区更新策略、简化着色器计算、压缩纹理、启用Mipmaps与背面剔除,并利用浏览器工具分析瓶颈。

如何通过javascript的webgl进行3d图形渲染,以及它如何与着色器语言协作处理图形管线?

WebGL通过JavaScript API让开发者直接操作GPU,以渲染高性能的3D图形。它本质上是一个低级的栅格化API,要求我们用JavaScript来组织场景数据、设置渲染状态,然后通过GLSL(OpenGL Shading Language)编写的着色器程序,在GPU上定义顶点和像素的最终呈现方式,从而完成整个图形渲染管线的处理。

解决方案

要通过JavaScript的WebGL进行3D图形渲染,我们首先需要理解它是一个相当底层的API。它不像Three.js那样的库,帮你抽象掉了很多细节。在WebGL里,你几乎是直接和GPU对话。

整个流程大致是这样的:

准备画布和上下文: 在HTML中创建一个


元素,然后用JavaScript获取它的WebGL渲染上下文(

gl = canvas.getContext('webgl')

)。这是所有渲染操作的入口。数据传输: 3D模型通常由顶点(位置、法线、纹理坐标等)构成。这些数据必须从JavaScript内存(CPU)传输到GPU内存中。这涉及到创建缓冲区对象(

gl.createBuffer()

),绑定它们(

gl.bindBuffer()

),并将数据复制进去(

gl.bufferData()

)。这是我们告诉GPU“这里有你要处理的几何体”的方式。着色器编写与编译: 这是WebGL的核心。我们需要用GLSL编写两种着色器:顶点着色器(Vertex Shader): 它的任务是处理每个顶点。它接收来自JavaScript的顶点数据(如位置),并可以进行变换(例如,通过矩阵乘法将模型空间坐标转换到裁剪空间),计算光照等。它最终输出裁剪空间中的顶点位置,以及其他需要传递给片段着色器的数据(通过

varying

变量)。片段着色器(Fragment Shader): 也叫像素着色器。它在光栅化阶段之后运行,对每个“片段”(可以理解为屏幕上的一个潜在像素)进行处理。它的任务是计算这个片段的最终颜色,可能会用到纹理、光照结果、深度信息等。我们将这些GLSL代码作为字符串传递给WebGL,然后让WebGL去编译(

gl.compileShader()

)和链接(

gl.linkProgram()

)它们,形成一个可执行的着色器程序。连接数据与着色器: 着色器需要知道如何获取我们上传到GPU的顶点数据,以及一些全局参数(比如摄像机位置、光照方向、时间等)。属性(Attributes): 顶点着色器通过

attribute

变量接收每个顶点的独特数据(如位置、颜色、法线)。JavaScript需要告诉WebGL,哪个缓冲区里的数据对应哪个

attribute

变量(

gl.getAttribLocation()

gl.vertexAttribPointer()

)。统一变量(Uniforms): 着色器通过

uniform

变量接收那些对所有顶点或片段都相同的数据(如变换矩阵、纹理采样器、光照参数)。JavaScript通过

gl.uniform*

系列函数来设置这些值。绘制指令: 一切准备就绪后,JavaScript发出绘制指令(

gl.drawArrays()

gl.drawElements()

),告诉GPU使用当前的着色器程序和数据来渲染指定的几何体。

从我的角度看,WebGL的魅力在于它提供了极致的控制力,让你能真正理解3D渲染的底层逻辑。但这份控制力也意味着陡峭的学习曲线,你需要亲手处理许多细节,比如矩阵数学、光照模型,甚至是内存管理。它就像是一把双刃剑,强大但需要小心驾驭。

立即学习“Java免费学习笔记(深入)”;

WebGL与传统图形库(如Three.js)有何不同,我该如何选择?

当谈到WebGL和像Three.js这样的高级库时,这就像是选择直接用汇编语言编程还是用Python写应用一样。它们解决的问题域不同,适用场景也大相径庭。

WebGL:

本质: WebGL是一个低级的JavaScript API,直接暴露了GPU的功能。它基于OpenGL ES 2.0规范。控制力: 提供对渲染管线、着色器、缓冲区和状态管理的最高级别控制。你可以精确地定义每个顶点和片段的渲染方式。学习曲线: 极陡峭。你需要深入理解3D数学(矩阵变换、向量运算)、渲染管线、GLSL着色器编程、GPU内存管理等概念。性能: 如果优化得当,可以实现极致的性能,因为你没有高级库带来的抽象层开销。适用场景:需要构建高度定制化渲染引擎的项目。对性能有极高要求,且需要精细控制渲染流程的应用。学习3D图形渲染底层原理和GPU编程。开发新的渲染技术或实验性效果。

Three.js(以及Babylon.js等):

本质: 是一个基于WebGL构建的高级JavaScript 3D库。它封装了大量的WebGL底层API,提供了更易用的抽象层。控制力: 提供了场景图、摄像机、光源、材质、几何体、加载器等高级抽象。你通常不需要直接编写GLSL着色器(除非你需要自定义材质)。学习曲线: 相对平缓。你可以很快地创建出复杂的3D场景,而无需深入了解底层的渲染细节。性能: 通常性能良好,但由于其抽象层,可能不如精心优化的原生WebGL应用。不过,对于大多数应用来说,性能绰绰有余。适用场景:快速开发3D应用、可视化、游戏原型。设计师或前端开发者希望在网页中加入3D内容,但不希望深入图形学细节。项目对开发速度和易用性要求更高。需要一个功能齐全、社区支持强大的3D框架。

如何选择?

这真的取决于你的项目需求、团队技能和时间预算。

如果你的目标是快速构建一个3D产品,或者你的团队更擅长前端开发而非图形学,那么Three.js几乎是毋庸置疑的选择。它能让你在短时间内看到成果,并专注于应用逻辑而非渲染细节。我个人在很多项目中都会选择Three.js,因为它能极大地提高开发效率,把更多精力放在创意实现上。

但如果你正在开发一个需要突破性能极限的3D游戏引擎,或者你需要实现一些Three.js无法提供的非常规渲染效果,又或者你就是想深入了解3D渲染的奥秘,那么直接学习和使用WebGL会是更好的路径。这会是一段充满挑战但回报丰厚的旅程,你会对GPU如何工作有一个更深刻的理解。

着色器语言GLSL在WebGL中扮演了什么角色,它有哪些核心概念?

GLSL(OpenGL Shading Language)在WebGL中扮演的角色,简单来说,就是GPU的“指令集”或者“程序语言”。WebGL本身只是一个API接口,它负责把数据传给GPU,然后告诉GPU“去运行这个GLSL程序”。没有GLSL,WebGL就像一个没有程序的电脑,无法完成任何有意义的渲染工作。它定义了GPU如何处理每一个顶点和每一个像素(片段)。

我常常觉得GLSL是3D渲染的灵魂,因为它直接决定了最终画面的视觉效果。

核心概念:

着色器类型:

顶点着色器 (Vertex Shader): 它的输入是每个顶点的数据(位置、法线、纹理坐标等)。主要任务是将这些顶点从模型空间转换到裁剪空间(通过一系列矩阵乘法),并计算一些可以在顶点级别完成的光照。它会为每个输入顶点运行一次。片段着色器 (Fragment Shader): 它的输入是光栅化后生成的每个“片段”的数据(通常是顶点着色器输出并插值后的数据,如插值后的颜色、纹理坐标)。主要任务是计算这个片段的最终颜色。这可能涉及纹理采样、复杂的像素级光照计算、应用各种效果等。它会为每个被光栅化到的像素运行一次。

变量类型: GLSL有几种特殊的变量类型,用于区分数据来源和用途:

attribute

用于顶点着色器,接收来自JavaScript缓冲区的“每顶点”数据。例如,

attribute vec3 a_position;

用于接收顶点位置,

attribute vec2 a_texCoord;

用于接收纹理坐标。这些数据会随着每个顶点而变化。

uniform

可以在顶点着色器和片段着色器中使用,接收来自JavaScript的“全局”数据。这些数据在一次绘制调用中对所有顶点或片段都是相同的。例如,

uniform mat4 u_matrix;

用于接收变换矩阵,

uniform sampler2D u_texture;

用于接收纹理。

varying

用于在顶点着色器和片段着色器之间传递数据。顶点着色器将数据写入

varying

变量,这些数据在光栅化过程中会被自动插值,然后作为输入传递给片段着色器。例如,

varying vec3 v_normal;

可以在顶点着色器中计算法线,然后传递给片段着色器进行逐像素光照。

内置变量:

gl_Position

顶点着色器必须设置的输出变量,表示顶点在裁剪空间中的位置。

gl_FragColor

片段着色器必须设置的输出变量,表示片段的最终颜色。还有一些其他内置变量,如

gl_PointSize

(顶点着色器控制点大小)、

gl_FragCoord

(片段的屏幕坐标)等。

数据类型: GLSL支持基本数据类型(

float

,

int

,

bool

)以及向量(

vec2

,

vec3

,

vec4

)和矩阵(

mat2

,

mat3

,

mat4

)类型。这些类型在3D数学中至关重要。

函数: GLSL内置了大量的数学函数(如

sin

,

cos

,

normalize

,

dot

,

cross

)和向量/矩阵操作函数,极大地简化了复杂的图形计算。

示例(概念性代码片段):

一个简单的顶点着色器:

attribute vec4 a_position;uniform mat4 u_matrix;void main() {    gl_Position = u_matrix * a_position; // 将顶点位置乘以变换矩阵}

一个简单的片段着色器:

precision mediump float; // 精度声明void main() {    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 输出红色}

这段代码,虽然简单,却展示了GLSL如何直接控制渲染。顶点着色器负责几何体的形状和位置变换,片段着色器负责每个可见像素的颜色。这正是WebGL强大之处的体现。

如何优化WebGL渲染性能,避免常见的陷阱?

优化WebGL渲染性能是一个持续的过程,它需要你对GPU的工作方式有一定的理解,并且通常涉及CPU和GPU之间的权衡。我个人在做一些复杂场景时,常常会因为一个不经意的操作导致帧率骤降,所以这些“坑”是真实存在的。

减少绘制调用(Draw Calls): 这是最常见的性能瓶颈之一。每次

gl.drawArrays()

gl.drawElements()

调用都会产生CPU开销,因为CPU需要向GPU发送指令。

几何体批处理(Batching): 将多个小对象合并成一个大对象,一次性绘制。例如,如果有很多棵小草,可以把它们的几何体数据合并到一个大的顶点缓冲区中,然后一次性绘制。实例渲染(Instancing): 如果有大量相同的几何体,但位置、旋转、颜色等属性不同,可以使用实例渲染(

gl.drawArraysInstanced()

gl.drawElementsInstanced()

)。你只需上传一次几何体数据,然后为每个实例提供一个小的属性数组。

最小化GPU状态切换: 每次更改GPU状态(如切换着色器程序、绑定不同的纹理、改变混合模式)都会有开销。

尽量将使用相同着色器、相同纹理、相同渲染状态的对象分组,然后一起绘制。

高效的数据传输: CPU和GPU之间的数据传输是相对缓慢的。

静态数据: 对于不经常改变的顶点数据,使用

gl.bufferData(..., gl.STATIC_DRAW)

。这告诉GPU数据是静态的,它可以将其优化存储。动态数据: 如果数据需要频繁更新,使用

gl.bufferData(..., gl.DYNAMIC_DRAW)

,或者更精确地使用

gl.bufferSubData()

只更新缓冲区的一部分。避免在每一帧都重新创建和上传整个缓冲区。

着色器优化: 复杂的着色器会消耗更多的GPU计算资源。

减少计算: 尽量在顶点着色器中完成计算,而不是在片段着色器中,因为片段着色器会为每个像素运行,数量远大于顶点。精度: 在GLSL中使用适当的精度修饰符(

highp

,

mediump

,

lowp

),尤其是在片段着色器中,

mediump

通常就足够了,可以显著提高性能。纹理查找: 纹理查找通常比复杂的数学计算更快。如果可以用纹理预计算一些值,就尽量用纹理。条件分支: 避免在着色器中进行复杂的条件分支(

if/else

),尤其是在片段着色器中,因为GPU的SIMD(单指令多数据)架构对此不友好。如果无法避免,确保分支的结果尽可能一致,减少发散。

纹理优化:

尺寸: 使用合适尺寸的纹理。过大的纹理会占用大量GPU内存,并增加传输时间。压缩纹理: 如果浏览器支持(需要扩展),使用压缩纹理格式(如ETC2, ASTC, S3TC)可以减少内存占用和带宽需求。Mipmaps: 为纹理生成Mipmaps(

gl.generateMipmap()

),这有助于GPU在渲染远距离物体时使用较小的纹理版本,提高缓存效率。

剔除(Culling): 不要绘制那些不可见的东西。

视锥体剔除(Frustum Culling): 不渲染在摄像机视锥体之外的物体。遮挡剔除(Occlusion Culling): 不渲染被其他物体完全遮挡的物体(这个通常更复杂,可能需要预计算或GPU查询)。背面剔除(Backface Culling): 大多数3D模型都是封闭的,我们可以通过

gl.enable(gl.CULL_FACE)

来剔除那些背对摄像机的三角形,节省片段着色器的工作。

Z-fighting(深度冲突)处理: 当两个物体在深度上非常接近时,可能会出现闪烁,这是因为深度缓冲区精度不足。

调整摄像机的近平面(

near

)和远平面(

far

)距离,使其尽可能小,以提高深度缓冲区的精度。对于共面物体,可以给其中一个物体一个微小的偏移。

内存管理: 及时释放不再使用的GPU资源,如纹理(

gl.deleteTexture()

)、缓冲区(

gl.deleteBuffer()

)、着色器程序(

gl.deleteProgram()

)。避免内存泄漏。

性能分析工具: 利用浏览器自带的开发者工具(如Chrome的“Performance”或“WebGL Inspector”扩展)来分析帧率、绘制调用、GPU内存使用等,定位性能瓶颈。

优化是一个迭代的过程,没有银弹。通常,我会先用一个简单的场景跑起来,然后逐步加入复杂性,同时不断地用工具去分析性能,找到最影响帧率的那个点,然后集中精力去解决它。很多时候,一个简单的批处理或者一个纹理尺寸的调整,就能带来意想不到的提升。

以上就是如何通过JavaScript的WebGL进行3D图形渲染,以及它如何与着色器语言协作处理图形管线?的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1521142.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
如何用Web Audio API实现实时的音频空间化效果?
上一篇 2025年12月20日 13:55:26
JS 粒子系统动画实现 – 使用 Canvas 创建高性能动态效果的方法
下一篇 2025年12月20日 13:55:35

相关推荐

  • composer require-dev和require有什么不同_Composer Require与Require-Dev区别解析

    require用于声明项目运行必需的依赖,如框架、数据库组件和第三方SDK,这些包会随项目部署到生产环境;2. require-dev用于声明仅在开发和测试阶段需要的工具,如PHPUnit、PHPStan、Faker等,不会默认部署到生产环境;3. 安装时composer install根据环境决定…

    2026年5月10日
    1000
  • 修复Django电商项目中AJAX过滤产品列表图片不显示问题

    在Django电商项目中,当使用AJAX动态加载过滤后的产品列表时,常遇到图片无法正常显示的问题。这通常是由于前端模板中图片加载方式(如data-setbg属性结合JavaScript库)与AJAX动态内容更新机制不兼容所致。解决方案是直接在AJAX返回的HTML中使用标准的标签来渲染图片,确保浏览…

    2026年5月10日
    000
  • 开源免费PHP工具 PHP开发效率提升利器

    推荐开源免费PHP开发工具以提升效率:VS Code、Sublime Text轻量高效,PhpStorm专业强大;调试用Xdebug、Kint、Ray;依赖管理选Composer;代码质量工具包括PHPStan、Psalm、PHP_CodeSniffer;数据库管理可用%ignore_a_1%MyA…

    2026年5月10日
    000
  • Matplotlib 地图中多类型图例的创建与优化

    Matplotlib 地图中多类型图例的创建与优化Matplotlib 地图中多类型图例的创建与优化Matplotlib 地图中多类型图例的创建与优化Matplotlib 地图中多类型图例的创建与优化

    本教程旨在解决matplotlib地图可视化中,如何在一个图例中同时展示颜色块(如区域分类)和自定义标记(如特定兴趣点)的问题。文章详细介绍了当传统`patch`对象无法正确显示标记时,如何利用`matplotlib.lines.line2d`创建标记图例句柄,并将其与颜色块图例句柄合并,从而生成一…

    2026年5月10日 用户投稿
    100
  • Golang JSON序列化:控制敏感字段暴露的最佳实践

    本教程探讨golang中如何高效控制结构体字段在json序列化时的可见性。当需要将包含敏感信息的结构体数组转换为json响应时,通过利用`encoding/json`包提供的结构体标签,特别是`json:”-“`,可以轻松实现对特定字段的忽略,从而避免敏感数据泄露,确保api…

    2026年5月10日
    000
  • 利用海象运算符简化条件赋值:Python教程与最佳实践

    本文旨在探讨Python中海象运算符(:=)在条件赋值场景下的应用。通过对比传统if/else语句与海象运算符,以及条件表达式,分析海象运算符在简化代码、提高可读性方面的优势与局限性。并通过具体示例,展示如何在列表推导式等场景下合理使用海象运算符,同时强调其潜在的复杂性及替代方案,帮助开发者更好地掌…

    2026年5月10日
    000
  • Debian syslog性能优化技巧有哪些

    提升Debian系统syslog (通常基于rsyslog)性能,关键在于精简配置和高效处理日志。以下策略能有效优化日志管理,提升系统整体性能: 精简配置,高效加载: 在rsyslog配置文件中,仅加载必要的输入、输出和解析模块。 使用全局指令设置日志级别和格式,避免不必要的处理。 自定义模板: 创…

    2026年5月10日
    000
  • 怎么在PHP代码中实现图片上传功能_PHP图片上传功能实现与安全处理教程

    首先创建含enctype的HTML表单,再用PHP接收文件,检查目录、移动临时文件,验证类型与大小,生成唯一文件名,并调整php.ini限制以确保上传成功。 如果您尝试在PHP项目中添加图片上传功能,但服务器无法正确接收或保存文件,则可能是由于表单配置、文件处理逻辑或安全限制的问题。以下是实现该功能…

    2026年5月10日
    100
  • 比特币新手教程 比特币交易平台有哪些

    比特币是一种去中心化的数字货币,基于区块链技术实现点对点交易,具有匿名性、有限发行和不可篡改等特点;新手可通过交易所购买,P2P交易获得比特币,常用平台包括Binance、OKX和Huobi;交易流程包括注册账户、实名认证、绑定支付方式、充值法币并下单购买,可选择市价单或限价单;比特币存储方式有交易…

    2026年5月10日
    000
  • c++中的SFINAE技术是什么_c++模板编程中的SFINAE原理与应用

    SFINAE 是“替换失败不是错误”的原则,指模板实例化时若参数替换导致错误,只要存在其他合法候选,编译器不报错而是继续重载决议。它用于条件启用模板、类型检测等场景,如通过 decltype 或 enable_if 控制函数重载,实现类型特征判断。尽管 C++20 引入 Concepts 简化了部分…

    2026年5月10日
    000
  • HTML如何隐藏滚动条或去除滚动条

    滚动条可以存在也可以不存在,本文主要介绍了html 隐藏滚动条和去除滚动条的方法的相关资料,大家一起来学习一下html隐藏滚动条或去除滚动条的方法吧。 1. html 标签加属性 XML/HTML Code复制内容到剪贴板 2.body中加入以下代码 立即学习“前端免费学习笔记(深入)”; html…

    用户投稿 2026年5月10日
    000
  • vscode上怎么运行html_vscode上运行html步骤【指南】

    首先保存文件为.html格式,再通过浏览器或Live Server插件打开预览;推荐安装Live Server实现本地服务器运行与实时刷新,提升开发体验。 在 VS Code 上运行 HTML 文件并不需要复杂的配置,只需几个简单步骤即可预览页面效果。VS Code 本身是一个代码编辑器,不直接运行…

    2026年5月10日
    100
  • RichHandler与Rich Progress集成:解决显示冲突的教程

    在使用rich库的`richhandler`进行日志输出并同时使用`progress`组件时,可能会遇到显示错乱或溢出问题。这通常是由于为`richhandler`和`progress`分别创建了独立的`console`实例导致的。解决方案是确保日志处理器和进度条组件共享同一个`console`实例…

    2026年5月10日
    000
  • 修复点击时按钮抖动:CSS垂直对齐实践

    本文探讨了在Web开发中,交互式按钮(如播放/暂停按钮)在点击时发生意外垂直位移的问题。通过分析CSS样式变化对元素布局的影响,我们发现这是由于按钮不同状态下的边框样式和内边距改变,以及默认的垂直对齐行为共同作用所致。核心解决方案是利用CSS的vertical-align属性,将其设置为middle…

    2026年5月10日
    000
  • Golang goroutine与channel调试技巧

    使用go run -race检测数据竞争,结合runtime.NumGoroutine监控协程数量,通过pprof分析阻塞调用栈,利用select超时避免永久阻塞,有效排查goroutine泄漏、死锁和数据竞争问题。 Go语言的goroutine和channel是并发编程的核心,但它们也带来了调试上…

    2026年5月10日
    000
  • 页面中文本域的值怎么设置

    标签定义多行的文本输入控件。 文本区中可容纳无限数量的文本,其中的文本的默认字体是等宽字体(通常是 Courier)。 可以通过 cols 和 rows 属性来规定 textarea 的尺寸,不过更好的办法是使用 CSS 的 height 和 width 属性。 注释:在文本输入区内的文本行间,用 …

    2026年5月10日
    000
  • 使用 Jupyter Notebook 进行探索性数据分析

    Jupyter Notebook通过单元格实现代码与Markdown结合,支持数据导入(pandas)、清洗(fillna)、探索(matplotlib/seaborn可视化)、统计分析(describe/corr)和特征工程,便于记录与分享分析过程。 Jupyter Notebook 是进行探索性…

    2026年5月10日
    000
  • 《魔兽世界》将于6月11日开启国服回归技术测试

    《魔兽世界》将于6月11日开启国服回归技术测试《魔兽世界》将于6月11日开启国服回归技术测试《魔兽世界》将于6月11日开启国服回归技术测试《魔兽世界》将于6月11日开启国服回归技术测试

    《%ign%ignore_a_1%re_a_1%》官方宣布,将于6月11日开启国服回归技术测试,时间为7天,并称可以在6月内正式开服,玩家们可以访问官网下载战网客户端并预下载“巫妖王之怒”客户端,技术测试详情见下图。 WordAi WordAI是一个AI驱动的内容重写平台 53 查看详情 以上就是《…

    2026年5月10日 用户投稿
    200
  • 如何在HTML中插入表单元素_HTML表单控件与输入类型使用指南

    HTML表单通过标签构建,包含action和method属性定义数据提交目标与方式,常用input类型如text、password、email等适配不同输入需求,配合label、required、placeholder提升可用性,结合textarea、select、button等控件实现完整交互,是…

    2026年5月10日
    000
  • 前端缓存策略与JavaScript存储管理

    根据数据特性选择合适的存储方式并制定清晰的读写与清理逻辑,能显著提升前端性能;合理运用Cookie、localStorage、sessionStorage、IndexedDB及Cache API,结合缓存策略与定期清理机制,可在保证用户体验的同时避免安全与性能隐患。 前端缓存和JavaScript存…

    2026年5月10日
    100

发表回复

登录后才能评论
关注微信