OpenGL入门006——着色器在纹理混合中的应用

本节将理解顶点和片段着色器在纹理混合中的应用

一些概念

纹理

  • 概述: 纹理是一张二维图像或一组数据,用来给3D模型赋予更多细节,模拟出更为复杂和逼真的外观。比如一块简单的立方体可以通过纹理变得像是由木材、金属或石头制作,而不需要为此增加实际的几何复杂度

纹理映射: 是将纹理图像的坐标(通常是二维UV坐标)映射到3D模型的表面,UV坐标系用于指定纹理在模型表面上的位置,其中:

  • U表示横向坐标(从左到右)
  • V表示纵向坐标(从上到下)

每个模型顶点都有一个或多个UV坐标,使得纹理能够精确地贴合到模型表面

纹理类型:

  • 颜色纹理:最常见的纹理类型,用于赋予物体颜色和图案
  • 法线纹理:用于模拟复杂的表面细节,如凹凸感和细小的表面特征,而无需修改几何体
  • 位移纹理:用来改变模型的几何结构,实际移动顶点以产生真实的凹凸效果
  • 环境贴图:用于模拟反射或折射效果,创建逼真的镜面或水面效果

时间依赖动画

概述: 使用时间变量来控制动画的状态变换,以确保动画帧以固定的速度播放或根据外部条件动态调整。

时间步进和概率: 动画通常分为帧,每一帧代表动画在特定时间点上的状态,为了使动画平滑,必须考虑帧率(每秒显示的帧数)和时间步进,常见方法包括固定时间步进和可变时间步进

插值方法: 时间依赖动画通常使用插值技术,在两个关键帧之间平滑过渡:

  • 线性插值:在起始点和结束点之间按线性比例过渡,适合简单的动画
  • 贝塞尔曲线和样条插值:用于创建更复杂、平滑的曲线运行,适合有自然过渡需求的动画,如角色行走或物体掉落

实战

简介

怎么在vscode上使用cmake构建项目,具体可以看这篇Windows上如何使用CMake构建项目 - 凌云行者的博客

目的: 本节将理解顶点和片段着色器在纹理混合中的应用

环境:

  • 编译工具链:使用msys2安装的mingw-gcc
  • 依赖项:glfw3:x64-mingw-static,glad:x64-mingw-static(通过vcpkg安装)

dependencies

shader.fs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 指定OpenGL着色器语言的版本为3.30
#version 330 core
// 输入变量,表示纹理坐标
in vec2 TexCoord;
// 输出变量,表示片段的最终颜色
out vec4 FragColor;
// uniform变量,表示第一个纹理
uniform sampler2D texture0;
// uniform变量,表示第二个纹理
uniform sampler2D texture1;
// uniform变量,表示混合比例
uniform float blendRatio;

void main() {
// 使用mix函数根据blendRatio混合两个纹理的颜色,并将结果赋值给输出变量FragColor
FragColor = mix(texture(texture0, TexCoord), texture(texture1, TexCoord), blendRatio);
}

shader.vs

1
2
3
4
5
6
7
8
9
10
#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec2 aTex;

out vec2 TexCoord;

void main() {
gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0);
TexCoord = aTex;
}

teenager.png

tex.png

utils

windowFactory.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#pragma once
#include <glad/glad.h> // gald前面不能包含任何opengl头文件
#include <GLFW/glfw3.h>
#include <functional>
#include <iostream>

using std::cout;
using std::endl;

class GLFWWindowFactory {
public:
// 默认构造函数
GLFWWindowFactory() {}
// 构造函数,初始化窗口
GLFWWindowFactory(int width, int height, const char* title) {
// 初始化glfw
glfwInit();
// 设置opengl版本
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
// 使用核心模式:确保不使用任何被弃用的功能
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

// 创建glfw窗口
this->window = glfwCreateWindow(width, height, title, NULL, NULL);
if (this->window == NULL) {
cout << "Failed to create GLFW window" << endl;
glfwTerminate();
exit(-1);
}
// 设置当前窗口的上下文
glfwMakeContextCurrent(this->window);
// 设置窗口大小改变的回调函数
glfwSetFramebufferSizeCallback(this->window, framebuffer_size_callback);
// 加载所有opengl函数指针
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
cout << "Failed to initialize GLAD" << endl;
}
// 再次设置当前窗口的上下文,确保当前上下文仍然是刚刚创建的窗口,是一个安全措施
glfwMakeContextCurrent(this->window);
// 设置窗口大小改变的回调函数
glfwSetFramebufferSizeCallback(this->window, framebuffer_size_callback);
}

// 获取窗口对象
GLFWwindow* getWindow() {
return this->window;
}

// 运行窗口,传入一个自定义的更新函数
void run(std::function<void()> updateFunc) {
// 启用深度测试,opengl将在绘制每个像素之前比较其深度值,以确定该像素是否应该被绘制
glEnable(GL_DEPTH_TEST);

// 循环渲染
while (!glfwWindowShouldClose(this->window)) { // 检查是否应该关闭窗口
// 清空屏幕所用的颜色
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
// 清空颜色缓冲,主要目的是为每一帧的渲染准备一个干净的画布
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// 处理输入
GLFWWindowFactory::process_input(this->window);

// 执行更新函数
updateFunc();

// 交换缓冲区
glfwSwapBuffers(this->window);
// 处理所有待处理事件,去poll所有事件,看看哪个没处理的
glfwPollEvents();
}

// 终止GLFW,清理GLFW分配的资源
glfwTerminate();
}

// 窗口大小改变的回调函数
static void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
// 确保视口与新窗口尺寸匹配,注意在视网膜显示器上,宽度和高度会显著大于指定值
glViewport(0, 0, width, height);
}

// 处理输入
static void process_input(GLFWwindow* window) {
// 按下ESC键时进入if块
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
// 关闭窗口
glfwSetWindowShouldClose(window, true);
}

private:
// 窗口对象
GLFWwindow* window;
};

shader.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
#ifndef SHADER_H
#define SHADER_H

#include <glad/glad.h>
#include <glm/glm.hpp>

#include <string>
#include <fstream>
#include <sstream>
#include <iostream>

using std::string;
using std::ifstream;
using std::stringstream;
using std::cout;
using std::endl;

class Shader {
public:
// 默认构造函数
Shader() {}
// 着色器程序ID
unsigned int ID;

// 构造函数
Shader(const char* vertexPath, const char* fragmentPath) {
string vertexCode;
string fragmentCode;
ifstream vShaderFile;
ifstream fShaderFile;

// 确保ifstream对象可以抛出异常
vShaderFile.exceptions(ifstream::failbit | ifstream::badbit);
fShaderFile.exceptions(ifstream::failbit | ifstream::badbit);
try {
// 打开文件
vShaderFile.open(vertexPath);
fShaderFile.open(fragmentPath);
// 读取文件缓冲区内容到stream中
stringstream vShaderStream, fShaderStream;
vShaderStream << vShaderFile.rdbuf();
fShaderStream << fShaderFile.rdbuf();
// 关闭文件处理器
vShaderFile.close();
fShaderFile.close();
// 将stream转换为字符串
vertexCode = vShaderStream.str();
fragmentCode = fShaderStream.str();
} catch (ifstream::failure& e) {
cout << "ERROR::SHADER::FILE_NOT_SUCCESSFULLY_READ: " << e.what() << endl;
}
const char* vShaderCode = vertexCode.c_str();
const char* fShaderCode = fragmentCode.c_str();
// 编译着色器
unsigned int vertex, fragment;
// 顶点着色器
vertex = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex, 1, &vShaderCode, NULL);
glCompileShader(vertex);
checkCompileErrors(vertex, "VERTEX");
// 片段着色器
fragment = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment, 1, &fShaderCode, NULL);
glCompileShader(fragment);
checkCompileErrors(fragment, "FRAGMENT");
// 着色器程序
ID = glCreateProgram();
glAttachShader(ID, vertex);
glAttachShader(ID, fragment);
glLinkProgram(ID);
checkCompileErrors(ID, "PROGRAM");
// 删除着色器
glDeleteShader(vertex);
glDeleteShader(fragment);
}

// 激活着色器
void use() {
glUseProgram(ID);
}

// 实用的uniform工具函数
// 用于在着色器程序中设置uniform值
// 设置一个布尔类型的uniform变量
void setBool(const std::string& name, bool value) const {
glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value);
}

// 设置一个整型的uniform变量
void setInt(const std::string& name, int value) const {
glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
}

// 设置一个浮点类型的uniform变量
void setFloat(const std::string& name, float value) const {
glUniform1f(glGetUniformLocation(ID, name.c_str()), value);
}

// 设置一个vec2类型的uniform变量
void setVec2(const std::string& name, const glm::vec2& value) const {
glUniform2fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}
// 设置一个vec2类型的uniform变量
void setVec2(const std::string& name, float x, float y) const {
glUniform2f(glGetUniformLocation(ID, name.c_str()), x, y);
}

// 设置一个vec3类型的uniform变量
void setVec3(const std::string& name, const glm::vec3& value) const {
glUniform3fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}
// 设置一个vec3类型的uniform变量
void setVec3(const std::string& name, float x, float y, float z) const {
glUniform3f(glGetUniformLocation(ID, name.c_str()), x, y, z);
}

// 设置一个vec4类型的uniform变量
void setVec4(const std::string& name, const glm::vec4& value) const {
glUniform4fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}
// 设置一个vec4类型的uniform变量
void setVec4(const std::string& name, float x, float y, float z, float w) {
glUniform4f(glGetUniformLocation(ID, name.c_str()), x, y, z, w);
}

// 设置一个mat2类型的uniform变量
void setMat2(const std::string& name, const glm::mat2& mat) const {
glUniformMatrix2fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}

// 设置一个mat3类型的uniform变量
void setMat3(const std::string& name, const glm::mat3& mat) const {
glUniformMatrix3fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}

// 设置一个mat4类型的uniform变量
void setMat4(const std::string& name, const glm::mat4& mat) const {
glUniformMatrix4fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}

private:
// 检查着色器编译/链接错误
void checkCompileErrors(GLuint shader, string type) {
GLint success;
GLchar infoLog[1024];
if (type != "PROGRAM") {
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(shader, 1024, NULL, infoLog);
cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << endl;
}
}
else {
glGetProgramiv(shader, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shader, 1024, NULL, infoLog);
cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << endl;
}
}
}
};
#endif

RectangleModel.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#pragma once
#include <glad/glad.h>
#include "shader.h"
#include <vector>
#include <chrono>
#include <cmath>

class RectangleModel {
public:
// 构造函数
RectangleModel(const Shader& shader);
// 析构函数
~RectangleModel();

// 绘制矩形
void draw();

private:
unsigned int VAO;
unsigned int VBO;
unsigned int EBO;
Shader shader;

// 着色器程序
unsigned int shaderProgram;
// 纹理
std::vector<unsigned int>texture;


// 编译着色器
void compileShaders();
// 设置缓冲区
void setElements();
// 加载纹理
void loadTexture();
// 绑定纹理
void bindTexture(GLuint& textured, const char* path);
};

RectangleModel.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
#include "RectangleModel.h"
#include <iostream>
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

using std::cout;
using std::endl;

// 构造函数
RectangleModel::RectangleModel(const Shader& shader) : shader(shader) {
// 加载纹理
loadTexture();
// 设置缓冲区
setElements();
}

// 析构函数
RectangleModel::~RectangleModel() {
// 删除VAO
glDeleteVertexArrays(1, &this->VAO);
// 删除VBO
glDeleteBuffers(1, &this->VBO);
// 删除EBO
glDeleteBuffers(1, &this->EBO);
}

/// public
// 绘制矩形
void RectangleModel::draw() {
// 使用着色器程序
this->shader.use();
// 绑定VAO
glBindVertexArray(VAO);

// 获取当前时间
auto now = std::chrono::system_clock::now();
// 计算毫秒数
auto duration = now.time_since_epoch();
double milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
// 计算混合比例
float blendRatio = 0.5f * (std::sin(milliseconds / 1000.0) + 1.0f);
// 设置uniform变量
shader.setFloat("blendRatio", blendRatio);

// 绘制矩形,即绘制两个三角形,GL_UNSIGNED_INT表示索引数组中的每个元素都是一个无符号整数
// 六个矩形
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);
}

/// private
void RectangleModel::setElements() {
float vertices[] = {
// 矩形1
-0.9f, -0.9f, 0.0f, 0.0f,
-0.5f, -0.9f, 1.0f, 0.0f,
-0.9f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 1.0f, 1.0f,

// 矩形2
-0.4f, -0.9f, 0.0f, 0.0f,
0.0f, -0.9f, 1.0f, 0.0f,
-0.4f, -0.5f, 0.0f, 1.0f,
0.0f, -0.5f, 1.0f, 1.0f,

// 矩形3
0.1f, -0.9f, 0.0f, 0.0f,
0.5f, -0.9f, 1.0f, 0.0f,
0.1f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, 1.0f, 1.0f,

// 矩形4
-0.9f, 0.1f, 0.0f, 0.0f,
-0.5f, 0.1f, 1.0f, 0.0f,
-0.9f, 0.5f, 0.0f, 1.0f,
-0.5f, 0.5f, 1.0f, 1.0f,

// 矩形5
-0.4f, 0.1f, 0.0f, 0.0f,
0.0f, 0.1f, 1.0f, 0.0f,
-0.4f, 0.5f, 0.0f, 1.0f,
0.0f, 0.5f, 1.0f, 1.0f,

// 矩形6
0.1f, 0.1f, 0.0f, 0.0f,
0.5f, 0.1f, 1.0f, 0.0f,
0.1f, 0.5f, 0.0f, 1.0f,
0.5f, 0.5f, 1.0f, 1.0f
};
// 索引数据
int indices[] = {
// 矩形 1
0, 1, 2,
1, 2, 3,

// 矩形 2
4, 5, 6,
5, 6, 7,

// 矩形 3
8, 9, 10,
9, 10, 11,

// 矩形 4
12, 13, 14,
13, 14, 15,

// 矩形5
16, 17, 18,
17, 18, 19,

// 矩形6
20, 21, 22,
21, 22, 23
};

// 生成一个VAO
glGenVertexArrays(1, &this->VAO);
// 绑定VAO,使其成为当前操作的VAO
glBindVertexArray(this->VAO);
// 生成一个VBO
glGenBuffers(1, &this->VBO);
// 绑定VBO, 使其成为当前操作的VBO,GL_ARRAY_BUFFER表示顶点缓冲区
glBindBuffer(GL_ARRAY_BUFFER, this->VBO);
// 为当前绑定的VBO创建并初始化数据存储,GL_STATIC_DRAW表示数据将一次性提供给缓冲区,并且在之后的绘制过程中不会频繁更改
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 生成一个EBO
glGenBuffers(1, &this->EBO);
// 绑定EBO,使其成为当前操作的EBO
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
// 传递索引数据
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

// 定义顶点属性的布局
// - index:顶点属性的索引
// - size:每个顶点属性的数量
// - type:数据类型
// - normalized:是否将非浮点数值归一化
// - stride:连续顶点属性之间的间隔
// - pointer:数据在缓冲区中的偏移量
// 设置顶点属性指针,位置属性
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
// 启用顶点属性
glEnableVertexAttribArray(0);
// 设置顶点属性指针,颜色属性
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
// 启用顶点属性
glEnableVertexAttribArray(1);
}

// 加载纹理
void RectangleModel::loadTexture() {
std::vector<std::string> path = { "teenager.png", "tex.png" };
texture.resize(path.size());
for (int i = 0; i < path.size(); i++) {
bindTexture(texture[i], path[i].c_str());
}
// 参数传入指定要激活的纹理单元,例如GL_TEXTURE0/1/*
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture[0]);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture[1]);

// 使用着色器程序
shader.use();
// 设置uniform变量
shader.setInt("texture0", 0);
shader.setInt("texture1", 1);
}

// 绑定纹理
void RectangleModel::bindTexture(GLuint& textureId, const char* path) {
glGenTextures(1, &textureId);
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_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 加载并生成纹理
stbi_set_flip_vertically_on_load(true);
int width, height, nrChannels;
unsigned char* data = stbi_load(path, &width, &height, &nrChannels, 0);
if (data) {
GLenum format;
if (nrChannels == 4)
format = GL_RGBA;
else if (nrChannels == 3)
format = GL_RGB;
else
format = GL_RED;

glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
}
else {
std::cout << "Failed to load texture" << std::endl;
}
stbi_image_free(data);
}

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include "utils/RectangleModel.h"
#include "utils/windowFactory.h"

int main() {
// 创建一个窗口Factory对象
GLFWWindowFactory myWindow(800, 600, "This is Title");
// 创建一个着色器对象
Shader shader("shader.vs", "shader.fs");
// 创建一个矩形模型对象
RectangleModel rectangle(shader);

// 运行窗口,传入一个lambda表达式,用于自定义渲染逻辑
myWindow.run([&]() {
// 绘制矩形
rectangle.draw();
});

return 0;
}

CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 设置CMake的最低版本要求
cmake_minimum_required(VERSION 3.10)
# 设置项目名称
project(Shader)

# vcpkg集成, 这里要换成你自己的vcpkg工具链文件和共享库路径
set(VCPKG_ROOT D:/software6/vcpkg/)
set(CMAKE_TOOLCHAIN_FILE ${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake)
set(CMAKE_PREFIX_PATH ${VCPKG_ROOT}/installed/x64-mingw-static/share)

# 查找所需的包
find_package(glad CONFIG REQUIRED)
find_package(glfw3 CONFIG REQUIRED)
find_package(glm CONFIG REQUIRED)

# 搜索并收集utils文件夹下的所有源文件
file(GLOB UTILS "utils/*.cpp", "utils/*.h")

# 添加可执行文件(还要加入utils文件夹下的源文件)
add_executable(Shader main.cpp ${UTILS})

# 链接所需的库
target_link_libraries(Shader PRIVATE glad::glad glfw glm::glm)

# 检查项目是否有dependeicies目录,如果存在,则在使用add_custom_command命令在构建后将dependencies目录中的文件复制到项目的输出目录
set(SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/dependencies")
if(EXISTS ${SOURCE_DIR})
add_custom_command(TARGET Shader POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${SOURCE_DIR} $<TARGET_FILE_DIR:Shader>)
endif()

最终效果


OpenGL入门006——着色器在纹理混合中的应用
http://example.com/2024/11/05/OpenGL入门006——着色器在纹理混合中的应用/
作者
凌云行者
发布于
2024年11月5日
许可协议