LearnOpenGL之摄像机

news2024/11/26 10:47:48

   ================================  前序===============================

         AndroidLearnOpenGL是本博主自己实现的LearnOpenGL练习集合:

        Github地址:https://github.com/wangyongyao1989/AndroidLearnOpenGL  

        系列文章:

        1、LearnOpenGL之入门基础

        2、LearnOpenGL之3D显示

        3、LearnOpenGL之摄像机

        4、LearnOpenGL之光照 

        ============================== 显示效果 ===============================               

                        

        ===================================================================

         OpenGL本身没有摄像机(Camera)的概念,但我们可以通过把场景中的所有物体往相反方向移动的方式来模拟出摄像机,产生一种我们在移动的感觉,而不是场景在移动。

     

一、摄像机/观察空间:

        当我们讨论摄像机/观察空间(Camera/View Space)的时候,是在讨论以摄像机的视角作为场景原点时场景中所有的顶点坐标:观察矩阵把所有的世界坐标变换为相对于摄像机位置与方向的观察坐标。要定义一个摄像机,我们需要它在世界空间中的位置、观察的方向、一个指向它右侧的向量以及一个指向它上方的向量。细心的读者可能已经注意到我们实际上创建了一个三个单位轴相互垂直的、以摄像机的位置为原点的坐标系。

       

        1、摄像机位置:

                获取摄像机位置很简单。摄像机位置简单来说就是世界空间中一个指向摄像机位置的向量。

    glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);

        2、摄像机方向:

        摄像机的方向指的是摄像机指向哪个方向,如果将两个矢量相减,我们就能得到这两个矢量的差,用场景原点向量减去摄像机位置向量的结果就是摄像机的指向向量。由于我们知道摄像机指向z轴负方向,但我们希望方向向量(Direction Vector)指向摄像机的z轴正方向。如果我们交换相减的顺序,我们就会获得一个指向摄像机正z轴方向的向量:

    glm::vec3 cameraTarget = glm::vec3(0.0f, 0.0f, 0.0f);
    glm::vec3 cameraDirection = glm::normalize(cameraPos - cameraTarget);

        3、右轴:

        我们需要的另一个向量是一个右向量(Right Vector),它代表摄像机空间的x轴的正方向。为获取右向量我们需要先使用一个小技巧:先定义一个上向量(Up Vector)。接下来把上向量和第二步得到的方向向量进行叉乘。两个向量叉乘的结果会同时垂直于两向量,因此我们会得到指向x轴正方向的那个向量(如果我们交换两个向量叉乘的顺序就会得到相反的指向x轴负方向的向量):

    glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f); 
    glm::vec3 cameraRight = glm::normalize(glm::cross(up, cameraDirection));

        4、上轴:

        现在我们已经有了x轴向量和z轴向量,获取一个指向摄像机的正y轴向量就相对简单了:我们把右向量和方向向量进行叉乘:

    glm::vec3 cameraUp = glm::cross(cameraDirection, cameraRight);

        5、Look At:

        使用矩阵的好处之一是如果你使用3个相互垂直(或非线性)的轴定义了一个坐标空间,你可以用这3个轴外加一个平移向量来创建一个矩阵,并且你可以用这个矩阵乘以任何向量来将其变换到那个坐标空间。这正是LookAt矩阵所做的,现在我们有了3个相互垂直的轴和一个定义摄像机空间的位置坐标,我们可以创建我们自己的LookAt矩阵了:

        

        其中R是右向量,U是上向量,D是方向向量P是摄像机位置向量。注意,位置向量是相反的,因为我们最终希望把世界平移到与我们自身移动的相反方向。把这个LookAt矩阵作为观察矩阵可以很高效地把所有世界坐标变换到刚刚定义的观察空间。LookAt矩阵就像它的名字表达的那样:它会创建一个看着(Look at)给定目标的观察矩阵。glm::LookAt函数需要一个位置、目标和上向量。

    glm::mat4 view;
    view = glm::lookAt(glm::vec3(0.0f, 0.0f, 3.0f), 
           glm::vec3(0.0f, 0.0f, 0.0f), 
           glm::vec3(0.0f, 1.0f, 0.0f));

          

二、摄像机移位:

       1、摄像机绕场景转:

       创建一个x和z坐标,它会代表圆上的一点,我们将会使用它作为摄像机的位置。通过重新计算x和y坐标,我们会遍历圆上的所有点,这样摄像机就会绕着场景旋转了。我们预先定义这个圆的半径radius,在每次渲染迭代中使用clock()函数重新创建观察矩阵,来扩大这个圆。  

    // create transformations
    glm::mat4 view = glm::mat4(1.0f);           //观察矩阵(View Matrix)
    glm::mat4 projection = glm::mat4(1.0f);     //投影矩阵(Projection Matrix)
    float radius = 10.0f;
    timeValue = clock() * 10 / CLOCKS_PER_SEC;
    float camX = static_cast<float>(sin(timeValue) * radius);
    float camZ = static_cast<float>(cos(timeValue) * radius);

    //观察矩阵(View Matrix)平移,glm::LookAt函数需要一个位置、目标和上向量。
    view = glm::lookAt(glm::vec3(camX, 0.0f, camZ), glm::vec3(0.0f, 0.0f, 0.0f),
                       glm::vec3(0.0f, 1.0f, 0.0f));
    projection = glm::perspective(glm::radians(45.0f)
                , (float) screenW / (float) screenH, 0.1f,100.0f);
    // pass them to the shaders (3 different ways)
    setMat4("projection", projection);
    setMat4("view", view);

        2、自动移动:

        将摄像机位置设置为之前定义的cameraPos。方向是当前的位置加上我们刚刚定义的方向向量。这样能保证无论我们怎么移动,摄像机都会注视着目标方向。

    view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);

        3、移动速度:

        图形程序和游戏通常会跟踪一个时间差(Deltatime)变量,它储存了渲染上一帧所用的时间。我们把所有速度都去乘以deltaTime值。结果就是,如果我们的deltaTime很大,就意味着上一帧的渲染花费了更多时间,所以这一帧的速度需要变得更高来平衡渲染所花去的时间。使用这种方法时,无论你的电脑快还是慢,摄像机的速度都会相应平衡,这样每个用户的体验就都一样了。

    float deltaTime = 0.0f; // 当前帧与上一帧的时间差
    float lastFrame = 0.0f; // 上一帧的时间

    float currentFrame = glfwGetTime();
    deltaTime = currentFrame - lastFrame;
    lastFrame = currentFrame;

         4、欧拉角:

        欧拉角(Euler Angle)是可以表示3D空间中任何旋转的3个值,由莱昂哈德·欧拉(Leonhard Euler)在18世纪提出。一共有3种欧拉角:俯仰角(Pitch)、偏航角(Yaw)和滚转角(Roll),下面的图片展示了它们的含义:

        

         俯仰角是描述我们如何往上或往下看的角,可以在第一张图中看到。第二张图展示了偏航角,偏航角表示我们往左和往右看的程度。滚转角代表我们如何翻滚摄像机,通常在太空飞船的摄像机中使用。每个欧拉角都有一个值来表示,把三个角结合起来我们就能够计算3D空间中任何的旋转向量了。

        对于我们的摄像机系统来说,我们只关心俯仰角和偏航角,所以我们不会讨论滚转角。给定一个俯仰角和偏航角,我们可以把它们转换为一个代表新的方向向量的3D向量。

        

         在xz平面上,看向y轴,我们可以基于第一个三角形计算来计算它的长度/y方向的强度(Strength)(我们往上或往下看多少)。从图中我们可以看到对于一个给定俯仰角的y值等于sinθ: 

    direction.y = sin(glm::radians(pitch)); // 注意我们先把角度转为弧度

         只更新了y值,仔细观察x和z分量也被影响了。从三角形中我们可以看到它们的值等于:

    direction.x = cos(glm::radians(pitch));
    direction.z = cos(glm::radians(pitch));

         偏航角找到需要的分量:

        

         就像俯仰角的三角形一样,我们可以看到x分量取决于cos(yaw)的值,z值同样取决于偏航角的正弦值。把这个加到前面的值中,会得到基于俯仰角和偏航角的方向向量:   

       // 译注:direction代表摄像机的前轴(Front),
       //这个前轴是和本文第一幅图片的第二个摄像机的方向向量是相反的    
    direction.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw));
    direction.y = sin(glm::radians(pitch));
    direction.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw));

         

三、摄像机类:

        Camer3D类提供构造函数中提供默认值,初始化时提供摄像机方向向量、右向量、上向量。结合Look At给GetViewMatrix()外供函数给外调用。

        1、Camera3D.h代码:

#ifndef ANDROIDLEARNOPENGL_CAMERA3D_H
#define ANDROIDLEARNOPENGL_CAMERA3D_H

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

enum Camera_Movement {
    FORWARD,
    BACKWARD,
    LEFT,
    RIGHT
};

// Default camera values
const float YAW = -90.0f;
const float PITCH = 0.0f;
const float SPEED = 1.5f;
const float SENSITIVITY = 0.1f;
const float ZOOM = 45.0f;

using namespace glm;

class Camera3D {

private:
    // camera Attributes
    vec3 Position;
    vec3 Front;
    vec3 Up;
    vec3 Right;
    vec3 WorldUp;
    // euler Angles
    float Yaw;
    float Pitch;
    // camera options
    float MovementSpeed;
    float Sensitivity;

    // calculates the front vector from the Camera's (updated) Euler Angles
    void updateCameraVectors();

public:
    float Zoom;

    // constructor with vectors
    Camera3D(
            vec3 position = vec3(0.0f, 0.0f, 10.0f),
            vec3 up = vec3(0.0f, 1.0f, 0.0f),
            float yaw = YAW,
            float pitch = PITCH);

    // constructor with scalar values
    Camera3D(float posX, float posY, float posZ
            , float upX, float upY, float upZ
            , float yaw, float pitch);

    mat4 GetViewMatrix();

    void ProcessKeyboard(Camera_Movement direction, float deltaTime);

    void ProcessXYMovement(float xoffset, float yoffset, bool constrainPitch = true);

    void ProcessScroll(float yoffset);


};

#endif //ANDROIDLEARNOPENGL_CAMERA3D_H

        2、Camera3D.cpp代码:

#include "Camera3D.h"

Camera3D::Camera3D(vec3 position, vec3 up, float yaw, float pitch) :
        Front(vec3(0.0f, 0.0f, -1.0f))
        , MovementSpeed(SPEED)
        , Sensitivity(SENSITIVITY),
        Zoom(ZOOM) {
    Position = position;
    WorldUp = up;
    Yaw = yaw;
    Pitch = pitch;
    updateCameraVectors();
}

Camera3D::Camera3D(float posX, float posY, float posZ
                    , float upX, float upY, float upZ, float yaw
                    , float pitch) 
                    : Front(glm::vec3(0.0f, 0.0f, -1.0f))
                    , MovementSpeed(SPEED)
                    , Sensitivity(SENSITIVITY), Zoom(ZOOM) {
    Position = glm::vec3(posX, posY, posZ);
    WorldUp = glm::vec3(upX, upY, upZ);
    Yaw = yaw;
    Pitch = pitch;
    updateCameraVectors();
}

mat4 Camera3D::GetViewMatrix() {

    return lookAt(Position, Position + Front, Up);
}

void Camera3D::ProcessKeyboard(Camera_Movement direction, float deltaTime) {
    float velocity = MovementSpeed * deltaTime;
    if (direction == FORWARD)
        Position += Front * velocity;
    if (direction == BACKWARD)
        Position -= Front * velocity;
    if (direction == LEFT)
        Position -= Right * velocity;
    if (direction == RIGHT)
        Position += Right * velocity;
}

void Camera3D::ProcessXYMovement(float xoffset, float yoffset, bool constrainPitch) {
    xoffset *= Sensitivity;
    yoffset *= Sensitivity;

    Yaw += xoffset;
    Pitch += yoffset;

    // make sure that when pitch is out of bounds, screen doesn't get flipped
    if (constrainPitch) {
        if (Pitch > 89.0f)
            Pitch = 89.0f;
        if (Pitch < -89.0f)
            Pitch = -89.0f;
    }
    Zoom = 45.0f;
    // update Front, Right and Up Vectors using the updated Euler angles
    updateCameraVectors();
}

void Camera3D::ProcessScroll(float yoffset) {
    Zoom -= (float) yoffset;
    if (Zoom < 25.0f)
        Zoom = 25.0f;
    if (Zoom > 100.0f)
        Zoom = 100.0f;
}

void Camera3D::updateCameraVectors() {
    // calculate the new Front vector
    glm::vec3 front;
    front.x = cos(glm::radians(Yaw)) * cos(glm::radians(Pitch));
    front.y = sin(glm::radians(Pitch));
    front.z = sin(glm::radians(Yaw)) * cos(glm::radians(Pitch));
    Front = glm::normalize(front);
    // also re-calculate the Right and Up vector
    // normalize the vectors, because their length gets closer to 0 the more you look up
    // or down which results in slower movement.
    Right = glm::normalize(glm::cross(Front, WorldUp));
    Up = glm::normalize(glm::cross(Right, Front));
}

 四、代码实现:

           在上一节LearnOpenGL之3D显示的代码框架下加入摄像机类,来实现“从Java层传入view的手势事件实现上下左右拖拽及放大缩小的效果。”

        1、首先在Android层的View中获取上下左右滑动及放大缩小事件:

package com.wangyongyao.androidlearnopengl.view;

import android.content.Context;
import android.graphics.Matrix;
import android.opengl.GLSurfaceView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;


import com.wangyongyao.androidlearnopengl.JniCall;
import com.wangyongyao.androidlearnopengl.utils.OpenGLUtil;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class GL3DCameraView extends GLSurfaceView implements GLSurfaceView.Renderer {

    private GestureDetector gestureDetector;
    private ScaleGestureDetector scaleGestureDetector;

    private static String TAG = GL3DCameraView.class.getSimpleName();
    private JniCall mJniCall;
    private Context mContext;
    private boolean isScaleGesture;

    private float downX;
    private float downY;


    public GL3DCameraView(Context context, JniCall jniCall) {
        super(context);
        mContext = context;
        mJniCall = jniCall;
        init();
    }

    public GL3DCameraView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        init();
    }

    private void init() {
        getHolder().addCallback(this);
        setEGLContextClientVersion(3);
        setEGLConfigChooser(8, 8, 8, 8, 16, 0);
        String fragPath = OpenGLUtil.getModelFilePath(mContext, "camera_3d_fragment.glsl");
        String vertexPath = OpenGLUtil.getModelFilePath(mContext, "camera_3d_vertex.glsl");
        String picSrc1 = OpenGLUtil.getModelFilePath(mContext, "yao.jpg");
        String picSrc2 = OpenGLUtil.getModelFilePath(mContext, "awesomeface.png");

        if (mJniCall != null) {
            mJniCall.setCameraGLSLPath(fragPath, vertexPath, picSrc1, picSrc2);
        }
        setRenderer(this);

        gestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener());
        scaleGestureDetector = new ScaleGestureDetector(getContext()
                , new ScaleGestureDetector.OnScaleGestureListener() {
            @Override
            public boolean onScale(ScaleGestureDetector detector) {
                // 处理缩放事件
                float scaleFactor = detector.getScaleFactor();
//                Log.e(TAG, "onScale scaleFactor: " + scaleFactor
//                        + "==getFocusX:" + detector.getFocusX()
//                        + "===getFocusY" + detector.getFocusY());
                mJniCall.CameraOnScale(scaleFactor, detector.getFocusX()
                        , detector.getFocusY(), 2);
                return true;
            }

            @Override
            public boolean onScaleBegin(ScaleGestureDetector detector) {
                // 开始缩放事件
//                Log.e(TAG, "onScaleBegin: " + detector);
                mJniCall.CameraOnScale(detector.getScaleFactor(), detector.getFocusX()
                        , detector.getFocusY(), 1);
                return true;
            }

            @Override
            public void onScaleEnd(ScaleGestureDetector detector) {
                // 结束缩放事件
//                Log.e(TAG, "onScaleEnd: " + detector);
                mJniCall.CameraOnScale(detector.getScaleFactor(), detector.getFocusX()
                        , detector.getFocusY(), 3);
                isScaleGesture = false;
            }
        });
    }


    public void onDrawFrame(GL10 gl) {
        if (mJniCall != null)
            mJniCall.CameraOpenGLRenderFrame();
    }

    public void onSurfaceChanged(GL10 gl, int width, int height) {
        if (mJniCall != null)
            mJniCall.initCamera3DOpenGl(width, height);
    }


    @Override
    public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {

    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (isScaleGesture) {
            gestureDetector.onTouchEvent(event);
            scaleGestureDetector.onTouchEvent(event);
            return true;
        }
        switch (event.getAction()) {
            case MotionEvent.ACTION_POINTER_2_DOWN: {
                isScaleGesture = true;
            }
            break;
            case MotionEvent.ACTION_POINTER_2_UP: {
                isScaleGesture = false;
            }
            break;
            case MotionEvent.ACTION_DOWN: {
//                Log.e(TAG, "onTouchEvent: " + event.getAction());
                downX = event.getX();
                downY = event.getY();
                mJniCall.CameraMoveXY(0, 0, 1);
            }
            break;
            case MotionEvent.ACTION_MOVE: {
//                Log.e(TAG, "onTouchEvent: " + event.getAction());
                float dx = event.getX() - downX;
                float dy = event.getY() - downY;
//                Log.e(TAG, "ACTION_MOVE:dx= "
//                        + dx + "==dy:" + dy);
                mJniCall.CameraMoveXY(dx, dy, 2);
            }
            break;
            case MotionEvent.ACTION_UP: {
//                Log.e(TAG, "onTouchEvent: " + event.getAction());
                downX = 0;
                downY = 0;
                mJniCall.CameraMoveXY(0, 0, 3);
            }
            break;
        }


        return true;
    }


}

         2、把上下左右滑动及放大缩小事件传递给OpenglesCamera3D.cpp:

void OpenglesCamera3D::setMoveXY(float dx, float dy, int actionMode) {
    LOGI("setMoveXY dx:%f,dy:%f,actionMode:%d", dy, dy, actionMode);
    float xoffset = dx - lastX;
    float yoffset = lastY - dy; // reversed since y-coordinates go from bottom to top
    lastX = dx;
    lastY = dy;
    mActionMode = actionMode;
    mCamera.ProcessXYMovement(xoffset, yoffset);
}

void OpenglesCamera3D::setOnScale(float scaleFactor, float focusX
            , float focusY, int actionMode) {
//    LOGI("setOnScale scaleFactor:%f,focusX:%f,focusY:%f
//           ,actionMode:%d", scaleFactor, focusX, focusY,
//         actionMode);
//    LOGI("setOnScale scaleFactor:%f", scaleFactor);
    float scale;
    if (actionMode == 1 || actionMode == 3) {
        scale = 45.0f;
    } else {
        if (scaleFactor > 1) {
            scale = (scaleFactor - 1) * 1000 + 45;
        } else {
           scale = 50 - (1 - scaleFactor) * 1000;
        }
    }
    LOGI("setOnScale scale:%f", scale);
    mCamera.ProcessScroll(scale);
}

        3、在OpenglesCamera3D.cpp的 renderFrame()中把Camera3D对摄像机的数据转换后跟观察矩阵和投影矩阵关联上:

void OpenglesCamera3D::renderFrame() {
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    // also clear the depth buffer now!
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 
    // bind Texture
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture1);
    //2、使用程序
    glUseProgram(gProgram);
    checkGlError("glUseProgram");
    //开启深度测试
    glEnable(GL_DEPTH_TEST);

    // create transformations
    //观察矩阵(View Matrix)
    glm::mat4 view = glm::mat4(1.0f); 
    //投影矩阵(Projection Matrix)          
    glm::mat4 projection = glm::mat4(1.0f);     

//    float radius = 10.0f;
//    timeValue = 10 / CLOCKS_PER_SEC;
//    float camX = static_cast<float>(sin(timeValue) * radius);
//    float camZ = static_cast<float>(cos(timeValue) * radius);
//    LOGI("setMoveXY camX:%f,camZ:%f", camX,camZ);
    //观察矩阵(View Matrix)平移,glm::LookAt函数需要一个位置、目标和上向量。
//    view = glm::lookAt(glm::vec3(camX, 0.0f, camZ), glm::vec3(0.0f, 0.0f, 0.0f),
//                       glm::vec3(0.0f, 1.0f, 0.0f));
//    projection = glm::perspective(glm::radians(45.0f), (float) screenW / (float) screenH, 0.1f,
//                                  100.0f);

    //观察矩阵(View Matrix)平移,glm::LookAt函数需要一个位置、目标和上向量。
    view = mCamera.GetViewMatrix();
    projection = glm::perspective(glm::radians(mCamera.Zoom)
                                , (float) screenW / (float) screenH,
                                  0.1f,
                                  100.0f);
    // pass them to the shaders (3 different ways)
    setMat4("projection", projection);
    setMat4("view", view);

    // render boxes
    glBindVertexArray(VAO);
    for (unsigned int i = 0; i < 10; i++) {
        // calculate the model matrix for each object and pass it to shader before drawing
        //模型矩阵(Model Matrix)
        glm::mat4 model = glm::mat4(1.0f);  
        //对获取到的模型移动到对应位置                 
        model = glm::translate(model, cameraCubePositions[i]);    
        float angle = 20.0f * i;
        if (i < 6) {
            double timeValue = clock() * 10 / CLOCKS_PER_SEC;
            angle = timeValue * 25.0f;
        }
        //让模型经过旋转矩阵的变化
        model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
        setMat4("model", model);
        glDrawArrays(GL_TRIANGLES, 0, 36);
    }

    checkGlError("glDrawArrays");
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1985780.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

UNION ALL 在单个子查询中排序不生效问题

业务场景 有两张表&#xff1a;表A&#xff0c;和表B&#xff0c;需要对A中数据按排序字段排序&#xff0c;对B表也按排序字段排序&#xff0c;然后返回并集。 写出如下SQL&#xff08;已简化&#xff09;&#xff1a; (select id from A order by sort desc) union all (se…

《python语言程序设计》2018年版第6章31题调用time.time()返回从1970年1月1日0点开始显示当前日期和时间

我没按要求显示结果。但是内容差不都&#xff0c;关键。每个31日或者月底就时间出现偏差 # 之前已经做好的当前的小时、分、秒。 def currentTime_output():currentTime time.time()totalSeconds int(currentTime)currentSecond totalSeconds % 60totalMinutes totalSecon…

正点原子imx6ull-mini-Linux驱动之Linux USB 驱动实验

USB 是很常用的接口&#xff0c;目前大多数的设备都是 USB 接口的&#xff0c;比如鼠标、键盘、USB 摄像 头等&#xff0c;我们在实际开发中也常常遇到 USB 接口的设备&#xff0c;本章我们就来学习一下如何使能 Linux 内核自带的 USB 驱动。注意&#xff01;本章并不讲解具体的…

本阿弗莱克和詹妮弗洛佩兹两次婚恋的完整时间表 每次都轰轰烈烈也都无疾而终

本阿弗莱克和詹妮弗洛佩兹于 2002 年在《鸳鸯绑匪》片场首次相识&#xff0c;当时洛佩兹与她的第二任丈夫克里斯贾德于 2001 年 9 月结婚。当时&#xff0c;阿弗莱克与格温妮丝帕特洛分分合合。洛佩兹提出离婚&#xff0c;不久后与阿弗莱克首次亮相情侣档。 2002 年 11 月&…

JavaEE: 死锁问题详解(5000字)

文章目录 死锁的出现场景1. 一个线程一把锁,这个线程针对这把锁,连续加锁了两次2. 两个线程,两把锁3. N个线程 , M个锁4. 内存可见性为什么会出现内存可见性问题呢?解决方法 volatile关键字 总结synchronized:死锁的四个必要条件(缺一不可)[重点]:内存可见性问题: 死锁的出现场…

【iOS】暑假第二周——网易云APP 仿写

目录 前言首页关于UINavigationBarAppearance “我的”账号夜间模式——多界面传值遇到的问题所用到的其他知识整理NSNotificationreloadData各种键盘模式 总结 前言 有了之前仿写ZARA的基础&#xff0c;本周我们仿写了网易云APP&#xff0c;在这里对多界面传值进行了首次应用—…

LISA: Reasoning Segmentation via Large Language Model

发表时间&#xff1a;CVPR 2024 论文链接&#xff1a;https://openaccess.thecvf.com/content/CVPR2024/papers/Lai_LISA_Reasoning_Segmentation_via_Large_Language_Model_CVPR_2024_paper.pdf 作者单位&#xff1a;CUHK Motivation&#xff1a;尽管感知系统近年来取得了显…

基于SSH的医院在线挂号系统设计与实现

点击下载源码 基于SSH的医院在线挂号系统设计与实现 摘 要 互联网技术迅速的发展给我们的生活带来很大的方便&#xff0c;同时也让许多行业迅速的发展起来。互联网技术已走向科技发展的巅峰期&#xff0c;我们要做的就是合理的使用互联网技术让我们的各个行业得到更快速的发展…

2024杭电多校06——1005交通管控

补题点这里 大意 一个操作杆可以对k个红绿灯进行操作&#xff0c;操作杆上的一个字符对应一个红绿灯&#xff0c;操作包括,-,0,问每种组合方案有多少种组合方式 : red->green->yellow->red -:green->red->yellow->green 可以用一个三进制数表示每个灯的状态…

Python(模块---pandas+matplotlib+pyecharts)

import pandas as pd import matplotlib.pyplot as plt dfpd.read_excel(简易数据.xlsx) # print(df) plt.rcParams[font.sans-serif][SimHei] #设置画布的大小 plt.figure(figsize(10,6)) labelsdf[电影中文名] ydf[国籍] # print(labels) # print(y)# import pandas as pd im…

[Webpack]webpack-dev-server设置多个路径代理时,proxy顺序有要求

问题背景 前端需要调用多个不同的后台时需要使用devServer.proxy做代理 问题现象 如下图设置ETL相关接口路径代理之后 调用ETL后台接口时产生404报错 问题原因 devServer.proxy在解析代理路径并替换的时候是按顺序解析的&#xff0c;我配置的三个代理中&#xff0c;/csm…

NCL数据分析与处理实践技术

NCAR Command Language&#xff08;NCL&#xff09;是由美国大气研究中心&#xff08;NCAR&#xff09;推出的一款用于科学数据计算和可视化的免费软件。它有着非常强大的文件输入和输出功能&#xff0c;可读写netCDF-3、netCDF-4 classic、HDF4、binary、ASCII数据&#xff0c…

Linux之软硬链接和动静态库

个人主页&#xff1a;点我进入主页 专栏分类&#xff1a;C语言初阶 C语言进阶 数据结构初阶 Linux C初阶 算法 C进阶 欢迎大家点赞&#xff0c;评论&#xff0c;收藏。 一起努力&#xff0c;一起奔赴大厂 目录 一.软硬链接 1.1如何软硬链接 1.2软硬链接的作用 …

ViP-LLaVA: Making Large Multimodal Models Understand Arbitrary Visual Prompts

发表时间&#xff1a;cvpr2024 论文链接&#xff1a;https://readpaper.com/pdf-annotate/note?pdfId2357936887983293952&noteId2426262228488986112 作者单位&#xff1a;University of Wisconsin–Madison Motivation&#xff1a;现在的多模态模型都关注整张图像的理…

html+css网页设计 qq官网首页1个页面无js

htmlcss网页设计 qq官网首页1个页面无js功能 页面1:1还原 网页作品代码简单&#xff0c;可使用任意HTML编辑软件&#xff08;如&#xff1a;Dreamweaver、HBuilder、Vscode 、Sublime 、Webstorm、Text 、Notepad 等任意html编辑软件进行运行及修改编辑等操作&#xff09;。 …

冲击性信号的频域特征

这是一个信号采样数学实验&#xff0c;你可以直观感受到冲击信号的时域和频域特征 1.原始冲击信号&#xff1a; 原始信号是一个频率为180Hz附近的一个冲击性信号&#xff1a; 2.冲击信号频谱 它的频谱&#xff0c;可能会超出你的想象&#xff0c;它的1x频率幅度可能并不最高…

iOS ------ autoreleasePool

一&#xff0c;autoReleasePool{} int main(int argc, const char * argv[]) {autoreleasepool {}return 0; }我们平时创建一个main函数的代码的时候&#xff0c;就会发现其中有一个这个东西autoreleasepool{}&#xff0c;使用clang编译之后&#xff1a;autoreleasepool{…}被…

对《国家汽车芯片标准体系建设指南》好奇,遂读

基础通用&#xff1a;基于汽车行业对芯片的可靠性、运行稳定性 和安全性等应用需求&#xff0c;提取出汽车芯片性通用要求&#xff0c;主要包括环境及可靠性、电磁兼容、功能安全和信息安全共4个方面的要求。 产品与技术应用&#xff1a;根据实现功能的不同&#xff0c;将汽车…

文献综述能否帮助研究人员认识特定学术领域的趋势和新兴主题

VersaBot一键生成文献综述 进行良好的文献综述可以成为研究人员识别特定学术领域的趋势和新兴主题的强大工具。就是这样; 1. 识别模式和重复出现的概念&#xff1a; 当您深入研究现有研究时&#xff0c;您自然会开始注意到不同研究中采用的重复出现的主题、想法和方法。这些模…

详解爬虫使用代理ip的几种方案

​ 在如今这个信息爆炸的时代&#xff0c;数据就是财富。对于许多从事数据分析、市场调研和大数据处理的人来说&#xff0c;网络爬虫已经成为了他们的得力助手。然而&#xff0c;随着网站对爬虫的防范措施越来越严格&#xff0c;使用代理IP已经成为了爬虫工作中的一项必备技能。…