【Unity Shaders】游戏性和画面特效——创建一个老电影式的画面特效
本系列主要参考《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里正式实现我们的画面特效!
- 准备好一张晕影(Vignette)纹理,一张灰层纹理,一张划痕纹理,你可以在本书资源(见文章最上方)里找到。
- 创建一个新的脚本,命名为OldFilmEffect.cs。创建一个新的Shader,命名为OldFilmEffectShader.shader。
- 使用前一章第一篇里的代码填充上述新的脚本和Shader。
- 把OldFilmEffect脚本添加到Camera上,并使用OldFilmEffectShader给OldFilmEffect脚本中的Cur Shader赋值。
实现
- 第一步我们要定义一些需要在面板中显示的变量,以便让用户进行调整。我们可以利用之前制作原型所用的Photoshop作为参考,来决定我们需要显示哪些变量。在脚本中添加如下代码:
#region Variables
public 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 - 然后,我们需要填充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);
}
} - 最后,我们需要在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部分。
- 首先,我们需要创建对应的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
} - 和往常一样,我们需要在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; - 现在,我们来填充最关键的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 Texture
fixed4 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)。
- 然后,我们需要添加对灰尘(dust)和划痕(scratches)的处理。添加如下代码:
// Process the Scratches UV and pixels
half2 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 pixels
half2 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。
- 然后,处理棕褐色调(Sepia Tone):
// Get the luminosity values from the render texture using the YIQ values
fixed lum = dot(fixed3(0.299, 0.587, 0.114), renderTex.rgb); // Add the constant calor to the lum values
fixed4 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)。
- 最后,我们把上述图层和颜色结合在一起,返回最终的画面颜色:
// Create a constant white color we can use to adjust opacity of effects
fixed3 constantWhite = fixed3(1, 1, 1); // Composite together the different layers to create final Screen Effect
finalColor = 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可以控制画面特效的透明度。
using UnityEngine;
using System.Collections; [ExecuteInEditMode]
public class OldFilmEffect : MonoBehaviour { #region Variables
public 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 Properties
public Material material {
get {
if (curMaterial == null) {
curMaterial = new Material(oldFilmShader);
curMaterial.hideFlags = HideFlags.HideAndDontSave;
}
return curMaterial;
}
}
#endregion // Use this for initialization
void 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 frame
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);
} 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 Texture
fixed4 vignetteTex = tex2D(_VignetteTex, i.uv); // Process the Scratches UV and pixels
half2 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 pixels
half2 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 values
fixed lum = dot(fixed3(0.299, 0.587, 0.114), renderTex.rgb); // Add the constant calor to the lum values
fixed4 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 effects
fixed3 constantWhite = fixed3(1, 1, 1); // Composite together the different layers to create final Screen Effect
finalColor = 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 Shaders】游戏性和画面特效——创建一个老电影式的画面特效的更多相关文章
- iOS中如何创建一个滑出式导航面板(1)
本文将介绍如何创建类似Facebook和Path iOS程序中的滑出式导航面板. 向右滑动 滑出式设计模式可以让开发者在程序中添加常用的导航功能,又不会浪费屏幕上宝贵的空间.用户可以在任意时间滑出导航 ...
- 【Unity Shaders】游戏性和画面特效——创建一个夜视效果的画面特效
本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...
- 【Unity Shaders】Transparency —— 使用alpha通道创建透明效果
本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...
- 【pygame游戏编程】第一篇-----创建一个窗口
下面我们一起来创建一个背景为蓝色的窗口作为游戏编程的开始: import sys import pygame def creat_screen(): #初始化pygame pygame.init() ...
- unity编辑器扩展_01(在工具栏中创建一个按钮)
代码: [MenuItem("Tools/Test",false,1)] static void Test() { Debug.Log("tes ...
- 【Unity Shaders】使用Unity Render Textures实现画面特效——建立画面特效脚本系统
本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...
- 【Unity Shaders】Lighting Models —— 光照模型之Lit Sphere
本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...
- 【Unity Shaders】法线纹理(Normal Mapping)的实现细节
写在前面 写这篇的目的是为了总结我长期以来的混乱.虽然题目是"法线纹理的实现细节",但其实我想讲的是如何在shader中编程正确使用法线进行光照计算.这里面最让人头大的就是各种矩阵 ...
- 【Unity Shaders】Transparency —— 使用渲染队列进行深度排序
本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...
随机推荐
- Python中内置函数的介绍
内置函数的功能介绍 常用内置函数如下: 1.abs() 绝对值 格式:abs(x) 例如:print(abs(-18)) >>> 18 返回值:number #该函数主要用于数值类的 ...
- 阿里技术一面,Java研发岗
之前过了个简单的简历面,过了几天后没打来以为凉了,然后昨晚又接到了电话,括号内容是回答说的,理解有限,不一定都对,欢迎纠正-加油每一个牛友们! 阿里一面: 1.学过哪些技术知识呢? 2.说说接口和抽象 ...
- SynchronizedMap和ConcurrentHashMap的深入分析
http://blog.sina.com.cn/s/blog_5157093c0100hm3y.html java5中新增了ConcurrentMap接口和它的一个实现类ConcurrentHashM ...
- web项目部署到阿里云服务器步骤
http://www.cnblogs.com/qq3111901846/p/6178855.html http://blog.csdn.net/liona_koukou/article/details ...
- python学习之路前端-HTML
HTML概述 HTML是英文Hyper Text Mark-up Language(超文本标记语言)的缩写,他是一种制作万维网页面标准语言(标记).相当于定义统一的一套规则,大家都来遵守他,这样就可以 ...
- MySQL NOW() 函数
定义和用法 NOW() 返回当前的日期和时间. 语法 NOW() 实例 下面是 SELECT 语句: SELECT NOW(),CURDATE(),CURTIME() 结果如下所示: NOW() CU ...
- Docker常见仓库Ubuntu
Ubuntu 基本信息 Ubuntu 是流行的 Linux 发行版,其自带软件版本往往较新一些. 该仓库提供了 Ubuntu从12.04 ~ 14.10 各个版本的镜像. 使用方法 默认会启动一个最小 ...
- 详解BLE 空中包格式—兼BLE Link layer协议解析
BLE有几种空中包格式?常见的PDU命令有哪些?PDU和MTU的区别是什么?DLE又是什么?BLE怎么实现重传的?BLE ACK机制原理是什么?希望这篇文章能帮你回答以上问题. 虽然BLE空中包(pa ...
- linux下内存的统计和内存泄露类问题的定位
在产品的开发中,通过对当前系统消耗内存总量的统计,可以对产品所需内存总量进行精确的评估,从而选择合适的内存芯片与大小,降低产品的成本.在遇到内存泄露类问题时,经常会对此束手无策,本文通过对proc下进 ...
- Android程序员必须掌握的知识点-多进程和多线程
当某个应用组件启动且该应用没有运行其他任何组件时,Android 系统会使用单个执行线程为应用启动新的 Linux 进程.默认情况下,同一应用的所有组件在相同的进程和线程(称为"主" ...