原文地址:http://www.adriancourreges.com/blog/2015/11/02/gta-v-graphics-study/
 

原文的简介:

GTA(侠盗猎车)系列自从1997年首部发售以来有了很大的进步,两年前,Rockstar发布的GTAV获得了成功,首日1100万套的销量打破了7项吉尼斯世界记录。

在PS3上游玩时,游戏的完成水准和技术质量给我留下了深刻印象。加载屏幕是最影响体验的 :而在GTA中你可以游戏几个小时,在一个巨大的开放世界中驾车数百公里,而没有任何的中断。考虑到大量资源的Streaming和PS3的硬件规格(256M内存和256显存),很惊讶20分钟都不会崩溃,这样真正的技术实力。     
 
这里要讨论的是Directx 11的PC版,使用了几个G的内存和GPU显存,尽管这里讨论的是PC的规格,但大部分应该也是适用于PS4和PS3的。

Frame分析

这是我们要分析的帧,有主角麦克和他的Rapid GT,以漂亮的洛杉矶为背景。
    
GTA V使用的是延迟渲染管线( Deferred Rendering Pipeline),使用多张HDR(高动态范围) Buffer来工作。但HDR不能直接正确的显示在显示器上,所以通过后处理用简单的Reinhard 操作(一种Tone mapping算法,可以把高动态范围映射到的动态范围)把他们转回到8位每个通道。

Environment Cubemap 环境立方图

第一步,是在游戏里渲染Cumbemap,     这张Cumbemap是每帧上实时生成的,用来渲染后面真实的反射效果。这部分是前渲染的( forward-rendered )。Cumbemap是如何被渲染的?对于不熟悉这技术的人来说,这个就像是现实世界的拍摄全景图片,把相机放在三脚架上,想象你正好站在一个大的立方体中心,并拍摄这个立方体的六个面,每拍摄1次旋转90度。
 
这正是游戏所做的:每个面被渲染到一张128x128的 HDR Texture上,第一个面的渲染就像这样:
把剩下的5个面用相同的处理重复进行,最后获得Cubemap
 从外面看的立方体
渲染每个面大约有30个Draw Call ,渲染的网格多边形数非常少,只有“风景”被绘制,(地形,天空,某些建筑),这就是为什么游戏里车辆的环境反射做的相当好,但 其他的车辆和角色 并不能被反射到。

Cubemap生成Spheremap

接下来,我们把获得的Cumbemap转换成一张Spheremap
                                                 
Spheremap
为什么要这么转换,我想通常这是在做优化:一个Cube map在Fragment Shader里,潜在的是访问 6个面的 128x128 texel,这里用Spheremap降低为两个“半球”的 128x128 texel,更好的是,因为相机通常是在车顶,大多只访问顶部的半球就可以。
 
球面投影在顶部和底部保留了反射的细节,而牺牲了侧面。这对GTA V来说很有用:车顶和车罩通常是面朝上的, 它们主要的是要来自顶部的反射获得更好的表现。

Culling和LOD(Level of Detail)

这一步由Compute Shader来处理的,无法用图片来解释。
基于到相机的距离,决定物体对像是用低模还是高模绘制,或者不绘制。
例如,超出一定距离的花草不再绘制,所以这步要计算每一个设置了LOD的物体对象。
这个计算实际在管线的位置在PS3与PC和PS4是不同的,在PS3上,所有这些计算都是运行在Cell的SPU上的。

G-Buffer 生成

渲染“主要”发生在这里,所有可见的Mesh一个个的绘制,但不会立刻进行着色计算,这些Draw Call只需要输出一些简单的着色用信息到叫做G-Buffer的不同缓冲中,GTA V里使用MRT,这样每个Draw Call可以一次输出到5个Render Target里。
然后,所有的这些缓冲用来合并计算每个Pixel的最终着色。因此,和“Deferred”相对的“Forward”。 这一步只有不透明物体被绘制,例如玻璃的透明Mesh需要在延迟管线中特别的延后处理。
所有的这些Render Target都是 LDR buffers (RGBA, 8 bits per channel)  保存了各种信息,参与最后着色值的计算。
这些缓冲有:     
Diffuse map:它保存的是Mesh的“内在颜色” (Albedo) ,代表了材质的一个属性,在理论上是没有受光照影响的。但你有注意到车辆上的白色高光么。有趣的是,GTA V 中是在输出到Diffuse map之前,先计算从太阳方向光的着色结果。
Alpha通道包含了一些“Blend”用的特殊信息(后面解释)。
Normal map :在每个Pixel(RGB)里保存法线向量信息(世界空间)。Alpha通道也被使用到,但不确定是用的什么方法,看起来像是一些植物接近相机的二进制Mask。
Specular map:包含了关于  specular/reflectance 的信息。
    Red: specular intensity 
    Green: glossiness (smoothness)
    Blue: fresnel intensity (通常属于相同材质的所有Pixel都是常量)
Irradiance map:Red通道看起来是包含的每个 Pixel 接受太阳光的 irradiance(通过 Pixel 的法线和位置,以及太阳照射方向),这里不能100%确定通道的作用,看起来像是受第二光源产生的 irradiance  。Blue是 Pixel 的 emissive(自发光)信息(霓虹灯和电灯泡使用)。Alpha通道除了标记对应的角色皮肤或植物外,大部分不会被使用。
那么,前面我们提到输出了5个render target,但这里只展示了其中的4个。最后一个RT,是特殊的depth-stencil buffer。下面是在Pass最后看到的结果。

Depth map : 它保存了每个 Pixel 到相机的距离。
直观的看,你会认为远处的 Pixel 是白色(深度为1)而越近处的 Pixel 越黑。这里的情况有所不同, : GTA V看起来使用的是 logarithmic    Z-buffer,并使用了 reversed-Z(Z的倒数) 。这么做是因为深度被 encode了,这样浮点数越接近0,精度越高。R eversed-Z 可以在排序深度非常接近的物体时有更高的精度,大大降低了   Z-fighting。这种方法在很长绘制距离的游戏里是必须的。也不是什么新技术,例如 Just Cause 2  中也使用了类似的技术。
这张图更方便理解 引自:  https://developer.nvidia.com/content/depth-precision-visualized 
Stencil: 用来识别不同类型的Mesh绘制,同一种类的Mesh的所有 Pixel 赋予相同的ID。例如一些 stencil  值就是:
0x89: 玩家控制的角色
0x82: 玩家驾驶的车辆
0x01: NPCs
0x02: 类似汽车,自行车的 交通工具
0x03: 植被和树叶
0x07: 天空

生成所有的这些Buffer大约要1900的Draw Call
要注意的是,场景的渲染是“从前向后”这种方法的优化是得益于“early Z rejection”,当场景被绘制时,很多的Fragment因为被更近处绘制的 Pixel 遮挡,没有通过深度测试。如果 Pixel 没有通过深度测试,那么GPU会把自动丢弃它,并且也会执行Pixel Shader。当你有大量的Pixel Shader时,“从后向前”渲染(画家算法)是一种性能方面最差的选择,而“从前向后”则是最优的。
顺便说一下,为了解释alpha通道在Diffuse Map中起的作用,来看下下面的截图:
这里可以注意到一些 Pixel 的缺失,特别是树木上看的更明显,就好像他们的Sprite上缺失了Texel。
我在PS3上也几次注意到这些缺陷(artifacts ),那时让我很不解。当Texture Sprite变得很小时,它会走样么? 现在我可以看到他们所以的Mipmap都是正确的,并不会产生走样问题。这个pattern很特殊,就好像是棋盘格那样,游戏渲染会跳过渲染的两个 Pixel 中的一个。为了确认这一点,我查看了D3D的字节码,果然:
dp2 r1.y, v0.xyxx, l(0.5, 0.5, 0.0, 0.0)  // Dot product of the pixel's (x,y) with (0.5, 0.5)
frc r1.y, r1.y                                            // Keeps only the fractional part: always 0.0 or 0.5
lt r1.y, r1.y, l(0.5)                                     // Test if the fractional part is smaller than 0.5

这些指令都很简单,相当于测试  (x + y) % 2 == 0  ,2个 Pixel 中的1个会被pass掉(x和y是 Pixel 坐标),这只是其中的几个丢弃像素的条件之一(另一个是 当 alpha < 0.75  ),但足够解释这个ditehr pattern了。
为了记住那些Mesh要用“ dithered mode ”绘制,这个信息就要保存在diffuse map的alpha通道中,就像下图所看到的那样。我认为这个模式是用在很远的距离或者是在LOD层次间过渡的,通过抛弃一些 Pixel ,节省了填充率和着色计算。

Shadows 阴影

游戏的阴影使用了CSM   (cascaded shadow maps):把4张 shadow map生成到1张 1024x4096的texture里,每张 shadow map是由不同的 t camera frustum创建的,随着迭代的进行, frustum获取了更大并包含更多的场景部分,这就确保了接近玩家的阴影用更高的分辨率保存,阴影越远细节也更少。下面4幅图是深度信息的预览:
这样做可以会有很高的消耗,因为需要渲染场景4次,但 frustum-culling避免了不必要的多边形渲染。这里的CSM大约产生了1000个 draw calls
通过这些深度信息,我们可以计算每个 Pixel 的阴影投射。引擎把阴影信息保存在   render targe中:由太阳方向光投射的阴影在Red通道,大气中云投射的阴影同时保存在Red和Green通道
shadow maps通过   dithering pattern  来采样(如果你仔细看下面的Texture,Red通道显示就像棋盘格那样)这样做是为了让阴影的边缘更加平滑。

然后这些缺陷会被修正,太阳的阴影和云的阴影合并到一张Buffer里,进行一些深度上的模糊(Blur)再把结果保存到 specular map的alpha通道中。
模糊操作的一个快速的说明:这个操作非常的昂贵,因为它需要从多张texture里获取数据。所以为了减低负荷,在执行模糊处理前,提前创建一个Texture:把Shadow Buffer降采样到1/8,再调用4次称作   Gather()的由Pixel Shader执行的轻量的模糊处理。这样可以给予一个轻量的估算,那些 Pixel 是被完全照明(没有阴影)的。然后在执行完整的深度感知的模糊时,第一步先读取这个提前创建的Buffer,如果这个像素是被完全照亮的,那么   pixel shader 立即输出为1并跳全部沉重的模糊计算。

Planar Reflection Map 平面反射贴图

这里不会涉及太多的细节,反射部分会在第2部分详细解释。在我们选取的这个场景中几乎看不到这个效果,但是这一部分是用来产生海水表面的reflection map。基本上是把场景再一次绘制到(650 draw Call)一张240x120的Texture上,只是颠倒了,这样看起来是反射在水面上。

Screen Space Ambient Occlusion 屏幕空间环境遮蔽

计算出一个线性版的depth-buffer ,然后使用它来创建SSAO。

先是创建一个噪声版(Noisy),然后连续使用两次深度感知的模糊(Blurred水平和垂直方向)来平滑结果。
所有的这些都是在一半的原始分辨率下进行的以提高性能。

G-Buffer Combination

最后,合并所有生成的buffer!
在   pixel shader  中从不同的buffer获取数据,并计算出在HDR中Pixel最后的着色值。在这次举例的夜景里,光源和他们的 irradiance 也一样会加入到上面的场景里。
 
                                                                                                                                                 
这就很有条理了,虽然我们仍然缺少海洋,天空还有透明的物体,但首先:麦克的表现还需要加强。

Subsurface Scattering 次级表面散射

麦克的皮肤着色稍微有些不同,脸上有很暗的地方,就像 身体 是用厚塑料做的。
这也就是为什么要执行SSS(Subsurface Scattering),来模拟光在皮肤里的传播。看看他的耳朵和嘴唇,在SSS pass后,光会使得他们有血色,给予了真实世界中应该产生的正确红色。
为什么SSS只对麦克起作用?首先只有他的轮廓被提取了出来。这可能要得益于之前生成的 stencil buffer:所有麦克的Pixel都有一个   0x89的值,那么当我们获取到麦克的Pixel,我们需要让SSS只作用在皮肤上,而不是衣服。
实际当所有的   G-Buffer被合并后,除了着色数据保存为RGB,还有一些数据被写入到alpha通道。更精确来说,   irradiance map 和  specular map  的alpha通道被用来创建二进制Mask:Pixel属于麦克和一些植物的在Alpha通道中设置为1。其他的Pixel像是服装,Alpha通道的值是0。
这样通过提供的输入到合并的G-Buffer的Target以及 depth-stencil buffer,SSS得以被应用。

你可能认为这个只是一个巧妙和局部的改进。虽说如此,但不要忘记在游戏时,我们作为人类会本能的倾向看向角色的脸,脸部任何的渲染改进,对深入体验都是一个很大的成功。游戏中,SSS被应用到你的角色和NPC上。

Water 水

这个例子里没有太多的水,我们在后面会介绍海洋和各处的游泳池。
GTA V中水的渲染包括反射(reflection)和折射(refraction)。

先前创建的对数 Z-buffer  用来生成它第2个版本: 一半分辨率的 线性的 Z-buffer。
海洋和水池被依次的绘制,以MRT模式一次输出到多个target上。
Water Diffuse map: 水的固有颜色
Water Opacity map: Red通道看起来保存的是一些水的不透明属性(opacity,例如海洋通常是0.102,水池是0.129)。Green通道保存的水面Pixel的深度(深的Pixel代表更加不透明的水,颜色受Diffuse map影响硬大,而浅水的Pixel几乎是透明的)。
要注意的是,所有的水池都是无条件的被渲染,即便是他们被场景中其他的Mesh所遮挡,也都会显示在Red通道里。而Green通道中,值有可见的Pixel会被计算,只有“水”的像素会计算到最终的图像里。

现在我们可以合并之前创建的buffer来生成 refraction map:
 
在 refraction map中,水池中注满了水(水越深越蓝),caustic效果也被加入了。
 
现在,进行水的最后渲染:再一次的顺序渲染海面和水池的网格,但这次把反射和折射合并,通过一些bump map来扰乱水面的法线。
 
                                                                                                                                            

Atmosphere 大气

light-shaft map,也被叫做“ volumetric shadow ”:它的作用是让大气或雾的那些不被太阳直接照射的部分变暗。
这个map以一半的分辨率生成,通过 ray-marching  每个pixel,并与太阳的s hadow map中值做对比。获得到有噪声的结果后,再对缓冲最模糊
接下来我们在场景里加入雾的效果:通过雾可以很方便的隐藏远处低多边形建筑细节上的缺陷。这个pass读取   light-shaft map和 depth-buffer来输出雾的信息。
然后是渲染天空和云彩:

天空实际是一个 draw call渲染的:使用的mesh是一个巨大的圆顶覆盖了整个场景。
这个步骤中加入一些 texture做成 Perlin noise 。
 

云的渲染也是相同的方法,一个巨大的mesh,这里是环的形状,在地平线上渲染。使用了一张 normal map  和  density map来渲染云彩:这是一张巨大的 2048x512的Texture的,而且是无缝的(左边和右边循环)、
 

Transparent Objects

这一步渲染场景中所有的透明物体对象,眼镜,挡风玻璃,飞散的灰尘颗粒等等。
 
全部透明物体的绘制只用了11个draw-call,粒子大量使用了instancing。

Dithering Smoothing

还记得前面我们顺便提到的在Diffuse map中的一些树木是dither的吧?
这次我们来修复这些缺陷:使用Pixel Shader执行的后处理效果,读取原始的颜色缓冲以及Diffuse map中的alpha通道的信息,就可以得知是那些pixel是dither的。每个pixel,可以对周围的两个pixel做采样,计算出最后“光滑”的颜色值。
 
这个巧妙的方法,有助于减少前面的计算量和填充率,现在只需要一个pass就可以“修复”图像:它的处理成本是恒定的,不收场景中几何体数量的影响。不过这个Filter并不完美,在PS3和PC上我还是注意到屏幕上有一些棋盘格的图案,有些情况Filter并不能处理。

Tone Mapping and Bloom

我们之前渲染的图像一直保存在HDR格式,每个RGB通道保存为1个16bit的浮点。这样就光照强度就可以有巨大的变化。但显示器并不能显示这种高范围的值,只能输出每个通道8bit的RGB颜色。
Tone Mapping用于把这些颜色值从HDR转换到LDR空间。这里有几种函数把一个范围映射到另一个范围。经典并广泛使用的方法就是 Reinhard,实际上也是我前面用来生成所有截图的方法,它给出的结果很接近游戏最终的渲染。

但GTA V真的是使用 Reinhard么?这里我们还是逆向分析一些shader的字节码:
// Suppose r0 is the HDR color, r1.xyzw is (A, B, C, D) and r2.yz is (E, F)
mul r3.xy, r1.wwww, r2.yzyy                  // (DE, DF)
mul r0.w, r1.y, r1.z                                    //  BC
[...]
div r1.w, r2.y, r2.z                                        // E/F
[...]
mad r2.xyz, r1.xxxx, r0.xyzx, r0.wwww  // Ax+BC
mad r2.xyz, r0.xyzx, r2.xyzx, r3.xxxx        // x(Ax+BC)+DE
mad r3.xzw, r1.xxxx, r0.xxyz, r1.yyyy        // Ax+B
mad r0.xyz, r0.xyzx, r3.xzwx, r3.yyyy        // x(Ax+B)+ DF
div r0.xyz, r2.xyzx, r0.xyzx                           // (x(Ax+BC)+DE) / (x(Ax+B)+DF)
add r0.xyz, -r1.wwww, r0.xyzx                     // (x(Ax+BC)+DE) / (x(Ax+B)+DF) - (E/F)

(x(Ax+BC)+DE) / (x(Ax+B)+DF) - (E/F) 是在  http://filmicgames.com/archives/75 中出现的典型的方程式,看来GTA V里使用的不是Reinhard,而是神秘海域2中的方法,这样不会让黑色区域失去饱和度。
float A = 0.15;
float B = 0.50;
float C = 0.10;
float D = 0.20;
float E = 0.02;
float F = 0.30;
float W = 11.2;
float3 Uncharted2Tonemap(float3 x)
{
return ((x*(A*x+C*B)+D*E)/(x*(A*x+B)+D*F))-E/F;
}
float4 ps_main( float2 texCoord  : TEXCOORD0 ) : COLOR
{
float3 texColor = tex2D(Texture0, texCoord );
texColor *= 16;  // Hardcoded Exposure Adjustment
float ExposureBias = 2.0f;
float3 curr = Uncharted2Tonemap(ExposureBias*texColor);
float3 whiteScale = 1.0f/Uncharted2Tonemap(W);
float3 color = curr*whiteScale;
float3 retColor = pow(color,1/2.2);
return float4(retColor,1);
}

转换到LDR的处理流程:
    将HDR buffer降采样到1/4的分辨率
    使用 compute shader计算buffer平均的亮度( luminance ) ,把结果输出到一张1x1的texture。
    计算新的曝光,用来控制场景的明暗
    使用 bright-pass filter,把亮度值高于一定阈值(由曝光值来决定)的pixel提取出来。
    场景中只有少数的pixel被filter保留了下来:比如车上有着强烈反射亮度的斑点。

    把这个 brightness buffer反复的减少尺寸并按原尺寸的1/16来做模糊,然后再upscale几次直到原尺寸的一半为止。
    把这个 bloom加入到原始的HDR pixel,再使用神秘海域2的 tone-map操作,把颜色转化到LDR,同时运用伽马校正(   gamma correction   ),从线性空间转化到sRGB空间。
最终的结果很大程度上取决于曝光值,下面是说明这个参数的图例:
曝光实际是在帧与帧之间慢慢演变的,不会有突然的变化。
这是为了模拟人眼的行为:游戏中你是否注意到,当在黑暗的隧道中驾车,突然离开隧道照射在阳光下时,所有的环境在几帧里看起来会非常亮?然后再逐渐的从"耀眼"恢复到"正常",同时曝光也调整到一个新的值。GTA V甚至考虑到给予的“明亮”到“黑暗”的曝光适应,比“黑暗”到"明亮"更快,就像人眼的行为一样。

Anti-Aliasing and Lens Distortion

如果anti-aliased(反走样)的方法使用的是FXAA,它是用来平滑网格的锯齿边缘。然后,为了模拟真实相机,使用一个简单的pixel Shader在图像上执行 lens-distortion。它不光是扭曲图像,也在帧的边缘稍微加入了色差,通过Red通道的比Green和Blue更稍加扭曲来实现。

UI

最后提及的是:UI,这里只包含了屏幕左上角的小地图。地图实际上是划分为若干个正方形的Tile,引擎只绘制显示在屏幕中的Tile。每个Tile都是通过一个draw call来绘制的。我把这些Tile上色,以方便了解结构。
 
Scissor Test  允许只渲染左下角,并抛弃外面的内容。所有的道路实际上是矢量的(见上面截图的线框),可以作为mesh渲染并放大到任意的等级。
然后小地图 绘制到主图像缓冲,把一些小的图标和部件添加在顶部。
所有这些使用了4155个 draw calls,包含了1113张 texture和88张 ender target。
 

转载于:https://www.cnblogs.com/TracePlus/p/5123297.html

Grand Theft Auto V (侠盗列车手5)图形研究相关推荐

  1. 侠盗猎车手5 MOD版/Grand Theft Auto V/gta5

    <侠盗猎车手5>简称<GTA5>(台版译作<横行霸道5>),是一款由Rockstar Games制作并发行的动作冒险游戏.游戏采用新版雷霆引擎(RAGE引擎),画面 ...

  2. Grand Theft Auto V 图形研究(2)

    原文链接 http://www.adriancourreges.com/blog/2015/11/02/gta-v-graphics-study-part-2/ Level of Detail 如果说 ...

  3. 深度学习 图形界面_如何使用深度学习创建逼真的侠盗猎车手5图形

    深度学习 图形界面 by Chintan Trivedi 通过Chintan Trivedi 如何使用深度学习创建逼真的侠盗猎车手5图形 (How to create realistic Grand ...

  4. 侠盗猎车手五手机版下载。_您现在应该使用的5个侠盗猎车手V Mod

    侠盗猎车手五手机版下载. If you thought Grand Theft Auto V was already one of the best gaming experiences of our ...

  5. 侠盗飞车猎车手破解版_侠盗猎车手有身份危机吗?

    侠盗飞车猎车手破解版 Just to be clear from the very beginning, I love Grand Theft Auto V. I also enjoyed GTA O ...

  6. gta5计算机或网络,《侠盗猎车手5(GTA5)》PC版需要连接网际网络及停止运行解决方法...

    GTA5正版 需要连接网际网络和停止运行的解决办法 其实方法很简单 1.先去steam官网上下载steam.2.在STEAM先不要有任何GTAV的下载进程,如果有请删除.3.然后进入Steam\ste ...

  7. Rockstar Games遭黑客攻击,《侠盗猎车手6》90个开发视频外泄

    当地时间9月19日,视频游戏开发商Rockstar Games证实,其 热门游戏<侠盗猎车手6>(Grand Theft Auto)开发片段遭到黑客大规模窃取 ,这一泄露事件立即在游戏圈迅 ...

  8. GTA4;侠盗猎车手4 作弊码

    使用方法:游戏中打开Niko的电话(按 向上 键),拨打以下电话即可得到对应秘籍效果. 秘籍代码: 1. 武器和生命----482-555-0100 2. 加血和装甲------362-555-010 ...

  9. 侠盗飞车4无法运行因计算机缺失,PC版《侠盗猎车手4》面临大量的问题

    [游侠新闻] 时至今日,谁也不曾料想到,<侠盗猎车手4>会出现这么多问题.4月份发售的时候,<侠盗猎车手4(Grand Theft Auto 4)>的PS3和X360版可谓好评 ...

最新文章

  1. qt发布后 mysql数据库_qt发布后 mysql数据库
  2. Datawhale组队学习 Task03:栈与递归(2天)
  3. TCP/IP详解--第十二章
  4. @RequestMapping对请求方法限定
  5. aix下java程序运行问题
  6. Educational Codeforces Round 95 (Rated for Div. 2)
  7. 【CodeForces - 675C】Money Transfers(思维,前缀和)
  8. LeetCode 427. 建立四叉树(递归)
  9. 使用 git pull 拉代码时提示:There is no tracking information for the current branch.
  10. 小程序获取用户地址信息api
  11. CUDA计算向量内积的程序(源自CUDA范例编程)
  12. Linux下打开Android调试器DDMS的方法
  13. Ubuntu 18.04 64位安装校园网客户端(完美解决)
  14. JavaScript 页面刷新方法
  15. cogs 1695. 梦游仙境
  16. 【绝对经典】骂人口误.......................
  17. 【GHM (AAAI‘2019)】
  18. Kubernetes K8S 1.20部署Ingress nginx 0.30
  19. 新年了,5G手机芯片,到底买谁?
  20. python画猫和老鼠代码_猫捉老鼠游戏(Python)

热门文章

  1. 持续集成/持续交付(CI/CD)
  2. android使用友盟实现第三方登录、分享以及微信回调无反应问题解决办法
  3. C语言GUI编程之数字记忆游戏——项目目录结构和初步的窗口布局
  4. pc端、移动端插入背景音乐,自动播放,循环播放
  5. python加密解密 sha256_如何在python上使用RSA私钥和SHA256解密
  6. 测试文章发布,不要点击
  7. 网页设计图片向上浮动_CSS 关于浮动
  8. 山东大学软件学院项目实训weblab-1
  9. python比较excel中两列数据_python入门之对比两份excel表格数据
  10. rEFInd引导系统(Ubuntu)