运行时的 glsl 着色器编译问题
Posted
技术标签:
【中文标题】运行时的 glsl 着色器编译问题【英文标题】:glsl shader compilation issue at runtime 【发布时间】:2011-11-06 00:41:01 【问题描述】:我正在开发一个使用 OpenGL 4.0 着色器的项目。
我必须为glShaderSource() 的调用提供一个字符数组数组,它表示着色器的源。
着色器编译失败,出现以下错误:
(0) : error C0206: invalid token "<null atom>" in version line
(0) : error C0000: syntax error, unexpected $end at token "<EOF>"
这是我的(hello world)着色器 - 直接来自 OpenGL 4.0 着色语言食谱
#version 400
in vec3 VertexPosition;
in vec3 VertexColor;
out vec3 Color;
void main()
Color = VertexColor;
gl_Position = vec4( VertexColor, 1.0 );
这是我将着色器文件读入我的 C++ 代码并在运行时编译着色器的代码:
const int nMaxLineSize = 1024;
char sLineBuffer[nMaxLineSize];
ifstream stream;
vector<string> vsLines;
GLchar** ppSrc;
GLint* pnSrcLineLen;
int nNumLines;
stream.open( m_sShaderFile.c_str(), std::ios::in );
while( (stream.good()) && (stream.getline(sLineBuffer, nMaxLineSize)) )
if( strlen(sLineBuffer) > 0 )
vsLines.push_back( string(sLineBuffer) );
stream.close();
nNumLines = vsLines.size();
pnSrcLineLen = new GLint[nNumLines];
ppSrc = new GLchar*[nNumLines];
for( int n = 0; n < nNumLines; n ++ )
string & sLine = vsLines.at(n);
int nLineLen = sLine.length();
char * pNext = new char[nLineLen+1];
memcpy( (void*)pNext, sLine.c_str(), nLineLen );
pNext[nLineLen] = '\0';
ppSrc[n] = pNext;
pnSrcLineLen[n] = nLineLen+1;
vsLines.clear();
// just for debugging purposes (lines print out just fine..)
for( int n = 0; n < nNumLines; n ++ )
ATLTRACE( "line %d: %s\r\n", n, ppSrc[n] );
// Create the shader
m_nShaderId = glCreateShader( m_nShaderType );
// Compile the shader
glShaderSource( m_nShaderId, nNumLines, (const GLchar**)ppSrc, (GLint*) pnSrcLineLen );
glCompileShader( m_nShaderId );
// Determine compile status
GLint nResult = GL_FALSE;
glGetShaderiv( m_nShaderId, GL_COMPILE_STATUS, &nResult );
C++ 代码按预期执行,但着色器编译失败。谁能发现我可能做错了什么?
我感觉这可能与行尾字符有关,但由于这是我第一次尝试着色器编译,所以我被卡住了!
我已阅读有关着色器编译的其他 SO 答案,但它们似乎特定于 Java/其他语言,而不是 C++。如果有帮助,我在win32平台上。
【问题讨论】:
如果将着色器作为文字包含在源代码中,它是否工作? 我不确定我是否遵循。在我的程序执行期间,着色器文件必须由 glsl 编译。但我不认为着色器有错。着色器是一个简单的hello world,取自一本书。我认为问题可能与换行符有关。 不是说这是错误,而是为什么在该位置使用vertexcolor? 这只是取自一本书的例子。着色器代码不是这里的问题,我认为问题可能与它提供给 glShaderSource() 调用的方式或行解析问题有关。 【参考方案1】:你犯了别人犯的错误。这是glShaderSource
的定义:
void glShaderSource(GLuint shader, GLsizei count, const GLchar **string, const GLint *length);
string
是一个字符串数组。它不是打算成为着色器中的行数组。编译器解释这个字符串数组的方式是将它们一个接一个地连接在一起。没有换行符。
由于stream.getline
不会将\n
字符放入字符串中,因此您生成的每个着色器字符串末尾都不会有换行符。因此,当glShaderSource
编译它们时,您的着色器将如下所示:
#version 400in vec3 VertexPosition;in vec3 VertexColor;out vec3 Color;...
这不是合法的 GLSL。
执行此操作的正确方法是将文件加载为字符串。
std::ifstream shaderFile(m_sShaderFile.c_str());
if(!shaderFile)
//Error out here.
std::stringstream shaderData;
shaderData << shaderFile.rdbuf(); //Loads the entire string into a string stream.
shaderFile.close();
const std::string &shaderString = shaderData.str(); //Get the string stream as a std::string.
然后您可以轻松地将其传递给glShaderSource
:
m_nShaderId = glCreateShader( m_nShaderType );
const char *strShaderVar = shaderString.c_str();
GLint iShaderLen = shaderString.size();
glShaderSource( m_nShaderId, 1, (const GLchar**)&strShaderVar, (GLint*)&iShaderLen );
glCompileShader( m_nShaderId );
如果你从某个地方复制了这个加载代码,那么我强烈建议你找到different place to learn about OpenGL。因为那是糟糕的编码。
【讨论】:
感谢您提供有关从何处了解 opengl 的有用信息。我写了你所说的糟糕的代码。但后来我来自 Windows 编程背景,每个人都使用 CString。所以我只是在使用标准库学习字符串和流。我的方法确实有效(在我在每一行的末尾放了一个 \n \0 之后),虽然我同意,在看到你使用字符串流的方法之后,它更加优雅。感谢您的提示。【参考方案2】:glShaderSource(m_nShaderId, nNumLines, (const GLchar**)ppSrc, (GLint*) pnSrcLineLen);
我知道 glShaderSource 的签名看起来很诱人,可以单独发送着色器的每一行。但这就是它的意义所在。能够发送多个数组的重点是可以将多个原始着色器源混合到一个着色器中,有点像包含文件。了解这一点,可以更轻松地读取着色器文件 - 并避免此类讨厌的错误。
使用 C++,您可以做得更好、更简洁。我已经在Getting garbage chars when reading GLSL files中写了以下内容
您使用的是 C++,所以我建议您利用它。我建议您不要读入一个自分配的 char 数组,而是读入一个 std::string:
#include <string>
#include <fstream>
std::string loadFileToString(char const * const fname)
std::ifstream ifile(fname);
std::string filetext;
while( ifile.good() )
std::string line;
std::getline(ifile, line);
filetext.append(line + "\n");
return filetext;
它会自动处理所有内存分配和适当的定界——关键字是 RAII:资源分配是初始化。稍后您可以上传着色器源,例如
void glcppShaderSource(GLuint shader, std::string const &shader_string)
GLchar const *shader_source = shader_string.c_str();
GLint const shader_length = shader_string.size();
glShaderSource(shader, 1, &shader_source, &shader_length);
你可以像这样一起使用这两个函数:
void load_shader(GLuint shaderobject, char * const shadersourcefilename)
glcppShaderSource(shaderobject, loadFileToString(shadersourcefilename));
【讨论】:
【参考方案3】:只是一个快速的预感:
您是否尝试过使用 NULL 作为长度参数调用 glShaderSource?在这种情况下,OpenGL 将假定您的代码是空终止的。
(因愚蠢而编辑)
【讨论】:
c 数组是基于 0 的,所以我的数组算术是正确的,但您的回答为我指明了正确的方向。我在最后一个参数中使用了 NULL,现在我得到了全新的错误! 啊,是的,对,对不起...天哪,这很尴尬,但这里是半夜:D 已解决。奖励答案,谢谢。我在 glShaderSource 上看到的文档非常简洁,他们根本没有解释最后一个参数。非常有用,再次感谢! @freefallr:请不要使用任何代码将每一行作为单独的字符串加载到glShaderSource
。只是不要这样做。
@freefallr 看来您刚刚接受了错误的答案。不,这不是主观决定(也不应该是随机决定)。以上是关于运行时的 glsl 着色器编译问题的主要内容,如果未能解决你的问题,请参考以下文章