本系列主要参考《Unity Shaders and Effects Cookbook》一书(感谢原书作者),同时会加上一点个人理解或拓展。

这里是本书所有的插图。这里是本书所需的代码和资源(当然你也可以从官网下载)。

========================================== 分割线 ==========================================

写在前面

终于到了本书的最后一章了,好激动有木有!作为压轴章,虽然只有两篇,但每篇的内容是比之前的任何一篇都要复杂。写完这章要去总结一下啦~那么,开始学习吧!

学习这本书的人绝大部分在业余时间玩过一两个游戏。那么,你肯定有感触,实时游戏的一个很重要的特性就是要让玩家有种身临其境的感觉。越是现代的游戏,越是使用了更多的画面特效来达到这种沉浸感。

使用画面特效,只需通过改变游戏画面,我们就可以把某个环境的氛围烘托成冷静到恐怖各个层次。想象我们在某个关卡中走入一个房间,然后游戏接管,开始播放过场动画。许多现代游戏都是用了不同的画面特效来改变当前时刻的气氛。而理解怎样创建这些在游戏性中被触发的画面特效将是我们下面要完成的工作。

在本章中,我们将会学习一些常见的游戏画面特效。这些包括,如何把一个正常的画面改变成一个老电影式的画面效果,在许多第一人称射击游戏中如何在屏幕上应用夜视效果(night vision effects)。

首先,我们来学习如何创建一个老电影式的画面特效。

游戏往往会建立在不同的背景时间上。一些发生在想象的世界中,一些发生在未来世界中,还有一些甚至发生在古老的西方,而那时候电影摄像机才刚刚发展起来,人们看到的电影都是黑白的,有时还会呈现出棕褐色调(a sepia effect,Unity Pro中有自带的脚本和Shader)的着色效果。这种效果看起来非常独特,我们将在Unity中使用画面特效来重现这种效果。

实现这个效果需要一些步骤。我们先来分析一下下面的图像,然后分解制作这样老电影视觉的步骤:

上面的图像实际是有一系列从网上找到的图片组合起来实现的。我们可以利用Photoshop来创建这样风格的图片,来帮助你完成画面特效的草图。进行这样的过程(在Photoshop里制作原型)不仅可以告诉我们需要哪些元素,还可以快速让我们知道应该使用哪些混合模式,以及如何构建屏幕特效的图层(layers)。不过作者说的Photoshop源文件我没有找到。。。

本文最后实现的效果大概就是下面这样啦:

而原始的画面是:

准备工作

现在让我们来看一下每一个图层是如何被组合在一起从而创建出最后的效果的,以便我们为Shader和脚本准备下所需的资源。

  • 棕褐色调(Sepia Tone):这种效果是比较容易是新建的,我们只需要从原始的render texture中把所有像素颜色转换到一个单一的颜色范围即可。这可以通过使用原始图像的光度(luminance)加上一个常量颜色值来实现。我们第一个图层看起来像下面这样:

  • 晕影效果(Vignette effect):我们总是可以看到,当使用老的电影投影机把老电影投影到屏幕上时,总有些模糊的边框。这是因为,老式投影仪使用的灯泡在中央的亮度高于四周的亮度。这种效果通常被称为晕影效果(Vignette Effect),而这正是我们屏幕特效的第二个图层。我们可以使用一张叠加的纹理覆盖在整个屏幕上来达到这种效果。下面的图像展示了这个图层单独看起来的样子:
  • 灰尘(Dust)和划痕(Scratches):最后一层图层就是灰尘(Dust)和划痕(Scratches)了。这个图层利用了两张不同的平铺(tiled)纹理,一个用于灰尘,一个用于划痕。使用它们的原因是因为我们想要使用不同的平铺速率,按时间来移动这两张纹理。由于老电影的每一帧通常都会出现一些小的划痕和灰尘,这使得整个画面看起来像电影正在放映。下面的图片展示了这个图层单独看起来的效果:

上面是分析了Photoshop里面各图层的样子和实现。现在,我们来使用上述纹理在Unity里正式实现我们的画面特效!

  1. 准备好一张晕影(Vignette)纹理,一张灰层纹理,一张划痕纹理,你可以在本书资源(见文章最上方)里找到。
  2. 创建一个新的脚本,命名为OldFilmEffect.cs。创建一个新的Shader,命名为OldFilmEffectShader.shader。
  3. 使用前一章第一篇里的代码填充上述新的脚本和Shader。
  4. 把OldFilmEffect脚本添加到Camera上,并使用OldFilmEffectShader给OldFilmEffect脚本中的Cur Shader赋值。

实现

我们的老电影式的画面特效中的每一个独立图层实际都很简单,但是,当我们把它们整合在一起我们就可以得到非常震撼的效果。现在你的画面特效脚本系统应该已经建立好了,现在我们来实现具体的脚本和Shader。
首先,我们来填写脚本的主要代码。
  1. 第一步我们要定义一些需要在面板中显示的变量,以便让用户进行调整。我们可以利用之前制作原型所用的Photoshop作为参考,来决定我们需要显示哪些变量。在脚本中添加如下代码:

     #region Variablespublic Shader oldFilmShader;public float oldFilmEffectAmount = 1.0f;public Color sepiaColor = Color.white;public Texture2D vignetteTexture;public float vignetteAmount = 1.0f;public Texture2D scratchesTexture;public float scratchesXSpeed;public float scratchesYSpeed;public Texture2D dustTexture;public float dustXSpeed;public float dustYSpeed;private Material curMaterial;private float randomValue;#endregion
  2. 然后,我们需要填充OnRenderImage函数。在这个函数里,我们将要把上述变量传递给Shader,使得Shader可以使用这些数据来处理render texture:
      void OnRenderImage (RenderTexture sourceTexture, RenderTexture destTexture){if (oldFilmShader != null) {material.SetColor("_SepiaColor", sepiaColor);material.SetFloat("_VignetteAmount", vignetteAmount);material.SetFloat("_EffectAmount", oldFilmEffectAmount);if (vignetteTexture) {material.SetTexture("_VignetteTex", vignetteTexture);}if (scratchesTexture) {material.SetTexture("_ScratchesTex", scratchesTexture);material.SetFloat("_ScratchesXSpeed", scratchesXSpeed);material.SetFloat("_ScratchesYSpeed", scratchesYSpeed);}if (dustTexture) {material.SetTexture("_DustTex", dustTexture);material.SetFloat("_DustXSpeed", dustXSpeed);material.SetFloat("_DustYSpeed", dustYSpeed);material.SetFloat("_RandomValue", randomValue);}Graphics.Blit(sourceTexture, destTexture, material);} else {Graphics.Blit(sourceTexture, destTexture);}}
  3. 最后,我们需要在Update函数中保证一些变量的范围:
       void Update () {vignetteAmount = Mathf.Clamp(vignetteAmount, 0.0f, 1.0f);oldFilmEffectAmount = Mathf.Clamp(oldFilmEffectAmount, 0.0f, 1.0f);randomValue = Random.Range(-1.0f, 1.0f);}

接下来,我们来实现关键的Shader部分。

  1. 首先,我们需要创建对应的Properties。这使得脚本和Shader之间可以进行通信。在Properties块中输入如下代码:

      Properties {_MainTex ("Base (RGB)", 2D) = "white" {}_VignetteTex ("Vignette Texture", 2D) = "white" {}_VignetteAmount ("Vignette Opacity", Range(0, 1)) = 1_ScratchesTex ("Scraches Texture", 2D) = "white" {}_ScratchesXSpeed ("Scraches X Speed", Float) = 10.0_ScratchesYSpeed ("Scraches Y Speed", Float) = 10.0_DustTex ("Dust Texture", 2D) = "white" {}_DustXSpeed ("Dust X Speed", Float) = 10.0_DustYSpeed ("Dust Y Speed", Float) = 10.0_SepiaColor ("Sepia Color", Color) = (1, 1, 1, 1)_EffectAmount ("Old Film Effect Amount", Range(0, 1)) = 1_RandomValue ("Random Value", Float) = 1.0}
  2. 和往常一样,我们需要在CGPROGRAM块中添加对应的变量,以便Properties块可以和CGPROGRAM块通信:
        SubShader {Pass {CGPROGRAM#pragma vertex vert_img#pragma fragment frag#include "UnityCG.cginc"uniform sampler2D _MainTex;uniform sampler2D _VignetteTex;uniform sampler2D _ScratchesTex;uniform sampler2D _DustTex;fixed4 _SepiaColor;fixed _VignetteAmount;fixed _ScratchesXSpeed;fixed _ScratchesYSpeed;fixed _DustXSpeed;fixed _DustYSpeed;fixed _EffectAmount;fixed _RandomValue;
  3. 现在,我们来填充最关键的frag函数,在这里我们将真正处理画面特效中的每一个像素。首先,我们来获取render texture和晕影纹理(Vignette texture):
             fixed4 frag (v2f_img i) : COLOR {half2 renderTexUV = half2(i.uv.x, i.uv.y + (_RandomValue * _SinTime.z * 0.005));fixed4 renderTex = tex2D(_MainTex, renderTexUV);// Get teh pixed from the Vignette Texturefixed4 vignetteTex = tex2D(_VignetteTex, i.uv);

    解释:frag函数是整个特效的关键所在。和Photoshop中的图层类似,我们的Shader也是处理的每一个图层,然后把它们再结合在一起。因此,我们的分析过程也是按每个图层,你可以想象Photoshop中图层是如何工作的。这样的思维可以帮助我们将来创建新的画面特效。

    这里的几行代码定义了UV坐标是如何为render texture工作的。由于我们想要模仿一个老电影的风格,我们可以在每一帧调整render texture的UV坐标,来模拟一个闪烁的效果。

    第一、二行对render texture的Y方向添加了一些偏移来达到上述的闪烁效果。它使用了Unity内置的_SinTime变量,来得到一个范围在-1到1的正弦值。然后再乘以了一个很小的值0.005,来得到一个小范围的偏移(-0.005, +0.005)。最后的值又乘以了_RandomValue变量,这是我们在脚本中定义的变量,它在Update函数中被随机生成为-1到1中的某一个值,来实现上下随机弹动的效果。在得到UV坐标后,我们在第二行使用了tex2D()函数在render texture上进行采样。

    最后一行很简单,直接使用tex2D()函数对晕影纹理进行采样,不需要再移动UV坐标了。

    通过上述代码我们现在得到了底色(renderTex)和第一层图层(vignetteTex)。

  4. 然后,我们需要添加对灰尘(dust)和划痕(scratches)的处理。添加如下代码:
                 // Process the Scratches UV and pixelshalf2 scratchesUV = half2(i.uv.x + (_RandomValue * _SinTime.z * _ScratchesXSpeed), i.uv.y + (_Time.x * _ScratchesYSpeed));fixed4 scratchesTex = tex2D(_ScratchesTex, scratchesUV);// Process the Dust UV and pixelshalf2 dustUV = half2(i.uv.x + (_RandomValue * _SinTime.z * _DustXSpeed), i.uv.y + (_Time.x * _DustYSpeed));fixed4 dustTex = tex2D(_DustTex, dustUV);

    解释:这些代码和上一步中的很类似,也就是我们需要生成移动后的UV坐标来修改当前图层在画面特效中的位置。我们还是使用了内置的_SinTime变量来得到一个-1到1范围内的值,再乘以我们的随机值_RandomValue,最后再乘以一个系数来调整移动的整体速度。一旦生成UV坐标后,我们就可以使用tex2D函数对灰尘纹理和划痕纹理进行采样。

    通过上述代码,我们得到了第二层图层中的scratchesTex和dustTex。

  5. 然后,处理棕褐色调(Sepia Tone):
                  // Get the luminosity values from the render texture using the YIQ valuesfixed lum = dot(fixed3(0.299, 0.587, 0.114), renderTex.rgb);// Add the constant calor to the lum valuesfixed4 finalColor = lum + lerp(_SepiaColor, _SepiaColor + fixed4(0.1f, 0.1f, 0.1f, 0.1f), _RandomValue);

    解释:这一步是处理老电影效果的颜色。通过上述代码,我们给整个画面染上了一种发黄的颜色。首先,我们把render texture转换到它的灰度版本(第一行)。我们使用了YIQ值中的光度值(luminosity,即Y表示的意思)来完成这个目的。YIQ值是NTSC电视系统标准使用的颜色空间。更多的关于YIQ颜色的内容可以参考文章最后的链接。这里我们只要知道,YIQ中的Y值就是任意图像的光度常量值,也就是说对任意图像我们都可以通过乘以这个常量值来得到这个图像的每个像素的光度值(luminosity)。因此,我们可以通过把render texture中的每一个像素点乘光度常量系数,来生成一个灰度图。这也就是第一行所做的事情。

    一旦我们得到光度值后,我们可以简单地添加一个颜色,来得到我们想要图像锁呈现的色调。这个颜色(_SepiaColor)是脚本传递给Shader的。我们还使用了一个lerp函数,其右边界值是_SepiaColor加上一个常量后所得到的一个更亮的颜色,并且以_RandomValue作为第三个参数,来模拟一个光度上的闪烁效果。

    通过上述代码,我们得到了第三层图层中的棕褐色调(暂时存储在finalColor)。

  6. 最后,我们把上述图层和颜色结合在一起,返回最终的画面颜色:
                    // Create a constant white color we can use to adjust opacity of effectsfixed3 constantWhite = fixed3(1, 1, 1);// Composite together the different layers to create final Screen EffectfinalColor = lerp(finalColor, finalColor * vignetteTex, _VignetteAmount);finalColor.rgb *= lerp(scratchesTex, constantWhite, _RandomValue);finalColor.rgb *= lerp(dustTex, constantWhite, (_RandomValue * _SinTime.z));finalColor = lerp(renderTex, finalColor, _EffectAmount);return finalColor;

    解释:最后,我们把每一个图层混合在一起完成最终的画面特效。这里,我们把所有图层乘起来,就像我们在Photoshop中将所有图层乘起来一样(当然那里是使用了混合模式)。每一个图层还使用了一个lerp函数以便我们可以调整透明度。

    其实这里没有非常清晰的解释,可以看出来上述lerp函数的参数很多同样使用了随机数来模拟一个闪烁弹动的效果。第一个lerp(对应vignetteTex)比较简单,我们可以通过在面板中调整Vignette Amount来调整晕影纹理的透明度。第二个lerp(对应scrachesTex)的右边界值是(1, 1, 1, 1),来模拟画面中划痕时隐时现的效果。第三个lerp(对应dustTex)的右边界同样使用了(1, 1, 1, 1),而且第三个参数还乘以了_SinTime,好吧,这里我也不知道为什么。。。最后一个lerp(对应renderTex)很好理解,此时的finalColor是所有图层相乘后得到的最终老电影效果,通过调整面板的Effect Amount可以控制画面特效的透明度。

完整的脚本和Shader如下:
OldFilmEffect脚本:
using UnityEngine;
using System.Collections;[ExecuteInEditMode]
public class OldFilmEffect : MonoBehaviour {#region Variablespublic Shader oldFilmShader;public float oldFilmEffectAmount = 1.0f;public Color sepiaColor = Color.white;public Texture2D vignetteTexture;public float vignetteAmount = 1.0f;public Texture2D scratchesTexture;public float scratchesXSpeed;public float scratchesYSpeed;public Texture2D dustTexture;public float dustXSpeed;public float dustYSpeed;private Material curMaterial;private float randomValue;#endregion#region Propertiespublic Material material {get {if (curMaterial == null) {curMaterial = new Material(oldFilmShader);curMaterial.hideFlags = HideFlags.HideAndDontSave;}return curMaterial;}}#endregion// Use this for initializationvoid Start () {if (SystemInfo.supportsImageEffects == false) {enabled = false;return;}if (oldFilmShader != null && oldFilmShader.isSupported == false) {enabled = false;}}void OnRenderImage (RenderTexture sourceTexture, RenderTexture destTexture){if (oldFilmShader != null) {material.SetColor("_SepiaColor", sepiaColor);material.SetFloat("_VignetteAmount", vignetteAmount);material.SetFloat("_EffectAmount", oldFilmEffectAmount);if (vignetteTexture) {material.SetTexture("_VignetteTex", vignetteTexture);}if (scratchesTexture) {material.SetTexture("_ScratchesTex", scratchesTexture);material.SetFloat("_ScratchesXSpeed", scratchesXSpeed);material.SetFloat("_ScratchesYSpeed", scratchesYSpeed);}if (dustTexture) {material.SetTexture("_DustTex", dustTexture);material.SetFloat("_DustXSpeed", dustXSpeed);material.SetFloat("_DustYSpeed", dustYSpeed);material.SetFloat("_RandomValue", randomValue);}Graphics.Blit(sourceTexture, destTexture, material);} else {Graphics.Blit(sourceTexture, destTexture);}}// Update is called once per framevoid Update () {vignetteAmount = Mathf.Clamp(vignetteAmount, 0.0f, 1.0f);oldFilmEffectAmount = Mathf.Clamp(oldFilmEffectAmount, 0.0f, 1.0f);randomValue = Random.Range(-1.0f, 1.0f);}void OnDisable () {if (curMaterial != null) {DestroyImmediate(curMaterial);}}
}

OldFilmEffectShader如下:

Shader "Custom/OldFilmEffectShader" {Properties {_MainTex ("Base (RGB)", 2D) = "white" {}_VignetteTex ("Vignette Texture", 2D) = "white" {}_VignetteAmount ("Vignette Opacity", Range(0, 1)) = 1_ScratchesTex ("Scraches Texture", 2D) = "white" {}_ScratchesXSpeed ("Scraches X Speed", Float) = 10.0_ScratchesYSpeed ("Scraches Y Speed", Float) = 10.0_DustTex ("Dust Texture", 2D) = "white" {}_DustXSpeed ("Dust X Speed", Float) = 10.0_DustYSpeed ("Dust Y Speed", Float) = 10.0_SepiaColor ("Sepia Color", Color) = (1, 1, 1, 1)_EffectAmount ("Old Film Effect Amount", Range(0, 1)) = 1_RandomValue ("Random Value", Float) = 1.0}SubShader {Pass {CGPROGRAM#pragma vertex vert_img#pragma fragment frag#include "UnityCG.cginc"uniform sampler2D _MainTex;uniform sampler2D _VignetteTex;uniform sampler2D _ScratchesTex;uniform sampler2D _DustTex;fixed4 _SepiaColor;fixed _VignetteAmount;fixed _ScratchesXSpeed;fixed _ScratchesYSpeed;fixed _DustXSpeed;fixed _DustYSpeed;fixed _EffectAmount;fixed _RandomValue;fixed4 frag (v2f_img i) : COLOR {half2 renderTexUV = half2(i.uv.x, i.uv.y + (_RandomValue * _SinTime.z * 0.005));fixed4 renderTex = tex2D(_MainTex, renderTexUV);// Get teh pixed from the Vignette Texturefixed4 vignetteTex = tex2D(_VignetteTex, i.uv);// Process the Scratches UV and pixelshalf2 scratchesUV = half2(i.uv.x + (_RandomValue * _SinTime.z * _ScratchesXSpeed), i.uv.y + (_Time.x * _ScratchesYSpeed));fixed4 scratchesTex = tex2D(_ScratchesTex, scratchesUV);// Process the Dust UV and pixelshalf2 dustUV = half2(i.uv.x + (_RandomValue * _SinTime.z * _DustXSpeed), i.uv.y + (_Time.x * _DustYSpeed));fixed4 dustTex = tex2D(_DustTex, dustUV);// Get the luminosity values from the render texture using the YIQ valuesfixed lum = dot(fixed3(0.299, 0.587, 0.114), renderTex.rgb);// Add the constant calor to the lum valuesfixed4 finalColor = lum + lerp(_SepiaColor, _SepiaColor + fixed4(0.1f, 0.1f, 0.1f, 0.1f), _RandomValue);// Create a constant white color we can use to adjust opacity of effectsfixed3 constantWhite = fixed3(1, 1, 1);// Composite together the different layers to create final Screen EffectfinalColor = lerp(finalColor, finalColor * vignetteTex, _VignetteAmount);finalColor.rgb *= lerp(scratchesTex, constantWhite, _RandomValue);finalColor.rgb *= lerp(dustTex, constantWhite, (_RandomValue * _SinTime.z));finalColor = lerp(renderTex, finalColor, _EffectAmount);return finalColor;}ENDCG}} FallBack "Diffuse"
}

保存后返回Unity。我们需要在面板中设置对应的图片和属性,像下面这样:

扩展链接

关于YIQ:
  • http://en.wikipedia.org/wiki/YIQ
  • http://www.blackice.com/colorspaceYIQ.htm
  • http://dcssrv1.oit.uci.edu/~wiedeman/cspace/me/infoyiq.html

转载于:https://www.cnblogs.com/xiaowangba/p/6314671.html

【Unity Shaders】游戏性和画面特效——创建一个老电影式的画面特效相关推荐

  1. unity3d 老电影式的屏幕特效

    做出这种古老效果,需要: 颜色发黄, 晕影效果, 灰尘与刮痕的效果. 建立一个c#脚本,将要放在摄像头中 先声明变量 oldFilmShader    所需shader(一会需要编写) OldFilm ...

  2. java 新建菜单选项_请完成下列Java程序:创建一个下拉式菜单,菜单项包括3个CheckboxM..._考试资料网...

    请完成下列Java程序:创建一个下拉式菜单,菜单项包括3个CheckboxMenultem(复选框),一条分割线和一个Exit项.要求打开或关闭复选框时,确定是哪个被切换,是开还是关,并输出它的状态: ...

  3. 创建一个TCP流式套接字

    #python网络套接字模块 from socket import *HOST = '172.60.50.218' PORT = 8888 ADDR = (HOST,PORT) BUFFERSIZE ...

  4. 【Unity Shaders】游戏性和画面特效——创建一个夜视效果的画面特效

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  5. Unity学习笔记第二章:如何创建一个2D游戏

    ps:发文章只是为了稳固自己的学习记录一下,如果有什么错误麻烦多指教 目前学习到了自己的第一个2D游戏,记录一下大概流程以及Sprite的操作 1:地形的创建设置以及Sprite的操作: 创建地形这块 ...

  6. 第 1 天|基于 AI 进行游戏开发:5 天创建一个农场游戏!

    欢迎使用 AI 进行游戏开发! 在本系列中,我们将使用各种 AI 工具,在 5 天内创建一个功能完备的农场游戏.到本系列结束时,你将了解到如何将多种 AI 工具整合到游戏开发流程中.本系列文章将向你展 ...

  7. HTML聊天框特效,利用jQuery实现响应式聊天窗口界面特效

    特效描述:利用jQuery实现 响应式 聊天窗口 界面特效.利用jQuery实现响应式聊天窗口界面特效 代码结构 1. 引入CSS 2. 引入JS 3. HTML代码 10 Conversations ...

  8. unity编辑器扩展_01(在工具栏中创建一个按钮)

    代码: [MenuItem("Tools/Test",false,1)]     static void Test()     {         Debug.Log(" ...

  9. (十二)nodejs循序渐进-高性能游戏服务器框架pomelo之创建一个游戏聊天服务器

    上个章节我们简单介绍了下pomelo的安装和目录结构,有读者可能觉得有点吃不消,为什么不再深入讲一讲目录结构和里边的库,这里我就不费口舌了,大家可以去官网参考文档说明,本文只告诉大家如何利用这个框架来 ...

最新文章

  1. 多线程——生产者与消费者(多)1.5新锁,问题解决
  2. 将mysql的变量置为0_MySQL 8.0 全局变量的修改持久化 set persist
  3. [每天一个知识点]26-软件工程-有多少管理是为了满足管理者的掌控感
  4. Android -- 自定义View小Demo,绘制四位数随机码(一)
  5. Springboot token令牌验证解决方案 在SpringBoot实现基于Token的用户身份验证
  6. IOI2008Island 基环树直径。
  7. Pocket Gems面经prepare: Diamond and Ruby
  8. 推荐x61使用nhc软件控制风扇
  9. SD卡、TF卡坏道及容量检测
  10. SwiftUI实战教程 第一章:前言
  11. f检验matlab计算,方差分析F检验的步骤和判定
  12. 图解Linux网络包接收过程
  13. 目标识别与跟踪算法matlab_极市直播| 朱政:基于孪生网络结构的SiamRPN系列目标跟踪算法...
  14. HTTP协议和web服务技术---Apche配置
  15. 「To B端增长黑客」 获客矩阵
  16. React基础-React中发送Ajax请求以及Mock数据
  17. 【C++学习笔记】复合类型和const限定符
  18. 电商平台如何实现财务分账?
  19. 浅谈自然辩证在现代科学领域的作用
  20. (数学实验)Matlab实现猜数小游戏(增加了错误输入的判断)

热门文章

  1. 全流程重构京东服务市场系统
  2. yate学习--yateclass.h--class YATE_API RefObject : public GenObject
  3. Android AutoSize屏幕适配中图标及字体放大的问题解决
  4. 微生物脂肪酸/长链烷烃单体C-13丰度测试服务重磅来袭
  5. html5清新文艺,清新文艺范的句子
  6. FileStream的读取和写入
  7. 怎么测试linux主机能否上网,Linux怎么测试网络带宽之speedtest
  8. 微信小程序全局组件注册使用
  9. Lopod 前端打印插件基础用法
  10. html实现12306的注册验证,12306的验证码才是真正的好设计,为何这样说呢?