美文网首页
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