由Unity摄像机截图颜色偏暗发现的

      该博客只为记录点滴所学,如有错误,还请读者指正,谢谢!

       在一次用Unity做VR项目的时候,里面有一个功能,就是模拟照相机照相,然后把照片显示出来。这里照相的功能用的就是摄像机的截屏,大概代码如下:

using UnityEngine;
using System.Collections;

public class ScreenShot : MonoBehaviour {

    public Camera cam;
	
	// Update is called once per frame
	void Update ()
    {
	    if(Input.GetKeyDown(KeyCode.T))
        {
            StartCoroutine(CaptureScreenshot2());
        }
	}

    private IEnumerator CaptureScreenshot2()
    {
        yield return new WaitForEndOfFrame();

        RenderTexture rt = cam.targetTexture;
        
        // 激活这个rt, 并从中中读取像素。  
        RenderTexture.active = rt;

        Texture2D screenShot = new Texture2D(rt.width,rt.height, TextureFormat.RGB24, false);
        screenShot.ReadPixels(new Rect(0,0,rt.width,rt.height), 0, 0);// 注:这个时候,它是从RenderTexture.active中读取像素  
        screenShot.Apply();
        
        RenderTexture.active = null; // JC: added to avoid errors  

        
        // 最后将这些纹理数据,成一个png图片文件  
        byte[] bytes = screenShot.EncodeToPNG();
        string filename = Application.dataPath + "/Screenshot.png";
        System.IO.File.WriteAllBytes(filename, bytes);
        Debug.Log(string.Format("截屏了一张照片: {0}", filename));  
    }  
}

然后发现得到的图片与期望的不一致,下面两幅图片,上面一幅是摄像机实时渲染在屏幕上的效果,下面一幅是截图的表现效果:



发现我们截屏得到的这张图比预期的暗了不少。经过多方面的查找,终于找到原因。

这是因为一个叫Gamma补偿的技术造成的,这里不去详细讨论Gamma的由来,因为我自己也还没很清楚,大致说一下自己的理解。

由于人眼对光的亮度感知不是线性的,而是曲线形的,就是说亮度增加1个单位,人眼的感受并不一定增加1个单位。人眼对暗一点的亮度范围感知更灵敏,比如亮度为0,就是黑色的,亮度为10,就是灰色偏暗的,我们能很容易地区分出亮度为0和亮度10这两个点。但是当亮度为256和246时,我们就不会觉得有这么明显了。虽然可能也能感觉到这是两个不同的亮度,但是不觉得有10个单位这么多。人眼的感知曲线大致如下图:


又由于我们计算机保存像素信息,一般是用8bit来保存。那么如何用这么有限的资源去更好的保存亮度信息,即用多一点的空间保存0到0.5区间的亮度,剩余少一点的空间保存0.5到1区间的亮度?这里就用到Gamma了。

我们可以用pow(color,gamma) (gamma一般取值2.2)来存储这些亮度信息。所以Unity里,128点对应的是中间灰度,但是它却是白色像素的反射率的0.21,而不是0.5。

在Unity里,对于图片有一个设置,如下图:


它是开启或关闭是否要做Gamma补偿的。如果勾选上的话,那它用的就是图片的原像素值,而不作任何补偿;如果不勾选的话,那就是需要做Gamma补偿。它的默认值是不勾选的。由于我们在摄像机屏幕截图并保存,然后我们由用它作为某个模型的贴图或者用UI的Image显示的时候,是不需要做Gamma补偿的。但是由于这个选项的默认开启,它做了一次Gamma补偿,其实就相当于多余计算了一遍pow(color,2.2)。这个时候我们就要把它变回来。

抵消Gamma补偿的方法有:如果能设置Bypass sRGB Sampling这个选项为true的话,尽量将其设为true;如果在运行时,不太可能设置这个值的话,就通过shader来计算一下最后的输出。记住,能设置这个选项的话,最好设置这个选项,不行的话才去用shader计算。因为这个Gamma值有可能不是2.2,而是1.8到2.5区间的某个值,所以你敲不定它。shader方式如下,在片段函数里一句代码就能实现:

fixed4 col = tex2D(_MainTex, i.uv);
fixed3 reCol = pow(col.rgb, 1.0 / 2.2);
return fixed4(reCol,col.a);

当然,如果你发现2.2这个值好像没有那么准确的时候,你可以改一下这个值,在1.8至2.5区间内。

当然,出现摄像机截图比预期的要暗这个问题,是在Unity的Player Settings里Color Space设置为Linear Space才会出现的。因为VR需要追求很好的画质,而Linear Space是比较正确的颜色空间。但是出于性能考虑,在手机端用不了Linear Space,所以Unity现在还保留着Gamma Space这个选项。

思绪有点混乱,但是知道了大概了。其实这里还有两个地方没有去说,一个是sRGB是什么东西,还有一个就是gamma其实是有两个的,一个叫Encoding Gamma,它是发生在将原始图片信息编码到存储介质中的,我们上面讨论的也是这个gamma;另一个叫Display Gamma,它是发生在显示器显示这个图片过程中的。详细信息的话,读者可以去百度。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值