Shader Model 3
Using Vertex Texture
顶点纹理白皮书中文版
翻译者
周波
zhoubo22@hotmail.com
版权所有
Philipp Gerasimov
Randima (Randy) Fernando
Simon Green
NVIDIA Corporation
仅以此文赠与
Rita 19
周
岁生日快乐
Shader Model 3.0:Using Vertex Textures SM3:
使用顶点纹理
随着
GPU
可编程特性的发展,
Vertex Shader
与
Pixel Shader
的差别越来越大。现在,
Geforce6
系列
gpu
将
Vertex Shader
与
Pixel Shader
之间的通用性特征向前发展了一大步。这篇文章特别介绍了
Shader Model3
的一项技术,
Vertex Shader Fetch
。它允许
Vertex Shader
像
Pixel Shader
一样从纹理中读取数据。
在现代图形处理中,顶点处理的性能表现不是受制于内存带宽、
cpu
速度,就是受制于
Pixel Shader
的处理能力。但这也意味着你可以实现一个复杂的
Vertex Shader
,提高画质,而且不会有多大损失。
Vertex Shader
的制作成本比
Pixel Shader
高,所以在最新的
6800
芯片里,
Vertex Shader
的数目要少于
Pixel Shader
。这样,我们就可以安心地实现一打漂亮的效果,比如流体的模拟等等。
这篇白皮书将同时向您展示如何在
OPENGL
以及
DIRECTX
中实现
Vertex Texture
。最后,我们将用一个游戏的范例向您演示使用
Vertex Texture
的情况
。
Specification
详解
DIRECTX
与
OPENGL
中都可以使用
Vertex Texture
。
DIRECTX9
MS DX9SDK
的开发文档中已经包括了
VERTEX TEXTURE
的详细说明。
Vertex Shader3(
即使用Vertex Shader3编译器生成的Shader)支持
vertex_fetch
,
4
种纹理样本。
Vertex Texture
,单从名称上看就同传统的
PIXEL TEXTURE
类似,但是同
PIXEL TEXTURE
比起来有一些差别,
硬件无法直接支持
Bilinear Trilinear
过滤,但是您可以手动在
Vertex Shader
中实现
反锯齿,内容同上。
自动
Mipmap LOD
,
无效
D3DCAPS
成员
MaxVertexShader30InstructionSlots
标识
Vertex Shader3
中代码的上限行数。
MaxVShaderInstructionsExecuted
标识了
Vertex Shader
的上限代码行数,包括
Texture Fetch
的数目。
DIRECTX9
支持软件
Vertex Processing
模式下使用
Vertex Texture
,这样甚至当硬件不支持
Vertex Texture
时也可以运行。
6800
支持使用
D3DFMT_R32F and D3DFMT_A32B32G32R32F
的纹理格式实现
Vertex Texture
。
OPENGL
顶点纹理查找通过
NV_V_PROGRAM3
扩展实现。详情请参阅
http://www.nvidia.com/dev_content/nvopenglspecs/GL_NV_vertex_program3.txt
这是标准
ARB vertex program language
的一项
Option
(
操作)。这就意味着你可以调用现有的ARB
API
,载入程序,设置参数。在程序开头加入以下代码就可以了:
OPTION NV_VERTEX_PROGRAM3
在程序里加入
Vertex Texture
使用
Vertex Texture
的步骤如下:
检查硬件的
Vertex Texture
支持情况
创建
Vertex Texture
资源
在
Vertex Shader
中加入需要的代码
下面具体来看看怎样在
DIRECTX
以及
OPENGL
中实现。
DIRECTX
第一,检查硬件是否支持,否则将不得不用软件方式实现。调用
IDirect3D9::CheckDeviceFormat
里的
D3DUSAGE_QUERY_VERTEXTEXTURE
旗标查询
硬件支持的
Vertex Texture
格式。
Software Vertex Texture
支持所有
Vertex Texture
格式。
OPENGL
OPENGL
里只需要检查硬件是否支持
NV_VERTEX_PROGRAM3
扩展。
GLUT
库的
glutExtensionSupported
函数可以完成这项任务。
Vertex Texture
数目的上限用下列代码获得
glGetIntegerv(
MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB, &vtex_units)
6
系列
GPU
最大支持
4
个活动纹理(
Active Texture
)。你可以尽情的在
Vertex Shader
中调用它们,不过要注意Vertex Shader的代码行数。
创建
Vertex Texture
资源
DIRECTX9
库中的任何纹理创建函数都可以创建顶点纹理,
IDirect3D9::CreateTexture,
IDirect3D9::CreateCubeTexture, IDirect3D9::CreateVolumeTexture
等等。
当使用
SVP
时,顶点纹理必须创建在
D3DPOOL_SCRATCH
池中。
OPENGL
基本纹理调用操作已经包括了
Vertex Texture
的绑定,使用
GL_TEXTURE_2D
。目前只有
GL_LUMINANCE_FLOAT32_ATI
与
GL_RGBA_FLOAT32_ATI
这
2
种格式支持
Vertex Texture
。这些格式都包含了
1
个或
4
个
32bit
浮点数据通道。注意,使用其他的纹理格式,或者使用不支持的过滤方式都可以导致驱动调用
Software Vertex Processing
处理,导致性能下降。
示例代码如下:
GLuint vertex_texture;
glGenTextures(
1, &vertex_texture);
glBindTexture(
GL_TEXTURE_2D, vertex_texture);
glTexParameteri(
GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(
GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_NEAREST_MIPMAP_NEAREST);
glTexImage2D(
GL_TEXTURE_2D, 0, GL_LUMINANCE_FLOAT32_ATI, width, height, 0,GL_LUMINANCE, GL_FLOAT, data);
在
Vertex Shader
里访问
Vertex Texture
DIRECTX9
程序调用
IDirect3DDevice9::SetTexture
设置
Vertex Texture
,样本索引为
D3DVERTEXTEXTURESAMPLER1
到
D3DVERTEXTEXTURESAMPLER3
。在
D3DPOOL_DEFAULT
里创建的
Vertex Texture
同时也可以设置成
PIXEL TEXTURE
。
Vertex Shader
里的纹理样本必须使用
DEL_SAMPLEType
标识。
//
汇编代码
dcl_texcoord0 v0
dcl_2D s0
texldl
r0, o0, s0
// HLSL / Cg
代码
sampler2D
tex;
vDisplacement = tex2Dlod ( tex, t ); // t.w
包括
MIPMAP LOD
数据
OPENGL
VP
的纹理查找功能通过
TEX,TXB, TXL or TXP
实现,就像在
Fragment Shader
里一样(或者在其他高等级语言中比如
CG
)
。与
Fragment Shader
的差异是
,
纹理查找功能无法自动计算
LOD
。
LOD
的意义是确定纹理在屏幕上缩放的尺寸大小。一般根据纹理坐标象素的改变频率计算,但这里的麻烦是,
Vertex Texture
由顶点访问,硬件很难计算
LOD
值。所以你不得不自己在
Vertex Processing
里计算
LOD
。
MIPMAP
类似普通的
Pixel Shader
纹理,它可以为
Vertex Texture
在性能与画质之间折中。早期的图形处理管线中没有
Pixel-Level
这一概念,无法计算顶点纹理的
LOD
。如果需要使用LOD我们不得不人工在
Vertex Shader
里计算
mipmap
。
示例代码如下:
#define maxMipLevels 10.0f
Out.HPOS = mul( ModelViewProj, vPos );
float
mipLevel = ( Out.HPOS.z / Out.HPOS.w ) * maxMipLevels;
float
vDisplacement = tex2Dbias( tex, float4( t, mipLevel, mipLevel );
这是根据顶点的深度计算
LOD
的算法,开销很小,精度能够让人满意。
#define maxMipLevels 10.0f
Out.HPOS = mul( ModelViewProj, vPos );
float
mipLevel = ( Out.HPOS.z / Out.HPOS.w ) * maxMipLevels;
float
mipLevelFloor = floor(mipLevel);
float
mipLevelCeiling = mipLevelFloor + 1;
float
mipLevelFrac = frac(mipLevel);
float
vDisplacementFloor = tex2D( tex, float4( t, mipLevelFloor,mipLevelFloor );
float
vDisplacementCeiling = tex2Dbias(tex,
float4(
t,mipLevelCeiling,mipLevelCeiling );
float
vDisplacement = vDisplacementFloor + vDisplacementCeiling
Filter
过滤
Vertex Texture
允许纹理过滤,但是要根据硬件的支持情况。
6
系列只支持
NEAREST-NEIGHBOR
过滤模式。你也可以手动在
Vertex Texture
里实现过滤。
Bilinear Filtering
#define textureSize 512.0f
#define texelSize 1.0f / 512.0f
float4 tex2D_bilinear( uniform sampler2D tex, float2 t )
{
float2
f = frac( t.xy * textureSize );
float4 t00 = tex2D( tex, t );
float4 t10 = tex2D( tex, t + float2( texelSize, 0.0f );
float4
tA = lerp( t00, t10, f.x );
float4 t01 = tex2D( tex, t + float2( 0.0f, texelSize ) );
float4 t11 = tex2D( tex, t + float2( texelSize, texelSize ) );
float4
tB = lerp( t01, t11, f.x );
return
lerp( tA, tB, f.y );
}
Bilinear Filtering With Mipmapping
float4 tex2D_bilinear( uniform sampler2D tex, float4 t )
{
float2
f = frac( t.xy * miplevelSize );
float4 t00 = tex2Dbias( tex, t );
float4 t10 = tex2Dbias( tex, t + float4( texelSize, 0.0f, 0.0f, 0,0f );
float4
tA = lerp( t00, t10, f.x );
float4 t01 = tex2Dbias( tex, t + float4( 0.0f, texelSize, 0.0f, 0.0f ) );
float4 t11 = tex2Dbias( tex, t + float4(texelSize, texelSize, 0.0f, 0.0f));
float4
tB = lerp( t01, t11, f.x );
return
lerp( tA, tB, f.y );
}
如果单纯站在性能的角度上考虑上述算法,还是
Bilinear
最好。
Bicubic
、
Trilinear
,以及其他的过滤算法都可以在
Vertex Shader
里实现。其中,
Trilinear
过滤对性能的要求要高一点,因为
Shader
需要从不同等级的
mipmap
里访问纹理。
Performance Tips
性能
6800
可以在一秒钟内生成
6
亿多个顶点。当然,这是在
Vertex Shader
没有任何“负载”的情况下测试的结果。如果使用
Vertex Texture Fetch
后是什么情况呢?我们的数字是每秒钟生成
3
千
3
百多万个位移顶点,计算了基本位移,使用
NEAREST
方式过滤。
3
千
3
百多万个位移顶点,意味着如果以每秒
30
帧的速度绘制画面,每一帧画面将有
100
多万个
Displacement Vertics
位移顶点。这比现在任何一款游戏在一帧画面里出现的顶点都要多,而且,并不是每个顶点都需要进行位移操作。你可以使用
6
系列
gpu
的动态分支功能,对每个定点是否需要进行位移操作进行预测。比如做一次
dot(V,N)
运算,测试顶点是否靠近阴影,如果远离阴影就可以避免位移操作。这时,你就可以把节省下来的硬件资源用于处理过滤等效果上。我们推荐,如果你的
Vertex Shader
很复杂,最好在处理过程的早期就对画面或顶点进行剪裁与剔除。
// OpenGL example
float4
vClipPos = mul( ModelViewProj, vPos );
float3
bClip = abs( vClipPos.xyz ) < ( vClipPos.www + vClipOffset );
if(
all(bClip) )
{
DoLightingAndDisplacement(
);
}
还有一点非常重要,“顶点纹理不应看作连续的
RAM
。顶点纹理在提取数据时不是真正的连续读取,而是会产生等待时间。因此使用顶点纹理的最佳方法就是先进行纹理提取,然后进行逻辑算法计算,这样能在使用纹理提取前避免等待时间。顶点纹理不是用来代替大量的常量的阵列,而是用于减少顶点数据,这样每个顶点只有少量的顶点纹理需要提取数据。”
——
摘自《
GPU_Programming_Guide_Chinese From NVIDIA
》
<Case Study>
目前,一些游戏已经开始使用
Vertex Texture
。比如下面要提到的这款游戏,由
Maddox GAME
开发,
Ubi Software
发行的
Pacific Fighter
。
现代游戏的设计中,飞行模拟类游戏最适合使用
dm
技术。这是因为,这些游戏的场景中包括大量的地形、河流、海洋等。
Dm
可以为这些场景提供更好的效果。让我们看一下这款使用
Displacement Mapping
的游戏。
IL-2 Sturmovik
系列游戏最近年来比较成功的飞行模拟类游戏,在中国武汉曾经进行过一场国际性比赛。游戏制作人员非常留心游戏业里出现的最新技术,并运用到他们的作品中。比如这款最新的
Pacific Fighter
,完全发挥了
6
系列
gpu
的性能。“
Vertex Shader
里可以访问纹理是
3D
加速硬件最值得期待的技术之一。”
Yuri Kryachko
,主程序员如是说。
在这款游戏中,海水的绘制非常重要。开发人员采用了
Vertex Texture
,实现了目前游戏领域中最真实的流水效果。在没有采用
Vertex Texture
之前,开发人员一般使用凹凸贴图模拟水面,但是与采用
Vertex Texture
和几何位移算法实现的效果比起来有天壤之别。图片对比如下。
这款游戏的
WaterShader
非常复杂,超过
140
行,用于用物理的方式计算水面的动画,以及反射折射效果。每一个顶点的位移都是由多个
dynamic normal maps
(动态向量映射)用几何方式计算出来的。而且
Shader
从多个纹理中读取数据进行过滤操作,使画面更加真实。
Yuri Kryacko
说,“当我们在
Vertex Shader
及
Pixel Shader
中同时使用动态分支功能时,性能得到了很大的提高。我们想再优化代码,使用新的
Shader
,提高整体的画质,使我们的引擎的真实性达到一个新的高度。”
Downloads
下载
想学习关于
Vertex Texture Fetch
更多的东西吗?从
NVIDIA
的站点上下载范例吧
http://download.nvidia.com/developer/SDK/Individual_Samples/samples.html
http://download.nvidia.com/developer/SDK/Individual_Samples/effects.html