当前位置: 代码迷 >> 综合 >> LearnGL - 12 - GLSL include - GL_ARB_shading_language_include (Extensions扩展) - 各种踩坑
  详细解决方案

LearnGL - 12 - GLSL include - GL_ARB_shading_language_include (Extensions扩展) - 各种踩坑

热度:60   发布时间:2024-01-31 20:20:17.0

文章目录

  • 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
  相关解决方案