本文为学习LearnOpenGL的学习笔记,如有书写和理解错误还请大佬扶正;

教程链接:

https://learnopengl-cn.github.io/01%20Getting%20started/07%20Transformations/​learnopengl-cn.github.io

一,基础概念

对绘制的图形进行位置,旋转,缩放变化实现整个界面的变换,主要通过各种变换矩阵实现(具体资料参考教程链接或者线性代数);

二,实现基础变换

由于OpenGL没有自带任何的矩阵和向量相关的函数,需要自己实现相关功能或者下载数学库,本教程使用的是外部下载的GLM库;

1,下载GLM数学库

  • 下载链接;

https://github.com/g-truc/glm/tags​github.com

  • 下载glm文件,解压找到里边glm文件夹拷贝到环境搭建时的include文件夹内;
目录
  • 使用前包含相关头文件,我们需要的GLM的大多数功能都可以从下面这3个头文件中找到;
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

2,测试一下GLM是否正常工作

glm::vec4 vec(1.0f, 0.0f, 0.0f, 1.0f);
// 如果使用的是0.9.9及以上版本
// 下面这行代码就需要改为:
// glm::mat4 trans = glm::mat4(1.0f)
glm::mat4 trans;
trans = glm::translate(trans, glm::vec3(1.0f, 1.0f, 0.0f));
vec = trans * vec;
std::cout << vec.x << vec.y << vec.z << std::endl;
//如若工作正常 这个代码片段将会输出210

3,具体实现代码

实现效果

更改渲染逻辑(LearnOpenGL.cpp文件)

//GLAD的头文件包含了正确的OpenGL头文件(例如GL/gl.h),所以需要在其它依赖于OpenGL的头文件之前包含GLAD。
#include <glad/glad.h>
#include <GLFW/glfw3.h>
//GLM数学库
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>#include "ShaderBase.h"          //基础的渲染着色器//通过定义STB_IMAGE_IMPLEMENTATION,预处理器会修改头文件,让其只包含相关的函数定义源码,等于是将这个头文件变为一个 .cpp 文件了
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"         //图片处理
#include <iostream>using namespace std;/*******************************************定义常量************************************************///设置窗口的宽和高
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;float mixValue = 0.2f;/*******************************************函数************************************************///响应键盘输入事件void processInput(GLFWwindow* window)
{//ESC 退出窗口//glfwGetKey()用来判断一个键是否按下。第一个参数是GLFW窗口句柄,第二个参数是一个GLFW常量,代表一个键。//GLFW_KEY_ESCAPE表示Esc键。如果Esc键按下了,glfwGetKey将返回GLFW_PRESS(值为1),否则返回GLFW_RELEASE(值为0)。if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS){//glfwSetWindowShouldClose()函数,为窗口设置关闭标志。第一个参数是窗口句柄,第二个参数表示是否关闭//这里为GLFW_TRUE,表示关闭该窗口。//注意,这时窗口不会立即被关闭,但是glfwWindowShouldClose()将返回GLFW_TRUE,到了glfwTerminate()就会关闭窗口。glfwSetWindowShouldClose(window, true);}//上键if (glfwGetKey(window, GLFW_KEY_UP) == GLFW_PRESS){mixValue += 0.01f;if (mixValue >= 1.0f)mixValue = 1.0f;}//下键if (glfwGetKey(window, GLFW_KEY_DOWN) == GLFW_PRESS){mixValue -= 0.01f;if (mixValue <= 0.0f)mixValue = 0.0f;}}//当用户改变窗口的大小的时候,视口也应该被调整。
//对窗口注册一个回调函数(Callback Function),它会在每次窗口大小被调整的时候被调用
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{//OpenGL渲染窗口的尺寸大小//glViewport函数前两个参数控制窗口左下角的位置。第三个和第四个参数控制渲染窗口的宽度和高度(像素)glViewport(0, 0, width, height);
}/*******************************************主函数************************************************///主函数
int main()
{//测试代码//glm::vec4 vec(1.0f, 0.0f, 0.0f, 1.0f);//glm::mat4 trans = glm::mat4(1.0f);//trans = glm::translate(trans, glm::vec3(1.0f, 1.0f, 0.0f));//vec = trans * vec;//std::cout << vec.x << vec.y << vec.z << std::endl;//初始化GLFWglfwInit();//声明版本与核心glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); //主版本号glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); //次版本号glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);//创建窗口并设置其大小,名称,与检测是否创建成功GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", nullptr, nullptr);if (window == nullptr){cout << "Failed to create GLFW window" << endl;glfwTerminate();return -1;}//创建完毕之后,需要让当前窗口的环境在当前线程上成为当前环境,就是接下来的画图都会画在我们刚刚创建的窗口上glfwMakeContextCurrent(window);//告诉GLFW我们希望每当窗口调整大小的时候调用这个函数glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);//glad寻找opengl的函数地址,调用opengl的函数前需要初始化gladif (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){std::cout << "Failed to initialize GLAD" << std::endl;return -1;}/*******************************************着色器************************************************///构建和编译ShaderShader ourShader01("vs01.vs", "fs01.fs");Shader ourShader02("vs02.vs", "fs02.fs");/*******************************************顶点数据************************************************///设置顶点数据和顶点属性//第一个三角形数据float vertices01[] = {// 位置              顶点颜色              UV0.1f, 0.9f, 0.0f,    1.0f,0.0f,0.0f,       0.0f,1.0f,0.9f, 0.9f, 0.0f,    0.0f,1.0f,0.0f,       1.0f,1.0f,0.9f, 0.1f, 0.0f,    0.0f,0.0f,1.0f,       1.0f,0.0f,0.1f, 0.1f, 0.0f,    1.0f,1.0f,1.0f,       0.0f,0.0f};unsigned int indices[] = {0, 1, 3,1, 2, 3};// 第二个三角形数据float vertices02[] = {//位置                                  UV-0.5f, 0.9f, 0.0f,                   0.5f,1.0f,-0.1f, 0.1f, 0.0f,                   1.0f,0.0f,-0.9f, 0.1f, 0.0f,                   0.0f,0.0f};/*******************************************VAO/VBO/EBO************************************************///创建 VBO 顶点缓冲对象 VAO顶点数组对象 EBO索引缓冲对象unsigned int VBOs[2], VAOs[2], EBOs[2];glGenVertexArrays(2, VAOs);glGenBuffers(2, VBOs);glGenBuffers(2, EBOs);//绑定VAO,VBO与EBO对象/*******************************************第一个************************************************/glBindVertexArray(VAOs[0]);glBindBuffer(GL_ARRAY_BUFFER, VBOs[0]);glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBOs[0]);// 复制顶点数据到缓冲内存中glBufferData(GL_ARRAY_BUFFER, sizeof(vertices01), vertices01, GL_STATIC_DRAW);//赋值顶点索引到缓冲内存中glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);//链接顶点属性,设置顶点属性指针glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);glEnableVertexAttribArray(0);//顶点颜色//属性位置值为1的顶点属性glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));//以顶点属性位置值作为参数,启用顶点属性;顶点属性默认是禁用的。glEnableVertexAttribArray(1);//顶点UV坐标glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));glEnableVertexAttribArray(2);/*******************************************第二个************************************************///绑定VAO,VBO与EBO对象glBindVertexArray(VAOs[1]);glBindBuffer(GL_ARRAY_BUFFER, VBOs[1]);//复制顶点数据到缓冲内存中glBufferData(GL_ARRAY_BUFFER, sizeof(vertices02), vertices02, GL_STATIC_DRAW);//链接顶点属性,设置顶点属性指针//顶点位置glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);//以顶点属性位置值作为参数,启用顶点属性;顶点属性默认是禁用的。glEnableVertexAttribArray(0);glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));glEnableVertexAttribArray(1);/*******************************************纹理对象************************************************///生成纹理//创建IDunsigned int texture1, texture2, texture3;/*******************************************第一个纹理对象************************************************///创建纹理对象//glGenTextures函数首先需要输入生成纹理的数量,然后把它们储存在第二个参数的unsigned int数组中glGenTextures(1, &texture1);//绑定纹理glBindTexture(GL_TEXTURE_2D, texture1);//设置纹理的环绕方式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);//声明变量 用来储存图片的宽度,高度和颜色通道个数int width, height, nrChannels;//因为OpenGL要求y轴0.0坐标是在图片的底部的,但是图片的y轴0.0坐标通常在顶部//翻转y轴stbi_set_flip_vertically_on_load(true);//stbi_load()函数 载入图片数据unsigned char *data = stbi_load(("resources/textures/container.jpg"), &width, &height, &nrChannels, 0);//判断数据是否加载成功if (data){//利用载入图片数据,生成纹理//当前绑定的纹理对象就会被附加上纹理图像glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);//为当前绑定的纹理自动生成所有需要的Mipmap(多级渐远纹理)glGenerateMipmap(GL_TEXTURE_2D);}else{std::cout << "Failed to load texture" << std::endl;}//删除加载的图片数据,释放内存stbi_image_free(data);/*******************************************第二个纹理对象************************************************///创建纹理对象//glGenTextures函数首先需要输入生成纹理的数量,然后把它们储存在第二个参数的unsigned int数组中glGenTextures(1, &texture2);//绑定纹理glBindTexture(GL_TEXTURE_2D, texture2);//设置纹理的环绕方式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);//声明变量 用来储存图片的宽度,高度和颜色通道个数data = stbi_load(("resources/textures/awesomeface.png"), &width, &height, &nrChannels, 0);//判断数据是否加载成功if (data){//利用载入图片数据,生成纹理glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);//为当前绑定的纹理自动生成所有需要的Mipmap(多级渐远纹理)glGenerateMipmap(GL_TEXTURE_2D);}else{std::cout << "Failed to load texture" << std::endl;}//删除加载的图片数据,释放内存stbi_image_free(data);/* ****************************************第二个三角形******************************************************///glGenTextures函数首先需要输入生成纹理的数量,然后把它们储存在第二个参数的unsigned int数组中glGenTextures(1, &texture3);//绑定纹理glBindTexture(GL_TEXTURE_2D, texture3);//设置纹理的环绕方式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);//因为OpenGL要求y轴0.0坐标是在图片的底部的,但是图片的y轴0.0坐标通常在顶部//翻转y轴stbi_set_flip_vertically_on_load(true);//stbi_load()函数 载入图片数据data = stbi_load(("resources/textures/wall.jpg"), &width, &height, &nrChannels, 0);//判断数据是否加载成功if (data){//利用载入图片数据,生成纹理//当前绑定的纹理对象就会被附加上纹理图像glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);//为当前绑定的纹理自动生成所有需要的Mipmap(多级渐远纹理)glGenerateMipmap(GL_TEXTURE_2D);}else{std::cout << "Failed to load texture" << std::endl;}//删除加载的图片数据,释放内存stbi_image_free(data);/*******************************************分配纹理单元************************************************///设置uniform前,激活着色器ourShader01.use();//glUniform1i设置每个采样器的方式告诉OpenGL每个着色器采样器属于哪个纹理单元。//只需要设置一次即可,所以这个会放在渲染循环的前面//手动设置//glUniform1i(glGetUniformLocation(ourShader01.ID, "texture1"), 0);//使用内置自定义函数ourShader01.setInt("texture1", 0);ourShader01.setInt("texture2", 1);/*******************************************第二个三角形************************************************/ourShader02.use();ourShader02.setInt("texture3", 2);/*******************************************渲染循环************************************************///程序可以一直运行,直到用户关闭窗口。这样我们就需要创建一个循环,叫做游戏循环//glfwWindowShouldClose()检查窗口是否需要关闭。如果是,游戏循环就结束了,接下来我们将会清理资源,结束程序while (!glfwWindowShouldClose(window)){//响应键盘输入processInput(window);//设置清除颜色glClearColor(0.2f, 0.3f, 0.3f, 1.0f);//清除当前窗口,把颜色设置为清除颜色glClear(GL_COLOR_BUFFER_BIT);/*******************************************绘制************************************************///获取时间float timeValue = glfwGetTime();float greenValue = sin(timeValue) / 2.0f + 0.5f;//激活链接程序,激活着色器,开始渲染//绘制第一个三角形//在绑定纹理之前先激活纹理单元glActiveTexture(GL_TEXTURE0);//glBindTexture()函数调用,会绑定这个纹理到当前激活的纹理单元//纹理单元GL_TEXTURE0默认总是被激活glBindTexture(GL_TEXTURE_2D, texture1);glActiveTexture(GL_TEXTURE1);glBindTexture(GL_TEXTURE_2D, texture2);ourShader01.use();ourShader01.setFloat("YOffset", greenValue);ourShader01.setFloat("mixValue", mixValue);//创建一个矩阵4X4glm::mat4 transform = glm::mat4(1.0f);//移动 旋转和缩放矩阵transform = glm::translate(transform, glm::vec3(0.5f, 0.0f, 0.0f));transform = glm::scale(transform, glm::vec3(0.5, 0.5, 0.5));transform = glm::rotate(transform, (float)glfwGetTime(), glm::vec3(0.0f, 0.0f, 1.0f));//为着色器中的矩阵赋值unsigned int transformLoc01 = glGetUniformLocation(ourShader01.ID, "transform01");glUniformMatrix4fv(transformLoc01, 1, GL_FALSE, glm::value_ptr(transform));//绑定VAOglBindVertexArray(VAOs[0]);//绘制四边形glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);//绘制三角形 三个顶点//glDrawArrays(GL_TRIANGLES, 0, 3);//绘制第二个//在绑定纹理之前先激活纹理单元glActiveTexture(GL_TEXTURE2);glBindTexture(GL_TEXTURE_2D, texture3);ourShader02.use();ourShader02.setVec4("outColor", 0.0f,greenValue,0.0f,1.0f);//创建一个矩阵4X4glm::mat4 transform02 = glm::mat4(1.0f);//移动 旋转和缩放矩阵transform02 = glm::scale(transform02, glm::vec3(greenValue, greenValue, greenValue));//为着色器中的矩阵赋值unsigned int transformLoc02 = glGetUniformLocation(ourShader02.ID, "transform02");glUniformMatrix4fv(transformLoc02, 1, GL_FALSE, glm::value_ptr(transform02));//绑定VAOglBindVertexArray(VAOs[1]);//绘制三角形glDrawArrays(GL_TRIANGLES, 0, 3);/*******************************************结束************************************************///交换颜色缓冲 glfwSwapBuffers(window);//处理事件glfwPollEvents();}//解除绑定glDeleteVertexArrays(2, VAOs);glDeleteBuffers(2, VBOs);glDeleteBuffers(2, EBOs);//释放前面所申请的资源glfwTerminate();return 0;
}

vs01.vs代码为

#version 330 core
layout (location = 0) in vec3 aPos;             //顶点位置
layout (location = 1) in vec3 aColor;           //顶点颜色
layout (location = 2) in vec2 aTexCoord;        //顶点的UV坐标out vec3 ourColor;                              //输出颜色
out vec2 TexCoord;                              //输出顶点UV坐标
//out vec3 ourPosition;
uniform float YOffset;                          //声明一个float 变量
uniform mat4 transform01;                      //声明一个矩阵void main()
{//赋值gl_Position = transform01*vec4(aPos.x, aPos.y - YOffset, aPos.z, 1.0);ourColor = aColor;TexCoord = vec2(aTexCoord.x, aTexCoord.y);   }

fs01.fs代码为

#version 330 core
out vec4 FragColor;in vec3 ourColor;
in vec2 TexCoord;
//in vec3 ourPosition;uniform sampler2D texture1; //声明一个贴图
uniform sampler2D texture2; //声明一个贴图
uniform float mixValue; //声明控制混合的变量void main()
{//采样贴图//mix()函数 接受两个值作为参数,并对它们根据第三个参数进行线性插值FragColor = mix(texture(texture1, TexCoord) * vec4(ourColor, 1.0), texture(texture2, vec2(TexCoord.x, TexCoord.y)), mixValue);
}

vs02.vs代码为

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord; //顶点的UV坐标out vec3 ourPosition;
out vec2 TexCoord; //输出顶点UV坐标
uniform mat4 transform02; //声明一个矩阵void main()
{gl_Position = transform02 * vec4(aPos, 1.0);TexCoord = vec2(aTexCoord.x, aTexCoord.y);ourPosition = aPos;
}

fs02.fs代码为

#version 330 core
out vec4 FragColor;in vec3 ourPosition;
in vec2 TexCoord;
uniform vec4 outColor;
uniform sampler2D texture3; //声明一个贴图void main()
{vec4 tex = texture(texture3, TexCoord);FragColor = tex * (outColor + vec4(ourPosition, 1.0f));
}

opengl源码 实现无缝切换图片过场_OpenGL学习笔记(六)变换相关推荐

  1. opengl源码 实现无缝切换图片过场_手把手讲解 Android hook技术实现一键换肤

    前言 产品大佬又提需求啦,要求app里面的图表要实现白天黑夜模式的切换,以满足不同光线下都能保证足够的图表清晰度. 怎么办?可能解决的办法很多,你可以给图表view增加一个toggle方法,参数Str ...

  2. 一款社区论坛小程序源码(修复登录图片发布上传问题)

    简介: 这是一款社区论坛小程序源码(修复登录图片发布上传问题) 内涵强大的功能 支持多种多样的发帖模式 比如发图文,发语音,发涂鸦,发视频等 另外也可以设置为只能会员才可以发 另外还拥有礼物功能,可以 ...

  3. 仿抖音短视频系统源码,获取系统图片

    仿抖音短视频系统源码,实现获取系统图片的相关代码如下: 首先开权限 <uses-permission android:name="android.permission.WRITE_EX ...

  4. 小程序源码:多功能图片处理器

    这是一款多功能的一款照片处理器 UI简洁,功能也还不错 免除服务器和域名即可搭建,特别的简单好上手 一键化功能支持: 人脸融合(人脸融合,两张脸融合成一张) 换底色(相当于就是给照片的底色换色,一般都 ...

  5. v42.05 鸿蒙内核源码分析(中断切换) | 系统因中断活力四射 | 百篇博客分析鸿蒙源码

    子曰:"知者不惑,仁者不忧,勇者不惧." <论语>:子罕篇 百篇博客系列篇.本篇为: v42.xx 鸿蒙内核源码分析(中断切换篇) | 系统因中断活力四射 硬件架构相关 ...

  6. Fedora 16上源码建立pydev + eclipse的OpenStack开发环境笔记草稿 ( by quqi99 )

    Fedora 16上源码建立pydev + eclipse的OpenStack开发环境笔记草稿  ( by quqi99 ) 作者:张华  发表于:2012-3-30 版权声明:可以任意转载,转载时请 ...

  7. 可能是全网首个前端源码共读活动,诚邀加入学习

    大家好,我是若川.从8月份到现在11月结束了.每周一期,一起读200行左右的源码,撰写辅助文章,截止到现在整整4个月了. 由写有<学习源码整体架构系列>20余篇的若川[若川视野公众号号主] ...

  8. C++简介源码讲解精辟版,C++入门级C++学习,C++与C的区别值得知晓

    C++简介源码讲解精辟版,C++入门级C++学习,C++与C的区别值得知晓 C语言和C++基础区别 C++标准输入和输出 命名空 1.命名空间的定义 : namespace 标识符{ } 例:name ...

  9. 视觉机器学习20讲-MATLAB源码示例(6)-贝叶斯学习算法

    视觉机器学习20讲-MATLAB源码示例(6)-贝叶斯学习算法 1. 贝叶斯学习算法 2. Matlab仿真 3. 小结 1. 贝叶斯学习算法 贝叶斯分类算法是统计学的一种分类方法,它是一类利用概率统 ...

最新文章

  1. oracle 截取 tr,oracle中实现截取字符串(substr)、查找字符串位置(instr)、替换字符串(replace)...
  2. 这算不算职场PUA?
  3. 小贝拉机器人是朋友_被Angelababy、周震南等摸头杀?机器人贝拉凭什么受宠
  4. python多久能上手_Python容易上手的爬虫项目,特别适合基础入门
  5. 如何快速清空一个文件内容
  6. 计算机工具栏查看,win10工具栏显示网速小工具_技术教程
  7. 单片机喇叭如何响出报警声音 C语言程序,单片机报警器声音产生的方法(报警声音)...
  8. EXCEL保存“加载宏”
  9. scrapy中文文档基础知识
  10. 【提升笔记本续航】WIN10笔记本打开电源选项中的处理器电源管理
  11. K650c + Ubuntu 15.04无法正常关机,重启
  12. 华为网页手机云服务器,华为Cloud 云服务
  13. 数据挖掘里的开源问题(PAKDD 2009 WORKSHOP CALL FOR PAPER)
  14. 对抛物线准线与焦点弦的思考与总结
  15. Prometheus(一)——概述、监控体系、生态组件、部署
  16. 2-6_Cleaning_Data清洗数据
  17. 【操作系统的目标和作用】
  18. 2021肥西实验高级中学高考成绩查询,高三年级召开2021年合肥市第二次教学质量检测成绩分析会...
  19. ASO第一步-什么是ASO,与SEO的区别?
  20. HTML+CSS实现拼多多官网首页

热门文章

  1. 上学与不上学的区别_这是我在全球最大的React会议上学到的
  2. react 组件构建_让我们用100行JavaScript构建一个React Chat Room组件
  3. python连接wws协议和http协议时ssl验证失败
  4. Python 为什么没有 main 函数?为什么我不推荐写 main 函数?
  5. python之邮件发送自动化
  6. CSS 类选择器详解——CSS 多类选择器
  7. google地图 反向地址解析(地址查询)
  8. Page.ClientScript.RegisterStartupScript() 方法与Page.ClientScript.RegisterClientScriptBlock() 方法
  9. 《图像超分》一些论文走读(SRCNN ,ESPCN ,VDSR ,SRGAN)
  10. PRML-系列一之1.2