前些篇:
我们学习 GLSL 的尝试光照计算,但是每个 shader 中一堆的重复代码,实在让人无法忍受
这篇:我们就给 GLSL 添加 #include"your_file_name.xxx"
的功能
本人才疏学浅,如有什么错误,望不吝指出。
在 GLSL 编写 shader 时,发现不能直接使用 #include"file_name.xxx"
写 shader 起来相当麻烦
然后我还想着去添加一下 GLSL Include 的功能
结果发现 OpenGL 4.5 竟然不支持 GL_ARB_shading_language_include
?
// static API
static std::vector<std::string> g_supportExtensions;
static void GetSupportExtensions() {
if (!g_supportExtensions.empty())
return;
GLint n, i;
glGetIntegerv(GL_NUM_EXTENSIONS, &n);
for (i = 0; i < n; i++) {
std::string extension = (char*)glGetStringi(GL_EXTENSIONS, i);
g_supportExtensions.push_back(extension);
}
}
// public API
bool CheckExtension(const std::string& extensionName) {
GetSupportExtensions();
for (int i = 0; i < g_supportExtensions.size(); i++) {
if (g_supportExtensions[i] == extensionName)
return true;
}
return false;
}
...
// 测试
GetSupportExtensions();
for (size_t i = 0; i < g_supportExtensions.size(); i++) {
std::cout << "Exts : " << g_supportExtensions[i].c_str() << "\n";
}
if (!CheckExtension("GL_ARB_shading_language_include")) {
std::cerr << "Has Been Check Exts Not Supported : " << initInfo->check_exts_vec[i].c_str() << "\n";
exit(EXIT_FAILURE);
}
输出:Has Been Check Exts Not Supported : GL_ARB_shading_language_include
下面是输出的详细内容:
GLFW header version: 3.3.2
GLFW library version: 3.3.2
GLFW library version string: "3.3.2 Win32 WGL EGL OSMesa VisualC"
OpenGL context version string: "4.5.0 - Build 24.20.100.6345"
OpenGL context version parsed by GLFW: 4.5.0
OpenGL profile mask (0x00000002): compat
OpenGL context renderer string: "Intel(R) UHD Graphics 630"
OpenGL context vendor string: "Intel"
OpenGL context shading language version: "4.50 - Build 24.20.100.6345"
Exts : GL_3DFX_texture_compression_FXT1
...省略中间的 N 项 Exts 内容...
Exts : WGL_EXT_swap_control
Has Been Check Exts Not Supported : GL_ARB_shading_language_include
在输出的 extensions 列表中,确实也没有 GL_ARB_shading_language_include
的字符串项
那要是编写 GLSL 就帧的蛋疼了!
虽然我可以写个程序,对所有的 shader 文件,凡是使用了:#include"filename.xxx"
格式的字符串都会处理导入文件的字符内容到对应的位置,然后再对这些内容 GLSL 代码编译,但是这样会有一个很严重的问题,GLSL 的编译错误日志对应的行号会变得不在精准,也就不便于维护了。
不过,我现在才发现有一个重要的信息:
OpenGL context renderer string: "Intel(R) UHD Graphics 630"
OpenGL context vendor string: "Intel"
OpenGL context shading language version: "4.50 - Build 24.20.100.6345"
使用的显卡 "Intel(R) UHD Graphics 630"
竟然是我的集成显卡,而不是独立显卡
那么先要去了解:
第一个问题,我去 bing、google 都没找到(怀疑是否我的梯子的 IP 段给国外做了技术封锁区的处理,因为发现之前可以搜索到的一些资料,现在发现搜索不到了,很有可能是中美科技战的影响)
所以我去搜索了第二个问题:参考:指定程序使用独立显卡
开始菜单->Nvidia Control Panel->管理3D设置->全局设置->高性能 NVIDIA 处理器
(注意你使用的显卡,如果不是N卡的,请尝试在开始菜单输入你的显卡厂商的名字,看看由没控制面板或是 Control Panel 之类的,打开后看看有无以下相关设置)
这样全局情况下所有新运行的程序,使用到硬件加速接口的都会使用N卡来渲染
测试完后记得调整回来,因为选择“自动选择”的话对独立显卡的负担会减少,因为有些APP可以使用集成显卡来减少负担。
记得不要选择 “程序设置” 的方式,如下:
因为我们的 OpenGL 程序还没启动,你也无法在自定义程序项中选择。
在上面设置好 “全局设置” 后,回到我们的 OpenGL 程序中看看测试:
GLFW header version: 3.3.2
GLFW library version: 3.3.2
GLFW library version string: "3.3.2 Win32 WGL EGL OSMesa VisualC"
OpenGL context version string: "4.6.0 NVIDIA 431.87"
OpenGL context version parsed by GLFW: 4.6.0
OpenGL profile mask (0x00000000): unknown
OpenGL context renderer string: "GeForce GTX 1060/PCIe/SSE2"
OpenGL context vendor string: "NVIDIA Corporation"
OpenGL context shading language version: "4.60 NVIDIA"
Exts : GL_AMD_multi_draw_indirect
...[省略 N 项 Exts]...
Exts : GL_ARB_shading_language_include
...[省略 N 项 Exts]...
Exts : WGL_EXT_swap_control
Maximum number of vertex attributes supported : 16
Maximun number of texture image units : 32
Maximun number of Combined texture image units : 192
TestingFlatShading\testing_flat_shading_in_fs, ShaderProgram init Error: Vertex Shader Compiling Error Status: 0, Infomation Log :
0(11) : warning C7555: 'attribute' is deprecated, use 'in/out' instead
0(12) : warning C7555: 'attribute' is deprecated, use 'in/out' instead
0(13) : warning C7555: 'attribute' is deprecated, use 'in/out' instead
0(16) : warning C7555: 'varying' is deprecated, use 'in/out' instead
0(17) : warning C7555: 'varying' is deprecated, use 'in/out' instead
0(18) : warning C7555: 'varying' is deprecated, use 'in/out' instead
0(18) : error C7560: OpenGL does not allow 'flat' with 'varying'
0(18) : error C7561: OpenGL requires 'in/out' with 'flat'
OK,这回使用的是N卡了:
OpenGL context version string: "4.6.0 NVIDIA 431.87"
OpenGL context version parsed by GLFW: 4.6.0
OpenGL profile mask (0x00000000): unknown
OpenGL context renderer string: "GeForce GTX 1060/PCIe/SSE2"
OpenGL context vendor string: "NVIDIA Corporation"
OpenGL context shading language version: "4.60 NVIDIA"
也可以找到:Exts : GL_ARB_shading_language_include
但是看到有新的警告:
TestingFlatShading\testing_flat_shading_in_fs, ShaderProgram init Error: Vertex Shader Compiling Error Status: 0, Infomation Log :
0(11) : warning C7555: 'attribute' is deprecated, use 'in/out' instead
是我之前使用的 attribute
来声明 VS 的顶点属性是过时的关键字,要使用 in/out
来定义,后面还有 varying
的使用警告:
0(16) : warning C7555: 'varying' is deprecated, use 'in/out' instead
也是过时关键字,要使用 in/out
来替代
还有最后一些其他的关键字错误:
0(18) : error C7560: OpenGL does not allow 'flat' with 'varying'
0(18) : error C7561: OpenGL requires 'in/out' with 'flat'
这时因为之前的 flat shading 平旦着色风格的 shader 中使用了 flat
来定义 varying
的插值数据不处理插值导致的,那么需要看看是否可以将 flat
使用在 in/out
中。
经过使用 Sublime 编辑器批量处理关键字:
*.vert
,除了: *.frag
的所有 attribute
都替换为:in
*.vert
,除了: *.frag
的所有 varying
都替换为:out
*.frag
,除了: *.vert
的所有 varying
都替换为:in
其中替换处理了25+个 shader 文件。
然后VS中F5,编译+运行发现没有任何警告与错误了
也说明 GLSL 中 flat
可以对 in/out
数据声明
为了实现 GLSL include 功能,主要会用到 C++ 应用层扩展的 OpenGL 的 API: glNamedStringARB
那么就需要看看你的 glad.h
头文件中是否有这两个 API 的声明,如果没有那么你需要到 GLAD WEB 服务页面 中重新声明支持 GLSL include 的API,如何使用 GLAD,可以参考我之前的文章:LearnGL - 01 - CreateWindow - GLAD
如果你的 OpenGL API 导入 不是使用 GLAD,那请先查询你的 OpenGL API 导入库中有无 glNamedStringARB
API 声明,如果有 就不用去更新 API 了,如果没有,需要你去更新 OpenGL API 导入库
那么回到 GLAD 的内容继续说明,在 GLAD WEB 服务页面 中的选项设置,可以参考我的,但是具体每个选项你应该根据你自己的情况来选择:
点击 GENERATE 按钮后,会跳转到生成好的文件下载页面,如下:
点击下载 glad.zip 后,替换一下 src,和 include 下的 源文件,以及 头文件 即可
OpenGL 的 API: glNamedStringARB
扩展 API,所以在 官方标准API文档 上,和 docsGL 上都没有相关的 API 说明
但可以在 官方的 extensions ABR 上可以找到对应的文档:/registry/OpenGL/extensions/ARB/
其中:extensions/ARB/ARB_shading_language_include.txt 就是介绍 OpenGL、GLSL 中的 include 的接口规范说明
也可以看看之前翻译的一篇:extensions/ARB/ARB_shading_language_include.txt API 中文翻译
该 API 的作用: 主要是对处理 GLSL 编译数据中添加: include 文件的名字 与 文件字符串内容 的映射,如下:
glNamedStringARB(
GL_SHADER_INCLUDE_ARB, // 这个标记ID是固定的
includes[i].size(), includes[i].c_str(),
content.size(), content.c_str());
对应:
glNamedStringARB(
GL_SHADER_INCLUDE_ARB, // 这个标记ID是固定的
"/123456" /* 注意必须以/ 字符开头 */, 7 /* 6 + 1加上 `null` 字符结尾*/,
nnnn /* nnnn 是 "123456 文件的代码内容的长度" */, "123455 include 文件的字符串内容");
这是一个存放了每个 shader 需要的变量、方法的文件
// jave.lin - my_global.glsl
#ifndef _MY_GLOBAL__GLSL__
#define _MY_GLOBAL__GLSL__
// camera uniform
uniform vec3 _CamWorldPos; // 镜头世界坐标
// scene uniform
uniform vec4 _Ambient; // .xyz 环境光颜色, .w 环境光系数
uniform int AmbientType; // 环境光类别,[测试用]
// object uniform
uniform float Glossy; // 光滑度
uniform vec3 DiffuseK; // 漫反射系数
uniform vec3 SpecularK; // 高光系数
// light uniform
uniform vec4 LightPos; // 灯光世界坐标位置,w==0,或名是方向光,w==1说明是点光源,w == 0.5 是聚光灯
uniform vec4 LightColor; // 灯光颜色,.xyz 顔色,.w 强度
// uniform vec3 LightDir; // 灯光类型为聚光灯的方向
// local uniform
uniform mat4 mMat; // m 矩阵
uniform mat4 vMat; // v 矩阵
uniform mat4 pMat; // p 矩阵
uniform mat4 mvpMat; // m.v.p 矩阵
uniform mat4 IT_mMat; // Model Matrix 的逆矩阵的转置矩阵
// 将对象空间的法线转换到世界空间下的法线
vec3 ObjectToWorldNormal(vec3 n) {
return normalize(mat3(IT_mMat) * n); // 等价于:transpose(I_mMat) * vec4(n, 0)
}
vec3 getWorldViewDir(vec3 worldPos) {
return normalize(_CamWorldPos - worldPos);
}
#endif /* _MY_GLOBAL__GLSL__ */ // 这里一定要加 /* 你的头文件宏 */,否则会报错,太无语了
这里重点说明一下,奇怪的 N卡的 GLSL 编译器
如上的代码中,留意最后一行:
#endif /* _MY_GLOBAL__GLSL__ */ // 这里一定要加 /* 你的头文件宏 */,否则会报错,太无语了
如果你使用的是
#endif
然后我将后面的注释加上,编译就没错误了
我是真的无语到极了!!!!!!
更奇怪的是: 下面的 my_phong.glsl 代码中最后的 #endif 也没在尾部再添加什么字符了,又可以了,我真的是无语了!!!
这是一个处理了 phong 光照模型的文件,有一些函数
// jave.lin - my_phong.glsl - phong 光照模型
#include "/Include/my_global.glsl"
#ifndef _MY_PHONG__GLSL__
#define _MY_PHONG__GLSL__
vec3 getAmbient(vec3 albedo) {
if (AmbientType == 0) {
return _Ambient.rgb * _Ambient.a;
} else {
return mix(_Ambient.rgb * _Ambient.a, albedo, _Ambient.a);
}
}
// jave.lin - my_phong.glsl
void phong_illumination(
in vec3 worldNormal,
in vec3 viewDir,
in vec3 albedo,
out vec3 ambient,
out vec3 diffuse,
out vec3 specular
) {
ambient = getAmbient(albedo);
if (LightPos.w == 0) {
// 下面使用的是Phong 光照模型
// 如果是方向光,那么 LightPos.xyz 是灯光方向的反方向
float D = max(0, dot(LightPos.xyz, worldNormal));
diffuse = LightColor.rgb * LightColor.a * D * DiffuseK * albedo;
vec3 H = normalize(LightPos.xyz + viewDir);
float S = 0;
if (D > 0) S = pow(max(0, dot(H, worldNormal)), Glossy);
specular = LightColor.rgb * LightColor.a * S * SpecularK;
} else {
// 点光源 或是 聚光灯
if (LightPos.w == 1) {
// 点光
} else { // LightPos.w == 0.5,即:LightPos.w !=0 && LightPos.w != 1
// 聚光灯
}
}
}
#endif
注意 #extension GL_ARB_shading_language_include : require
放在 #include
之前
// jave.lin - testing_includes.vert
#version 450 compatibility
#extension GL_ARB_shading_language_include : require
#include "/Include/my_global.glsl"
// vertex data
in vec3 vPos; // 顶点坐标
in vec2 vUV0; // 顶点纹理坐标
in vec3 vNormal; // 顶点法线
// vertex data - interpolation
out vec2 fUV0; // 给 fragment shader 传入的插值
out vec3 fNormal; // 世界坐标顶点法线
out vec3 fWorldPos; // 世界坐标
void main() {
vec4 worldPos = mMat * vec4(vPos, 1.0); // 世界坐标
fUV0 = vUV0; // UV0
fNormal = ObjectToWorldNormal(vNormal); // 世界坐标顶点法线
fWorldPos = worldPos.xyz; // 世界坐标
gl_Position = pMat * vMat * worldPos; // Clip pos
}
// jave.lin - testing_includes.frag
#version 450 compatibility
#extension GL_ARB_shading_language_include : require
#include "/Include/my_global.glsl"
#include "/Include/my_phong.glsl"
// interpolation - 插值数据
in vec2 fUV0; // uv 坐标
in vec3 fNormal; // 顶点法线
in vec3 fWorldPos; // 世界坐标
uniform sampler2D main_tex;
void main() {
vec3 albedo = texture(main_tex, fUV0).rgb;
vec3 worldNormal= normalize(fNormal); // 世界坐标法线再次归一化一次,因为插值之后可能会导致不是归一化的值
vec3 viewDir = getWorldViewDir(fWorldPos); // 顶点坐标 指向 镜头坐标 的方向
vec3 ambient = vec3(0);
vec3 diffuse = vec3(0);
vec3 specular= vec3(0);
phong_illumination(worldNormal, viewDir, albedo, ambient, diffuse, specular);
gl_FragColor = vec4(ambient + diffuse + specular, 1.0);
}
相比之前我们光照计算的文件来说,代码简洁了非常多,而且,可以在多个 shader 中都可以这么使用,只要将一些通用的 shader 都写到 include 文件中,其他 shader 都可以共用了,使用方便很多。
之前会报错,要放在任意的代码之前,然而我放在 #version 也报错了,因为这个是 GLSL 硬性规定 #version 必须是放在有效的代码中的:第一句
然后我将 #version 450
该为 430
之后,就没再报这个错误了,这篇文章的博主也遇到和我一样的问题:OpenGL shader文件 include
但是现在你可以看到我上面的代码中,我用会 #version 450
后,又不报错了,这只能说 N卡对 GLSL 的编译处理不太完善!(OpenGL 只是一个 API 接口规范,但是实现是具体显卡设备产商去实现的)
C++调用对 shader 编译流程变化不大
主要是我们封装好了一个专门处理 include 需要的类:
// my_gl_include_exts.h
/* author : jave.lin
对 GL Include Extensions 实现的一些功能函数
*/
#ifndef _MY_GL_INCLUDE_EXTS__H_
#define _MY_GL_INCLUDE_EXTS__H_
#include
#include
#include
#include"glad/glad.h"
#include"my_gl_check_error.h"
#include"my_get_str_hash.h"
namespace my_util {
#define PRINT_GLSL_INCLUDES
typedef std::unordered_map<int, std::string> includes_map_t;
class GLSL_Including {
public:
static void include_Shader(std::string str);
static void clear_include();
private:
static void find_includes(std::string str, std::vector<std::string>& result);
static includes_map_t map;
};
// 查询所有需要 include 的名字
void GLSL_Including::find_includes(std::string str, std::vector<std::string>& result) {
// TODO : 排除掉所有在注释中的内容
// TODO : 后面有空可以看看能否 C++ 的正则库来替代实现
size_t offset = 0;
size_t include_flag_idx = str.find("#include", offset);
while (include_flag_idx != -1) {
size_t char1_flag = str.find("\"", include_flag_idx + 8);
size_t char2_flag = str.find("\"", char1_flag + 1);
if (char1_flag == -1 || char2_flag == -1) {
std::cerr << "error flag1 : " << char1_flag << ", flag2 : " << char2_flag << "\n";
exit(-1);
}
else {
std::string include_file_name = str.substr(char1_flag + 1, char2_flag - char1_flag - 1);
result.push_back(include_file_name);
}
offset = char2_flag;
include_flag_idx = str.find("#include", offset);
}
}
// 清理所有的 glsl include 名字字符串内容,目前是测试用,看看没有错误
// 因为 include 有可能会在很多 shader 中多有用到,所以 include 后一般不会 delete
void GLSL_Including::clear_include() {
includes_map_t::const_iterator map_it = map.cbegin();
for (; map_it != map.end(); map_it++) {
glDeleteNamedStringARB((*map_it).second.size(), (*map_it).second.c_str());
#ifdef PRINT_GLSL_INCLUDES
std::cout << "DeleteNamedStringARGB : " << (*map_it).second.c_str() << "\n";
#endif
checkGLError();
}
map.clear();
}
// 导入指定 shader 源码中,并自动所有
// include 了对应文件内容,不会清理掉
// 供后续的其他 shader 使用
void GLSL_Including::include_Shader(std::string str) {
std::vector<std::string> includes;
find_includes(str, includes);
if (includes.size() == 0) return;
for (size_t i = 0; i < includes.size(); i++) {
std::string name = includes[i];
int hash = getHash(name);
if (map.find(hash) != map.end()) {
#ifdef PRINT_GLSL_INCLUDES
//std::cout << "Already included : " << includes[i].c_str() << "\n";
#endif
continue;
}
std::string file_name = "../../Dependencies/Shaders" + includes[i];
std::string content, error;
readFile(file_name, content, error);
if (!error.empty()) {
std::cerr << error.c_str() << "\n";
exit(-1);
}
glNamedStringARB(
GL_SHADER_INCLUDE_ARB,
includes[i].size(), includes[i].c_str(),
content.size(), content.c_str());
checkGLError();
map.insert(includes_map_t::value_type(hash, name));
#ifdef PRINT_GLSL_INCLUDES
std::cout << "Including : " << includes[i].c_str() << "\n";
#endif
}
}
includes_map_t GLSL_Including::map;
}
#endif
该类主要实现:还有对 shader 源文件中的 #include "you_file_name.xxx"
的检测,并导入到 GLSL 的虚拟文件系统(virtual file system)中(它内部是使用 tree location 树形结构定位的, 是一个树形结构的 include文件名字,与 include文件字符串内容 的映射系统 )
在外部使用,就只要对每一个 shader 没以前添加一句:GLSL_Including::include_Shader(shader_source_str);
,的调用即可: