文章目录
- GL_ARB_shading_language_include 不支持?
- 留意使用的显卡是否集成显卡
- 设置使用独立显卡
- 扩展的 API
- GLAD、GLAD Web 页面使用
- API
- 实践
- 先准备两个被 #include 的文件
- my_global.glsl
- 注意的一些错误 - 坑1
- my_phong.glsl
- testing_includes.vert/frag 带有 include 的 shader
- #extension GL_ARB_shading_language_include : require 有时报错要放在任意代码之前 - 坑2
- C++应用层 glNamedStringARB 调用
- my_gl_include_exts.h
- References
LearnGL - 学习笔记目录
前些篇:
- LearnGL - 11.1 - 实现简单的Gouraud-Phong光照模型
- LearnGL - 11.2 - 实现简单的Phong光照模型
- LearnGL - 11.3 - 实现简单的Blinn-Phong光照模型
- LearnGL - 11.4 - 实现简单的Flat BlinnPhong光照模型
我们学习 GLSL 的尝试光照计算,但是每个 shader 中一堆的重复代码,实在让人无法忍受
这篇:我们就给 GLSL 添加 #include"your_file_name.xxx"
的功能
本人才疏学浅,如有什么错误,望不吝指出。
在 GLSL 编写 shader 时,发现不能直接使用 #include"file_name.xxx"
写 shader 起来相当麻烦
然后我还想着去添加一下 GLSL Include 的功能
GL_ARB_shading_language_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"
竟然是我的集成显卡,而不是独立显卡
设置使用独立显卡
那么先要去了解:
- 如果指定 OpenGL 使用的显卡?
- 或是应用程序使用指定的显卡?
第一个问题,我去 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
数据声明
扩展的 API
为了实现 GLSL include 功能,主要会用到 C++ 应用层扩展的 OpenGL 的 API: glNamedStringARB
GLAD、GLAD Web 页面使用
那么就需要看看你的 glad.h
头文件中是否有这两个 API 的声明,如果没有那么你需要到 GLAD WEB 服务页面 中重新声明支持 GLSL include 的API,如何使用 GLAD,可以参考我之前的文章:LearnGL - 01 - CreateWindow - GLAD
如果你的 OpenGL API 导入不是使用 GLAD 那么请查询 glNamedStringARB
是否有这个 API 声明,如果有就不用去更新 API 了,如果没有,那就需要你使用的 OpenGL API 导入的工具平台上更是 API,添加 include 扩展即可
那么回到 GLAD 的内容继续说明,在 GLAD WEB 服务页面 中的选项设置,可以参考我的,但是具体每个选项你应该根据你自己的情况来选择:
点击 GENERATE 按钮后,会跳转到生成好的文件下载页面,如下:
点击下载 glad.zip 后,替换一下 src,和 include 下的 源文件,以及 头文件 即可
API
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 文件的字符串内容");
实践
先准备两个被 #include 的文件
my_global.glsl
这是一个存放了每个 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__ */ // 这里一定要加 /* 你的头文件宏 */,否则会报错,太无语了
注意的一些错误 - 坑1
这里重点说明一下,奇怪的 N卡的 GLSL 编译器
如上的代码中,留意最后一行:
#endif /* _MY_GLOBAL__GLSL__ */ // 这里一定要加 /* 你的头文件宏 */,否则会报错,太无语了
如果你使用的是
#endif
那么就会报错:
然后我将后面的注释加上,编译就没错误了
我是真的无语到极了!!!!!!
如下图:
更奇怪的是: 下面的 my_phong.glsl 代码中最后的 #endif 也没在尾部再添加什么字符了,又可以了,我真的是无语了!!!
my_phong.glsl
这是一个处理了 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
testing_includes.vert/frag 带有 include 的 shader
// 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; // UV0fNormal = 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 都可以共用了,使用方便很多。
#extension GL_ARB_shading_language_include : require 有时报错要放在任意代码之前 - 坑2
之前会报错,要放在任意的代码之前,然而我放在 #version 也报错了,因为这个是 GLSL 硬性规定 #version 必须是放在有效的代码中的:第一句
然后我将 #version 450
该为 430
之后,就没再报这个错误了,这篇文章的博主也遇到和我一样的问题:OpenGL shader文件 include
但是现在你可以看到我上面的代码中,我用会 #version 450
后,又不报错了,这只能说 N卡对 GLSL 的编译处理不太完善!(OpenGL 只是一个 API 接口规范,但是实现是具体显卡设备产商去实现的)
C++应用层 glNamedStringARB 调用
C++调用对 shader 编译流程变化不大
主要是我们封装好了一个专门处理 include 需要的类:
my_gl_include_exts.h
// my_gl_include_exts.h
/* author : jave.lin 对 GL Include Extensions 实现的一些功能函数 */#ifndef _MY_GL_INCLUDE_EXTS__H_
#define _MY_GL_INCLUDE_EXTS__H_#include<vector>
#include<string>
#include<unordered_map>
#include"glad/glad.h"
#include"my_gl_check_error.h"
#include"my_get_str_hash.h"namespace my_util {#define PRINT_GLSL_INCLUDEStypedef 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 后一般不会 deletevoid 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_INCLUDESstd::cout << "DeleteNamedStringARGB : " << (*map_it).second.c_str() << "\n";
#endifcheckGLError();}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";
#endifcontinue;}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_INCLUDESstd::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);
,的调用即可:
References
- extensions/ARB/ARB_shading_language_include.txt API 中文翻译
- extensions/ARB/ARB_shading_language_include.txt 就是介绍 OpenGL、GLSL 中的 include 规范
- OpenGL shader文件 include
- How to Using the #include in glsl support ARB_shading_language_include