【OpenGL】LearnOpenGL学习笔记09 - 材质、光照贴图

发布于:2025-08-18 ⋅ 阅读:(11) ⋅ 点赞:(0)

上接:https://blog.csdn.net/weixin_44506615/article/details/150411373?spm=1001.2014.3001.5501
完整代码:https://gitee.com/Duo1J/learn-open-gl

一、材质

材质就是用来描述一个物体"应该是什么样的",它具备什么视觉特征、物理特性的一系列参数和纹理的集合

在这之前,我们使用了objectColor变量来定义我们的立方体的颜色,接下来我们将其抽离出来,封装成一个材质结构体,为光照模型的每一个分量提供一个颜色,以便之后我们应用贴图来进行更细颗粒度的控制
直接上代码
FragmentShader.fs

#version 330 core

// 物体材质结构体
struct Material {
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
    float shininess;
};

// 物体受光照强度结构体
struct Light {
    vec3 position;
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

in vec3 FragPos;
in vec3 Normal;

out vec4 FragColor;

uniform vec3 lightColor;
uniform vec3 viewPos;
uniform Material material;
uniform Light light;

void main()
{
    // 环境光
    vec3 ambient = lightColor * material.ambient * light.ambient;

    // 漫反射
    vec3 normal = normalize(Normal);
    vec3 lightDir = normalize(light.position - FragPos);
    vec3 diffuse = max(dot(normal, lightDir), 0.0) * lightColor * material.diffuse * light.diffuse;

    // 镜面反射
    vec3 viewDir = normalize(viewPos - FragPos);
    vec3 reflectDir = reflect(-lightDir, normal);
    vec3 specular = pow(max(dot(viewDir, reflectDir), 0), material.shininess) * lightColor * material.specular * light.specular;

    FragColor = vec4(ambient + diffuse + specular, 1.0f);
}

Main.cpp

// 传入材质参数
shader.SetVec3("material.ambient", glm::vec3(1.0f, 0.5f, 0.31f));
shader.SetVec3("material.diffuse", glm::vec3(1.0f, 0.5f, 0.31f));
shader.SetVec3("material.specular", glm::vec3(0.5f, 0.5f, 0.5f));
shader.SetFloat("material.shininess", 32.0f);
shader.SetVec3("light.ambient", glm::vec3(0.2f, 0.2f, 0.2f));
shader.SetVec3("light.diffuse", glm::vec3(0.5f, 0.5f, 0.5f));
shader.SetVec3("light.specular", glm::vec3(1.0f, 1.0f, 1.0f));
shader.SetVec3("light.position", lightPos);

编译运行,顺利的话可以看见以下图像
材质

二、光照贴图

现在我们的立方体看起来还是一个纯色的塑料小方块,如果我们想要为我们的立方体赋予更多的细节,比如让我们的立方体变成一个木条箱,那么我们就需要用到光照贴图

1. 漫反射贴图 (Diffuse Map)

首先我们需要一张贴图资产,可以右键保存以下图片或是在顶部的git仓库中的Resource目录找到所有需要用到的资产
我们将图片命名为T_Box_Diffuse.png
漫反射贴图
接下来修改我们的Material结构体以及片段着色器
FragmentShader.fs

struct Material {
	// 将ambient和diffuse颜色改为sampler2D采样器
    sampler2D diffuse;
    vec3 specular;
    float shininess;
};

in vec2 TexCoords;

// ...
// 修改环境光和漫反射计算
void main()
{
    // 环境光
    vec3 ambient = lightColor * vec3(texture(material.diffuse, TexCoords)) * light.ambient;

    // 漫反射
    vec3 normal = normalize(Normal);
    vec3 lightDir = normalize(light.position - FragPos);
    vec3 diffuse = max(dot(normal, lightDir), 0.0) * lightColor * vec3(texture(material.diffuse, TexCoords)) * light.diffuse;

	// ...
}

在片段着色器中,我们又将用到TexCoords,也就是UV坐标,所以我们需要修改下顶点数组、顶点属性以及顶点着色器
VertexShader.vs

#version 330 core

layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;

out vec3 FragPos;
out vec3 Normal;
out vec2 TexCoords;

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

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

Main.cpp

float vertices[] = {
	// positions          // normals           // texture coords
	-0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f, 0.0f,
	 0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f, 0.0f,
	 0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f, 1.0f,
	 0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f, 1.0f,
	-0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f, 1.0f,
	-0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f, 0.0f,

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

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

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

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

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

int step = 8, curStep = 0;
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, step * sizeof(float), (void*)curStep);
glEnableVertexAttribArray(0);
curStep += 3;
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, step * sizeof(float), (void*)(curStep * sizeof(float)));
glEnableVertexAttribArray(1);
curStep += 3;
// UV坐标
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, step * sizeof(float), (void*)(curStep * sizeof(float)));
glEnableVertexAttribArray(2);
curStep += 2;

接下来我们需要在c++中加载这张漫反射贴图并传递到着色器中
之后我们会经常处理纹理贴图,方便起见,我们现在来封装一个简单的纹理类
Texture.h 新建

#pragma once

#include <iostream>

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include "stb_image.h"

/**
* 纹理类
*/
class Texture
{
public:
	/**
	* 纹理宽度
	*/
	int width = 0;

	/**
	* 纹理高度
	*/
	int height = 0;

	/**
	* 纹理通道数
	*/
	int channel = 0;

private:
	/**
	* 纹理ID
	*/
	unsigned int textureID = 0;

public:
	explicit Texture(const char* path);

	/**
	* 获取纹理ID
	*/
	unsigned int GetTextureID();
};

Texture.cpp 新建

#include "Texture.h"

Texture::Texture(const char* path)
{
	unsigned char* textureData = stbi_load(path, &width, &height, &channel, 0);
	glGenTextures(1, &textureID);

	if (textureData)
	{
		glBindTexture(GL_TEXTURE_2D, textureID);

		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);

		if (channel == 4)
		{
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, textureData);
		}
		else
		{
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, textureData);
		}

		glGenerateMipmap(GL_TEXTURE_2D);
		stbi_image_free(textureData);
		std::cout << "Load texture: " << path << " success, with: " << width << " height: " << height << " channel: " << channel << std::endl;
	}
	else
	{
		std::cout << "[Error] Failed to load texture: " << path << std::endl;
	}
}

unsigned int Texture::GetTextureID()
{
	return textureID;
}

接下来加载贴图并传入到着色器
Main.cpp

Texture boxTex("F:/Scripts/Cpp/LearnOpenGL/learn-open-gl/Resource/T_Box_Diffuse.png");

while (!glfwWindowShouldClose(window))
{
	// ...
	shader.SetInt("material.diffuse", 0);
	
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, boxTex.GetTextureID());

	glBindVertexArray(VAO);
	glDrawArrays(GL_TRIANGLES, 0, 36);
	
	// ...
}

编译运行,顺利的话可以看见以下图像
如果遇到什么问题,可以参考顶部的git仓库
漫反射贴图

2. 镜面反射贴图 (Specular Map)

我们注意到,箱子上的高光部分,无论是金属边框还是中间的木条上都有高光,但是木条是比较粗糙的,我们不希望木条上有这么强烈的高光表现,接下来就要用到镜面反射贴图
同样,你可以右键保存或是在顶部的git仓库的Resource目录找到
镜面反射贴图
这张贴图通过颜色值来表示某一像素是否需要受到高光 (黑或白),以及受到高光的强度 (灰)
首先修改我们的片段着色器
FragmentShader.fs

#version 330 core

// 物体材质结构体
struct Material {
    sampler2D diffuse;
    sampler2D specular;
    float shininess;
};

// ...

void main()
{
	// ...

    // 镜面反射
    vec3 viewDir = normalize(viewPos - FragPos);
    vec3 reflectDir = reflect(-lightDir, normal);
    vec3 specular = pow(max(dot(viewDir, reflectDir), 0), material.shininess) * lightColor * vec3(texture(material.specular, TexCoords)) * light.specular;

	// ..
}

接着,加载镜面反射贴图并传递到着色器中

Texture boxSpecularTex("F:/Scripts/Cpp/LearnOpenGL/learn-open-gl/Resource/T_Box_Specular.png");

// ...

while (!glfwWindowShouldClose(window))
{
	// ...
	shader.SetInt("material.specular", 1);
	// ...
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, boxSpecularTex.GetTextureID());
	
	glBindVertexArray(VAO);
	glDrawArrays(GL_TRIANGLES, 0, 36);

	// ...
}

编译运行,顺利的话可以看见以下图像
只有木箱的边缘金属部分会受到高光表现
镜面反射贴图
完整代码可在顶部git仓库中找到
下接:https://blog.csdn.net/weixin_44506615/article/details/150446490?spm=1001.2014.3001.5502


网站公告

今日签到

点亮在社区的每一天
去签到