OpenGL光照:颜色

news/2024/10/21 9:08:44/

知识点归纳

 现实世界中有无数种颜色,每一个物体都有它们自己的颜色。我们要做的工作是使用(有限的)数字来模拟真实世界中(无限)的颜色,因此并不是所有的现实世界中的颜色都可以用数字来表示。然而我们依然可以用数字来代表许多种颜色,并且你甚至可能根本感觉不到他们与真实颜色之间的差异。颜色可以数字化的由红色(Red)、绿色(Green)和蓝色(Blue)三个分量组成,它们通常被缩写为RGB。这三个不同的分量组合在一起几乎可以表示存在的任何一种颜色。
我们在现实生活中看到某一物体的颜色并不是这个物体的真实颜色,而是它所反射(Reflected)的颜色。换句话说,那些不能被物体吸收(Absorb)的颜色(被反射的颜色)就是我们能够感知到的物体的颜色。例如,太阳光被认为是由许多不同的颜色组合成的白色光。如果我们将白光照在一个蓝色的玩具上,这个蓝色的玩具会吸收白光中除了蓝色以外的所有颜色,不被吸收的蓝色光被反射到我们的眼中,使我们看到了一个蓝色的玩具。
白色的阳光是一种所有可见颜色的集合,物体吸收了其中的大部分颜色,它仅反射了那些代表这个物体颜色的部分,这些被反射颜色的组合就是我们感知到的颜色
当我们把光源的颜色与物体的颜色相乘,所得到的就是这个物体所反射该光源的颜色(也就是我们感知到的颜色)
 来看一下光源色和物体颜色的反射运算:

// 光源的颜色值lightColor(RGB每个分量值可取范围为0到1)
glm::vec3 lightColor(1.0f, 1.0f, 1.0f);
// 物体的颜色值toyColor
glm::vec3 toyColor(1.0f, 0.5f, 0.31f);
// 当光源直射物体时,反射后的反射光颜色值result
glm::vec3 result = lightColor * toyColor; // = (1.0f, 0.5f, 0.31f);

 我们可以看到玩具在进行反射时吸收了白色光源颜色中的大部分颜色,但它对红、绿、蓝三个分量都有一定的反射,反射量是由物体本身的颜色所决定的。这也代表着现实中的光线原理。由此,我们可以定义物体的颜色为这个物体从一个光源反射各个颜色分量的多少
我们可以通过物体对不同颜色光的反射来的得到意想不到的不到的颜色,从此创作颜色已经变得非常简单。
 教程原文

程序归纳

 该章节程序的最终运行结果:
在这里插入图片描述
 通用的顶点着色器:

#version 330 core
layout( location = 0 ) in vec3 position;uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;void main()
{gl_Position = projection * view * model * vec4(position, 1.0f);
}

 普通物体的片段着色器

#version 330 core
out vec4 color;uniform vec3 objectColor;
uniform vec3 lightColor;void main()
{color = vec4(lightColor * objectColor, 1.0f);
}

 光源的片段着色器

#version 330 core
out vec4 color;void main()
{color = vec4(1.0f); //设置四维向量的所有元素为 1.0f
}

 主程序

// 标准输出
#include <string>// GLEW
#define GLEW_STATIC
#include <GL/glew.h>// GLFW
#include <GLFW/glfw3.h>// 着色器类和摄像机类
#include "Shader.h"
#include "Camera.h"// GLM 数学库
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>// 图片数据读取库
#include <SOIL.h>// 定义窗口大小
GLuint screenWidth = 800, screenHeight = 600;// GLFW窗口需要注册的函数
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 Do_Movement();// 定义摄像机
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
// 定义数组存储按键信息
bool keys[1024];
// 
GLfloat lastX = 400, lastY = 300;
bool firstMouse = true;GLfloat deltaTime = 0.0f;
GLfloat lastFrame = 0.0f;glm::vec3 lightPos(1.2f, 0.0f, -2.0f);// The MAIN function, from here we start our application and run our Game loop
int main()
{// 初始化GLFW glfwInit();// 设置glfw使用的OpenGL版本glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);// 设置使用OpenGL的核心模式glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);// 设置窗口大小可改变性 为不可变glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);// GLFW会自动创建一个每像素4个子采样点的深度和样本缓冲。这也意味着所有缓冲的大小都增长了4倍。glfwWindowHint(GLFW_SAMPLES, 4);// 创建GLFW的窗口GLFWwindow* window = glfwCreateWindow(screenWidth, screenHeight, "LearnOpenGL", nullptr, nullptr);// 设置window窗口线程为主线程glfwMakeContextCurrent(window);// 注册窗口的事件glfwSetKeyCallback(window, key_callback);         // 按键检测glfwSetCursorPosCallback(window, mouse_callback); // 鼠标移动检测 glfwSetScrollCallback(window, scroll_callback);   // 滚轮滑动检测// 设置隐藏光标模式glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);// 设置GLEW为更现代的模式glewExperimental = GL_TRUE;// 初始化GLEWglewInit();// 设置视口的位置和大小(位置是相对于窗口左下角的)glViewport(0, 0, screenWidth, screenHeight);// 开启深度测试功能glEnable(GL_DEPTH_TEST);// 根据顶点着色器和片段着色器位置创建着色器程序Shader lightingShader("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\vertexShader.txt", "C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\fragmentShader.txt");Shader lampShader("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\vertexShader.txt", "C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\lightFragmentShader.txt");// 顶点数据(位置+纹理坐标)GLfloat vertices[] = {-0.5f, -0.5f, -0.5f,0.5f, -0.5f, -0.5f,0.5f,  0.5f, -0.5f,0.5f,  0.5f, -0.5f,-0.5f,  0.5f, -0.5f,-0.5f, -0.5f, -0.5f,-0.5f, -0.5f,  0.5f,0.5f, -0.5f,  0.5f,0.5f,  0.5f,  0.5f,0.5f,  0.5f,  0.5f,-0.5f,  0.5f,  0.5f,-0.5f, -0.5f,  0.5f,-0.5f,  0.5f,  0.5f,-0.5f,  0.5f, -0.5f,-0.5f, -0.5f, -0.5f,-0.5f, -0.5f, -0.5f,-0.5f, -0.5f,  0.5f,-0.5f,  0.5f,  0.5f,0.5f,  0.5f,  0.5f,0.5f,  0.5f, -0.5f,0.5f, -0.5f, -0.5f,0.5f, -0.5f, -0.5f,0.5f, -0.5f,  0.5f,0.5f,  0.5f,  0.5f,-0.5f, -0.5f, -0.5f,0.5f, -0.5f, -0.5f,0.5f, -0.5f,  0.5f,0.5f, -0.5f,  0.5f,-0.5f, -0.5f,  0.5f,-0.5f, -0.5f, -0.5f,-0.5f,  0.5f, -0.5f,0.5f,  0.5f, -0.5f,0.5f,  0.5f,  0.5f,0.5f,  0.5f,  0.5f,-0.5f,  0.5f,  0.5f,-0.5f,  0.5f, -0.5f};// 设置VBO和VAOGLuint VBO, VAO;// 先申请VAO和VBO的显存glGenVertexArrays(1, &VAO);glGenBuffers(1, &VBO);// 绑定VAOglBindVertexArray(VAO);// 绑定VBOglBindBuffer(GL_ARRAY_BUFFER, VBO);// 将顶点数据传输到显存中glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);// 位置数据解析glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);glEnableVertexAttribArray(0);glBindVertexArray(0); // 解绑VAOGLuint lightVAO;glGenVertexArrays(1, &lightVAO);glBindVertexArray(lightVAO);// 只需要绑定VBO不用再次设置VBO的数据,因为容器(物体)的VBO数据中已经包含了正确的立方体顶点数据glBindBuffer(GL_ARRAY_BUFFER, VBO);// 设置灯立方体的顶点属性指针(仅设置灯的顶点数据)glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);glEnableVertexAttribArray(0);glBindVertexArray(0);// 游戏循环while (!glfwWindowShouldClose(window)){// 计算帧相差时间GLfloat currentFrame = glfwGetTime();deltaTime = currentFrame - lastFrame;lastFrame = currentFrame;// 窗口的事件检测glfwPollEvents();// 根据事件移动摄像机Do_Movement();// 设置清空屏幕颜色缓存所使用颜色glClearColor(0.1f, 0.1f, 0.1f, 1.0f);// 清空 屏幕颜色缓存、深度缓存glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// 被光源照亮的普通物体的着色器lightingShader.Use();GLint objectColorLoc = glGetUniformLocation(lightingShader.Program, "objectColor");GLint lightColorLoc = glGetUniformLocation(lightingShader.Program, "lightColor");glUniform3f(objectColorLoc, 1.0f, 0.5f, 0.31f);// 我们所熟悉的珊瑚红glUniform3f(lightColorLoc, 1.0f, 1.0f, 1.0f); // 依旧把光源设置为白色// 创建观察矩阵( 只要创建矩阵记得初始化= glm::mat4(1.0f) )glm::mat4 view = glm::mat4(1.0f);// 获取观察矩阵(根据摄像机的状态)view = camera.GetViewMatrix();// 创建投影矩阵glm::mat4 projection = glm::mat4(1.0f);// 计算投影矩阵(fov视野为摄像机的属性camera.Zoom)projection = glm::perspective(camera.Zoom, (float)screenWidth / (float)screenHeight, 0.1f, 1000.0f);// 计算顶点着色器中矩阵的位置值GLint modelLoc = glGetUniformLocation(lightingShader.Program, "model");GLint viewLoc = glGetUniformLocation(lightingShader.Program, "view");GLint projLoc = glGetUniformLocation(lightingShader.Program, "projection");// 将观察矩阵和投影矩阵传入对应的位置(记得转换)glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projection));// 定义模型矩阵glBindVertexArray(VAO);glm::mat4 model = glm::mat4(1.0f);glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));glDrawArrays(GL_TRIANGLES, 0, 36);glBindVertexArray(0);// 使用光源着色器lampShader.Use();modelLoc = glGetUniformLocation(lampShader.Program, "model");viewLoc = glGetUniformLocation(lampShader.Program, "view");projLoc = glGetUniformLocation(lampShader.Program, "projection");glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projection));model = glm::mat4(1.0f);model = glm::translate(model, lightPos);model = glm::scale(model, glm::vec3(1.0f)); // Make it a smaller cubeglUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));glBindVertexArray(lightVAO);glDrawArrays(GL_TRIANGLES, 0, 36);glBindVertexArray(0);// 交换前后缓冲区glfwSwapBuffers(window);}// 释放VAO和VBO的显存glDeleteVertexArrays(1, &VAO);glDeleteVertexArrays(1, &lightVAO);glDeleteBuffers(1, &VBO);// 释放glfw的内存glfwTerminate();return 0;
}// 根据按键信息移动摄像机
void Do_Movement()
{// 如果某个键按下,就执行摄像机对应的方法(更新摄像机的位置)if (keys[GLFW_KEY_W])camera.ProcessKeyboard(FORWARD, deltaTime);if (keys[GLFW_KEY_S])camera.ProcessKeyboard(BACKWARD, deltaTime);if (keys[GLFW_KEY_A])camera.ProcessKeyboard(LEFT, deltaTime);if (keys[GLFW_KEY_D])camera.ProcessKeyboard(RIGHT, deltaTime);
}// 按键回调函数
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{// 如果按下ESE则设置窗口应该关闭if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)glfwSetWindowShouldClose(window, GL_TRUE);if (key >= 0 && key < 1024){// 如果按下某一个键,则设置其keys为true,如果松开则设置回falseif (action == GLFW_PRESS)keys[key] = true;else if (action == GLFW_RELEASE)keys[key] = false;}
}// 鼠标移动的回调函数
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{// 第一次进窗口鼠标坐标很大,所以第一次调用函数我们需要把它设置为一个正常的值if (firstMouse){lastX = xpos;lastY = ypos;firstMouse = false;}// 计算鼠标在竖直方向和水平方向的偏移(屏幕坐标系往右x大,但往下是y大,所以运算顺序不同)GLfloat xoffset = xpos - lastX;GLfloat yoffset = lastY - ypos;  // Reversed since y-coordinates go from bottom to left// 更新鼠标的坐标lastX = xpos;lastY = ypos;// 调用摄像机的鼠标移动函数(根据位置偏移更新摄像机方式)camera.ProcessMouseMovement(xoffset, yoffset);
}// 鼠标滚动的回调函数
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{// 调用摄像机的鼠标滚动函数(根据位置偏移更新摄像机fov视野大小)camera.ProcessMouseScroll(yoffset);
}

 如果你想运行以上程序,我为你提供着色器头文件和摄像机头文件的源代码。记得更改程序中创建着色器时的路径。
 着色器头文件(Shader.h):

#ifndef SHADER_H
#define SHADER_H#include <string>
#include <fstream>
#include <sstream>
#include <iostream>#include <GL/glew.h>class Shader
{
public:GLuint Program;// Constructor generates the shader on the flyShader(const GLchar* vertexPath, const GLchar* fragmentPath){// 1. Retrieve the vertex/fragment source code from filePathstd::string vertexCode;std::string fragmentCode;std::ifstream vShaderFile;std::ifstream fShaderFile;// ensures ifstream objects can throw exceptions:vShaderFile.exceptions(std::ifstream::badbit);fShaderFile.exceptions(std::ifstream::badbit);try{// Open filesvShaderFile.open(vertexPath);fShaderFile.open(fragmentPath);std::stringstream vShaderStream, fShaderStream;// Read file's buffer contents into streamsvShaderStream << vShaderFile.rdbuf();fShaderStream << fShaderFile.rdbuf();// close file handlersvShaderFile.close();fShaderFile.close();// Convert stream into stringvertexCode = vShaderStream.str();fragmentCode = fShaderStream.str();}catch (std::ifstream::failure e){std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;}const GLchar* vShaderCode = vertexCode.c_str();const GLchar* fShaderCode = fragmentCode.c_str();// 2. Compile shadersGLuint vertex, fragment;GLint success;GLchar infoLog[512];// Vertex Shadervertex = glCreateShader(GL_VERTEX_SHADER);glShaderSource(vertex, 1, &vShaderCode, NULL);glCompileShader(vertex);// Print compile errors if anyglGetShaderiv(vertex, GL_COMPILE_STATUS, &success);if (!success){glGetShaderInfoLog(vertex, 512, NULL, infoLog);std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;}// Fragment Shaderfragment = glCreateShader(GL_FRAGMENT_SHADER);glShaderSource(fragment, 1, &fShaderCode, NULL);glCompileShader(fragment);// Print compile errors if anyglGetShaderiv(fragment, GL_COMPILE_STATUS, &success);if (!success){glGetShaderInfoLog(fragment, 512, NULL, infoLog);std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;}// Shader Programthis->Program = glCreateProgram();glAttachShader(this->Program, vertex);glAttachShader(this->Program, fragment);glLinkProgram(this->Program);// Print linking errors if anyglGetProgramiv(this->Program, GL_LINK_STATUS, &success);if (!success){glGetProgramInfoLog(this->Program, 512, NULL, infoLog);std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;}// Delete the shaders as they're linked into our program now and no longer necesseryglDeleteShader(vertex);glDeleteShader(fragment);}// Uses the current shadervoid Use(){glUseProgram(this->Program);}
};#endif

 摄像机头文件(Camera.h):

#pragma once// Std. Includes
#include <vector>// GL Includes
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>// 常量指定摄像机的移动按键
enum Camera_Movement {FORWARD,BACKWARD,LEFT,RIGHT
};// 这里定义摄像机一些属性的初始值
// 摄像机旋转的两个值
const GLfloat YAW = -90.0f;
const GLfloat PITCH = 0.0f;
// 摄像机移动速度
const GLfloat SPEED = 3.0f;
// 摄像机旋转速度
const GLfloat SENSITIVTY = 0.25f;
// 摄像机角度(关乎远近)
const GLfloat ZOOM = 45.0f;// An abstract camera class that processes input and calculates the corresponding Eular Angles, Vectors and Matrices for use in OpenGL
class Camera
{
public:// 摄像机的位置、方向、上向量、右向量、世界上向量glm::vec3 Position;glm::vec3 Front;glm::vec3 Up;glm::vec3 Right;glm::vec3 WorldUp;// 旋转角度GLfloat Yaw;GLfloat Pitch;// 移动速度和旋转速度GLfloat MovementSpeed;GLfloat MouseSensitivity;// 摄像机的视野(fov)GLfloat Zoom;// 无参构造函数将使用定义的全局变量值对摄像机对象的所有属性进行初始化Camera(glm::vec3 position = glm::vec3(0.0f, 0.0f, 0.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();}// 根据摄像机位置、朝向、上向量生成其对应的观察矩阵glm::mat4 GetViewMatrix(){return glm::lookAt(this->Position, this->Position + this->Front, this->Up);}// 当按下某一个键时,根据按键和帧时间进行移动(移动摄像机的位置)void ProcessKeyboard(Camera_Movement 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;// Make sure that when pitch is out of bounds, screen doesn't get flippedif (constrainPitch){if (this->Pitch > 89.0f)this->Pitch = 89.0f;if (this->Pitch < -89.0f)this->Pitch = -89.0f;}// 更新摄像机状态this->updateCameraVectors();}// 根据鼠标滚轮的缩放信息调整视野fov的大小void ProcessMouseScroll(GLfloat yoffset){// 鼠标滚动数值太大因此缩小一些yoffset = yoffset * 0.1f;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:// 更新摄像机状态(使用Yaw、Pitch、WorldUp,得到Front、Right、Up)void updateCameraVectors(){// 根据旋转信息Yaw和Pitch计算摄像机的朝向glm::vec3 front;front.x = cos(glm::radians(this->Yaw)) * cos(glm::radians(this->Pitch));front.y = sin(glm::radians(this->Pitch));front.z = sin(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));}
};

http://www.ppmy.cn/news/51721.html

相关文章

Linux中vim常用命令

记录下来&#xff0c;下次忘了好找。 一些常用命令 保存退出 :wq 不保存退出 :q 不保存强制退出 :q! esc进入普通模式 用Vim写一个文件&#xff0c;写完保存退出的流程 1.编辑文件&#xff0c;若文件已存在&#xff0c;则存放于原文件&#xff1b;若文件不存在&…

20230422 | 24. 两两交换链表中的节点、19.删除链表的倒数第N个节点、面试题 02.07. 链表相交、142. 环形链表 II

1、24. 两两交换链表中的节点 初始时&#xff0c;cur指向虚拟头结点&#xff0c;然后进行如下三步&#xff1a; 操作之后&#xff0c;链表如下&#xff1a; 看这个可能就更直观一些了&#xff1a; /*** Definition for singly-linked list.* public class ListNode {* i…

【MyBatis基础】SpringBoot集成MyBatisPlus的基于字段隔离的多租户实现

文章目录 1. 多租户在数据存储上的实现方式2. 基于字段的隔离方式实践3. 补充 摘要&#xff1a;多租户的含义是让多用户共用相同的系统或组件&#xff0c;但是又能保证各个用户之间数据是隔离的。多租户技术可以实现多个租户共用系统实例&#xff0c;而且同时能实现系统实例的个…

Bean作用域和生命周期

修改的Bean案例 User&#xff1a; package com.bean;import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Controller;public class User {private int id;private String name;Overridepublic String toString() {return "Use…

Tomcat 配置与部署

http 协议就是 http 客户端和 http 服务器之间通信的协议 , 而Tomcat 就是 java 圈子中最广泛使用的 http 服务器. 下载Tomcat Tomcat官网 Tomcat 的版本 , 和后续的 servlet 版本是强相关的 , 此处使用 tomcat 8 , 对应的 servlet 就是 3.1 下载一个 zip 压缩包解压缩即可 T…

什么蓝牙耳机适合学生党?学生党蓝牙耳机性价比排行

现如今&#xff0c;市场上有各种各样的品牌和蓝牙耳机&#xff0c;让人在选择时不免眼花缭乱。作为学生党&#xff0c;在选择一样东西的时候&#xff0c;性价比无疑会成为其选择的重要参考因素。下面&#xff0c;我来给大家分享几款适合学生党的高性价比蓝牙耳机&#xff0c;一…

通达信结构紧凑形态选股公式编写思路

在威廉欧奈尔的《笑傲股市》、马克米勒维尼的《股票魔法师》等书籍中都有结构紧凑形态的相关描述&#xff0c;股票在形成基底时&#xff0c;价格波动幅度逐渐减小&#xff0c;量能逐步萎缩&#xff0c;同时价格相对强度较高。 结构紧凑的形态通过眼睛观察&#xff0c;一般可以…

ChatGPT应用篇:如何快速生成精美PPT提高工作效率-附资料下载

一、ChatGPT生成markdown源代码 问&#xff1a; 我想做一份ChatGPT变现方法的PPT&#xff0c;请生成丰富的教学展示内容&#xff0c;因为生成PPT是需要MarkDown格式的&#xff0c;请您输出Markdown格式的内容 ChatGPT回复&#xff1a; 二、Mindshow登录/注册 用浏览器打开Mi…