一、欧拉角

三种欧拉角:

  • 俯仰角(Pitch):沿x轴旋转的角,从上往下看的角
  • 偏航角(Yaw):沿y轴旋转的角,从左往右看的角
  • 滚转角(Roll):沿z轴旋转的角(对于摄像机而言,一般不关心这个)

关于坐标轴:

  • 自身坐标系:物体自身的坐标轴,显然如果物体进行了俯仰、偏航、滚筒的旋转操作,那么坐标轴方向也会被改变
  • 世界坐标系:和物体无关,用来描述物体在世界中的位置,有唯一的原点和轴向
  • 惯性坐标系:自身坐标系到世界坐标系的过渡,原点为对应物体的原点,会随物体的移动而改变,轴向和世界坐标系的轴向一致,不会因为物体的旋转操作而改变

一个值得思考的问题

我们假设一个物体的位置是(5, 6, 15),欧拉角是(50°, 30°, 70°)

  • 对于物体移动,从(0, 0, 0)到(5, 6, 15),按照世界坐标/惯性坐标的轴向移动:顺序无关,也就说按照(0, 0, 0) → (5, 0, 0) → (5, 6, 0) → (5, 6, 15)的方式移动和按照(0, 0, 0) → (0, 6, 0) → (0, 6, 15) → (5, 6, 15)的方式移动不会影响物体的最终位置,尽管这看上去像是句废话
  • 对于物体移动,从(0, 0, 0)到(5, 6, 15),按照自身轴向移动:顺序无关,同上,毕竟移动并不会改变自身坐标轴的朝向
  • 对于物体旋转,从(0, 0, 0)到(50°, 30°, 70°),按照世界坐标/惯性坐标的轴向旋转:顺序有关!也就是说按照(0, 0, 0) → (50°, 0, 0) → (50°, 30°, 0) → (50°, 30°, 70°)的方式旋转和按照(0, 0, 0) → (0, 30°, 0) → (50°, 30°, 0) → (50°, 30°, 70°)的方式得出来的物体状态是不同的!可以尝试拿身边的物品感受一下
  • 对于物体旋转,从(0, 0, 0)到(50°, 30°, 70°),按照自身坐标系轴向旋转:顺序有关,同上,这个就很明显了,因为你每次旋转都会导致坐标轴同时发生改变

这下问题就大了,也就是如果我们单纯的说一个物体的欧拉角是(50°, 30°, 70°),那么好像并不能确定物体的状态

规定:必须要保证一个欧拉角确定唯一的状态,为了解决这个问题,那就需要确定旋转次序①!并且对于每次更新欧拉角的操作②,底层都从(0, 0, 0)重新开始计算,又或者使用四元数替代欧拉角③

对于①规定旋转顺序(rotate order):

3个轴共有6种顺序

举个例子:对于Unity3D来讲,就是y-x-z的顺序,即

关于内在旋转(intrinsic rotations)和外在旋转(extrinsic rotations):

上面所有的计算都是内在旋转,也就是每次旋转围绕的轴是上次旋转之后坐标系的某个轴,还有一种旋转描述方式为外在旋转,也就是每次旋转的轴都是原坐标系中的轴,一个结论是:内在旋转y-x-z与外在旋转z-x-y的公式和结果相同,这也是为什么有些地方给出的Unity3D旋转顺序是z-x-y的顺序,其实是一样的,不过后者是外在旋转顺序,前者是内在旋转顺序

对于②重新计算:

很好理解,还是Unity3D,假设你已经通过rotate(50°, 0, 0)让物体绕x轴旋转了50°,那么再次rotate(50°, 30°, 0)的话,并不是在(50°, 0, 0)的基础上进行旋转,而是重新从(0, 0, 0)开始,按照Y-X-Z的顺序/规则旋转

这样在①②的情况下,每一个欧拉角就唯一确定了物体的状态

对于③四元数:

四元数是完全替代欧拉角的一种表示方法,相对于欧拉角更加复杂和困难,这一章就暂时不讲了。。

其实对于理论逻辑/底层计算来讲,都应该使用四元数而并非欧拉角,这是因为上面提出的问题虽然可以被解决,但也因此出现了一个新的问题:万向节死锁(Gimbal Lock)

万向节死锁:

网上关于万向节死锁的讨论其实真的非常多,这里当然不会重新讲一遍,当然讲了也未必有别人讲得好(这也是OpenGL的教程并非数学教程),这里只做小小的补充吧

你可能看了很多篇文章也不太能完全能理解万向节死锁,包括但不限于为什么这么算,产生的原因和造成的影响

其实上面已经包含对万向节死锁产生原因的解释了,我们为了确保欧拉角的唯一,所以采用了一系列的解决方案,也就是确定了旋转顺序,并且每次都是重新从(0, 0, 0)开始计算等,因为在Unity3D上是Y-X-Z的顺序,所以我们想在Unity3D上复现万向节死锁非常容易:

  1. 新建一个圆柱体,Reset一下它的位置和旋转属性
  2. 让它沿x轴旋转90°
  3. 这个时候你就会发现修改Y的度数和Z的度数都是偏航!失去了滚转的自由度

二、摄像机视角

回到OpenGL,延续下上一章:OpenGL基础15:输入控制

上一章实现了摄像机的移动和缩放,那么这一章就把最后的鼠标控制摄像机的视角也实现了吧

LookAt里面有3个属性,摄像机位置,目标位置和世界上向量,改变摄像机视角的方法正是改变这个目标位置

对于控制视角,我们需要关心摄像机的俯仰角和偏航角,并将其转换为向量

一步一步来,我们先只考虑俯仰角,如下图:

我们现在在OpenGL的ZY平面上,其中蓝色线与橙色线的夹角就是俯仰角,设蓝色边为单位长度1,那么绿色线的长度就是,橙色线的长度就是,因此我们就可以得出 

代码添加如下:其中 pitch 的初始值为 0°

void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{if (firstMouse){lastX = xpos;lastY = ypos;firstMouse = false;}GLfloat xoffset = xpos - lastX;GLfloat yoffset = lastY - ypos;lastX = xpos;lastY = ypos;GLfloat sensitivity = 0.05;xoffset *= sensitivity;yoffset *= sensitivity;pitch += yoffset;if (pitch > 89.0f)pitch = 89.0f;if (pitch < -89.0f)pitch = -89.0f;glm::vec3 front;front.x = 0;front.y = sin(glm::radians(pitch));front.z = -cos(glm::radians(pitch));cameraFront = glm::normalize(front);}

然后只考虑偏航角,还是上面的图,只不过这次所在的平面并非YZ而是XZ,

一样的公式,可以得出

两者结合:

我们当然要指定顺序,一样按照Y-X的顺序来,不过这样的话,pitch操作就会被yaw的操作所影响,看下上面的两个公式:

分析后发现,这正是对应的旋转矩阵对向量相乘得出的结果(最终正负和坐标系有关)

因此我们只需要套入上面的矩阵运算就可以得到最终结果:

代码如下:其中 yaw 和 pitch 的初始值都为 0°

void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{if (firstMouse){lastX = xpos;lastY = ypos;firstMouse = false;}GLfloat xoffset = xpos - lastX;GLfloat yoffset = lastY - ypos;lastX = xpos;lastY = ypos;GLfloat sensitivity = 0.05;xoffset *= sensitivity;yoffset *= sensitivity;yaw += xoffset;pitch += yoffset;if (pitch > 89.0f)pitch = 89.0f;if (pitch < -89.0f)pitch = -89.0f;glm::vec3 front;front.x = sin(glm::radians(yaw)) * cos(glm::radians(pitch));front.y = sin(glm::radians(pitch));front.z = -cos(glm::radians(yaw)) * cos(glm::radians(pitch));cameraFront = glm::normalize(front);}

最终效果:

三、摄像机类

像之前的Shader.h一样,我们将摄像机单独抽出来:

也正是上面的完整代码(Shader.h,两个着色器未改变)

Camera.h:

#ifndef CAMERA_H
#define CAMERA_H
#include<vector>
#include<opengl/glew.h>
#include<glm/glm.hpp>
#include<glm/gtc/matrix_transform.hpp>
enum Camera_Movement
{FORWARD,BACKWARD,LEFT,RIGHT
};const GLfloat YAW = 0.0f;           //y轴,偏航
const GLfloat PITCH = 0.0f;         //x轴,俯仰
const GLfloat ZOOM = 45.0f;         //视角,用于缩放
const GLfloat SPEED = 1.0f;         //速度,用于移动
const GLfloat SENSITIVTY = 1.0f;   //鼠标灵敏度class Camera
{public:glm::vec3 Position;glm::vec3 Front, Up, Right;glm::vec3 WorldUp;GLfloat Yaw, Pitch;GLfloat MovementSpeed;GLfloat MouseSensitivity;GLfloat Zoom;Camera(glm::vec3 position = glm::vec3(0.0f, 0.0f, 3.0f), glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f), GLfloat yaw = YAW, GLfloat pitch = PITCH): Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVTY), Zoom(ZOOM){this->Position = position;this->WorldUp = up;this->Yaw = yaw;this->Pitch = pitch;this->updateCameraVectors();}Camera(GLfloat posX, GLfloat posY, GLfloat posZ, GLfloat upX, GLfloat upY, GLfloat upZ, GLfloat yaw, GLfloat pitch):Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVTY), Zoom(ZOOM){this->Position = glm::vec3(posX, posY, posZ);this->WorldUp = glm::vec3(upX, upY, upZ);this->Yaw = yaw;this->Pitch = pitch;this->updateCameraVectors();}//获取对应的LookAt矩阵glm::mat4 GetViewMatrix(){//printf("%.2f, %.2f, %.2f\n", this->Position.x, this->Position.y, this->Position.z);return glm::lookAt(this->Position, this->Position + this->Front, this->WorldUp);}void ProcessKeyboard(int direction, GLfloat deltaTime){GLfloat velocity = this->MovementSpeed * deltaTime;if (direction == FORWARD)this->Position += this->Front * velocity;if (direction == BACKWARD)this->Position -= this->Front * velocity;if (direction == LEFT)this->Position -= this->Right * velocity;if (direction == RIGHT)this->Position += this->Right * velocity;}void ProcessMouseMovement(GLfloat xoffset, GLfloat yoffset, GLboolean constrainPitch = true){xoffset *= this->MouseSensitivity;yoffset *= this->MouseSensitivity;this->Yaw += xoffset;this->Pitch += yoffset;if (constrainPitch){if (this->Pitch > 89.0f)this->Pitch = 89.0f;if (this->Pitch < -89.0f)this->Pitch = -89.0f;}this->updateCameraVectors();}void ProcessMouseScroll(GLfloat yoffset){if (this->Zoom >= 1.0f && this->Zoom <= 45.0f)this->Zoom -= yoffset;if (this->Zoom <= 1.0f)this->Zoom = 1.0f;if (this->Zoom >= 45.0f)this->Zoom = 45.0f;}private:void updateCameraVectors(){glm::vec3 front;front.x = sin(glm::radians(this->Yaw)) * cos(glm::radians(this->Pitch));front.y = sin(glm::radians(this->Pitch));front.z = -cos(glm::radians(this->Yaw)) * cos(glm::radians(this->Pitch));this->Front = glm::normalize(front);this->Right = glm::normalize(glm::cross(this->Front, this->WorldUp));this->Up = glm::normalize(glm::cross(this->Right, this->Front));}
};
#endif

main:

#include<iostream>
#include<opengl/glew.h>
#define GLEW_STATIC
#include<GLFW/glfw3.h>
#include"Camera.h"
#include<glm/glm.hpp>
#include<glm/gtc/matrix_transform.hpp>
#include<glm/gtc/type_ptr.hpp>
#include"Shader.h"
#include<opengl/freeglut.h>
#include<SOIL.h>bool keys[1024];
Camera camera;
GLfloat lastX, lastY;
bool firstMouse = true;
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void cameraMove();
const GLuint WIDTH = 800, HEIGHT = 600;int main()
{glfwInit();glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr);glfwMakeContextCurrent(window);glfwSetKeyCallback(window, key_callback);glfwSetCursorPosCallback(window, mouse_callback);glfwSetScrollCallback(window, scroll_callback);//glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);glewExperimental = GL_TRUE;glewInit();int width, height;glfwGetFramebufferSize(window, &width, &height);glViewport(0, 0, width, height);Shader shaderYellow("VShader.txt", "FShaderY.txt");GLfloat vertices[] = {-0.5f, -0.5f, -0.5f,  0.0f, 0.0f,0.5f, -0.5f, -0.5f,  1.0f, 0.0f,0.5f,  0.5f, -0.5f,  1.0f, 1.0f,0.5f,  0.5f, -0.5f,  1.0f, 1.0f,-0.5f,  0.5f, -0.5f,  0.0f, 1.0f,-0.5f, -0.5f, -0.5f,  0.0f, 0.0f,-0.5f, -0.5f,  0.5f,  0.0f, 0.0f,0.5f, -0.5f,  0.5f,  1.0f, 0.0f,0.5f,  0.5f,  0.5f,  1.0f, 1.0f,0.5f,  0.5f,  0.5f,  1.0f, 1.0f,-0.5f,  0.5f,  0.5f,  0.0f, 1.0f,-0.5f, -0.5f,  0.5f,  0.0f, 0.0f,-0.5f,  0.5f,  0.5f,  1.0f, 0.0f,-0.5f,  0.5f, -0.5f,  1.0f, 1.0f,-0.5f, -0.5f, -0.5f,  0.0f, 1.0f,-0.5f, -0.5f, -0.5f,  0.0f, 1.0f,-0.5f, -0.5f,  0.5f,  0.0f, 0.0f,-0.5f,  0.5f,  0.5f,  1.0f, 0.0f,0.5f,  0.5f,  0.5f,  1.0f, 0.0f,0.5f,  0.5f, -0.5f,  1.0f, 1.0f,0.5f, -0.5f, -0.5f,  0.0f, 1.0f,0.5f, -0.5f, -0.5f,  0.0f, 1.0f,0.5f, -0.5f,  0.5f,  0.0f, 0.0f,0.5f,  0.5f,  0.5f,  1.0f, 0.0f,-0.5f, -0.5f, -0.5f,  0.0f, 1.0f,0.5f, -0.5f, -0.5f,  1.0f, 1.0f,0.5f, -0.5f,  0.5f,  1.0f, 0.0f,0.5f, -0.5f,  0.5f,  1.0f, 0.0f,-0.5f, -0.5f,  0.5f,  0.0f, 0.0f,-0.5f, -0.5f, -0.5f,  0.0f, 1.0f,-0.5f,  0.5f, -0.5f,  0.0f, 1.0f,0.5f,  0.5f, -0.5f,  1.0f, 1.0f,0.5f,  0.5f,  0.5f,  1.0f, 0.0f,0.5f,  0.5f,  0.5f,  1.0f, 0.0f,-0.5f,  0.5f,  0.5f,  0.0f, 0.0f,-0.5f,  0.5f, -0.5f,  0.0f, 1.0f};GLuint VBO, VAO, texture;glGenVertexArrays(1, &VAO);glGenBuffers(1, &VBO);glGenTextures(1, &texture);glBindVertexArray(VAO);glBindBuffer(GL_ARRAY_BUFFER, VBO);glBindTexture(GL_TEXTURE_2D, texture);glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);glEnableVertexAttribArray(0);glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));glEnableVertexAttribArray(1);int picWidth, picHeight;glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);unsigned char* image = SOIL_load_image("Texture/wood.jpg", &picWidth, &picHeight, 0, SOIL_LOAD_RGB);glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, picWidth, picHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, image);glGenerateMipmap(GL_TEXTURE_2D);SOIL_free_image_data(image);glBindTexture(GL_TEXTURE_2D, 0);glBindBuffer(GL_ARRAY_BUFFER, 0);glBindVertexArray(0);glEnable(GL_DEPTH_TEST);while (!glfwWindowShouldClose(window)){glfwPollEvents();glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);glClear(GL_DEPTH_BUFFER_BIT);cameraMove();glBindTexture(GL_TEXTURE_2D, texture);shaderYellow.Use();float radius = 5.0f;float camX = sin(glfwGetTime()) * radius;float camZ = cos(glfwGetTime()) * radius;glm::mat4 model = glm::mat4(1.0f);glm::mat4 view = glm::mat4(1.0f);glm::mat4 projection = glm::mat4(1.0f);model = glm::rotate(model, glm::radians(57.0f), glm::vec3(-0.5f, 1.0f, 0.0f));view = camera.GetViewMatrix();projection = glm::perspective(glm::radians(camera.Zoom), (GLfloat)WIDTH / (GLfloat)HEIGHT, 0.1f, 100.0f);GLint modelLoc = glGetUniformLocation(shaderYellow.Program, "model");GLint viewLoc = glGetUniformLocation(shaderYellow.Program, "view");GLint projLoc = glGetUniformLocation(shaderYellow.Program, "projection");glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projection));glBindVertexArray(VAO);glDrawArrays(GL_TRIANGLES, 0, 36);glBindVertexArray(0);glfwSwapBuffers(window);}glDeleteVertexArrays(1, &VAO);glDeleteBuffers(1, &VBO);glfwTerminate();return 0;
}GLfloat deltaTime = 0.0f;
GLfloat lastFrame = 0.0f;
void cameraMove()
{GLfloat currentFrame = glfwGetTime();deltaTime = currentFrame - lastFrame;lastFrame = currentFrame;GLfloat cameraSpeed = 1.0f * deltaTime;if (keys[GLFW_KEY_W])camera.ProcessKeyboard(Camera_Movement(FORWARD), deltaTime);if (keys[GLFW_KEY_S])camera.ProcessKeyboard(Camera_Movement(BACKWARD), deltaTime);if (keys[GLFW_KEY_A])camera.ProcessKeyboard(Camera_Movement(LEFT), deltaTime);if (keys[GLFW_KEY_D])camera.ProcessKeyboard(Camera_Movement(RIGHT), deltaTime);
}void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)glfwSetWindowShouldClose(window, GL_TRUE);if (action == GLFW_PRESS)           //如果当前是按下操作keys[key] = true;else if (action == GLFW_RELEASE)            //松开键盘keys[key] = false;
}void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{camera.ProcessMouseScroll(yoffset);
}void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{if (firstMouse){lastX = xpos;lastY = ypos;firstMouse = false;}GLfloat xoffset = xpos - lastX;GLfloat yoffset = lastY - ypos;lastX = xpos;lastY = ypos;GLfloat sensitivity = 0.05;xoffset *= sensitivity;yoffset *= sensitivity;camera.ProcessMouseMovement(xoffset, yoffset);
}

OpenGL基础16:视角相关推荐

  1. OpenGL基础54:点光源阴影

    前置: OpenGL基础53:阴影映射(下) 一.万象阴影贴图 之前成功实现了平行光阴影,生成阴影贴图时使用的矩阵是正交矩阵,若是想要实现点光源的阴影效果,那么理论上只需要修改投影矩阵为透视矩阵就好了 ...

  2. OpenGL基础25:多光源(附简单GLSL配置)

    到这里,光照基础就已经接近尾声了,当然对于光照渲染的学习,这可能只是百步中的一步,尽管如此,至少还是做到了从 0 到 1 的一个过程,就像之前刚学会"HelloWorld"一样,一 ...

  3. opengl基础学习专题 (二) 点直线和多边形

    题外话 随着学习的增长,越来越觉得自己很水.关于上一篇博文中推荐用一个 学习opengl的 基于VS2015的 simplec框架.存在 一些问题. 1.这个框架基于VS 的Debug 模式下,没有考 ...

  4. OpenGL基础53:阴影映射(下)

    接上文:OpenGL基础52:阴影映射(上) 五.阴影失真 按照上文的计算的结果,一个很明显的问题是:对于参与计算深度贴图的物体,其表面可以看到这样的栅格状的阴影,这种常见的错误表现也叫做阴影失真(S ...

  5. OpenGL基础50:HDR

    一.HDR与LDR 由于显示器只能显示值为0.0到1.0间的颜色,因此当数据存储在帧缓冲(Framebuffer)中时,亮度和颜色的值也是默认被限制在0.0到1.0之间的,这个颜色范围即是LDR(Lo ...

  6. OpenGL基础44:光照矫正(上)

    对于openGL的API,倒是没有必要花太多时间,重点应该还是在着色器上 一.采样器.glActiveTexture和glBindTexture 在之前测试简单光照时可能出现的两个问题,尽管它们可能不 ...

  7. OpenGL基础40:Uniform缓冲

    前置:OpenGL基础39:GLSL内建变量与接口块 想想之前代码,glUniform()和glGetUniformLocation()的使用数量是不是过于频繁了,对于每个着色器的每一个uniform ...

  8. OpenGL基础35:帧缓冲(下)之简单图像处理

    在之前的章节,所有的物体都是中规中矩的显示的,只考虑了光照对物体的影响,那假设想要显示特殊的效果该怎么操作呢?例如马赛克风.将所有的物体都显示为黑白色,就像上世纪80年代的灰白电视一样,又或者说将整个 ...

  9. OpenGL基础31:混合

    在很多游戏场景中,地面往往都不是完全干净和平坦的,如果是草坪,那么肯定会有一些长得比较高的杂草,而对于沙地,往往总会有一些奇形怪状的石头等,一般来讲确实可以用模型,但是贴图也是一个不错的选择 一.Al ...

最新文章

  1. Win8 x64 + Office Word 2013 x64 无法自动加载 Endnote X6 的解决方案
  2. Java程序员从笨鸟到菜鸟之(八十九)跟我学jquery(五)jquery中的ajax详解
  3. python自动化框架2019_《一头扎进》系列之Python+Selenium自动化测试框架实战篇6 - 价值好几K的框架,呦!这个框架还真牛叉哦!!!...
  4. 知识点old1908
  5. 实验五 burpsuite重放攻击实验
  6. winform button设计(一)
  7. 网页设计太麻烦?15款免费优质Bootstrap UI工具包助你效率倍增!
  8. embedding与pytorch中squeeze()和unsqueeze()函数介绍
  9. [WCF权限控制]WCF的三种授权模式
  10. single无效,使用maxLines
  11. 如何用Flutter实现跨平台移动开发
  12. 微信H5公众号获取openid爬坑记
  13. 利用R语言如何判别和分类
  14. 从零开始的UBOOT的学习8--命令体系
  15. Java SE基础(十六)集合
  16. ParameterizedTypeReference使用
  17. 百度地图清除标注和聚合点
  18. Unicode 码和汉字,十进制之间互转!二进制、十六进制,十进制的转换
  19. EBS 销售RMA接收报错VALIDATE_RMA_LOT_QUANTITIES_ERROR
  20. Java里的正无穷和负无穷

热门文章

  1. python有什么用-python都可以做什么用
  2. 学python需要什么基础-零基础学Python应该学习哪些入门知识及学习步骤安排
  3. 0基础学python要多久-27岁0基础自学Python,多久可以找到工作?
  4. 自学python书籍怎么选-学习Python可以看书籍学习吗?老男孩Python入门课程
  5. python是什么类型的语言-为什么说 Python 是强类型语言?
  6. 如何利用Matlab完成数字1-9的语音识别
  7. 打docker镜像_分分钟搞定阿里云私有Docke镜像仓库的搭建
  8. 预定义类型未定义或导入_LimeSurvey基础教程(二)——问题类型
  9. jQuery之animate自定义动画
  10. 润乾报表鼠标移动行、单元格变色