美文网首页
Camera学习记录_2

Camera学习记录_2

作者: 梧叶已秋声 | 来源:发表于2019-10-13 12:45 被阅读0次

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

    Camera学习记录_1中,大致学习了camera的原理和简单应用。本篇的目的是Camera的旋转。旋转是Camera制作3D效果的核心。

    旋转

    旋转是Camera制作3D效果的核心,不过它制作出来的并不能算是真正的3D,而是伪3D,因为View是没有厚度的。

    // (API 12) 可以控制View同时绕x,y,z轴旋转,可以由下面几种方法复合而来。
    void rotate (float x, float y, float z);
    
    // 控制View绕单个坐标轴旋转
    void rotateX (float deg);
    void rotateY (float deg);
    void rotateZ (float deg);
    
    绕X轴旋转
    绕Y轴旋转
    绕Z轴旋转

    以上三张图分别为,绕x轴,y轴,z轴旋转的情况,至于为什么没有显示z轴,是因为z轴是垂直于手机屏幕的,在屏幕上的投影就是一个点。

    关于旋转,有以下几点需要注意:

    旋转中心

    旋转中心默认是坐标原点,对于图片来说就是左上角位置。

    我们都知道,在2D中,不论是旋转,错切还是缩放都是能够指定操作中心点位置的(可以用这个方法xxxRotate(angle, pivotX, pivotY)),但是在3D中却没有默认的方法,如果我们想要让图片围绕中心点旋转怎么办? 这就要使用到在Matrix原理提到过的方法:

    1. 先将坐标系原点移动到指定位置,使用平移 T
    2. 对坐标系进行旋转,使用旋转 S (围绕原点旋转)
    3. 再将坐标系平移回原来位置,使用平移 -T
    

    伪代码如下

    Matrix temp = new Matrix();     // 临时Matrix变量
    this.getMatrix(temp);           // 获取Matrix
    temp.preTranslate(-centerX, -centerY);  // 使用pre将旋转中心移动到和Camera位置相同。
    temp.postTranslate(centerX, centerY);   // 使用post将图片(View)移动到原来的位置
    

    这里讲了大致思路。详细部分看下面的例子。
    官方示例-Rotate3dAnimation

    public class Rotate3dAnimation extends Animation {
        private final float mFromDegrees;
        private final float mToDegrees;
        private final float mCenterX;
        private final float mCenterY;
        private final float mDepthZ;
        private final boolean mReverse;
        private Camera mCamera;
        /**
         * 创建一个绕y轴旋转的3D动画效果,旋转过程中具有深度调节,可以指定旋转中心。
         * 
         * @param fromDegrees   起始时角度
         * @param toDegrees     结束时角度
         * @param centerX       旋转中心x坐标
         * @param centerY       旋转中心y坐标
         * @param depthZ        最远到达的z轴坐标
         * @param reverse       true 表示由从0到depthZ,false相反
         */
        public Rotate3dAnimation(float fromDegrees, float toDegrees,
                float centerX, float centerY, float depthZ, boolean reverse) {
            mFromDegrees = fromDegrees;
            mToDegrees = toDegrees;
            mCenterX = centerX;
            mCenterY = centerY;
            mDepthZ = depthZ;
            mReverse = reverse;
        }
        @Override
        public void initialize(int width, int height, int parentWidth, int parentHeight) {
            super.initialize(width, height, parentWidth, parentHeight);
            mCamera = new Camera();
        }
        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            final float fromDegrees = mFromDegrees;
            float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);
            final float centerX = mCenterX;
            final float centerY = mCenterY;
            final Camera camera = mCamera;
            final Matrix matrix = t.getMatrix();
            camera.save();
          
            // 调节深度
            if (mReverse) {
                camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime);
            } else {
                camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime));
            }
          
            // 绕y轴旋转
            camera.rotateY(degrees);
          
            camera.getMatrix(matrix);
            camera.restore();
            
            // 调节中心点
            matrix.preTranslate(-centerX, -centerY);
            matrix.postTranslate(centerX, centerY);
        }
    }
    

    xml

    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical">
    
        <ImageView
            android:id="@+id/image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/test"/>
    </LinearLayout>
    

    MainActivity

    public class MainActivity extends AppCompatActivity{
    
         @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            ImageView view = (ImageView) findViewById(R.id.image);
            assert view != null;
            view.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // 计算中心点(这里是使用view的中心作为旋转的中心点)
                    final float centerX = v.getWidth() / 2.0f;
                    final float centerY = v.getHeight() / 2.0f;
    
                    final Rotate3dAnimation rotation = new Rotate3dAnimation( 0, 180, centerX, centerY, 0f, true);
    
                    rotation.setDuration(3000);                         //设置动画时长
                    rotation.setFillAfter(true);                        //保持旋转后效果
                    rotation.setInterpolator(new LinearInterpolator()); //设置插值器
                    v.startAnimation(rotation);
                }
            });
        }
    
    }
    

    但是官方的api demo旋转会失真。


    1.gif

    如何解决这一问题呢?
    解决问题关键Matrix的是MPERSP_0MPERSP_1

    image.png

    加了 <------- 的部分为修改部分。
    Rotate3dAnimation

    public class Rotate3dAnimation extends Animation {
        private final float mFromDegrees;
        private final float mToDegrees;
        private final float mCenterX;
        private final float mCenterY;
        private final float mDepthZ;
        private final boolean mReverse;
        private Camera mCamera;
        float scale = 1;    // <------- 像素密度
    
        /**
         * 创建一个绕y轴旋转的3D动画效果,旋转过程中具有深度调节,可以指定旋转中心。
         * @param context     <------- 添加上下文,为获取像素密度准备
         * @param fromDegrees   起始时角度
         * @param toDegrees     结束时角度
         * @param centerX       旋转中心x坐标
         * @param centerY       旋转中心y坐标
         * @param depthZ        最远到达的z轴坐标
         * @param reverse       true 表示由从0到depthZ,false相反
         */
        public Rotate3dAnimation(Context context, float fromDegrees, float toDegrees,
                                 float centerX, float centerY, float depthZ, boolean reverse) {
            mFromDegrees = fromDegrees;
            mToDegrees = toDegrees;
            mCenterX = centerX;
            mCenterY = centerY;
            mDepthZ = depthZ;
            mReverse = reverse;
    
            // <-------获取手机像素密度 (即dp与px的比例)
            scale = context.getResources().getDisplayMetrics().density;
        }
        @Override
        public void initialize(int width, int height, int parentWidth, int parentHeight) {
            super.initialize(width, height, parentWidth, parentHeight);
            mCamera = new Camera();
        }
        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            final float fromDegrees = mFromDegrees;
            float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);
            final float centerX = mCenterX;
            final float centerY = mCenterY;
            final Camera camera = mCamera;
            final Matrix matrix = t.getMatrix();
            camera.save();
    
            // 调节深度
            if (mReverse) {
                camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime);
            } else {
                camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime));
            }
    
            // 绕y轴旋转
            camera.rotateY(degrees);
    
            camera.getMatrix(matrix);
            camera.restore();
    
            // <-------修正失真,主要修改 MPERSP_0 和 MPERSP_1
            float[] mValues = new float[9];
            matrix.getValues(mValues);              //获取数值
            mValues[6] = mValues[6]/scale;          //数值修正
            mValues[7] = mValues[7]/scale;          //数值修正
            matrix.setValues(mValues);              //重新赋值
    
    
            // 调节中心点
            matrix.preTranslate(-centerX, -centerY);
            matrix.postTranslate(centerX, centerY);
        }
    }
    

    MainActivity

    public class MainActivity extends AppCompatActivity{
    
         @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            ImageView view = (ImageView) findViewById(R.id.image);
            assert view != null;
            view.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // 计算中心点(这里是使用view的中心作为旋转的中心点)
                    final float centerX = v.getWidth() / 2.0f;
                    final float centerY = v.getHeight() / 2.0f;
    
                    //<------- 括号内参数分别为(上下文,开始角度,结束角度,x轴中心点,y轴中心点,深度,是否扭曲)
                    final Rotate3dAnimation rotation = new Rotate3dAnimation(MainActivity.this, 0, 180, centerX, centerY, 0f, true);
    
    
                    rotation.setDuration(3000);                         //设置动画时长
                    rotation.setFillAfter(true);                        //保持旋转后效果
                    rotation.setInterpolator(new LinearInterpolator()); //设置插值器
                    v.startAnimation(rotation);
                }
            });
        }
    
    }
    

    这个例子中 通过 给Matrix 的MPERSP_0 和 MPERSP_1重新赋值来修正bug。
    MPERSP_0 = MPERSP_0 / density ,MPERSP_1 = MPERSP_1 / density 。具体原理未知。
    首先来看PERSP是什么。

    出处:Unity下Iso和Persp两种模式的区别
    Unity中有一种Persp模式,透视视野。在persp模式下,物体在scene界面上所呈现的画面是给人一种距离摄像头近的物体显示的大,距离摄像头远的物体显示的小。

    image.png

    没有找到具体Persp是什么的答案,只知道有一种模式叫Persp模式,这种模式是透视视野。这种模式跟android中的camera的视野类似。

    再来回顾一下 缩放因子density:density = dpi / 160,常见取值1.0,2.0,3.0。 dp与px换算公式 :px = dp * density。

    以下部分为个人猜测,说错了不负责。

    发生失真的原因应该是操作过程中单位不一致导致的。
    android的世界中单位是dp,而是图像和camera 的 单位是px。
    MPERSP_0 / density ,MPERSP_1 / density 是为了把px转成dp,从而使旋转过程中单位统一。

    相机位置
    我们可以使用translate和rotate来控制拍摄对象,也可以移动相机自身的位置,不过这些方法并不常用(看添加时间就知道啦)。

    void setLocation (float x, float y, float z); // (API 12) 设置相机位置,默认位置是(0, 0, -8)
    
    float getLocationX ();  // (API 16) 获取相机位置的x坐标,下同
    float getLocationY ();
    float getLocationZ ();
    

    我们知道近大远小,而物体之间的距离是相对的,让物体远离相机和让相机远离物体结果是一样的,实际上设置相机位置基本可以使用translate替代。

    虽然设置相机位置用处并不大,但还是要提几点注意事项:

    相机和View的z轴距离不能为0

    这个比较容易理解,当你把一个物体和相机放在同一个位置的时候,相机是拍摄不到这个物体的,正如你拿一张卡片放在手机侧面,摄像头是拍摄不到的。

    虚拟相机前后均可以拍摄

    当View不断接近摄像机并越过摄像机位置时,仍能看到View,并且View大小会随着距离摄像机的位置越来越远而逐渐变小,你可以理解为它有前置摄像头和后置摄像头。

    摄像机右移等于View左移

    View的状态只取决于View和摄像机之间的相对位置,不过由于单位不同,摄像机平移一个单位等于View平移72个像素。下面两段代码是等价的:

    Camera camera = new Camera();
    camera.setLocation(1,0,-8);     // 摄像机默认位置是(0, 0, -8)
    Matrix matrix = new Matrix();
    camera.getMatrix(matrix);
    Log.e(TAG, "location: "+matrix.toShortString() );
    
    Camera camera2 = new Camera();
    camera2.translate(-72,0,0);
    Matrix matrix2 = new Matrix();
    camera2.getMatrix(matrix2);
    Log.e(TAG, "translate: "+matrix2.toShortString() );
    

    结果:

    location: [1.0, 0.0, -72.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]
    translate: [1.0, 0.0, -72.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0
    

    要点

    View显示状态取决于View和摄像机之间的相对位置
    View和相机的Z轴距离不能为0

    参考链接:
    安卓自定义View进阶-Matrix Camera
    Unity下Iso和Persp两种模式的区别

    相关文章

      网友评论

          本文标题:Camera学习记录_2

          本文链接:https://www.haomeiwen.com/subject/bgshmctx.html