游戏开发论坛

 找回密码
 立即注册
搜索
查看: 9298|回复: 3

RELIEF技术原理及实现步骤

[复制链接]

33

主题

159

帖子

272

积分

中级会员

Rank: 3Rank: 3

积分
272
QQ
发表于 2012-2-8 13:39:00 | 显示全部楼层 |阅读模式
  Relief是一种欺骗眼睛的技术,在fx composer里呆呆地看例程看了两天,总算想通了它的原理,这里写下我个人对RELIEF原理的理解,至于详细步骤,全在附件里。
 
  下面的图片分别是 1普通无反射光高洛得着色,2NormalBump效果 3Parralax效果 4 Rlief效果 5 ReliefSHADOW效果模型是一个简单8顶点的正方体
 





 
Relief 事实上是一种视差效果,只是一种基于经验的光照模型。
 
一、首先,必须理解TangentSpace切空间的概念
 
  由NormalMap引申出来的技术基本上就三种 NormalBump,parralax 和relief,这三种Shader技术全都需要用到Tangent Space的概念,Tangent Space是一种特殊的空间,ViewSpace视空间大家都知道,是以摄像机为原点,摄像机的xyz三方向构成的空间,WorldSpace是以xyz(0,0,0)为原点的空间,而TangentSpace(切空间)是个什么?我去年在做parralax的例程的时候在网上找过 ,有人解释说是 以顶点到视点的射线为Binormal,再以垂直于normal与binormal的射线为tangent构造出来的空间。我一直深信不移,可是在最近学Relief效果的时候发现,这绝对是错的。
 
对TangentSpace的理解,正确的是:
 
  -TangentSpace是以当前顶点为原点(所以每个顶点各有一个切空间)
  -以顶点的normal为z轴
  -以顶点所在纹理的U轴为Tangent方向(x方向)
  -以顶点所在纹理V轴的负方向 为Binormal方向(y方向)

  构造出来的空间(可能比较难理解,但TangentSpace是视差技术的基础)
 
二、顶点着色器
 
  在fxComposer的例程里,NormalBump parralax relief及扩展的reliefShadow技术中,所使用的VertexShader都是一样的:
 
V_OUT NormalBump_VS(V_IN IN)
{
        V_OUT OUT=(V_OUT)0;
        //output the vertex's position in mxWVP and mxWorldView
        OUT.v4Pos = mul(IN.v4Pos,g_mxWVP);//use OUT.v4Pos=mul(float4(IN.v4Pos.xyz,1.0f),g_mxWVP);//????
        OUT.v3PosVS = mul(IN.v4Pos,g_mxWorldView).xyz;
        //制作一个3X3的世界-视转换矩阵
        float3x3 mx3WorldView;
        mx3WorldView[0]=g_mxWorldView[0].xyz;
        mx3WorldView[1]=g_mxWorldView[1].xyz;
        mx3WorldView[2]=g_mxWorldView[2].xyz;
        //输出视空间下的光线方向
        OUT.v3LightDirVS=mul(-g_v3LightDir,mx3WorldView);
        //下面 输出视空间下的TANGENT BINORMAL NORMAL三个分量,
        /*由于这三个分量正好是组建一个切空间的xyz ,也就是说我们可以在pixel shader里 使用这里传出的TBN三个向量,组建一个转换矩阵,将视空间里的向量转到切空间的对应值(或者将切空间的向量转至视空间)。这是很重要的。
        */
        OUT.v3TangentVS = mul(IN.v3Tangent,mx3WorldView);
        OUT.v3BinormalVS= mul(IN.v3Binormal,mx3WorldView);
        OUT.v3NormalVS  = mul(IN.v3Normal,mx3WorldView);
       
        //输出纹理座标
        OUT.v2Tex=IN.v2Tex;
        return OUT;//完成
        }
 
  以上就是normalBump parralax relief三种技术通用的Vertex Shader段,应该是比较容易理解的,要注意的是,进入着色器的顶点属性并不是基础的 possition texcood 和normal ,而是增加了tangent 与 binormal两个元素。顶点的TBN是在directX工作区段使用D3DXComputeTangentFrameEx()函数取得,具体可以参考direct3d9 SDK里parralax例程。
 
三、NormalBump的片段着色器
 
  NormalBump是一种很实用的技术,很多D3D9的游戏,比如我最忠爱的大航海OL的城内场景就用了NormalBump技术。其原理简单地来说,就是按当前顶点所在的纹理座标,从NormalMap(法线纹理)上取得rgb值,对应转换为当前顶点法线向量的xyz分量(即:用法线纹理里的法向量取代顶点真正的法向量进行光照计算)。
 
  原理很简单,怎么实现它?难点在于,NormalMap里取出的法向量怎么样变换到ViewSpace中去。这里就用到了从顶点着色器出来的三个值:v3TangentVS(在视空间中的tangent向量) v3BinomalVS(视空间中的binormal向量) v3NormalVS (视空间中的normal向量)
 
  用这三个向量组成的3X3矩阵可以将TangentSpace里的Normal向量转至视空间。有了这个从法线纹理中取出的并且转入视空间的Normal向量,再结合LightDirectionVS 足以做出效果不错的凹凸效果。
 
/////////////////////////////////////////////////////////////////////////// NormalBump的片段着色器
float4 NormalBump_PS(V_OUT IN)        :COLOR0
{
        //从法线纹理中取出当前顶点的纹理法向量(MapNormal)
//注意这个 v3MapNormal 所在的空间是TangentSpace
        float3 v3MapNormal = tex2D(spNormalMap,IN.v2Tex).xyz-float3(0.5f,0.5f,0.5f);//in tangent space
        //从切空间TangentSpace 转到视空间ViewSpace
        //这里直接使用3分量直接组成向量,你也可以使用TBN做一个3x3矩阵来转换
        v3MapNormal = normalize(v3MapNormal.x*IN.v3TangentVS
                                                        -v3MapNormal.y*IN.v3BinormalVS
                                                        +v3MapNormal.z*IN.v3NormalVS);
        //取得当前点的纹理色彩 c3Tex
        float3 c3Tex = tex2D(spBase,IN.v2Tex).xyz;//RGB
        //下面是光照模型,不多解释了,
//无非以前用到法向量的地方,改成从法线纹理取出的法向量
        //View and Light's directions in VS(the vector is form vertex!)
        float3 v3ViewDirVS=normalize(IN.v3PosVS);
        float3 v3LightDirVS=normalize(IN.v3LightDirVS);
        //compute diffuse and specular terms
        float att = saturate(dot(v3LightDirVS,IN.v3NormalVS));
        float diff = saturate(dot(v3LightDirVS,v3MapNormal));
        float spec = saturate(dot(normalize(v3LightDirVS-v3ViewDirVS),v3MapNormal));//dot (H,N)
        spec = pow(spec,g_fSpecularExponent);
        //compute the final color
        float3 FinalColor = g_c4Ambient.xyz*c3Tex+
                                att*(c3Tex*diff+g_c4Specular.xyz*spec);
        return float4(FinalColor.rgb,1.0f);
}//结束
 
四、Parralax的片段着色器解释
 
  Parralax技术是在NormalBump的基础上形成的一种简单的视差效果,NormalBump只是取法线纹理中的法线来替换原始法线,进入光照模型后实现凹凸效果。至于所在顶点的高度差,则完全不过问,而事实上,你可以使你的纹理有不同的高度,纹理的高度信息又在哪里?
 
  NormalBump技术用到了NormalMap的rgb分量,但如果是一张32位的纹理,它除了rgb之外还有一个alpha通道,alpha通道本意是用来作透明处理的,但并不是只能用来作透明的,在NormalMap中 alpha通道就是纹理的高度值
 



 
上面两张图分别是 石头法线纹理的 rgb分量(左图)及alpha分量(右图)
 
Alpha值越低(黑),所代表的高度也越低。
 
  所以,进入parralax 及relief的法线纹理必须是32位(or 64bit)argb的图,其中alpha通道指示了当前纹理所指向顶点的高度。
 
Parrlax相对于NormalBump而言,只多了一个步骤:按高度进行纹理座标的偏移
 
float2 v2Tex = -fHeight*mul(mx3TStoVS,v3ViewDirVS).xy+IN.v2Tex;
 
其中fHeight是从法线纹理alpha位取出并格式化的相对高度值,
 
mul(mx3TStoVS,v3ViewDirVS).xy 是视向量转至纹理空间UVSpace后的方向
 
也就是说,在纹理上,按高度及视线的方向,进行纹理座标的偏移 ,进一步体现凹凸的效果.
 
////////////////parrlax片段着色器代码
float4 Parallax_PS(V_OUT IN) : COLOR0
{
        float4 v4MN = tex2D(spNormalMap,IN.v2Tex);
        float3 v3MapNormal = v4MN.xyz-float3(0.5,0.5,0.5);
        float  fHeight     = v4MN.w*0.06-0.03;
        float3x3 mx3TStoVS = float3x3(IN.v3TangentVS,IN.v3BinormalVS,IN.v3NormalVS);
        float3 v3ViewDirVS=normalize(IN.v3PosVS);
        float3 v3LightDirVS=normalize(IN.v3LightDirVS);
        float2 v2Tex = -fHeight*mul(mx3TStoVS,v3ViewDirVS).xy+IN.v2Tex;//texcood offset
       
        v3MapNormal = normalize(-v3MapNormal.x*IN.v3TangentVS
                                                        -v3MapNormal.y*IN.v3BinormalVS
                                                        +v3MapNormal.z*IN.v3NormalVS);
        //load the Texture's color (using float3 )
        float3 c3Tex = tex2D(spBase,v2Tex).xyz;//RGB
        float att = saturate(dot(v3LightDirVS,IN.v3NormalVS));
        float diff = saturate(dot(v3LightDirVS,v3MapNormal));
        float spec = saturate(dot(normalize(v3LightDirVS-v3ViewDirVS),v3MapNormal));//dot (H,N)
        spec = pow(spec,g_fSpecularExponent);
        //compute the final color
        float3 FinalColor = g_c4Ambient.xyz*c3Tex+
                                att*(c3Tex*diff+g_c4Specular.xyz*spec);
        return float4(FinalColor.rgb,1.0f);
}//parrlax PS 结束
 
四、Relief效果解释:
 
  Parrlax相对于NormalBump的效果有了很大的提高,但是缺点是很明显的,首先, texcood在边界上的情况下,容易出界 比如 tex = float2(0,0)的情况,如果向纹理的左上方向偏移-0.01,-0.02,实际取得的纹理会变成 texParrlaxed = float2(0.99,0.98);这当然是我们不愿意的。
 
  另一个缺点是纹理的偏移不能表现遮盖效果,在局部还会出现挤压或拉伸纹理造成失真的情况:如下图:
 

 
  Parrlax在实际使用时会出现纹理失真,所以某些时候的效果甚至还不如NormalBump,所以又出现了Relief的技术,大部分看过direct3d SDK 里parralaxSample(其实是Relief)效果的都会觉得很惊奇,能用法线纹理在一个平面上表现出如此生动的凹凸效果。但那个Sample里的内容又实在是让人困惑。我第一次看那个例程时用了一个星期,结果还是放弃了,后来接触到了FxComposer,才在里面看懂了Relief的含意,而这次只用了一天。还是那句话,d3d SDK真让人怀疑是不是有意误导别人的。Relief的意义在于纹理偏移的方式与parrlax不同 。
 
  首先取得 视方向在纹理空间的转换,这里 v3DirTS是视向量转换到TangentSpace的产物
 
float2 v2UVDir = -v3DirTS.xy;
float fOffset = GetParralaxOffset(spNormalMap,IN.v2Tex,v2UVDir);
 
  v2UVDir是纹理空间(2维空间)的一个向量,表现的是视点到顶点的方向,也叫视差向量
GetParrlaxOffset()是一个局部函数。作用是:按当前视差方向,在法线纹理上找到一个坡度的范围内,视差方向上纹理高度最高的一个点,如下图:
 

 
定义为
 
//////////////////////////////////// GetParralaxOffset()函数代码
float GetParralaxOffset(
                in sampler2D spHeight,//the normalMap's sampler
                in float2 v2UVOrg,//the original Texcood
                in float2 v2UVDir)//the parralax direction in UVspace)
{
       
        const int iStep1 = 15;//第一段搜索的步数
        const int iStep2 = 5;//第二段搜索的步数
        float fSize = 1.0/iStep1;//第一段搜索每步的步长
        float fDepth = 0.0;//这里fDepth即是用来对比的高度值,也是一个迭代器
        //按视差方向搜索当前坡最高点
        for (int i =0; i< iStep1; i++)
        {
                // 取出这一步所在纹理上的高度值
                float4 c4Temp = tex2D(spHeight,v2UVOrg+v2UVDir*fDepth);
                // 如果这步取出的高度大于上一步的,迭代器累加
                if(fDepth < c4Temp.w)
                        fDepth += fSize;
        }// 第一段搜索完成
        // 二段搜索,注意如果第一段已经搜索到坡顶,二段搜索要往回偏移
        //而如果第一段搜索时尚未搜索到坡顶,则二段继续按视差方向修正
        //这是一种经验的做法,比如你45度向下看一个坡,你不可能只看到坡顶而遮盖坡顶后面所有的东西,接近坡顶,而又低于坡顶的这一部分,你也是看的到的。找个实物来试试- -!。
        for(int j=0; j<iStep2; j++)
        {
                fSize /=2;
                float4 c4Temp = tex2D(spHeight,v2UVOrg+v2UVDir*fDepth);
                if(fDepth < c4Temp.w)
                        fDepth += (2*fSize);
                ////向回偏移,每次偏移前一次的二分之一
                fDepth -= fSize;
        }
        return fDepth;
}//结束 输出为在UV空间,视差方向上的偏移量
 
  好,到这里,偏移量(float型)有了,UV上的视差向量有了 ,就可以计算出当前点视差偏移后的纹理座标了。
 
float2 v2UVNew = IN.v2Tex + v2UVDir*fOffset;
 
  然后,按这个新的纹理座标取色彩纹理及法线纹理进光照模型计算。就出现你想要的RLIEF效果了,这里不再把relief的片段着色器代码帖出了,可以在我的例程CODE里找,或者你也可以用FxComposer看原版的relief效果。(绝对别去看d3dSDK里的parrlax例程)
 
再讲一下这三个技术的优缺点,仅表个人观点:
 
  relief的缺点,虽然没有计算过FPS,不过相信RELIEF应该比较吃显卡的性能。主要是在计算offset这段。最少的情况下两段搜索加起来也有20步,每个step取一次tex2D做高度比较,如果加上ShadowMap或ShadowValue 和SKinMesh这些个技术,我不知道那些个老显卡跑不跑的起来。但是RELIEF的效果 喔买高,这个效果实在是让人喜欢。
 
  PARRALAX,我已经表示无语了,我之前做的水果贪吃蛇就是用PARRALAX的,结果效果没表现出来,倒是经常出现纹理偏移出界。但一个技术关键还是在于用在什么地方和怎么用。只能说至少我是没用对地方。如果不算上纹理出界的毛病,45度视角以内的效果跟RELIEF差不了多少,但效率!效率!
 
  至于NormalBump 这是很基础的技术,但也是最实用的。大部分的时候,你做RELIEF的消耗掉的性能,还不如NormalBump加上复杂些的Mesh来的省心省力。
 
  至此,我的RELIEF旅行结束,不算上在D3DX SDK里浪费的时间,看例程花了2天,写自己的程序花了3天,写这个东东 花了1天,虽然花的时间长了点儿,但如果对你有帮助,我还是很有成就感的。
 
徐 潇
Dana9919@163.com
QQ:61092517
 
本文源码:
http://upload.gameres.com/20122/sf_912953_2046.7z (GameRes下载,1.7MB)
 
http://115.com/file/dp23f2f0#Relief效果.zip
http://115.com/file/be3yxl8n#Relief效果CODE.zip
 
  公开的RELIEF CODE 与Relief效果查看器有一定的区别,效果查看器使用C#做外框架(只因为懒的在D3D里做控件)。

  在程序中使用 A W S D R F 及 方向键可以调整视角,由于做了一些小小的修改,在视角<30左右的情况下,Relief的效果将不会有更大的变化(不是故意的,但是懒的去修改回来)
 

58

主题

1437

帖子

2207

积分

金牌会员

Rank: 6Rank: 6

积分
2207
发表于 2012-2-8 17:21:00 | 显示全部楼层

Re:RELIEF技术原理及实现步骤

写的很好,我这样的外行都看明白了,
建议把图片放到开始的地方,代码放文章靠后的地方。
你的每段代码会流失10%的读者。
不过还是喜欢,普通无反射光高洛得着色,
后面几个看起来不太真实和舒服。

0

主题

3

帖子

9

积分

新手上路

Rank: 1

积分
9
QQ
发表于 2012-3-1 10:05:00 | 显示全部楼层

Re:RELIEF技术原理及实现步骤

非常有用!参考你的文章后改进了一些渲染,使得资源使用量下降不少~以前用的类似起源贴图,好几层贴图~

2

主题

31

帖子

57

积分

注册会员

Rank: 2

积分
57
发表于 2012-7-23 15:23:00 | 显示全部楼层

Re:RELIEF技术原理及实现步骤

关于TangentSpace的理解

按一般推导,纹理坐标=顶点坐标向量的转置*[矩阵]
顶点坐标向量= float3(pos-(0,0,0))
问题是你没办法求出这个矩阵,因为二个(顶点坐标向量的转置)构成一个广义矩阵,很难求出逆矩阵。
所以[矩阵]很难求,但[矩阵]的逆阵很容易求。

这个逆阵就是 切线到模型空间的转换。
之后利用正交性质,就得到TBN阵了。
对于那种常见水平网格平面模型,因为 TBN 相同,就可以直接手工计算得到。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2025-2-27 22:50

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表