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


LearnGL - 学习笔记目录

前些篇:

我们学习 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,那请先查询你的 OpenGL API 导入库中有无 glNamedStringARB API 声明,如果有 就不用去更新 API 了,如果没有,需要你去更新 OpenGL API 导入库

那么回到 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

注意 #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 都可以共用了,使用方便很多。

#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_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);,的调用即可:

在这里插入图片描述


References

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值