Shader 程序在OGRE中的应用
1. 综述
本文主要阐述在OGRE程序中使用可编程管线来实现对实体的渲染具体过程。主要是从以OGRE使用shader的方式,并发散shader的渲染基础知识,提供需要研究的方向。
本文组织是以stepBystep方式展开,共包括
1.着色程序定义
2.着色程序声明
3.着色程序在材质脚本中的应用
2. 第一步: 定义shader着色程序
概览:
shader源文件是指在渲染管线中使用的GPU程序。用来完成渲染的顶点变换和像素变换两个过程。Shader的程序格式主要包括四种:汇编程序、GLSL、HLSL、CG。(CG教程中第三章),但是,由于汇编的开发效率较低,多数shader由后三者完成。(DemoTexture.hlsl)
详细步骤:
首先是顶点程序:
1 struct VS_INPUT 2 { 3 float4 Position : POSITION0; 4 float2 Texcoord : TEXCOORD0; 5 6 }; 7 8 struct VS_OUTPUT 9 { 10 float4 Position : POSITION0; 11 float2 Texcoord : TEXCOORD0; 12 13 };
我们看到,首先定义了两个结构体VS_INPUT、VS_OUTPUT 分别作为输入输出的参数(因为当参数较多的传入传出时结构体时最好的选择)。结构体中两个参数 分别存储位置和贴图信息 具体参数意义参考CG教程中第七章。
VS_OUTPUT vs_main( in VS_INPUT Input ,VS_OUTPUT Output,uniform float4x4 matViewProjection) { Output.Position = mul( matViewProjection ,Input.Position); Output.Texcoord = Input.Texcoord; return( Output ); }
接下来是一个以VS_OUTPUT为返回值的函数vs_main,当然还包含一些参数,这些参数就是作为GPU默认传入的参数,而第三个参数一会我们将会看到。
在函数体内部我们做了关于位置和材质的处理后,返回存储了其值的结构体变量。
接下来是像素着色程序:
struct PS_INPUT { float2 Texcoord : TEXCOORD0; };
首先定义了一个作为输入接收容器的结构体PS_INPUT,接收从顶点着色程序而来的参数,顶点的坐标信息不会传入,所以只有材质信息是它需要的。
float4 ps_main( in PS_INPUT Input,uniform sampler2D DemobaseMap : register(s0)) : COLOR0 { return tex2D( DemobaseMap,Input.Texcoord ) * 1.5; }
接下来是一个以四维浮点数为返回值的函数ps_main,用来处理获得的纹理并返回像素颜色。而为什么一定要四维浮点数呢?因为在GPU中都是以四元组的形式处理参数的,不足四维的会以0代替之,矩阵处理可以参考3D数学编程基础。
到这里整个shader程序就完成了他所有的工作:顶点处理和像素处理,而对于管线(CG教程中第二章)的其他部分(裁剪、光栅化等)是由GPU其他固定程序完成的。
3. 第二步: 声明shader着色程序
概览:
在C++或者其他某些语言中,函数在使用之前需要声明,表示存在这样一个函数,我们可以拿来用。在使用我们上面的定义的顶点着色程序和片段着色程序之前,我们需要先声明一下。
在一般的语言声明时候,较为简单,只要说明函数的原型、参数列表即可,但是在这里的程序声明就较为复杂一点,因为我们需要指定源文件、程序类型、指定入口程序、版本信息等。(DemoTexture.program)(OGRE帮助文档 3.1.4节)
详细步骤:
首先是顶点程序的声明:
vertex_program Demo/TexV hlsl
{
source DemoTexture.hlsl
entry_point vs_main
target vs_2_0
}
可以看到这里的程序结构较为清晰:vertex_program: 表明是顶点程序, Demo/TexV: 程序名称。 hlsl:程序格式是 基于Directx的着色程序(基于OpenGL的为 glsl,CG的是cg)
大括号里面: source DemoTexture.hlsl 指定顶点程序的源文件。
entry_point vs_main 指定入口函数(当定义多个函数时用来指定)
target vs_2_0 指定版本
关于版本的支持OGRE帮助文档 3.1.4节。当然你可以通过调用:
GpuProgramManager::getSingleton().getSupportedSyntax()
得到当前显卡支持的语法列表。
接下来是像素程序的声明:
fragment_program Demo/TexF hlsl
{
source DemoTexture.hlsl
entry_point ps_main
target ps_2_0
}
在像素着色程序声明和顶点的基本是一样的。我们看到源文件是同一个,但是我们指定的入口函数不一样,所以不会产生分析。另外,对于GLSL中是不需要指定入口函数和目标定义的,因为它以main()函数作为入口,并且它的源文件以编译到了本地图形处理单元了,则不需要中间编译(OGRE帮助文档 3.1.7节)
4. 第三步: 在材质脚本中引用 shader程序
概览:
下面是对声明过的顶点着色程序和片段着色程序的引用了。
着色程序在OGRE中作为材质中Pass中的一部分(符合硬件逻辑,其实着色程序在GPU处理中确实是替换了渲染通道(pass)的一部分)来引用的。(DemoTexture.material)
在引用中指定引用函数名称和初始化参数。
详细步骤:
以下是引用了shader程序的材质脚本:material Demo/Texture
{ technique { pass { vertex_program_ref Demo/TexV { param_named_auto matViewProjection viewproj_matrix } fragment_program_ref Demo/TexF { } texture_unit { texture Fieldstone.tga } } } }
我们看到在这个材质的Pass(关于参考OGRE帮助文档 3.1.2节)内多了两个定义如下:
vertex_program_ref Demo/TexV { param_named_auto matViewProjection viewproj_matrix } fragment_program_ref Demo/TexF { }
当材质中出现这两个定义是,表示对于使用这个材质脚本的实体来说已经放弃了固定管线的渲染方式,而是只通过shader来渲染。当程序无法找到你的源文件,或者你的shader程序有错误的话,将导致无法处理的局面:没有任何材质可用的白色实体。
在引用体当中我们看到如下代码:
param_named_auto matViewProjection viewproj_matrix
这便是对于我们在源程序中的matViewProjection中的初始化赋值函数。而viewproj_matrix则是一个在GPU中固化的一个坐标值,有由GPU自动获得。关于参数的详细介绍,参考OGRE帮助文档 3.1.9节。
这样,我们就完成了对顶点程序和片段程序的引用和初始化过程。当加载此材质时,见到有shader程序的引用,就会采用指向的顶点着色程序和片端着色程序来处理相应的传入的变量并将处理结果传出。
效果如图所示:
5. 总结
以上就是在OGRE中使用顶点着色程序和片段着色程序的全部过程,通过书写shader程序、程序的声明、程序的引用,主要使我们清楚了整个shader程序的加载过程和一些基本的程序概念。整个脚本的加载过程如图所示:
更多Demo请参考: