OpenGL光照之基础光照

news2025/1/31 8:17:49

文章目录

  • 环境光照
  • 漫反射光照
  • 计算漫反射光照
  • 镜面光照
  • 代码

 现实世界的光照是极其复杂的,而且会受到诸多因素的影响,这是我们有限的计算能力所无法模拟的。因此OpenGL的光照使用的是简化的模型,对现实的情况进行近似,这样处理起来会更容易一些,而且看起来也差不多一样。这些光照模型都是基于我们对光的物理特性的理解。其中一个模型被称为冯氏光照模型(Phong Lighting Model)。冯氏光照模型的主要结构由3个分量组成:环境(Ambient)、漫反射(Diffuse)和镜面(Specular)光照。下面这张图展示了这些光照分量看起来的样子:
在这里插入图片描述
 环境光照(Ambient Lighting):即使在黑暗的情况下,世界上通常也仍然有一些光亮(月亮、远处的光),所以物体几乎永远不会是完全黑暗的。为了模拟这个,我们会使用一个环境光照常量,它永远会给物体一些颜色。
 漫反射光照(Diffuse Lighting):模拟光源对物体的方向性影响(Directional Impact)。它是冯氏光照模型中视觉上最显著的分量。物体的某一部分越是正对着光源,它就会越亮。
 镜面光照(Specular Lighting):模拟有光泽物体上面出现的亮点。镜面光照的颜色相比于物体的颜色会更倾向于光的颜色。
 为了创建有趣的视觉场景,我们希望模拟至少这三种光照分量。我们将以最简单的一个开始:环境光照。

环境光照

 光通常都不是来自于同一个光源,而是来自于我们周围分散的很多光源,即使它们可能并不是那么显而易见。光的一个属性是,它可以向很多方向发散并反弹,从而能够到达不是非常直接临近的点。所以,光能够在其它的表面上反射,对一个物体产生间接的影响。考虑到这种情况的算法叫做全局照明(Global Illumination)算法,但是这种算法既开销高昂又极其复杂。
 由于我们现在对那种又复杂又开销高昂的算法不是很感兴趣,所以我们将会先使用一个简化的全局照明模型,即环境光照。
 把环境光照添加到场景里非常简单。我们用光的颜色乘以一个很小的常量环境因子,再乘以物体的颜色,然后将最终结果作为片段的颜色:

void main()
{
    float ambientStrength = 0.1;
    vec3 ambient = ambientStrength * lightColor;

    vec3 result = ambient * objectColor;
    FragColor = vec4(result, 1.0);
}

 如果你现在运行你的程序,你会注意到冯氏光照的第一个阶段已经应用到你的物体上了。这个物体非常暗,但由于应用了环境光照(注意光源立方体没受影响是因为我们对它使用了另一个着色器),也不是完全黑的。它看起来应该像这样:
在这里插入图片描述

漫反射光照

 环境光照本身不能提供最有趣的结果,但是漫反射光照就能开始对物体产生显著的视觉影响了。漫反射光照使物体上与光线方向越接近的片段能从光源处获得更多的亮度。为了能够更好的理解漫反射光照,请看下图:
在这里插入图片描述
 图左上方有一个光源,它所发出的光线落在物体的一个片段上。我们需要测量这个光线是以什么角度接触到这个片段的。如果光线垂直于物体表面,这束光对物体的影响会最大化(译注:更亮)。为了测量光线和片段的角度,我们使用一个叫做法向量(Normal Vector)的东西,它是垂直于片段表面的一个向量(这里以黄色箭头表示)。

计算漫反射光照

 我们现在对每个顶点都有了法向量,但是我们仍然需要光源的位置向量和片段的位置向量。由于光源的位置是一个静态变量,我们可以简单地在片段着色器中把它声明为uniform:

uniform vec3 lightPos;

 然后在渲染循环中(渲染循环的外面也可以,因为它不会改变)更新uniform。我们使用在前面声明的lightPos向量作为光源位置:

lightingShader.setVec3("lightPos", lightPos);

 最后,我们还需要片段的位置。我们会在世界空间中进行所有的光照计算,因此我们需要一个在世界空间中的顶点位置。我们可以通过把顶点位置属性乘以模型矩阵(不是观察和投影矩阵)来把它变换到世界空间坐标。这个在顶点着色器中很容易完成,所以我们声明一个输出变量,并计算它的世界空间坐标:

out vec3 FragPos;  
out vec3 Normal;

void main()
{
    gl_Position = projection * view * model * vec4(aPos, 1.0);
    FragPos = vec3(model * vec4(aPos, 1.0));
    Normal = aNormal;
}

 最后,在片段着色器中添加相应的输入变量。

in vec3 FragPos;

 现在,所有需要的变量都设置好了,我们可以在片段着色器中添加光照计算了。
 我们需要做的第一件事是计算光源和片段位置之间的方向向量。前面提到,光的方向向量是光源位置向量与片段位置向量之间的向量差。我们同样希望确保所有相关向量最后都转换为单位向量,所以我们把法线和最终的方向向量都进行标准化:

vec3 norm = normalize(Normal);
vec3 lightDir = normalize(lightPos - FragPos);

 当计算光照时我们通常不关心一个向量的模长或它的位置,我们只关心它们的方向。所以,几乎所有的计算都使用单位向量完成,因为这简化了大部分的计算(比如点乘)。所以当进行光照计算时,确保你总是对相关向量进行标准化,来保证它们是真正地单位向量。忘记对向量进行标准化是一个十分常见的错误。
 下一步,我们对norm和lightDir向量进行点乘,计算光源对当前片段实际的漫反射影响。结果值再乘以光的颜色,得到漫反射分量。两个向量之间的角度越大,漫反射分量就会越小:

float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;

 如果两个向量之间的角度大于90度,点乘的结果就会变成负数,这样会导致漫反射分量变为负数。为此,我们使用max函数返回两个参数之间较大的参数,从而保证漫反射分量不会变成负数。负数颜色的光照是没有定义的,所以最好避免它,除非你是那种古怪的艺术家。
 现在我们有了环境光分量和漫反射分量,我们把它们相加,然后把结果乘以物体的颜色,来获得片段最后的输出颜色。

vec3 result = (ambient + diffuse) * objectColor;
FragColor = vec4(result, 1.0);

如果你的应用(和着色器)编译成功了,你可能看到类似的输出:
在这里插入图片描述

镜面光照

 和漫反射光照一样,镜面光照也决定于光的方向向量和物体的法向量,但是它也决定于观察方向,例如玩家是从什么方向看向这个片段的。镜面光照决定于表面的反射特性。如果我们把物体表面设想为一面镜子,那么镜面光照最强的地方就是我们看到表面上反射光的地方。你可以在下图中看到效果:
在这里插入图片描述
 我们通过根据法向量翻折入射光的方向来计算反射向量。然后我们计算反射向量与观察方向的角度差,它们之间夹角越小,镜面光的作用就越大。由此产生的效果就是,我们看向在入射光在表面的反射方向时,会看到一点高光。
 观察向量是我们计算镜面光照时需要的一个额外变量,我们可以使用观察者的世界空间位置和片段的位置来计算它。之后我们计算出镜面光照强度,用它乘以光源的颜色,并将它与环境光照和漫反射光照部分加和。
 要得到观察者的世界空间坐标,我们直接使用摄像机的位置向量即可(它当然就是那个观察者)。那么让我们把另一个uniform添加到片段着色器中,并把摄像机的位置向量传给着色器:

uniform vec3 viewPos;
lightingShader.setVec3("viewPos", camera.Position);

 现在我们已经获得所有需要的变量,可以计算高光强度了。首先,我们定义一个镜面强度(Specular Intensity)变量,给镜面高光一个中等亮度颜色,让它不要产生过度的影响。

float specularStrength = 0.5;

 如果我们把它设置为1.0f,我们会得到一个非常亮的镜面光分量,这对于一个珊瑚色的立方体来说有点太多了。下一节教程中我们会讨论如何合理设置这些光照强度,以及它们是如何影响物体的。下一步,我们计算视线方向向量,和对应的沿着法线轴的反射向量:

vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);

 需要注意的是我们对lightDir向量进行了取反。reflect函数要求第一个向量是从光源指向片段位置的向量,但是lightDir当前正好相反,是从片段指向光源(由先前我们计算lightDir向量时,减法的顺序决定)。为了保证我们得到正确的reflect向量,我们通过对lightDir向量取反来获得相反的方向。第二个参数要求是一个法向量,所以我们提供的是已标准化的norm向量。
 剩下要做的是计算镜面分量。下面的代码完成了这件事:

float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
vec3 specular = specularStrength * spec * lightColor;

 我们先计算视线方向与反射方向的点乘(并确保它不是负值),然后取它的32次幂。这个32是高光的反光度(Shininess)。一个物体的反光度越高,反射光的能力越强,散射得越少,高光点就会越小。在下面的图片里,你会看到不同反光度的视觉效果影响:
在这里插入图片描述
我们不希望镜面成分过于显眼,所以我们把指数保持为32。剩下的最后一件事情是把它加到环境光分量和漫反射分量里,再用结果乘以物体的颜色:

vec3 result = (ambient + diffuse + specular) * objectColor;
FragColor = vec4(result, 1.0);

我们现在为冯氏光照计算了全部的光照分量。根据你的视角,你可以看到类似下面的画面:
在这里插入图片描述

代码

main.cpp

#include <glad/glad.h> 
#include <GLFW/glfw3.h>
#include <iostream>
#include <cmath> 
#include "../shader.h"
#include "../stb_image.h"
#include "../camera.h"
#include <glm/glm.hpp> 
#include <glm/gtc/matrix_transform.hpp> 
#include <glm/gtc/type_ptr.hpp>


float vertices[] = {
	 -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
	  0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
	  0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
	  0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
	 -0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
	 -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,

	 -0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,
	  0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,
	  0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,
	  0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,
	 -0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,
	 -0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,

	 -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,
	 -0.5f,  0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
	 -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
	 -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
	 -0.5f, -0.5f,  0.5f, -1.0f,  0.0f,  0.0f,
	 -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,

	  0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,
	  0.5f,  0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
	  0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
	  0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
	  0.5f, -0.5f,  0.5f,  1.0f,  0.0f,  0.0f,
	  0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,

	 -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,
	  0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,
	  0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
	  0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
	 -0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
	 -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,

	 -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,
	  0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,
	  0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
	  0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
	 -0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
	 -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f
};


const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
// camera
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
glm::vec3 lightPos(1.2f, 1.0f, 2.0f);


float ratio = 0.5;
void processInput(GLFWwindow* window);
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
float deltaTime = 0.0f; // 距离上一帧的时间间隔 
float lastFrame = 0.0f; // 上一帧发生的时间
bool firstMouse = true;
float yaw = -90.0f;
float pitch = 0.0f;
float lastX = 800.0f / 2.0;
float lastY = 600.0 / 2.0;
float fov = 45.0f;


int main() {
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef __APPLE__ 
	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
	GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
	if (window == NULL) {
		std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();
		return -1;
	}
	//GLFW将窗口的上下文设置为当前线程的上下文
	glfwMakeContextCurrent(window);
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
	glfwSetCursorPosCallback(window, mouse_callback);
	glfwSetScrollCallback(window, scroll_callback);

	//GLAD
	// glad: 加载所有OpenGL函数指针
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
		std::cout << "Failed to initialize GLAD" << std::endl;
		return -1;
	}

	Shader ourShader("shaders/shader.vs","shaders/shader.fs");
	Shader lightShader("shaders/shader.vs", "shaders/lightShader.fs");

	//创建VBO和VAO对象,并赋予ID
	unsigned int VBO, VAO;
	glGenVertexArrays(1, &VAO);
	glGenBuffers(1, &VBO);
	//绑定VBO和VAO对象
	glBindVertexArray(VAO);
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	//为当前绑定到target的缓冲区对象创建一个新的数据存储。
	//如果data不是NULL,则使用来自此指针的数据初始化数据存储
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

	//告知Shader如何解析缓冲里的属性值
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
	//开启VAO管理的第一个属性值
	glEnableVertexAttribArray(0);

	//告知Shader如何解析缓冲里的属性值
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
	//开启VAO管理的第一个属性值
	glEnableVertexAttribArray(1);


	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindVertexArray(0);

	stbi_set_flip_vertically_on_load(true);
	unsigned int texture, texture1;
	glGenTextures(1, &texture);
	glBindTexture(GL_TEXTURE_2D, texture);

	// 加载并生成纹理 
	int width, height, nrChannels;
	unsigned char* data = stbi_load("../pics/container.jpg", &width, &height, &nrChannels, 0); 
	if (data) 
	{
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
		glGenerateMipmap(GL_TEXTURE_2D);

		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

		//float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
		//glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);

		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	}
	else 
	{
			std::cout << "Failed to load texture" << std::endl;
	}
	stbi_image_free(data);

	glGenTextures(1, &texture1);
	glBindTexture(GL_TEXTURE_2D, texture1);
	data = stbi_load("../pics/awesomeface.png", &width, &height, &nrChannels, 0);
	if (data)
	{
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
		glGenerateMipmap(GL_TEXTURE_2D);

		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_MIPMAP_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	}
	else
	{
		std::cout << "Failed to load texture" << std::endl;
	}
	stbi_image_free(data);

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, texture);
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, texture1);
	glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

	ourShader.use();
	ourShader.setInt("texture1", 0);
	ourShader.setInt("texture2", 1);

	// 渲染循环
	while (!glfwWindowShouldClose(window)) {
		processInput(window);

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

		//glClearColor(0.2f, 0.3f, 0.3f, 1.0f); //状态设置
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //状态使用


		ourShader.use();
		glm::mat4 model = glm::mat4(1.0f);
		// pass projection matrix to shader (note that in this case it could change every frame)
		glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
		ourShader.setMat4("projection", projection);

		// camera/view transformation
		glm::mat4 view = camera.GetViewMatrix();
		ourShader.setMat4("view", view);


		ourShader.setFloat("ratio", ratio);

		glEnable(GL_DEPTH_TEST);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		// glfw: 交换缓冲区和轮询IO事件(按键按下/释放、鼠标移动等)

		glBindVertexArray(VAO);

		ourShader.setMat4("model", model);
		ourShader.setVec3("objectColor", 1.0f, 0.5f, 0.31f);
		ourShader.setVec3("lightColor", 1.0f, 1.0f, 1.0f);
		ourShader.setVec3("lightPos", lightPos);
		ourShader.setVec3("viewPos", camera.Position);
		glDrawArrays(GL_TRIANGLES, 0, 36);


		lightShader.use();
		model = glm::mat4(1.0f);
		model = glm::translate(model, lightPos);
		model = glm::scale(model, glm::vec3(0.2f));
		lightShader.use();
		lightShader.setMat4("model", model);
		lightShader.setMat4("projection", projection);
		lightShader.setMat4("view", view);
		glDrawArrays(GL_TRIANGLES, 0, 36);

		glfwSwapBuffers(window);
		glfwPollEvents();
	}
	// glfw: 回收前面分配的GLFW先关资源. 
	glfwTerminate();
	glDeleteVertexArrays(1, &VAO);
	glDeleteBuffers(1, &VBO);
	glDeleteProgram(ourShader.ID);

	return 0;
}

void processInput(GLFWwindow* window) 
{
	float cameraSpeed = 2.5f * deltaTime;
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
		glfwSetWindowShouldClose(window, true);

	if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
		camera.ProcessKeyboard(FORWARD, deltaTime);
	if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
		camera.ProcessKeyboard(BACKWARD, deltaTime);
	if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
		camera.ProcessKeyboard(LEFT, deltaTime);
	if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
		camera.ProcessKeyboard(RIGHT, deltaTime);

}

void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
	glViewport(0, 0, width, height);
}

void mouse_callback(GLFWwindow* window, double xposIn, double yposIn)
{
	float xpos = static_cast<float>(xposIn);
	float ypos = static_cast<float>(yposIn);

	if (firstMouse)
	{
		lastX = xpos;
		lastY = ypos;
		firstMouse = false;
	}

	float xoffset = xpos - lastX;
	float yoffset = lastY - ypos; // reversed since y-coordinates go from bottom to top

	lastX = xpos;
	lastY = ypos;

	camera.ProcessMouseMovement(xoffset, yoffset);

}

void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) {
	fov -= (float)yoffset;
	if (fov < 1.0f) fov = 1.0f;
	if (fov > 75.0f) fov = 75.0f;
}

shader.vs

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;

out vec3 Normal;
out vec3 FragPos; 
out vec3 lightPos; 

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
    Normal = mat3(transpose(inverse(model))) * aNormal;
    gl_Position = projection * view * model * vec4(aPos, 1.0);
    FragPos = vec3(model * vec4(aPos,1.0));
}

shader.fs

#version 330 core 
out vec4 FragColor; 
in vec3 Normal;
in vec3 FragPos; 

uniform vec3 lightPos;
uniform vec3 objectColor; 
uniform vec3 lightColor; 
uniform vec3 viewPos; 
void main() 
{ 
	// ambient 
	float ambientStrength = 0.1; 
	vec3 ambient = ambientStrength * lightColor;
	// diffuse  
	vec3 norm = normalize(Normal); 
	vec3 lightDir = normalize(lightPos - FragPos);
	float diff = max(dot(norm, lightDir), 0.0); 
	vec3 diffuse = diff * lightColor; 

	// specular 
	float specularStrength = 0.5; 
	vec3 viewDir = normalize(viewPos - FragPos); 
	vec3 reflectDir = reflect(-lightDir, norm); 
	float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32); 
	vec3 specular = specularStrength * spec * lightColor; 
	vec3 result = (ambient + diffuse + specular) * objectColor;
	FragColor = vec4(result, 1.0);

}

lightShader.fs

#version 330 core 
out vec4 FragColor; 

void main() { 
	FragColor = vec4(1.0); 
}

参考链接

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

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

相关文章

MyBatis-Plus(2.0)

ActiveRecord ActiveRecord(简称AR)一直广受动态语言&#xff08;PHP、Ruby等&#xff09;的喜爱&#xff0c;而java作为准静态语言&#xff0c;对于ActiveRecord往往只能感叹器优雅 什么是ActiveRecord&#xff1f; ActiveRecord也属于ORM(对象关系映射)层&#xff0c;由Rail…

视频|人人能看懂的苹果visionOS空间设计课程

本周的重磅消息无疑是苹果Vision Pro以及对应的visionOS&#xff0c;考虑到苹果头显硬件上当前以第一方App为主&#xff0c;因此本届WWDC的一个重点就是释放visionOS和相关能力给开发者&#xff0c;让开发者尽快打造出更多、更优质的第三方App阵容。 与此同时&#xff0c;苹果也…

【vue3】10-vue组件化额外知识补充(下)-动态组件-组件缓存-v-model在组件上的应用

组件化-额外知识补充&#xff08;下&#xff09; 动态组件的使用(了解)keep-alive&#xff08;理解&#xff09;认识keep-alivekeep-alive的使用 异步组件的使用webpack分包处理&#xff08;了解&#xff09;Vue中实现异步组件 组件的v-model组件的混入Mixin&#xff08;了解&a…

阿里云弹性公网EIP收费价格表(按量/包年包月/配置费)

阿里云弹性公网EIP怎么收费&#xff1f;EIP地域不同价格不同&#xff0c;EIP计费模式分为包年包月和按量付费&#xff0c;弹性公网IP可以按带宽收费也可以按使用流量收费&#xff0c;阿里云百科分享阿里云弹性公网IP不同地域、不同计费模式、按带宽和按使用流量详细收费价格表&…

基于springboot+vue技术的在线考试系统源码数据库

源码看这里 https://download.csdn.net/download/2301_76965813/87881785 前台 学生登录&#xff1a; 如果没有账号&#xff0c;首先需要以学生的身份进行注册&#xff0c;即输入姓名、性别、密码以及班级&#xff0c;注册成功之后输入账户名和密码进行登录&#xff0c;进入…

conda虚拟环境配置和系统相关配置

一、conda虚拟环境 首先&#xff0c;尽量别在base中直接安装自己的包。原因有以下两个&#xff1a; 1.base环境是conda运行的基础&#xff0c;没法一键清除&#xff0c;如果损坏base环境要么滚回要么卸载重装&#xff0c;比较麻烦 2.base中很多包并不是项目需要的包&#xff0c…

Spring5

Spring5 文章目录 Spring5一.框架概述二.入门案例三.IOC容器1.IOC概念2.IOC底层原理3.IOC接口 四.IOC操做Bean管理1.Bean管理2.基于xml配置文件方式实现(1).创建对象(2).注入属性第一种注入:set方法第二种注入:有参数的构造p名称空间注入(了解)注入空值和特殊符号外部Bean内部B…

itop-3568开发板驱动学习笔记(27)设备树(六)pinctrl

《【北京迅为】itop-3568开发板驱动开发指南.pdf》 学习笔记 文章目录 pinctrl 子系统简介pinctrl 设备树语法pinctrl 客户端pinctrl 服务端瑞星微平台全志平台三星平台iMX 平台 pinctrl 子系统简介 Linux pinctrl 子系统用来管理 GPIO 引脚&#xff0c;它主要完成了以下三种功…

MySQL数据库,从入门到精通:第三篇——MySQL 数据库规范和基础查询语句

MySQL数据库&#xff0c;从入门到精通&#xff1a;第三篇——MySQL 数据库规范和基础查询语句 第三篇_MySQL 数据库规范和基础查询语句1. SQL概述1.1 SQL背景知识1.2 SQL语言排行榜1.3 SQL 分类 2. SQL语言的规则与规范2.2 SQL大小写规范 &#xff08;建议遵守&#xff09;2.3 …

设计模式-03.02-创建型-工厂建造者原型

工厂模式【常用】 工厂模式很重要&#xff0c;后面的很多架构设计&#xff0c;都是工厂模式联合着其它设计模式使用。 一般情况下&#xff0c;工厂模式分为三种更加细分的类型&#xff1a;简单工厂、工厂方法和抽象工厂。不过&#xff0c;在 GoF 的《设计模式》一书中&#xff…

docker资源控制cgroup

一、CPU控制 cgroups&#xff0c;是一个非常强大的linux内核工具&#xff0c;他不仅可以限制被namespace隔离起来的资源, 还可以为资源设置权重、计算使用量、操控进程启停等等。所以cgroups (Control groups) 实现了对资源的配额和度量。 cgroups有四大功能: 资源限制:可以…

SpringAOP详解

目录 前言&#xff1a; 相关概念 切点表达式规则 代码演示 SpringAOP实现原理 织入&#xff08;代理的生成时机&#xff09; JDK和CGLIB区别 小结&#xff1a; 前言&#xff1a; AOP&#xff08;Aspect Oriented Programming&#xff09;是思想&#xff08;面向切面编程…

文心一言 VS 讯飞星火 VS chatgpt (37)-- 算法导论5.4 1题

一、一个屋子里必须要有多少人&#xff0c;才能让某人和你生日相同的概率至少为1/2? 必须要有多少人&#xff0c;才能让至少两个人生日为 7月 4 日的概率大于 1/2? 文心一言&#xff1a; 一个屋子里必须要有多少人&#xff0c;才能让某人和你生日相同的概率至少为1/2&#…

Linux下ElasticSearch7.9.2安装配置(包含服务器配置、启动停止脚本、开放端口和elasticsearch-head插件的使用)

Linux下ElasticSearch7.9.2安装配置 前言1.下载安装1.1 使用wget的方式下载1.2 官网下载 2.上传到服务器并解压3.修改es配置文件3.1 es目录简介3.2 修改配置文件 4. 创建用户并赋权5. 服务器修改配置5.1 修改文件句柄数和线程数5.2 关闭swapping5.3 修改虚拟内存 6. 启动es6.1 …

“配置DHCP Snooping实验:保护网络中的DHCP服务和防止欺骗攻击“

"配置DHCP Snooping实验&#xff1a;保护网络中的DHCP服务和防止欺骗攻击" 【实验目的】 部署DHCP服务器。熟悉DHCP Snooping的配置方法。验证拓扑。 【实验拓扑】 实验拓扑如图所示。 设备参数如下表所示。 设备 接口 IP地址 子网掩码 默认网关 R1 F0/0 …

腾讯云轻量应用服务器和云服务器区别详细说明

腾讯云轻量应用服务器是什么&#xff1f;腾讯云轻量服务器和云服务器有什么区别&#xff1f;为什么轻量应用服务器费用更低&#xff1f;是因为轻量服务器CPU内存性能比云服务器CVM性能差吗&#xff1f;轻量应用服务器适合中小企业或个人开发者搭建企业官网、博客论坛、微信小程…

基于SPAD / SiPM技术的激光雷达方案

激光雷达(LiDAR)是一种测距技术&#xff0c;近年来越来越多地用于汽车先进驾驶辅助系统(ADAS)、手势识别和3D映射等应用。尤其在汽车领域&#xff0c;随着传感器融合的趋势&#xff0c;LiDAR结合成像、超声波、毫米波雷达&#xff0c;互为补足&#xff0c;为汽车提供全方位感知…

物联网Lora模块从入门到精通(五)光照与温湿度传感器

一、前言 在程序开发中&#xff0c;光照与温湿度的获取是十分常见与重要的&#xff0c;本文我们主要是使用M21温湿度光照三合一传感器&#xff0c;其中温湿度数据通过协议获取&#xff0c;而光照通过ADC获取。 二、代码实现 本文内容较为简单&#xff0c;且后续文章将在本文基…

网络编程Demo:Java的阻塞与非阻塞模式以及Netty

前言 IO既神秘&#xff0c;双简单 IO是什么 从表面理解&#xff0c;IO是输入&#xff08;input&#xff09;、输出(output)的英文首字母的缩写形式&#xff0c;可以简单理解为计算机的输入与输出&#xff0c;描述计算机的数据流动&#xff0c;如使用键盘输入了一个“hello w…

GreenPlum版本升级

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…