潘李亮 2007-3-16
xheartblue@163.com
关于学习,中国有句古话叫“学以致用”,可见把学到的东西用于实际实践中是多么的重要,现在学习Direct3D/HLSL的人非常多,教程也非常多。但是很多人不知道看完这些教程后该干什么,或者说可以怎么利用学到的知识,本文针对已经学习过Direct3D/HLSL的初学者,讲述如果将HLSL用于数字图像处理,带领大家一起体会HLSL的强大。
本文会对Direct3D/HLSL做一个简单的介绍,但是假设读者已经了解和掌握了Direct3D/HLSL的基本知识。
简介.
1)Direct3D和HLSL
众所周知,Direct3D是微软开发的用于编写Windows下高性能图形程序的3D API。通过Direct3D,我们可以访问高速的图形加速卡。它是DirectX众多成员的一部分。
HLSL 全称High Level Shading Language . 是MS推出Direct3D 9时的一个重要更新。所谓的Shading Language还需要从Direct3D的图形管道说起,Direct3D在Direct3D 8以前只能工作在固定管道(Fixed Function Pipe-line)的模式下,在固定管道模式下,图元从提交到被转化成可以显示的像素是按照实现定义好的流程和算法来完成,可以认为是固化在硬件中的死功能。
从Direct3D 8开始,微软在Direct3D中引入了可编程管道(Programable Function Pipeline)的概念,在可编程管道中,开发人员可以自己编写用于处理顶点和像素的程序,这些程序是运行在GPU上而不是CPU上的。在Direct3D里面,用于处理顶点的程序叫Vertex Shader,用于处理像素的叫Pixel Shader。(目前最新的Direct3D10中又引入了Geometry Shader的概念)。因为硬件的水平在进步,所以可编程管道的处理能力也在不断的提高,根据不同的硬件能力,Shader的版本也已经有对应的不同版本。从Direct3D发布的最早的Shader Model 1.0到现在主流的Shader Model 3.0,可编程管道已经能提供一点范围的通用编程能力了,这就是所谓的GPGPU。
从名字上可知,HLSL是一种高级语言(High Level),那么必然有与之对应的Low Level Shading Language,这个低级的语言就是ASM的Shader。它是类似于汇编语言,难以编写和维护,而HLSL则跟我们熟悉的C/C++语言非常类似。大大降低了开发人员学习的成本。HLSL本身就是微软和nVidia联合开发的,nVidia的版本称为Cg,也就是C for Graphics。可想而知,它和C是有同样的血统的。
本文不是Direct3D和HLSL的教程,如果读者觉得以上的概念还比较陌生,请先学习Direct3D的基础知识。同时关于如何在Direct3D应用程序中使用HLSL编写的Vertex Shader和Pixel Shader,请参阅其它的教程和微软的DirectX SDK。
2)RenderMonkey简介
现在的开发人员可能都比较熟悉IDE的工作模式,尤其是使用Visual Studio一类开发工具的Windows程序开发人员。在一个统一的开发环境中,可以编写和调试程序。HLSL作为一种新的语言,GPU编程作为一种新事物,目前还没有很好的IDE能完整的支持编写,调试一体化的工作方式。在本文我们将使用ATI的一个相对比较好用的开发HLSL的IDE: RenderMonkey。
RenderMonkey是由前ATI开发的,用于编写Shader,并调试Shader的一个工具。由于RenderMonkey支持插件,所以RenderMonkey既可以编写OpenGL的GLSL也可以编写Direct3D的HLSL。它能支持创建RenderTarget,多Pass渲染,可以自由选择用哪个shader model来编译代码。并能加亮显示shader代码。
经典的RenderMonkey界面如下图
左边为工作区,右边为预览区域。下面为信息输出区。在左边的工作区里可以看到。我们可以对Shader的工程进行分组,其中每一个可以独立工作的工程称为一个Effect。在同一时候预览区中只能预览当前激活的Effect。每个Effect由不同的对象组成,其中比较重要的对象如下:
1) Pass . 这个pass就是渲染中常提到的pass.代表一遍的渲染
2) 几何体。就是类红色茶壶表示的,它代表在渲染中使用的几何体。
3) 纹理对象和RenderTarget对象(用一个铅笔表示)
4) Shader中用到的参数,这些参数可以是自定义的,也可以是预定义的(比如当前的观察矩阵,摄像机的位置等参数)。
5) 每个pass中用到的Shader。这些shader可以在RenderMonkey的代码编辑器中进行编辑,并调用命令来编译。
因为文章篇幅的关系,也不采用编写Direct3D程序加载HLSL的方式来做演示程序,而是直接使用RenderMonkey来作为演示的平台。关于如何使用RenderMonkey,请参照RenderMonkey的帮助,或者打开RenderMonkey自带的例子,很容易就能掌握这个工具的使用方法。
GPGPU
本文将要介绍的是如何用HLSL来实现PhotoShop的滤镜效果,也就是说需要通过GPU来进行数字图像处理。这是目前很流行的GPGPU的应用的一种。
我们知道,GPU和CPU的工作方式和用途都是不同的,CPU是通用的处理器,而GPU是专用于处理3D图形显示的,因此CPU的指令集更加丰富,而GPU的指令集更加有针对性,因此这就决定了GPU在牺牲了CPU的灵活性的前提上有更快的运行速度。GPU特别适合处理那种可以大规模并行的算法,比如某些数字图像处理算法。
因为目前我们的程序只能通过Direct3D的API才能访问到GPU,一般我们采用Pixel Shader来进行GPGPU,所以我们要使用GPU来处理数据的时候,必须完成以下几件事:
1).将数据提交给GPU
2).调用对应命令让GPU开始处理数据
3).从GPU哪里取回处理完毕的数据。
我们可以通过两种方法将数据提交到GPU,纹理和shader的参数,纹理中一般保存我们需要进行处理的数据,而shader参数则一般是用于数据处理算法需要用到的一些参数。当然这也不是绝对的。
当数据已经准备完毕后,我们调用Direct3D的drawPrimitive函数在屏幕上绘制一个纹理相同大小的矩形,把GPGPU的算法写到用于绘制这个矩形的Pixel Shader中。当Direct3D开始绘制这个矩形以后,会为每一个象素调用一次整个Pixel Shader,然后把Pixel Shader的输出写入到RenderTarget中,因为我们绘制的矩形的大小和纹理的大小是一致的,所以输出象素和纹理的象素可以做到一一对应的关系,也就是说纹理中的每一个象素在经过Pixel Shader的运算后被输出掉RenderTarget里,等于对这个数据调用了一次我们需要的算法。我们知道现代的GPU中往往有大量的Pixel Shader处理单元,而这些处理都是可以并行运行的,可想而知,这个处理是非常快速的。
经过前面的步骤,处理完的数据已经到了RenderTarget里了,我们可以事先自己创建一个RenderTarget(通常和输入纹理等大)来接受步骤2中的数据,然后Lock这个RenderTarget取回数据。也可以在步骤二中直接把图象绘制到屏幕上,通过Capture屏幕来得到输出(对于图象处理也够了,就是速度慢,而且显得非常傻)。
GPGPU简单介绍到这里。详细的GPGPU资料请参考www.gpgpu.org。 同时nvidia的网站和发布的SDK上也有很多关于GPGPU的例子。
接下来我们使用RenderMonkey来搭建一个用于数字图像处理的架子,以实现类似PhotoShop的滤镜效果
RenderMonkey图像处理的架子-图像黑白化
下面我们通过一个简单的例子,先来完成一个最简单的图像处理-把图像黑白化。来说明RenderMonkey如何来处理数字图像。
Render Monkey和VC类似,内置了一些工程代码。在这里我们在RenderMonkey的工作区菜单里选择Add Effect -> DirectX->Screen-AlignedQuad. 在生成的工程中,我们看到RenderMonkey为我们显示了一个默认的图片,首先我们就是要修改这个图片,我们双击那个base图片对应的节点,选择一个我们要演示的图片。如下图。
接下来,我们要开始进行我们关键的一步,编写处理图像的算法,我们双击刚才建立的项目中的single pass -> pixel shader . 开始编辑Pixel Shader的代码。
我们知道,一个RGB颜色的亮度和各个分量之间的关系的公式为:
GrayValue = 0.3 * R + 0.59*G + 0.11 *B
根据这个公式,我们的代码如下:
sampler2D Texture0; float4 main( float2 texCoord : TEXCOORD0 ) : COLOR { float4 _inColor = tex2D( Texture0, texCoord ); float h = 0.3 * _inColor.x + 0.59 * _inColor.y + 0.11* _inColor.z; float4 _outColor = float4(h,h,h,1.0);</ |