当前位置: 首页 > 工具软件 > Horde3D > 使用案例 >

3D引擎:Horde3D:如何解析Shader文件(一)

匡旭东
2023-12-01
在上一帖中,http://blog.csdn.net/jinghouxiang/article/details/49994983

说了 如何从 Android的jni层获取 Assets文件夹下 文件的路径名, 获取路径名后 需要将文件中的数据读取到char 数组中

假如 assets下 有一文件为 Shader/test.shader
rootDir 为我们读取文件的根目录: assets/
这样定义 加载 test.shader数据的 函数如下
bool ParseText::loadShader(std:: string &filename)
{
       fullFileName = rootDir +filename; 
      LOG_INFO( "ParseText","begin to open file" );
       zip_file * file = zip_fopen( apkArchive, fullFileName .c_str(), 0);

       if(file == NULL)
      {
             return false ;
      }

      LOG_INFO( "ParseText","end open file" );
       //char data[];
       int size = file->bytes_left ;
       char *data = (char *)malloc( sizeof(char )*size);
       int state;


      state = zip_fread(file, ( void *)data, size);
      LOG_INFO( "ParseText","begin to read file: %d, %d" , state,  size);
       if(state == 0)
      {
             return false ;
      }
      data[size]= '\0';


       if(!load(data, size))
      {
             return false ;
      }


       return true ;
}


 以上使用libzip的 两个api,
zip_open 从 解压的 apk中 获取 路径名为 fullFileName的文件;
zip_fread,读取 字节数 为 size大小 的 文件内容到 char数组中

以上代码的 load(const char *data, int size) 函数,即为 Horde3D Resource类的 load函数,功能一样,都是对加载的数据进行处理。

Shader类 作为 Resource类的子类,其load函数主要是对  shader文件进行处理:
假设shader文件内容如下:
[[FX]]

// Samplers
sampler2D albedoMap;

// Contexts
context OVERLAY
{
     BlendMode = Blend;
}

[[VS_OVERLAY]]

varying vec2 texCoords;


[[FS_OVERLAY]]

uniform vec4 olayColor;

可以看出,Horde3D的  shader文件写法 有3块:
 1 FX section:
     这一部份 为 一个 总体的概括,主要说明 shader的内容涉及到的 渲染管线流程
 2 VS_OVERLAY section:
   这一部分为 自定义的 顶点着色器的部分
 3 FS_OVERLAY section:
   这一部分为自定义的  片段着色器的部分

在load 函数中 首先要对shader文件 进行每个section的分割,然后再调用parse函数 对 每块进行解析,赋予相应属性的值。

下面,着重说下,Horde3D是如何对 shader文件的内容进行 分段,以及分离出每句的:

 对 Shader文件的内容进行 分段:
 bool ParseText::load( const char *data, int size)
{
       //Parse sections
       const char *pData = data;
       const char *eof = data +size;

       while (pData <eof)
      {
             if (pData < eof-1 && *pData == '['&&*(pData+1)== '[' )
            {
                  pData += 2;

                   //Parse section name
                   const char *sectionNameStart = pData;
                   while (pData <eof && *pData != ']' && *pData != '\n' &&*pData !='\r' )++pData;
                   const char *sectionNameEnd = pData++;

                   //Check for correct closing of name
                   if (pData >= eof || *pData++!=']' ) return false;

                   //Parse content
                   const char *sectionContentStart = pData;
                   while ((pData < eof && *pData != '[')||(pData < eof-1&&*(pData+1)!='[' ))++pData;
                   const char *sectionContentEnd = pData;

                   if (sectionNameEnd - sectionNameStart == 2 &&
                              *sectionNameStart == 'F' &&*(sectionNameStart+1)=='X' )
                  {
                         //FX section
                         if (fxCode != 0x0) return false ;
                         fxCode = new char[sectionContentEnd - sectionContentStart+1];
                         memcpy ( (void *)fxCode , sectionContentStart, sectionContentEnd - sectionContentStart );
                         fxCode [sectionContentEnd - sectionContentStart] = '\0' ;
                  } else
                  {

                  }
            } else
                  ++pData;
      }<pre name="code" class="plain">
 
   
  load函数在一开始,会去掉文本的 注释部分。根据文本的定义, 我们可以看出每个section开头 :都有个 形如 [[ NAME  ]]的标题,  load函数里 就是根据这个 标题 [[]]的特性 来解析出每部分。解析出 FX部分后,将 FX的 内容 赋给 fxCode 。 后面  顶点着色器 和 片段着色器的 内容也是一样 它们 会分别 读取 存在 字符串里给 CodeResource进行处理 。
 如果,给属性附上某个属性值,还需要对每段的内容进行分句,拿到每一句后,才能进一步确定它是哪个值, 这主要是靠Tokenizer 这个内部类:
protected 变量:
     int _line;   //  1开始,记录文本中的行数
     const char *_p;   // 指向文本中的字符数据,起始位置为文本首地址
 
    char  _token[tokenSize], _prevToken[tokenSize]; // getNextToken() 每次去取出的字符串, _token 表示当前取出的字符串
    static const ptrdiff_t  tokenSize = 128;  // 表示取出的字符串的最大字符数 
     
 
  protected 方法:
     void checkLineChange():
       当遇到 '\r', '\n'会 换行, 对 line自动+1, p指针指向下一行
     void skip(const char *chars):
        当p指向的字符 在chars字符串里时,就跳到下一行。 如果不在,p指针就停止向前
    bool seekChar(const char *chars);
        它与 skip相反, 当p指向的字符不在 chars里时,会跳到下一行
       
     void getNextToken()
       这是重点要对待的api
       它首先使用 skip(" \t\n\r") 跳过所有空格
       然后 如果有 双引号的内容 ,  通过分别确定 " 和"的 地址 将引号之间的内容取出来
    如果没有 
      则 当遇到  空格 \t \n \r { } () <> = , ; 这些字符时 p会停止,  然后 根据前后指向的地址把内容取出来
    拿以上文本举例子, getNextToken会取一下内容:
 albedoMap
     ;
     context
     OVERLAY
     {
     BlendMode
      =
      Blend
      ;
     }
    .....
通过对以上 Tokenizer的分析,就可以看出   如何 将 Shader文本的内容 赋给 shader 变量的属性。  




 类似资料: