使用结构从 .txt 文件中读取着色器

Posted

技术标签:

【中文标题】使用结构从 .txt 文件中读取着色器【英文标题】:Reading a shader from a .txt file using a structure 【发布时间】:2015-06-12 03:46:20 【问题描述】:

我已经开始学习如何使用 OpenGL,我非常讨厌在 main() 之前将我的着色器声明为全局变量。

我认为创建一个可以从我的项目目录中的文件中读取着色器的结构或类会很酷。文件读取工作得很好,但由于某种原因,它实际上不会像我在 main 之前声明着色器那样输出图像。这是我的例子。

着色器读取结构:

#include "allHeader.h"

struct shaderReader

    shaderReader::shaderReader(std::string);
    const GLchar* source;
;


shaderReader::shaderReader(std::string name)

std::string line, allLines;
std::ifstream theFile(name);
if (theFile.is_open())

    while (std::getline(theFile, line))
    
        allLines = allLines + line + "\n";
    

    source = allLines.c_str();
    std::cout << source;

    theFile.close();

else
    
    std::cout << "Unable to open file.";
    

ma​​in() 之前的区域快照

shaderReader vertexShader = shaderReader("vertex.txt");
shaderReader fragmentShader = shaderReader("fragment.txt");

const GLchar* vertexSource =
"#version 150 core\n"
"in vec2 position;"
"void main() "
"   gl_Position = vec4(position, 0.0, 1.0);"
"";
const GLchar* fragmentSource =
"#version 150 core\n"
"out vec4 outColor;"
"void main() "
"   outColor = vec4(1.0, 1.0, 1.0, 1.0);"
"";

int main()

     //stuff

作品

    GLuint vertexShaderObject = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShaderObject, 1, &vertexSource, NULL);
    glCompileShader(vertexShaderObject);

    //Now let's create the Fragment Shader Object
    GLuint fragmentShaderObject = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShaderObject, 1, &fragmentSource, NULL);
    glCompileShader(fragmentShaderObject);

由于某些原因不起作用

    const GLchar* ob1 = vertexShader.source;
    const GLchar* ob2 = fragmentShader.source;
    GLuint vertexShaderObject = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShaderObject, 1, &ob1, NULL);
    glCompileShader(vertexShaderObject);

    //Now let's create the Fragment Shader Object
    GLuint fragmentShaderObject = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShaderObject, 1, &ob2, NULL);
    glCompileShader(fragmentShaderObject);

也不起作用

    GLuint vertexShaderObject = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShaderObject, 1, &vertexShader.source, NULL);
    glCompileShader(vertexShaderObject);

    //Now let's create the Fragment Shader Object
    GLuint fragmentShaderObject = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShaderObject, 1, &fragmentShader.source, NULL);
    glCompileShader(fragmentShaderObject);

上面的代码在工作时会在黑屏中间打印出一个白色三角形。但是,当不工作时,我只会黑屏。我尝试检查着色器是否存在编译错误,但完全没有错误。我认为结构中可能有问题,我没有做对。感谢您阅读我的问题,如果您有类似的问题,希望我的问题对您有所帮助。

【问题讨论】:

在阅读完代码后,您让allLines 字符串超出范围。因此,包含着色器代码的内存将在您使用之前被释放。 将着色器放入.txt 文件中。在开始和结束处将每一行包裹在"" 中。以; 结束。然后const GLChar* some_shader =,换行,#include "shader.txt"。你看!编译时间字符串,运行时不加载文件,你的着色器不碍事? 嘿,@RetoKoradi allLines 究竟是如何超出范围的? @Yakk 如果可能的话,你能举个例子作为答案吗,谢谢。 @BryantheLion 你在构造函数中声明了allLines。所以当构造函数结束时allLines被销毁了。 您不要将allLines的内容复制到source中。你得到一个指向allLines 内部数据的指针,并将该指针分配给source。然后,当allLines 在构造函数结束时超出范围时,它会释放其内存,而source 现在指向已释放的内存。 【参考方案1】:

您以无效的方式混合使用了 C++ (std::string) 和 C (char*) 字符串。

在构造函数中,您在 C++ string 中构建代码,这是一个对象,它会随着字符串的增长自动分配和重新分配内存以保存字符串数据。它还会在构造函数结束时超出范围时自动释放该数据。

问题的根源在这里:

source = allLines.c_str();

source 是一个类/结构成员。 string 上的 c_str() 方法为您提供了指向对象内部字符串数据的指针。如上所述,当allLinesshaderReader 构造函数的末尾超出范围时,此数据被释放,因此您最终会得到一个指向已释放内存的指针。这会导致未定义的行为,因为该内存现在可以用于其他用途。

解决此问题的最简洁方法是一致地使用 C++ 字符串。将结构定义更改为:

struct shaderReader

    shaderReader::shaderReader(std::string);
    std::string source;
;

然后在构造函数中,可以直接将源码读入这个类成员:

while (std::getline(theFile, line))

    source = source + line + "\n";

您必须使用char* 的唯一原因是因为glShaderSource() 需要一个。最安全的方法是在最后一刻转换它:

const GLchar* ob1 = vertexShader.source.c_str();
GLuint vertexShaderObject = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShaderObject, 1, &ob1, NULL);
glCompileShader(vertexShaderObject);

这样,您只需要非常临时地使用指向内部string 数据的指针,而不必为其他C 风格的字符串和内存管理而烦恼。

【讨论】:

谢谢,这实际上很聪明,我不知道为什么我以前没有想过做这样的事情。这可能是这里介绍的最佳方法。 我尝试做一些不同的事情,应该工作,但我得到一个调试断言失败。我所做的只是在我的结构定义中创建了一个新变量const GLchar* Source。然后在while 循环之后,我简单地创建了Source = source.c_str() 并在我的函数中glShaderSource(vertexShaderObject, 1, &amp;vertexShader.Source, NULL); 它可以工作,但是当我关闭应用程序时它给了我这个错误。 没关系,原因是我保留了旧的析构函数定义。 if(source) delete[] source; source = nullptr; @reto-koradi 我尝试将您的想法转换为函数,但在尝试此操作时会导致 opengl 上下文崩溃(将片段视为从 txt 读取着色器的“字符串”)。我怀疑我只是在用指针做一些愚蠢的事情。我可以保证字符串包含一个可用的着色器。 -------------------------------------------------- ------ glShaderSource(vertexShaderObject, 1, getFragmentShaderSrc(), NULL); -> 使用这个函数: ----------------------> const GLchar * const * getFragmentShaderSrc() const GLchar* d = fragment.c_str();返回 &d; 【参考方案2】:

改变

source = allLines.c_str();

进入

source = new char[allLines.length()+1];
strcpy(source,allLines.c_str());

并且在shaderReader的析构函数中,释放为source分配的内存

if(source)

    delete[] source;
    source = nullptr;

参考: https://***.com/a/12862777/3427520

【讨论】:

这很好,我只需要稍微调整一下就可以将char* 转换为const GLchar*,非常感谢您的回答。 知道为什么定义我的析构函数会导致调试断言失败? 如果 OP 正在使用 C++,并且在其余代码中已经使用 C++ 字符串,您为什么建议将代码存储在 C 字符串中? 呃,很抱歉我错过了……是的,你的答案更好。 @BryantheLion 可能你不小心删除了 allLines.c_str() 的内存,会导致 Assertion 失败。【参考方案3】:

我很想将您的着色器存储在 std::vector&lt;GLchar&gt; 中,这样您就不必担心分配和释放内存:

可能是这样的:

struct shaderReader

    shaderReader(std::string);
    std::vector<GLchar> source;
    const GLchar* data;
    GLint size;
;

shaderReader::shaderReader(std::string name)

    std::string line, allLines;
    std::ifstream theFile(name);
    if(theFile.is_open())
    
        while(std::getline(theFile, line))
        
            allLines = allLines + line + "\n";
        

        source.assign(allLines.begin(), allLines.end());
        size = source.size();
        data = source.data();

        theFile.close();
    
    else
    
        std::cout << "Unable to open file.";
    


int main()

    shaderReader sr("fragment-shader.txt");
    GLuint shader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(shader, 1, &sr.data, &sr.size);

【讨论】:

以上是关于使用结构从 .txt 文件中读取着色器的主要内容,如果未能解决你的问题,请参考以下文章

从着色器读取数据

是否可以从顶点着色器读取数据?

链接着色器导致文件未读取错误

GLSL:无法从 FBO 读取纹理并使用片段着色器渲染到另一个 FBO

如何在着色器之后读取像素?

C - 读取文件并将文本放入具有动态内存分配的字符指针