opengl源码 实现无缝切换图片过场_OpenGL学习笔记(六)变换
本文为学习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/tagsgithub.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学习笔记(六)变换相关推荐
- opengl源码 实现无缝切换图片过场_手把手讲解 Android hook技术实现一键换肤
前言 产品大佬又提需求啦,要求app里面的图表要实现白天黑夜模式的切换,以满足不同光线下都能保证足够的图表清晰度. 怎么办?可能解决的办法很多,你可以给图表view增加一个toggle方法,参数Str ...
- 一款社区论坛小程序源码(修复登录图片发布上传问题)
简介: 这是一款社区论坛小程序源码(修复登录图片发布上传问题) 内涵强大的功能 支持多种多样的发帖模式 比如发图文,发语音,发涂鸦,发视频等 另外也可以设置为只能会员才可以发 另外还拥有礼物功能,可以 ...
- 仿抖音短视频系统源码,获取系统图片
仿抖音短视频系统源码,实现获取系统图片的相关代码如下: 首先开权限 <uses-permission android:name="android.permission.WRITE_EX ...
- 小程序源码:多功能图片处理器
这是一款多功能的一款照片处理器 UI简洁,功能也还不错 免除服务器和域名即可搭建,特别的简单好上手 一键化功能支持: 人脸融合(人脸融合,两张脸融合成一张) 换底色(相当于就是给照片的底色换色,一般都 ...
- v42.05 鸿蒙内核源码分析(中断切换) | 系统因中断活力四射 | 百篇博客分析鸿蒙源码
子曰:"知者不惑,仁者不忧,勇者不惧." <论语>:子罕篇 百篇博客系列篇.本篇为: v42.xx 鸿蒙内核源码分析(中断切换篇) | 系统因中断活力四射 硬件架构相关 ...
- Fedora 16上源码建立pydev + eclipse的OpenStack开发环境笔记草稿 ( by quqi99 )
Fedora 16上源码建立pydev + eclipse的OpenStack开发环境笔记草稿 ( by quqi99 ) 作者:张华 发表于:2012-3-30 版权声明:可以任意转载,转载时请 ...
- 可能是全网首个前端源码共读活动,诚邀加入学习
大家好,我是若川.从8月份到现在11月结束了.每周一期,一起读200行左右的源码,撰写辅助文章,截止到现在整整4个月了. 由写有<学习源码整体架构系列>20余篇的若川[若川视野公众号号主] ...
- C++简介源码讲解精辟版,C++入门级C++学习,C++与C的区别值得知晓
C++简介源码讲解精辟版,C++入门级C++学习,C++与C的区别值得知晓 C语言和C++基础区别 C++标准输入和输出 命名空 1.命名空间的定义 : namespace 标识符{ } 例:name ...
- 视觉机器学习20讲-MATLAB源码示例(6)-贝叶斯学习算法
视觉机器学习20讲-MATLAB源码示例(6)-贝叶斯学习算法 1. 贝叶斯学习算法 2. Matlab仿真 3. 小结 1. 贝叶斯学习算法 贝叶斯分类算法是统计学的一种分类方法,它是一类利用概率统 ...
最新文章
- oracle 截取 tr,oracle中实现截取字符串(substr)、查找字符串位置(instr)、替换字符串(replace)...
- 这算不算职场PUA?
- 小贝拉机器人是朋友_被Angelababy、周震南等摸头杀?机器人贝拉凭什么受宠
- python多久能上手_Python容易上手的爬虫项目,特别适合基础入门
- 如何快速清空一个文件内容
- 计算机工具栏查看,win10工具栏显示网速小工具_技术教程
- 单片机喇叭如何响出报警声音 C语言程序,单片机报警器声音产生的方法(报警声音)...
- EXCEL保存“加载宏”
- scrapy中文文档基础知识
- 【提升笔记本续航】WIN10笔记本打开电源选项中的处理器电源管理
- K650c + Ubuntu 15.04无法正常关机,重启
- 华为网页手机云服务器,华为Cloud 云服务
- 数据挖掘里的开源问题(PAKDD 2009 WORKSHOP CALL FOR PAPER)
- 对抛物线准线与焦点弦的思考与总结
- Prometheus(一)——概述、监控体系、生态组件、部署
- 2-6_Cleaning_Data清洗数据
- 【操作系统的目标和作用】
- 2021肥西实验高级中学高考成绩查询,高三年级召开2021年合肥市第二次教学质量检测成绩分析会...
- ASO第一步-什么是ASO,与SEO的区别?
- HTML+CSS实现拼多多官网首页
热门文章
- 上学与不上学的区别_这是我在全球最大的React会议上学到的
- react 组件构建_让我们用100行JavaScript构建一个React Chat Room组件
- python连接wws协议和http协议时ssl验证失败
- Python 为什么没有 main 函数?为什么我不推荐写 main 函数?
- python之邮件发送自动化
- CSS 类选择器详解——CSS 多类选择器
- google地图 反向地址解析(地址查询)
- Page.ClientScript.RegisterStartupScript() 方法与Page.ClientScript.RegisterClientScriptBlock() 方法
- 《图像超分》一些论文走读(SRCNN ,ESPCN ,VDSR ,SRGAN)
- PRML-系列一之1.2