Я хочу использовать директиву #include
в файлах GLSL, чтобы мне не приходилось переписывать один и тот же код для каждого создаваемого мной шейдера GLSL. Я попытался создать для этой цели свой собственный небольшой парсер, но не смог найти хороший способ найти все операторы #include
в файле GLSL. Я хочу извлечь пути включенных файлов, прочитать их и записать их содержимое в реальный шейдер (не исходный файл GLSL, который использует директиву #include, а содержимое, считанное в std::string
).
Я попытался найти шейдерные включения, используя следующий код:
std::string include = "#include";
std::string shaderSource = ReadFile("shader.glsl");
std::uint32_t offset = 0, position = 0;
while ((position = shaderSource.find(include, offset)) != std::string::npos)
{
offset += include.size();
// parse the shader
}
Однако я понимаю, что этот подход может работать не во всех сценариях, и считаю, что решение должно быть более сложным, чем то, что я реализовал. Я был бы признателен, если бы вы помогли мне и сообщили, есть ли какие-либо проблемы с моим вопросом в комментариях, вместо того, чтобы голосовать против и закрывать его.
@Dúthomhas Я не пишу в настоящий файл GLSL, так что, думаю, все будет в порядке. Хотя я не думаю, что понял вашу точку зрения.
@Newbieprogrammer, какой у тебя вопрос? Похоже, вы уже знаете, как считать файл в строку и затем выполнить в нем поиск.
Почему нельзя просто использовать #include
?
@Eljay У OpenGL нет файловой системы для шейдеров, так что нет, я не могу.
«Я понимаю, что этот подход может работать не во всех сценариях». Вам необходимо предоставить несколько примеров файлов, которые вы читаете, и ожидаемого результата.
Я не совсем адекватно объяснил. Почему у вас не может быть исходных файлов с директивами #include
, которые выполняют предварительную обработку (с использованием препроцессора C++ или любого другого препроцессора, который вы предпочитаете (например, раньше я использовал m4 и Python)) в файлы GLSL в качестве выходных данных.
@Eljay О, я понимаю, потому что у меня есть класс Shader, который принимает файловую систему::path, а не исходный код.
@Ted Lyngmo Если подумать, совершенно очевидно: если длина пути включения больше длины «#include», то цикл while не прервется. Надеюсь, я ясно выразился.
@Newbieprogrammer Извините, но это не прояснило ситуацию. Пожалуйста, обновите свой вопрос. Включите пример файла, который вы читаете shaderSource
, или просто назначьте shaderSource
содержимым такого файла. См. минимально воспроизводимый пример .
Нет проблем, я обновлю свой вопрос.
Скорее всего, вы хотите offset = position + include.size()
. В противном случае вы будете снова и снова находить одну и ту же подстроку "#include"
. Допустим, подстрока находится в позиции 100. Сначала вы найдете ее при поиске, начиная со смещения 0; затем снова, начиная со смещения 8; затем снова, начиная со смещения 16; ... Тогда как, как только вы нашли его в позиции 100, вы хотите начать поиск со смещения 108 (или 100 + длина всей директивы).
@Игорь Тандетник Спасибо! Попробую реализовать и посмотреть, получится или нет. Я ценю вашу помощь.
Возможно актуально: Как использовать #include в поддержке glsl ARB_shading_language_include
@Wyck прав - вам нужно просто передать компилятору GLSL данные заголовочного файла, включить расширение include
и позволить ему фактически заменить содержимое там, где это необходимо.
Если сложность того, что вам нужно для поддержки функций, минимальна, вы можете использовать препроцессор вашего компилятора C/C++, чтобы выполнить это во время сборки.
пример.glsl
#include "common.glsl"
void main()
{
FragColor = vertexColor;
}
common.glsl
out vec4 FragColor;
in vec4 vertexColor;
Командная строка Visual Studio
cl /P common.glsl /Fipreprocessed.glsl /EP
preprocessed.glsl (сгенерирован)
out vec4 FragColor;
in vec4 vertexColor;
void main()
{
FragColor = vertexColor;
}
Эквивалентная команда в gcc
gcc -E -P common.glsl -o preprocessed.glsl
Спасибо, но я хотел знать, смогу ли я сделать это с помощью кода C++, не используя компилятор для автоматического выполнения этого.
Примечание. Эта версия выполнена с использованием кода C++, как вы просили в своем комментарии к другому моему ответу.
Идея состоит в том, чтобы найти текст формата #include "filename"
и рекурсивно заменить этот текст содержимым включенного файла.
Я позаботился об указании дополнительных каталогов включения. Но я также использовал несколько сокращений, жертвуя надежностью, особенно с регулярным выражением для сопоставления ""
или <>
.
Эта версия принимает в качестве исходных входных данных строку (а не файл). Если вы хотите получить входные данные из файла, вы можете просто обработать строку с помощью одной директивы #include
, которая включает нужный файл, или расширить эту реализацию, если вы хотите сначала прочитать исходный файл.
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <regex>
#include <vector>
#include <filesystem>
// Read a file and return its contents as a string
std::string readFile(const std::string& fileName) {
std::ifstream file(fileName);
if (!file.is_open()) {
throw std::runtime_error("Could not open file: " + fileName);
}
std::stringstream buffer;
buffer << file.rdbuf();
return buffer.str();
}
// Find the file in the current directory or in additional include directories
std::string findIncludeFile(const std::string& fileName, const std::vector<std::string>& includeDirs) {
if (std::filesystem::exists(fileName)) {
return fileName; // File found in the current directory
}
for (const auto& dir : includeDirs) {
std::filesystem::path filePath = std::filesystem::path(dir) / fileName;
if (std::filesystem::exists(filePath)) {
return filePath.string(); // File found in one of the include directories
}
}
throw std::runtime_error("File not found: " + fileName);
}
// Process `#include` directives
std::string processIncludes(const std::string& input, const std::vector<std::string>& includeDirs) {
std::regex includeRegex(R"(#include\s*["<](.*?)[">])");
std::smatch match;
std::string output = input;
std::string::const_iterator searchStart(output.cbegin());
while (std::regex_search(searchStart, output.cend(), match, includeRegex)) {
std::string includeFile = match[1].str();
std::string fileContent;
try {
std::string filePath = findIncludeFile(includeFile, includeDirs);
fileContent = readFile(filePath);
}
catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
fileContent = "";
}
// Calculate the position in the full string for the replacement
auto matchPos = match.position(0) + (searchStart - output.cbegin());
output.replace(matchPos, match.length(0), fileContent);
// Adjust the searchStart to continue at the inserted content (to allow nested includes)
searchStart = output.cbegin() + matchPos;
}
return output;
}
int main() {
// Example input string containing `#include` directives
std::string input = R"(#include "example.glsl")";
// List of additional include directories
std::vector<std::string> includeDirs = { "./include", "./shaders" };
try {
std::string output = processIncludes(input, includeDirs);
std::cout << "Processed Output:\n" << output << std::endl;
}
catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
return 0;
}
пример.glsl
#include "common.glsl"
void main()
{
FragColor = vertexColor;
}
common.glsl
out vec4 FragColor;
in vec4 vertexColor;
Выход:
out vec4 FragColor;
in vec4 vertexColor;
void main()
{
FragColor = vertexColor;
}
Спасибо, я ценю вашу помощь.
Я в замешательстве; такое ощущение, что ты идешь назад. Включение позволяет повторно использовать данные без необходимости сохранять несколько копий в каждом файле. Это усложняет обслуживание.