基于XNA的3D图形GPU渲染技术
一篇科技论文,希望大家多多支持:《XNA 3D粒子系统》
基于XNA的3D图形GPU渲染技术
摘要:介绍3D图形渲染的流程与XNA中的Vertex Shader、Pixel Shader和HLSL的引入及发展,阐述了它们的基本原理、应用及工作特点。
关键词:XNA;Shader;GPU;3D;HSLS
GPU 3D Graphi Shader technology in XNA
Summary:This paper presents the pipelining of render.The leading-in and the development of vertex shader and pixel shader and HLSL in XNA are introduced.The basic theory and application and feature of them are them are expatiated.
Key Words:XNA;Shader;GPU;3D;HSLS
引言
到21世纪的今天,3D技术已经发展成一门比较成熟的计算机图形学技术。在3D技术中对物体的顶点和像素的渲染是最主要的计算,它决定了3D图形的真实感。Microsoft在DirectX 8中提出顶点渲染(Vertex Shader)和像素渲染(Pixel Shader)。顶点渲染主要用来修饰3D物体的几何形状和控制光亮和阴影;像素渲染则主要用来操纵物体表面的色彩和外观。通过顶点渲染和像素渲染使得原来固定的渲染流水线变成开发人员能够灵活控制的可编程流水线。XNA现在已经支持3.0版的顶点渲染和像素渲染,最高可支持128位浮点色彩精度。浮点色彩在动态和精度上增加给图像质量带来了质的飞跃,让过去很多不可能的特效变成现实。
一、显卡渲染流程及Shader的引入
在XNA中主要通过HLSL(高级着色语言)进行GPU(图形处理器)的渲染编程。HLSL是由微软拥有及开发的一种语言,只能供微软的Direct3D使用。HLSL是微软抗衡GLSL的产品,同时不能与OpenGL标准兼容。他跟Nvidia的Cg非常相似。
HLSL的主要作用为将一些复杂的图像处理,快速而又有效率地在显示卡上完成,与组合式或低阶Shader Language相比,能降低在编写复杂特殊效果时所发生编程错误的机会。
XNA中使用HLSL进行GPU渲染的流程
首先,显卡从AGP或PCI-E总线接收要显示形体的顶点数据。这些数据包括位置、法线、贴图坐标等等,这些都是未经过任何变换,也就是形体在建模坐标系(Modeling Coordinate System)下的坐标。
然后,顶点数据被送到顶点处理单元,在这里进行坐标变换、光照计算等工作,变换的结果是把每个三角形变换到设备坐标系(Device Coordinate System)下。这里用到的变换矩阵、灯光等信息都是处理每一批顶点时一次性传给显卡的,作为显卡的资源。
在顶点处理圈定了三角形的范围后,由插值处理单元来确定三角形内的像素的位置和像素的其他一些必要参数,如颜色、贴图坐标等属性。另外每个像素还要通过深度检测和模板检测决定最终是否绘制。
最后,需要绘制的像素被送进像素处理模块,进行贴图像素取值、贴图混合等工作,必要的话每个像素光照也在这里完成。这里贴图等信息也是作为显卡的资源。像素最终的处理结果被放进后备缓冲区中。
二、顶点渲染的应用及其特点
通过顶点渲染,使开发者有能力控制渲染管道内的变换与光照部分,可以使用自己定义的光照模型,可以实时地产生纹理坐标。
Vertex Shader 2.0引入了流程控制,增加了条件跳转、循环和子程序,并提供正弦、余弦及其他功能强大的函数,这样大大简化了代码的编写,并且能够表现更加复杂的效果。例如:计算某些3D场景中的光源是Vertex Shader的一个经常应用,开发人员能够仅用一条普通的光源Shader通过调用子程序来产生具有不同的自定义色彩、形状、位置的任意数量的光源效果。Vertex Shader 3.0中不再限制程序的长度(Vertex Shader 2.0中对程序的长度限制为256条),开发人员可以通过一个很长的程序来完成各种复杂的效果。Vertex Shader 3.0还支持Vertex Frequency Stream Divide(r顶点渲染复用流分频器),程序员可以为每一个相同的模型设定不同的运动参数,让大量相同的模型形态各异、栩栩如生。
三、像素渲染的应用及其特点
Pixel Shader让开发人员可以自定义像素级的光照运算,可以用Normal Map 等技术来创造意想不到的效果。强大的可编程Pixel Shader是得以实现具有真实照片和电影质量级别效果的真正精华所在。
Pixel Shader 2.0可以支持高级程序语言和汇编语言,开发者还可以将汇编代码嵌入较高级的程序语言中。Pixel Shader 2.0最大可以使用16 材质和160条指令,新增了很多强化的运算和操作。例如,2.0版的Pixel Shader 能够使用压缩过的凹凸贴图--比标准的凹凸贴图占用更少的内存,这意味着我们能够不受限制地在游戏中使用更多细致的凹凸贴图效果。Pixel Shader 3.0将像素着色的精度提高到32位,而且不再限制像素着色程序的长度,从而可以高效地实现更为复杂的特效。Pixel Shader 3.0还支持MultipleRender Targe(t多渲染目标)技术,将一个像素的不同数据(位置、法线、颜色和材质等)在不同的缓冲区中同时进行渲染,同步渲染代替了分步渲染,大大提高了着色的效率。这项技术具有许多有价值的应用。
例如,在即时运算的3D场景中使用图像过滤或者后曲线处理。
四、XNA及相关技术
XNA是微软推出的所谓"通用软件开发平台",它的目标是让游戏开发过程更加轻松简单快速。XNA中,X代表微软掌握的技术资源,DirectX和Xbox;N代表次世代(NextGeneration);A代表架构(Architecture)。通过XNA可以开发基于Windows、Xbox或Zune游戏,可以合个人电脑、家用游戏机、便携式媒体播放器达到共享,以便在不同间移植游戏非常容易,且成本降低。XNA还可以使开发者将时间和精力更多的用在思考如何让游戏更加有趣,面不是技术本身。
XNA以DirectX为原型,微软希望把XNA发展为所有游戏开发平台的通用标准。如此一来将实现游戏开发工具的无缝嵌入和平滑过渡。在"大一统"尚未成形时,微软将透过XNA整合自家门下的Windows、Xbox与WindowsCE系统,在个人电脑、家用游戏机、移动设备这三种游戏平台上将开发工作融会贯通。
微软发面的软件包XNA Game Studio Express是建立在Microsoft Visual C#的基础之上方便学生和游戏爱好都开发同时基于Microsoft Windows、Xbox和Zune游戏的一组工具集。XNA Game Studio依赖于Visual C#语言,通过扩展它来实现游戏开发。XNA Game Studio同时包含了XNA Framework,这是一组基于.NET 且面向游戏开的发的一组托管库,同时XNA Game Studio还为你的游戏提供了入理图像和声音素材的工具。
五、XNA水面渲染实例
最简单的水波渲染方法是通过改变顶点的法向量来实惠的,这种方法最大的缺点就是当近距离观察时,可以看到水面实际是静止的。为了增加3D游戏中水波的真实性,让顶点真正运动起来是必须的。我们可以把水面运动看作一系列正弦或余弦波的叠加,根据不同的时间和顶点坐标位置,计算出当前顶点的高度。如比较常用的Gerstner Wave。以下是3D Gerstner Wave的3D波动方程,注意这里的x是一个矢量x(x,z),代表顶点的坐标位置,w为角速度,k为波矢量,k为波数,x0为顶点的初始位置。
如果需要渲染出具有更高真实度的水面,那Gerstner Wave方法就有些力不从心。我们可以使用FFT来模拟更真实的水面,它使用了统计波的模型(statistical wave model),通过快速傅立叶(FFT)变换求值。基本的思想是创建一个类似海面的,具有相同频谱的波形域,然后通过傅立叶变换转换到空间域。因此,水体表面实际上就是由许多由风产生的正弦波线性叠加而成。以下是所使用的波形方程:
公式中,h表示t时刻在网格位置 处的水面高度。 表示波矢(wave vector), 的计算公式如下
空间谱(spatial spectrum) 通过过滤复数形式的白噪声(complex white noise)获得:
对FFT生成水面的细节超出了本文讨论范围,但总的来说和Gerstner Wave是类似的,只不过方程更复杂而已。
混合多个Gerstner wave得到的水面网格
这个例子展示了如何使用XNA框架和HLSL进行渲染编程。
以下是程序的主框架
public class main : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
Camera camera;
Water water;
public main()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}
protected override void Initialize()
{
camera = new Camera(MathHelper.PiOver4, GraphicsDevice.Viewport.AspectRatio, 1, 3000, new Vector3(0, 0, 200), Vector3.Forward, Vector3.Up,Window.ClientBounds);
water = new Water(this, "SkyboxTex", camera);
water.Position=new Vector3(0, -120, 0);
Components.Add(water);
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
}
protected override void Update(GameTime gameTime)
{
camera.UpdateMatrix((float)gameTime.ElapsedGameTime.TotalMilliseconds);
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
foreach(ModelMesh mesh in terrain.Meshes)
{
foreach (BasicEffect effect in mesh.Effects)
{
effect.World = Matrix.CreateTranslation(0,-60,0);
effect.View = camera.View;
effect.Projection = camera.Projection;
}
mesh.Draw();
}
base.Draw(gameTime);
}
}
}
HLSL核心代码如下
// 顶点渲染
struct VSOUTPUT {
float4 vPos : POSITION;
float2 vTex : TEXCOORD0;
float3 vTanToCube[3] : TEXCOORD1;
float2 vBump0 : TEXCOORD4;
float2 vBump1 : TEXCOORD5;
float2 vBump2 : TEXCOORD6;
float3 vView : TEXCOORD7; };
// 水波
struct Wave {
float fFreq; // 频率 (2PI / 波长)
float fAmp; // 振幅
float fPhase; // 相伴 (速度 * 2PI / 波长)
float2 vDir; // 方向 };
Wave Waves[NUMWAVES] = {
{ 1.0f, 1.00f, 0.50f, float2( -1.0f, 0.0f ) },
{ 2.0f, 0.50f, 1.30f, float2( -0.7f, 0.7f ) },
{ .50f, .50f, 0.250f, float2( 0.2f, 0.1f ) },
};
float EvaluateWave( Wave w, float2 vPos, float fTime ) {
return w.fAmp * sin( dot( w.vDir, vPos ) * w.fFreq + fTime * w.fPhase );
}
float EvaluateWaveDifferential( Wave w, float2 vPos, float fTime ) {
return w.fAmp * w.fFreq * cos( dot( w.vDir, vPos ) * w.fFreq + fTime * w.fPhase );
}
float EvaluateWaveSharp( Wave w, float2 vPos, float fTime, float fK )
{
return w.fAmp * pow( sin( dot( w.vDir, vPos ) * w.fFreq + fTime * w.fPhase )* 0.5 + 0.5 , fK );
}
float EvaluateWaveSharpDifferential( Wave w, float2 vPos, float fTime, float fK )
{
return fK * w.fFreq * w.fAmp * pow( sin( dot( w.vDir, vPos ) * w.fFreq + fTime * w.fPhase )* 0.5 + 0.5 , fK – 1 ) * cos( dot( w.vDir, vPos ) * w.fFreq + fTime * w.fPhase );
}
VSOUTPUT VS_Water( float4 inPos : POSITION, float3 inNor : NORMAL, float2 inTex : TEXCOORD0, float3 inTan : TANGENT, float3 inBin : BINORMAL ) {
VSOUTPUT OUT = (VSOUTPUT)0;
// 产生水波
Waves[0].fFreq = fWaveFreq;
Waves[0].fAmp = fWaveAmp;
Waves[1].fFreq = fWaveFreq * 2.0f;
Waves[1].fAmp = fWaveAmp * 0.5f;
Waves[2].fFreq = fWaveFreq * 3.0f;
Waves[2].fAmp = fWaveAmp * 1.0f;
// 水波总数
inPos.y = 0.0f;
float ddx = 0.0f, ddy = 0.0f;
for( int I = 0; I < NUMWAVES; i++ ) {
inPos.y += EvaluateWave( Waves[i], inPos.xz, fTime );
float diff = EvaluateWaveDifferential( Waves[i], inPos.xz, fTime);
ddx += diff * Waves[i].vDir.x;
ddy += diff * Waves[i].vDir.y; }
// 输出position
OUT.vPos = mul( inPos, matWorldViewProj );
// 产生法线图texture coordinates
OUT.vTex = inTex * vTextureScale;
fTime = fmod( fTime, 100.0 );
OUT.vBump0 = inTex * vTextureScale + fTime * vBumpSpeed;
OUT.vBump1 = inTex * vTextureScale * 2.0f + fTime * vBumpSpeed * 4.0;
OUT.vBump2 = inTex * vTextureScale * 4.0f + fTime * vBumpSpeed * 8.0;
// 计算切线
float3 vB = float3( 1, ddx, 0 );
float3 vT = float3( 0, ddy, 1 );
float3 vN = float3( -ddx, 1, -ddy );
float3x3 matTangent = float3x3( fBumpHeight * normalize( vT ), fBumpHeight * normalize( vB ), normalize( vN ) );
OUT.vTanToCube[0] = mul( matTangent, matWorld[0].xyz );
OUT.vTanToCube[1] = mul( matTangent, matWorld[1].xyz );
OUT.vTanToCube[2] = mul( matTangent, matWorld[2].xyz );
// 计算世界空间向量
float4 vWorldPos = mul( inPos, matWorld );
OUT.vView = matViewI[3].xyz – vWorldPos;
return OUT;
}
// 像素渲染
float3 Refract( float3 vI, float3 vN, float fRefIndex, out bool fail )
{
float fIdotN = dot( vI, vN );
float k = 1 – fRefIndex * fRefIndex * ( 1 – fIdotN * fIdotN );
fail = k < 0;
return fRefIndex * vI – ( fRefIndex * fIdotN + sqrt(k) )* vN;
}
float4 PS_Water( VSOUTPUT IN ) : COLOR0 {
float4 t0 = tex2D( s0, IN.vBump0 ) * 2.0f – 1.0f;
float4 t1 = tex2D( s0, IN.vBump1 ) * 2.0f – 1.0f;
float3 vN = t0.xyz + t1.xyz + t2.xyz;
float3x3 matTanToWorld;
matTanToWorld[0] = IN.vTanToCube[0];
matTanToWorld[1] = IN.vTanToCube[1];
matTanToWorld[2] = IN.vTanToCube[2];
float3 vWorldNormal = mul( matTanToWorld, vN );
vWorldNormal = normalize( vWorldNormal );
// 计算反射向量
IN.vView = normalize( IN.vView );
float3 vR = reflect( -IN.vView, vWorldNormal );
float4 vReflect = texCUBE( s1, vR.zyx );
vReflect = texCUBE( s1, vR );
vReflect.rgb *= ( 1.0 + vReflect.a * fHDRMultiplier );
float fFacing = 1.0 – max( dot( IN.vView, vWorldNormal ), 0 );
float fFresnel = fFresnelBias + ( 1.0 – fFresnelBias ) * pow( fFacing, fFresnelPower);
// 计算最终颜色
float4 vWaterColor = lerp( vDeepColor, vShallowColor, fFacing );
return vWaterColor * fWaterAmount + vReflect * vReflectionColor * fReflectionAmount * fFresnel;
}
最终渲染效果图
六、结论
通过上面的代码和图片可以看出XNA框架设计的非常方便简捷,有很高的开发效率。可通过少量的代码实现一个完整的游戏,给开发人员带来了极大的方便。相比DirectX来说,代码量可能只有四分之一。同时XNA对HLSL的调用也非常的方便,渲染效果也能达到次世代水平。目前微软已经发面了XNA Game Studio 3.0 Beta,相信XNA将会受到更多游戏开发者的青睐。
参考文献
[2]Allen Sherrod. Ultimate Game Programming With DirectX
[3]叶思义,李震宇. XNA PC·XBOX360 C#游戏程式设计
[4]尚晶晶,Direct3D游戏开发技术详解
[5] Lynn Thomas Harrison.Introduction to 3D Game Engine Design Using DirectX 9 and C#