Camera学习记录_1

写在前面:
本文主要通过学习安卓自定义View进阶-Matrix Camera这篇文章去学习android.graphics.Camera,因此大部分文字出自这篇文章。

Camera
官方是这样描述的:

A camera instance can be used to compute 3D transformations and generate a matrix that can be applied, for instance, on a Canvas.

大致翻译如下:
camera 用于 计算 3D transformations 和 生成 matrix(matrix可应用于如Canvas等方面)。

先来看看transformations 。

这里的transformations 我觉得应该是这个。android.view.animation.Transformation
定义是:

Defines the transformation to be applied at one point in time of an Animation.

大致意思应该是transformation 用于某个时间点的动画吧。

其构造函数定义:

public Transformation ()
Creates a new transformation with alpha = 1 and the identity matrix.

大致意思应该是,应该是创建了一个 transformation ,具有2个属性:1.alpha = 1(alpha 的范围是0到1,0为不可见,1为完全可见)2.一个 identity matrix,即单位矩阵。


https://www.jianshu.com/p/dbfae6f27173

大致就是getMatrix () 和 getAlpha()函数,应该就是返回单位矩阵和透明度,还有就是相应的set方法这是这些数据。其他的也没有看出什么。

matrix

由于上篇已经分析过matrix了,这里不再详述。

下面是重点了

3D

什么是3d?
2d就是只有x和y轴的二维平面,3d就是存在x、y和z轴的三维空间。(个人理解)

android中2D 和 3D 坐标

那么三维空间在android中如何实现?毕竟手机屏幕是一个2D的平面啊。
首先来看看android官方对于现实世界的描述。
The physical world

In the physical world, objects can be stacked or attached to one another, but cannot pass through each other. They cast shadows and reflect light.

Material Design reflects these qualities in how surfaces are displayed and move across the Material UI. Surfaces, and how they move in three dimensions, are communicated in ways that resemble how they move in the physical world. This spatial model can also be applied consistently across apps.
机翻大致如下:
在物理世界中,对象可以堆叠或附着在一起,但不能互相穿过。他们投下 阴影 并反射。 材质设计反映了这些品质,即如何在材质UI中显示和移动表面。曲面及其在三维中的移动方式以类似于物理世界中移动方式的方式进行通信。此空间模型也可以在应用程序中一致地应用。

上面这段话的意思大致是,现实世界中,3D的原理是 阴影
那么 阴影 是如何在android的世界中实现的?手机屏幕是一个2D的平面,android中要如何构建 阴影以实现3d的显示?

Light and shadows
Material surfaces cast shadows when they obstruct light sources.
首先

  1. 存在光
  2. 有物质存在
  3. 当光照像物质的时候,阴影就会产生。

其实关于光与阴影我脑海里第一个冒出来的是树荫。



先来看看android中的光。

In the Material Design environment, virtual lights illuminate the UI. Key lights create sharper, directional shadows, called key shadows. Ambient light appears from all angles to create diffused, soft shadows, called ambient shadows.
机翻如下:
在“材料设计”环境中,虚拟灯照亮了UI。关键灯光会创建更锐利的方向性阴影,称为关键阴影。环境光从各个角度出现,以创建扩散的柔和阴影,称为环境阴影。

大致就是说,android中有虚拟灯,虚拟灯可以发出2种光:Key lights 和 ambient light ,从而产生 key shadows和 ambient shadows 。
key shadows 的特点是 sharper, directional 。
ambient shadows 的特点是 soft ,diffused 。
这里应该稍微有点类似太阳和月亮的光吧,虽然月亮本身不会发光。
虽然太阳和月亮照射人都会产生影子,但是产生的阴影差异还是很明显的。
应该类似于2种光:直射光与散射光。
官网给出的图片Shadow cast by a key light和Shadow cast by ambient light
,em。。其实我觉得差别不是很大,明显一点的就是一个阴影更深一点,一个阴影更浅一点。


image.png

image.png

Light sources
Shadows in the Material environment are cast by a key light and ambient light. In Android and iOS development, shadows occur when light sources are blocked by Material surfaces at various positions along the z-axis. On the web, shadows are depicted by manipulating the y-axis only. The following example shows a card with an elevation of 6dp.
机翻如下:
材质环境中的阴影由关键灯光和环境灯光投射。在Android和iOS开发中,当光源在沿z轴的各个位置处被“材质”表面阻挡时,会出现阴影。在网络上,仅通过操纵y轴即可描绘阴影。以下示例显示了海拔为6dp的卡片。

image.png

这3个都是Z轴高6dp的图片,但是推荐用第三种, key and ambient lights同时使用。

这里有一个问题我没有找到答案,android中的这个光如何产生的,虽然我一开始怀疑这个光是屏幕的背光跟本身现实世界的环境光,但是这2种光都是实际存在的而不是虚拟的,因此这一部分先略过。
前面知道了android中有2种光,会产生两种阴影,下面是关于Shadows 的解释。

Shadows provide cues about depth, direction of movement, and surface edges. A surface’s shadow is determined by its elevation and relationship to other surfaces.
机翻如下:
阴影提供有关深度,运动方向和表面边缘的提示。表面的阴影取决于其高程以及与其他表面的关系。

material.io真的是间接性可访问。。
这句话我的关注点是 shadow is determined by its elevation and relationship to other surfaces.
elevation and relationship to other surfaces决定了shadow 。
先来看elevation 。
首先来看elevation 的定义。

Elevation is the relative distance between two surfaces along the z-axis.
机翻:
高程是沿z轴的两个表面之间的相对距离。

大致应该是:Elevation 是沿z轴的 两个 平面 的相对距离。

图一为正面图,图二为侧面图,Z方向上,与顶点坐相比,elevation 分别为1dp和8dp的图像,这2个图像之间的elevation 为7dp

Depicting elevation
要想成功描绘 出 elevation ,有3点需要注意:

  1. edges
    图像的边缘必须要清楚得表现出来。
    例如下图,2个正方形,部分重叠,图一中没有清晰显示出2个图像的边框部分,图二清晰地显示出了边框部分。

    By default, Material surfaces use shadows to indicate edges. Other methods can be used to indicate edges, such as:
    Giving surfaces different colors
    Giving surfaces different opacities
    上图就是通过Giving surfaces different colors这个方法去显示边框的,但是通常是通过shadows 去实现的。
    Elevation is depicted by consistent use of shadow

    这4个图形,由于阴影不同,显示出了不同的边框。
  2. overlap
    overlap意思是部分重叠。

When a surface overlaps another surface, either partially or completely, it indicates that the two surfaces occupy different elevations (but not the degree, or amount, of difference between them). Surfaces at higher elevations appear in front of those at lower elevations, meaning they are positioned at different elevations along the z-axis. Surfaces may overlap one another by default, or become overlapped as a result of motion that changes their position in the UI.
When surfaces have different opacities or insufficient contrast from one another, it can make it difficult to tell which surface is in front of another. Avoid ambiguous overlap by ensuring surface edges are clearly defined.
机翻:
当一个表面与另一表面部分或完全重叠时,表示两个表面占据不同的高程(但不是两个高度之间的差异程度或数量)。高海拔的表面出现在低海拔的表面之前,这意味着它们沿z轴位于不同的海拔高度。默认情况下,表面可能会彼此重叠,或者由于移动会改变其在UI中的位置而导致表面重叠。 当表面具有不同的不透明度或彼此之间的对比度不足时,可能很难分辨出哪个表面在另一个表面的前面。通过确保明确定义表面边缘,避免模棱两可的重叠。

image.png

图① :Shadows show surface edges, surface overlap, and the degree of elevation.
图② :Different surface colors show surface edges and overlap, but not the degree of elevation.
图③ :Opacity shows surface edges and overlap, but not the degree of elevation.
图二和三直接把通过不同颜色和透明度显示elevation的缺点表现出来了,虽然能显示边缘和重叠,但是不能表现出高度。
一开始我觉得图二还是能显示出高度的,但是后来想想,或许设置了图片交叠部分某一图片不可见,这样就无法说明高度了。

  1. Distance
    The degree of elevation difference between surfaces can be expressed using scrimmed backgrounds, or using shadows.
    2种方式显示距离:1.添加背景色 2.使用阴影。
    其实我觉得这里应该是利用的人的空间想象力,因为现实世界中习惯了这种视图所以给人一种3D的错觉。


    添加背景色
使用阴影

看完Depicting elevation 我已经混乱了,描绘 出 elevation 需要用 shadows ,而shadows is determined by its elevation 。这两者之间的关系对我来说简直就像先有鸡还是先有蛋一样复杂。
后来我想啊,理解问题要结合实际,实际使用如何用到这个的呢?

出处:https://www.jianshu.com/p/363075694482
在Android API21,新添加了一个属性:android:elevation,用以在xml定义View的深度(高度),也即z方向的值。

最简单的使用就是在定义控件的时候定义这个属性。


    

    

    
    
    

https://www.jianshu.com/p/363075694482

先看ViewA、ViewB,因为ViewB是第二个子View,ViewA是第一个,所以B会覆盖在A的上面。
然后ViewC、D,跟AB相比较,区别就在于ViewC多了一个elevation属性,有了一个比ViewD更大的Z值,所以,即使它在ViewD的前面,但是依然能够盖住D~

上面阴影效果不明显,但是如果增加elevation的值效果会更加明显。
修改后xml如下。


    
    
    

运行结果

阴影此时较为明显。
个人总结:elevation 是个Z轴上的距离属性,通过定义控件的android:elevation值,可以显示不同效果的阴影。Elevation 是因,shadows 是果。

至此,理解 3D的的基础 阴影 与光 这一部分的分析 就结束了。
接下来回到本文的主题:Camera的学习。
在此之前,还有一个概念需要弄清楚。
三维变换与投影,这是计算机图形学部分的内容。

image.png

出处:
(计算机图形学)三维变换与投影
https://wenku.baidu.com/view/e2889780d4bbfd0a79563c1ec5da50e2524dd1c0.html?rec_flag=default&sxts=1570866624049

camera使用的是透视投影。

image.png
image.png

image.png

近大远小可以通过手机的相机去简单复现。
首先打开手机的相机,然后对着一个实际的物体(例如水杯)实际测试一下。当摄像头距离水杯较近的时候显示在屏幕上的图像较大,当摄像头与水杯的距离增大屏幕上的图像就会减小。

出处: 透视投影原理
在计算机三维图像中,投影可以看作是一种将三维坐标变换为二维坐标的方法,常用到的有正交投影和透视投影。正交投影多用于三维健模,透视投影则由于和人的视觉系统相似,多用于在二维平面中对三维世界的呈现。
基本的透视投影模型由视点E和视平面P两部分构成(要求 E不在平面P上)。视点可以认为是观察者的位置,也是观察三维世界的角度。视平面就是渲染三维对象透视图的二维平面。如图1所示。对于世界中的任一点X, 构造一条起点为E并经过X点的射线R,R与平面P的交点Xp即是X点的透视投影结果。三维世界的物体可以看作是由点集合 { Xi} 构成的,这样依次构造起点为E,并经过点Xi的射线Ri,这些射线与视平面P的交点集合便是三维世界在当前视点的透视图,如图2所示。

图1 透视投影的基本模型

图2 透视图成像原理

简单了解后,回到camera类上面。

出处:https://www.gcssloop.com/customview/matrix-3d-camera
在一个虚拟的3D的立体空间中,由于我们无法直接用眼睛去观察这一个空间,所以要借助摄像机采集信息,制成2D影像供我们观察。简单来说,摄像机就是我们观察虚拟3D空间的眼睛。

Camera基本方法
基本方法就有两个save 和restore,主要作用为保存当前状态和恢复到上一次保存的状态,通常成对使用,常用格式如下:

camera.save();      // 保存状态
...             // 具体操作
camera.retore();    // 回滚状态

常用方法
void getMatrix (Matrix matrix)
计算当前状态下矩阵对应的状态,并将计算后的矩阵赋值给参数matrix。

void applyToCanvas (Canvas canvas)
计算当前状态下单矩阵对应的状态,并将计算后的矩阵应用到指定的canvas上。

平移

声明:以下示例中 Matrix 的平移均使用 postTranslate 来演示,实际情况中使用set、pre 或 post 需要视情况而定。
void translate (float x, float y, float z)
和2D平移类似,只不过是多出来了一个维度,从只能在2D平面上平移到在3D空间内平移,不过,此处仍有几个要点需要重点对待。

沿x轴平移

camera.translate(x, 0, 0);

matrix.postTranslate(x, 0);

两者x轴同向,所以 Camera 和 Matrix 在沿x轴平移上是一致的。

沿y轴平移

Matrix用的是2D坐标系,而Camera用的是3D坐标系,因此y轴是相反的。

Camera camera = new Camera();
camera.translate(0, 100, 0);    // camera - 沿y轴正方向平移100像素

Matrix matrix = new Matrix();
camera.getMatrix(matrix);
matrix.postTranslate(0,100);    // matrix - 沿y轴正方向平移100像素

Log.i(TAG, "Matrix: "+matrix.toShortString());

结果是。Matrix: [1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]
因为2者抵消了。

沿z轴平移

当View和摄像机在同一条直线上时: 此时沿z轴平移相当于缩放的效果,缩放中心为摄像机所在(x, y)坐标,当View接近摄像机时,看起来会变大,远离摄像机时,看起来会变小,近大远小。
当View和摄像机不在同一条直线上时: 当View远离摄像机的时候,View在缩小的同时也在不断接近摄像机在屏幕投影位置(通常情况下为Z轴,在平面上表现为接近坐标原点)。相反,当View接近摄像机的时候,View在放大的同时会远离摄像机在屏幕投影位置。

近大远小前面有思考,那么问题是第二部分。
这一部分的理解,可以打开手机中的相机,当现实世界的杯子,不在相机的视角范围内的时候(即View和摄像机不在同一条直线上时),直线移动相机,拉开杯子与相机的距离,使杯子能显示在屏幕上。水杯大小虽然变小了,但是水杯能显示在屏幕上了,这样应该可以理解当View远离摄像机的时候,View在缩小的同时也在不断接近摄像机在屏幕投影位置这句话了吧。

这里通过一个简单的demo去理解。
自定义View。

public class MyView extends View {
    private Matrix mMatrix;
    private Camera camera ;
    public MyView(Context context) {
        super(context);
    }

    public MyView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Paint paint = new Paint();
        mMatrix = new Matrix();
        camera = new Camera();
        camera.translate(0,0,10);
        camera.getMatrix(mMatrix);
        Log.d("Main" ,   "mMatrix = " + mMatrix.toString());
        canvas.drawBitmap( BitmapFactory.decodeResource(getResources(), R.drawable.test), mMatrix,paint);
    }
}

xml


   

MainActivity

public class MainActivity extends AppCompatActivity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

    }

}

原图如下。


test.png

当改变Z轴平移参数的时候,图像会发生改变。近大远小,100是远离(因为Z轴向里)。translate 100和translate 10相比,明显translate 10人物放大了,而且图像上少了些光影。

Z轴translate 100
Z轴translate 10

如果定义成一个连续的在Z轴上变换的话可以改成这样。

public class MyView extends View {
    private Matrix mMatrix;
    private Camera mCamera ;
    private float[] mFloats = {-100,-10,0,10,100};
    private int mIndex;
    private Paint mPaint;
    private boolean isDraw = true;

    public MyView(Context context) {
        super(context);
    }

    public MyView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint();

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mMatrix = new Matrix();
        mCamera = new Camera();
        mCamera.translate(0,0,mFloats[mIndex]);
        mIndex++;
        if (mIndex == 5){
            isDraw = false;
        }
        mCamera.getMatrix(mMatrix);
        Log.d("Main" ,   "mMatrix = " + mMatrix.toString());
        canvas.drawBitmap( BitmapFactory.decodeResource(getResources(), R.drawable.test), mMatrix,mPaint);

    }

    public void startThread(){
        new Thread(thread).start();
    }

    private final Runnable thread = new Runnable() {
        @Override
        public void run() {
            while (isDraw)  {
                postInvalidate();
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();

                }

            }
        }


    };
}
public class MainActivity extends AppCompatActivity{
    private MyView mMyView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mMyView = (MyView) findViewById(R.id.my_view);
        mMyView.startThread();
    }

}

Log如下所示。

mMatrix = Matrix{[1.2100841, 0.0, 0.0][0.0, 1.2100841, 0.0][0.0, 0.0, 1.0]}
mMatrix = Matrix{[1.0176679, 0.0, 0.0][0.0, 1.0176679, 0.0][0.0, 0.0, 1.0]}
mMatrix = Matrix{[1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]}
mMatrix = Matrix{[0.98293513, 0.0, 0.0][0.0, 0.98293513, 0.0][0.0, 0.0, 1.0]}
mMatrix = Matrix{[0.852071, 0.0, 0.0][0.0, 0.852071, 0.0][0.0, 0.0, 1.0]}

比较Matrix的值后发现,改变的只有MSCALE_X和MSCALE_Y。
也就是说,实际上这里是通过对X和Y进行缩放操作去实现Z轴的操作的。因为毕竟是伪3D,不能操纵实际不存在的东西。

那么这个值是怎么计算出来的呢?具体计算涉及到数学部分暂时略过,这里思考一下大致原理。
首先要知道在android的世界中,Camera这个虚拟相机的实际位置。

出处:https://www.gcssloop.com/customview/matrix-3d-camera
Android 上面观察View的摄像机默认位置在屏幕左上角,,而且是距屏幕有一段距离的,假设灰色部分是手机屏幕,白色是上面的一个View,摄像机位置看起来大致就是下面这样子的(为了更好的展示摄像机的位置,做了一个空间转换效果的动图)。


摄像机的位置默认是 (0, 0, -576)。其中 -576= -8 x 72,虽然官方文档说距离屏幕的距离是 -8, 但经过测试实际距离是 -576 像素,当距离为 -10 的时候,实际距离为 -720 像素。我使用了3款手机测试,屏幕大小和像素密度均不同,但结果都是一样的。
这个魔数可以在 Android 底层的图像引擎 Skia 中找到。在 Skia 中,Camera 的位置单位是英寸,英寸和像素的换算单位在 Skia 中被固定为 72 像素,而 Android 中把这个换算单位照搬了过来。

-8 这个数据应该是根据setLocation()这个函数的解释得来的。

Sets the location of the camera. The default location is set at 0, 0, -8.

但是这里没有camera的单位。
skia是个2D向量图形处理函数库,包含字型、坐标转换,以及点阵图都有高效能且简洁的表现。
https://github.com/google/skia

-576= -8 x 72 这个换算我没有找到理论依据,但是可信度应该还是比较高的。图形的基本单位在计算机的世界中的确是px,在物理世界中是英寸。
在知道图像位置的情况下,当图像在Z轴上移动时,通过Camera类,可以根据移动的距离去计算出Matrix,这个Matrix就是绘制View所需的数据。

参考链接:
安卓自定义View进阶-Matrix Camera

Android Elevation
https://www.jianshu.com/p/363075694482
https://www.material.io/design/environment/surfaces.html#
彻底理解 Android 中的阴影
https://www.jianshu.com/p/087d4496f72c

你可能感兴趣的:(Camera学习记录_1)