- 博客(118)
- 收藏
- 关注
原创 UE4 材质学习笔记13(格斯特纳波)
我们改变了表面的形状了,但我们还没改变表面法线的朝问,所以就会产生一种非常奇怪的视错觉,就好像我们的表面仍然极致平坦 但其实不是,然后我们需要在数学公式中重新计算表面法线,然后与我们的波纹法线结合起来。现在我们还需要的是位置与方向,所以引入绝对世界位置,默认的是包含材质偏移的,这是顶点移动后的世界位置,我们实际上不能移动它或者使用它,因为这是着色器的一部分 它将移动顶点,因此我们需要选择“绝对世界位置(排除材质着色器偏移),这将为我们提供顶点在移动之前的位置。接着创建几何公式移动顶点,模拟海浪的效果。
2024-10-21 21:50:08 538
原创 UE4 材质学习笔记12(水体反射和折射)
然后设置折射效果,当光从空气进入水中,由于空气和水的密度不同 光线会有一点弯曲,这就会导致水面下的东西看起来扭曲了,水的折射率是1.333,但是直接设置折射效果的话旋转视角会发现水会突然消失了。接下来我们要使用屏幕空间反射,在材质的细节面板,在“半透明”下 你可以看到有个属性是用于屏幕空间反射的,其作用是它会取用屏幕或者正在渲染的图像,而且如果一个像素需要反射,它会查看那个反射是否在屏幕上的任何其他像素上可用,但是屏幕空间反射的缺点是它们只能使用在屏幕上的数据.
2024-10-21 16:14:44 430
原创 UE4 材质学习笔记11(水波纹着色器/水深度着色器)
首先采样法线贴图,然后使用Panner节点滚动UV制造出水在流动的效果。这里使用绝对世界坐标采样的原因是因为这样可以将水映射到世界空间中的水的平面上,而且我可以使用多个平面,水的效果就会平铺在平面上。接下俩要往波纹中制造一些混乱。所以又做了一层UV扰动并且将两次采样的结果相加,这里的DeriveNormalZ它会取用由这两个法线贴图生成的X和Y,而且它会计算Z,但是可以注意到前面两个缩放几乎相同,分别是0.0035和0.003. 我们呢可以创造一个非常大的图像,就像是些非常大的波纹在缓慢横穿表面。
2024-10-21 12:34:01 995
原创 UE4 材质学习笔记10(程序化噪波/覆雪树干着色器/岩层着色器)
柏林噪波是一种能生成很好的随机图案的算法,它是一个无限的、不重复的图案,可以采用这种基础图案并以多种方式对其进行修改,将它缩放并进行多次组合,就可以创建一个分形图案。这些组合的缩放等级称为一个Octave这是产生柏林噪波基础图案的核心公式(优化版本)当这段代码被编译时,会产生下面的消耗,并且这只是核心图案,你通常把这个东西循环三到四次 甚至多达八次,所以你可以使用多达488个指令和64个纹理采样,所以这是非常耗能的。
2024-10-13 18:43:42 646
原创 UE4 材质学习笔记09(雨水水坑着色器/完整雨水着色器)
要用到这样一个噪声贴图,我们要做的就是,做出水坑并让水坑在这种浑浊的噪点中产生,因此水坑将从最暗的斑点生长,然后随着它继续占据越来越亮的像素而生长现在水坑将从上到下投射到世界空间中,所以我需要引入WorldPosition(世界位置)节点,使用RG通道,也就是XY分量,因为z分量是向上的,乘上一个较小的数得到一个正确的大小单位。接着调整UV让我们呢只会在最暗的地方得到水坑,首先创建一个常量表示水坑的大小,接着创建两个二维向量表示水坑最大和最小的时候的大小,黑色的就是水坑。
2024-10-13 16:25:55 1082
原创 UE4 材质学习笔记08(雨滴流淌着色器/雨水涟漪着色器)
法线贴图在红色通道和绿色通道上,那是法线的X轴和Y轴,在蓝色通道中 我有个用于雨滴流淌的蒙版,在Alpha通道中,有个时间偏移蒙版。这些贴图都是可以在PS上制作做来的,雨滴流淌图可以直接用笔刷画出来然后模糊下线,然后可以将这个图作为高度图并使用NVIDIA Photoshop插件将其转换为法线贴图直接输出以后是这样的效果,但是我不想用UV坐标把它投射到立方体上,实际上我想把它们投射到世界空间中,这样不管物体有多大,也不管物体去哪里或者如何旋转,这些小水流将始终保持向上和向下的方向。
2024-10-11 18:43:45 814
原创 UE4 材质学习笔记07(叶子摇晃着色器/雨水潮湿着色器/材质函数/雨滴着色器)
现实世界中叶子的晃动十分复杂,这次我们将实现每个叶子都动起来,来接近现实世界的效果先让它整体动起来,可以用time节点,然后用sin节点限制移动的范围接下来就是找到一个方法,把这条树枝上的单个叶子分离开来,这样我就可以让单个叶子以不同的幅度摆动 ,我要做的是画一个看起来像这样的蒙版遮罩,你不需要为这个蒙版准备一个高分辨率的纹理,只要你能把单个叶子指定为属于红色组、绿色组或蓝色组就行然后将三个时间值乘以我们的蒙版,根据我们对叶子的分组,会得到三个不同的值,然后加到UV上,每个叶子会根据所属的组来摆动。
2024-10-10 21:09:49 699
原创 UE4 材质学习笔记06(布料着色器/体积冰着色器)
首先需要一个菲涅尔项,菲涅尔项是用来计算观察向量和表面法线之间的点积,虽然有现成的节点可以给我们使用,但是我们还是选择自己去实现它,因为我们想对它进行更多的自定义。首先找到Camera Vector和Pixel NormalWS节点,然后进行点积。这次通过着色器模仿布料的外观,但是并不能模拟布料的物理性质。
2024-10-09 16:33:53 508
原创 UE4 材质学习笔记05(凹凸偏移和视差映射/纹理压缩设置)
这需要一个高度图并且它的分辨率很低,只有256*256,事实上,如果高度图的分辨率比较低并且有点模糊,效果反而会更好然后将高度图输出到BumpOffset节点的height插槽中, 之后利用得到的UV进行贴图采样。现在移动摄像机的时候有一些视差移动,表面看起来不是平的,纹理坐标正在根据我的视角在移动。下面是分别使用两种方法效果的对比,后者看起来更有起伏感。
2024-10-08 23:01:47 646
原创 UE4 材质学习笔记04(着色器性能优化)
当然这是在项目完成的时候,因为不同的硬件的性能不同,每个着色器的行为也会由不同。因为并不是所有指令在GPU上运行所需时间都相同 ,可能有的指令运行要1000ms,有的只需要200ms,这样着色器主要由第一个指令或者第二个指令组成的话性能就会差距很大。这种使用性能视图的方法并不完美,因为它只会给出一个概况,让你知道哪个的着色器比其它的更耗能。所以只要我们减少指令的数量就会使得着色器更高效的运作,但是这也不是一个完美的办法,第一个:可以使用场景的视图模式的优化视图模式的着色器复杂度。
2024-10-06 23:53:41 459
原创 UE4 材质学习笔记03(翻书(Flipbook)动画/环境混合)
如果你想让游戏以每秒30帧的速度运行,所有内容都必须在33毫秒内渲染出来,如果你想让游戏以每秒60帧的速度运行的话,必须在16毫秒内。所以当一个效果需要很多细节的时候,往往会离线创建它,然后将这个效果的每一帧放到一张纹理上,之后通过shader回放每一帧例如下面就是一个爆炸效果的每一帧材质编辑器里的FlipBook节点可以帮助我们播放每一帧,我们只需要连接时间节点以及行列数以及原来的UV,这个节点就可以给出我们合适的UV坐标来进行贴图采样。
2024-10-05 23:57:26 480
原创 UE4 材质学习笔记02(数据类型/扭曲着色器)
Float:用于需要单一数据通道的事物,如金属度、高光度和粗糙度Float2:带有两个通道,通常用于存储UV坐标Float3:用于需要颜色或向量的东西,例如基础颜色和法线Float4:用于存储带有Alpha的颜色可以将两个向量合并成一个新的向量(更高维度的)
2024-10-04 18:05:48 646
原创 UE4 材质学习笔记01(什么是着色器/PBR基础)
下面这个时材质的预览界面,右下角可以切换不同的形状,另外左上角还可以切换透视视图和正交视图然后左上角这个地方可以切换不同选项来查看模型,右面的显示可以隐藏或者显示网格,背景或者统计数据。PBR是基于物理的渲染,过去计算机图形只是试图模仿光线的外观,但是使用PBR是在模拟光线的实际行为。这样可以创建更逼真的图像。PBR有两个主要组成部分,光照属性和表面属性。光照属性主要由游戏引擎本身控制,表面属性则是由创建的着色器定义它们。
2024-10-03 18:47:41 905
原创 Learn ComputeShader 15 Grass
首先blender与unity的坐标轴不同,z轴向上,不是y轴通过小键盘的数字键可以快速切换视图,选中物体以后按下小键盘的点可以将物体聚焦于屏幕中心首先我们创建一个平面,宽度为0.2m,然后切换到正交前视图,复制两个平面。shift+D可以复制面接着将上下两个面旋转45°至中间面的中心。先按下R然后按下Y可以绕y轴旋转,然后按G键可以移动面然后切换到正交顶视图(数字键7)将两个复制的面分别向左向右旋转10.5°左右最后加上材质和贴图以后的效果就是下面这样。
2024-09-16 22:16:53 704
原创 Learn ComputeShader 14 A flocking example
之前我们都是在computeshader中计算粒子的位置,然后在顶点片段着色器渲染粒子。通过使用共享的compute buffer。事实上,我们不应该频繁的在cpu和gp之间传输数据,因为这样非常消耗性能。下面我们使用一个更好的办法来解决这个问题。首先先介绍一下Boid 算法,我们后面就是参考这个算法来完成我们的粒子系统。Boid 算法是一种用于模拟群体行为的算法,最早由克雷格·雷诺兹(Craig Reynolds)在 1986 年提出。
2024-09-13 17:08:12 906
原创 Learn ComputeShader 13 Adding a mesh to each particle
接着我们去到顶点着色器就可以获取到修改后的顶点信息,和上次一样的设置颜色,然后转换位置到裁剪空间,这次还要加上uv,因为我们一会要对提额图采样。这次不一样的是每一个instance有六个顶点,这是我们在onrenderobject中设置的。这里只需要设置顶点数组的uv就可以了。目前效果:目前我们看不到五角星,尽管在shader中我们对五角星贴图进行了采样,这是因为我们没有设置混合模式。然后去computeshader中更新顶点位置信息,其余内容都和上次一样,这里要记住三角形顶点是顺时针排列的。
2024-09-11 14:28:37 247
原创 Learn ComputeShader 12 Setting up a buffer-based particle effect
然后填充粒子数据到computebuffer中,分别传递buffer到computeshader和shader中,这里很关键的一部分就是我们在computershader中修改粒子数据,可以在shader中的buffer访问到修改后的数据。然后看一下我们的顶点着色器,首先是两个参数,第二实例ID就是逐渐增加的,从0增加到particleCount,第一个参数是每个实例的顶点索引,因为这次粒子都是点,所以永远是0,如果是三角形就会是0,1,2.随着粒子的生命周期减少,这是每个通道的颜色变化。
2024-09-09 19:53:42 423
原创 Learn ComputeShader 11 Star Glow Effect
我们需要创建一些临时纹理来存储渲染过程的中间阶段,之后先通过第一个pass将原图像提亮,下面是关键代码, Graphics.Blit(source, brightnessTex, material, 1)这个函数的就是通过material的第二个pass对图像进行处理,创建一个新的pass,_MainTex_TexelSize.xy就是图像宽度的倒数。我们首先要对源图像的亮部区域进行提亮,然后进行模糊添加光芒。下面是一开始的效果,只是一个循环播放的粒子系统。要以特定角度模糊上一步的图像,然后迭代多次。
2024-09-08 16:23:44 390 1
原创 Learn ComputeShader 10 HUD Overlay
之后我们就可以通过调整后的坐标在指定 轴上生成一条线。用的就是下面这个函数,这个函数主要就是检测x和y的接近程度(程度取决于后两个参数),只要接近就会返回1,否则返回0。为了生成的水平和垂直的线都在屏幕中央,我们有必要进行坐标转换。根据原图像的宽高比对像素坐标进行缩放,同时调整屏幕中心点的位置。首先生成一个动态的绕着圆弧旋转的直线,同时在直线后面增加一个尾随的渐变效果。可以利用下面的函数。最后就是生成水平轴两边的小三角形,可以利用下面的函数。首先生成水平和垂直两条竖线以及周围的三层圆圈。
2024-09-07 17:13:23 645
原创 Learn ComputeShader 09 Night version lenses
夜视仪通常都是绿色的,我们首先计算出灰度值,然后用灰度值乘上我们设置的一个类似夜视仪的绿色,灰度值越大颜色就越接近我们设置的颜色,反之越接近黑色。我们首先将像素的y坐标转换到0-1的范围内,然后生成一个周期性的值模仿扫描线的循环,然后加上0.3避免扫描线的强度过大,最后将它限制在0-1范围内。可以根据下图理解,很明显通过这个操作在多个像素显示了相同的颜色,并且很多像素颜色被丢失了,自然就会有降低分辨率的效果。最后就是制作夜视仪的望远镜的效果。这样以后得到的结果就更接近低分辨率相机的效果。
2024-09-07 13:09:49 245
原创 Learn ComputeShader 08 Blurring an image
假设要模糊一个像素,模糊半径为10,如下图所示模糊一个像素需要合并400个像素的颜色,如果要模糊整张图像那么计算量是非常大的。为了简化计算量,我们将这个模糊分成两个阶段。首先,水平模糊图像,然后使用这个图像垂直模糊图像,这个方法可以极大减少计算量原始 模糊的计算量:假设图像的尺寸为,我们对每个像素都要应用这个 21x21 的卷积核。因此:对于每个像素,都要处理它周围的 21x21 个像素,总共需要处理 441 个像素的加权平均。
2024-09-06 15:25:56 323
原创 Learn ComputeShader 07 Post Processing
OnRenderImage(RenderTexture source, RenderTexture destination)这个函数可以获取摄像机渲染后的图像到source,并通过对原图像的一系列处理之后设置到destination上。首先设置纹理的宽度和高度,然后获取线程组的x和y的大小,这里GetKernelThreadGroupSizes的都三个参数out _表示忽略。轴的线程组尺寸,这是 C# 语言中的一种方式,用于表示对某个输出值不感兴趣,不需要它的实际值。首先声明需要用到的属性。
2024-09-05 20:38:51 394
原创 Learn ComputeShader 06 Mesh deformation
位置的话先将立方体的位置向量归一化长度变为1,这样就有了一个半径为1的球体,接着乘上半径,然后乘上0.01是因为向量归一化以后长度放大了一百倍,如果不乘就会导致出现的球体非常大。接着声明顶点数组存储所有顶点信息,另一个数组也是存储顶点位置,这样可以保证我们修改顶点数组以后还保留初始顶点信息。返回的是模型的局部顶点坐标,这些坐标是在模型的局部空间中定义的,并且不包括任何缩放、旋转或平移的变换。然后获取组件MeshFIlter以获取顶点数据.并且将顶点数据填充到数组中,接着在cpu上接收修改后的顶点信息。
2024-08-12 20:24:47 387
原创 Learn ComputeShader 05 Using noise in the shader
接着要实现变化的噪声,只需要加上时间对随机函数的影响,呈现出类似于老式电视屏幕上的噪音的效果。实现原理也很简单,只是在每个线程使用随机函数获得一个随机值。这里直接输出perlin噪声的结果看看。另外在下面的链接里有很棒的噪声函数库。首先实现一个简单的噪声效果。
2024-08-10 00:50:32 150
原创 Learn ComputeShader 04 Orbiting Stars
先生成两个随机方向向量,接着根据叉乘计算出他俩的垂直向量,然后设置时间变量控制星星周期性变化,pos的取值范围为-0.5-0.5,所以还要乘2映射到-1到1.同时也要传递时间变量到GPU,然后在每一帧中修改预制体的位置即可完成效果。这次要实现的是环绕中心运行的星星效果,通过gpu计算每个星星的位置,传到cpu进而修改每个星星的位置。之前使用的buffer是只读的,并且只是从cpu传向gpu,这次要实现的是从gpu向cpu传递数据。是用于从 GPU 侧的缓冲区中提取数据,并将其复制到 CPU 侧的数组。
2024-08-09 02:02:09 326
原创 Learn ComputeShader 03 Passing data to shader
之前传递数据都是通过setInt,setVector等方法进行的,但是这些方法并不能一下传递大量数据,比如一个结构体数组,一个数据块。需要设置圆圈以及背景的颜色,要在同一个脚本中同时实现背景以及圆圈的绘制,需要编写两个核函数,并且先调用背景核函数,然后调用圆圈核函数覆盖背景颜色,这里两个核函数共用一张共享纹理。首先要做的是创建一个结构体来存储数据的单个实例,这里我们主要想实现控制圆的运动并将圆转回原点.,circledata表示所有圆圈的数据,在代码中先启用设置背景颜色的核函数,接着启用绘制圆圈的核函数。
2024-08-04 16:53:43 262
原创 Learn ComputeShader 02 Multiple kernels
前面已经成功创建了第一个compute shader,并且使用它替换掉quad的材质的纹理,现在我们将要在计算着色器中创建多个kernel。就是直接判断当前像素位置距离中心点的距离,也就是将像素到圆心的距离和半径的长度相比。现在只需要改变脚本里的kernel函数名称,就可以让quad显示不同的颜色了。其实思路很简单,也就是根据id.xy的范围来显示不同的颜色。接着要实现一个特别的核函数,要实现四个角分别显示不同的颜色。首先调整上次的计算着色器,让它显示为红色。然后再次创建一个kernel,显示为黄色。
2024-04-22 22:33:07 369
原创 Learn ComputeShader 01 First Computer Shader
前面已经说过,每个线程组会有64个线程分别处理64(8*8)个像素, 如果我们要绘制一个256*256分辨率的纹理,就分别需要(256/8,256/8)个线程组。线程组ID是(0,0,0)对应的id.xy应该是下面这样的,这个时候id.x和y指向纹理的左下角,多线程在左下角处理一个8*8的像素。比如说x,y,z分别是(4,2,3),那么每个线程组都会被调用,一共调用4*2*3=24次。如果x,y,z分别是(4,2,1),那么每个线程组都会被调用,一共调用4*2*1=8次。
2024-04-21 16:59:45 788
原创 Learn SRP 02
这是一种拆分类-结构体的方法,把他们放入不同的部分,存储在不同的文件中。这样做的目的是为了更好的组织代码。典型的用例是将自动生成的代码与手动编写的代码分开。就编译器而言,它们都是同一类定义的一部分。他们在。
2024-04-13 21:03:12 1190
原创 Learn OpenGL 32 IBL
基于图像的光照(Image based lighting, IBL)是一类光照技术的集合。其光源不是如中描述的可分解的直接光源,而是将周围环境整体视为一个大光源。IBL 通常使用(取自现实世界或从3D场景生成的)环境立方体贴图 (Cubemap) ,我们可以将立方体贴图的每个像素视为光源,在渲染方程中直接使用它。这种方式可以有效地捕捉环境的全局光照和氛围,使物体其环境。由于基于图像的光照算法会捕捉部分甚至全部的环境光照,通常认为它是一种更精确的环境光照输入格式,甚至也可以说是一种全局光照的粗略近似。
2024-04-01 17:10:32 748
原创 Learn OpenGL 32 PBR光照
在本章节中,我们把重点放在将之前讨论的理论转化为实际的渲染器,这个渲染器将使用直接的(或解析的)光源:比如点光源,定向灯或聚光灯。我们先来看看上一个章提到的反射方程的最终版:我们大致上清楚这个反射方程在干什么,但我们仍然留有一些迷雾尚未揭开。比如说我们究竟将怎样表示场景上的辐照度(Irradiance), 辐射率(Radiance) L我们知道辐射率L(在计算机图形领域中)表示光源的辐射通量(Radiant flux),或光源在给定立体角ω下发出的光能。
2024-03-27 22:05:00 1065
原创 Learn OpenGL 31 PBR理论
PBR,或者用更通俗一些的称呼是指基于物理的渲染(Physically Based Rendering),它指的是一些在不同程度上都基于与现实世界的物理原理更相符的基本理论所构成的渲染技术的集合。正因为基于物理的渲染目的便是为了使用一种更符合物理学规律的方式来模拟光线,因此这种渲染方式与我们原来的Phong或者Blinn-Phong光照算法相比总体上看起来要更真实一些。
2024-03-27 20:09:22 673
原创 Learn OpenGL 30 SSAO
AO的原理是通过将褶皱、孔洞和非常靠近的墙面变暗的方法近似模拟出间接光照但是环境光遮蔽这一技术会带来很大的性能开销,因为它还需要考虑周围的几何体。我们可以对空间中每一点发射大量光线来确定其遮蔽量,但是这在实时运算中会很快变成大问题。在2007年,Crytek公司发布了一款叫做屏幕空间环境光遮蔽(Screen-Space Ambient Occlusion, SSAO)的技术,这一技术使用了屏幕空间场景的深度而不是真实的几何体数据来确定遮蔽量。
2024-03-26 21:29:31 1191
原创 Learn OpenGL 29 延迟着色法
我们现在一直使用的光照方式叫做或者,它是我们渲染物体的一种非常直接的方式,在场景中我们根据所有光源照亮一个物体,之后再渲染下一个物体,以此类推。它非常容易理解,也很容易实现,但是同时它对程序性能的影响也很大,因为对于每一个需要渲染的物体,程序都要对每一个光源每一个需要渲染的片段进行迭代,这是多的!因为大部分片段着色器的输出都会被之后的输出覆盖,正向渲染还会在场景中因为高深的复杂度(多个物体重合在一个像素上)浪费大量的片段着色器运行时间。,为了解决上述问题而诞生了,它大幅度地改变了我们渲染物体的方式。
2024-03-25 22:13:19 1214
原创 Learn OpenGL 28 Bloom(泛光)
明亮的光源和区域经常很难向观察者表达出来,因为监视器的亮度范围是有限的。一种区分明亮光源的方式是使它们在监视器上发出光芒,光源的光芒向四周发散。这样观察者就会产生光源或亮区的确是强光区。(译注:这个问题的提出简单来说是为了解决这样的问题:例如有一张在阳光下的白纸,白纸在监视器上显示出是出白色,而前方的太阳也是纯白色的,所以基本上白纸和太阳就是一样的了,给太阳加一个光晕,这样太阳看起来似乎就比白纸更亮了)光晕效果可以使用一个后处理特效泛光来实现。泛光使所有明亮区域产生光晕效果。
2024-03-25 15:54:19 997
原创 Learn OpenGL 27 HDR
一般来说,当存储在帧缓冲(Framebuffer)中时,亮度和颜色的值是默认被限制在0.0到1.0之间的。这个看起来无辜的语句使我们一直将亮度与颜色的值设置在这个范围内,尝试着与场景契合。这样是能够运行的,也能给出还不错的效果。但是如果我们遇上了一个特定的区域,其中有多个亮光源使这些数值总和超过了1.0,又会发生什么呢?答案是这些片段中超过1.0的亮度或者颜色值会被约束在1.0,从而导致场景混成一片,难以分辨:这是由于大量片段的颜色值都非常接近1.0,在很大一个区域内每一个亮的片段都有相同的白色。
2024-03-25 11:23:37 797
原创 Learn OpenGL 26 视差贴图
视差贴图(Parallax Mapping)技术和法线贴图差不多,但它有着不同的原则。和法线贴图一样视差贴图能够极大提升表面细节,使之具有深度感。它也是利用了视错觉,然而对深度有着更好的表达,与法线贴图一起用能够产生难以置信的效果。视差贴图和光照无关,我在这里是作为法线贴图的技术延续来讨论它的。需要注意的是在开始学习视差贴图之前强烈建议先对法线贴图,特别是切线空间有较好的理解。视差贴图属于位移贴图(Displacement Mapping)技术的一种,它对根据储存在纹理中的几何信息对顶点进行位移或偏移。
2024-03-24 21:29:17 955
原创 Learn OpenGL 25 法线贴图
关于法线贴图还有最后一个技巧要讨论,它可以在不必花费太多性能开销的情况下稍稍提升画质表现。当在更大的网格上计算切线向量的时候,它们往往有很大数量的共享顶点,当法向贴图应用到这些表面时将切线向量平均化通常能获得更好更平滑的结果。这样做有个问题,就是TBN向量可能会不能互相垂直,这意味着TBN矩阵不再是正交矩阵了。法线贴图可能会稍稍偏移,但这仍然可以改进。使用叫做格拉姆-施密特。
2024-03-24 16:56:38 1145
空空如也
空空如也
TA创建的收藏夹 TA关注的收藏夹
TA关注的人