用HLSL实现凹凸纹理映射---(读书笔记)
用HLSL实现凹凸纹理映射有很多种方法,现在介绍的这种有一个缺点就是对美工资源的要求比较高.
首先我们需要两张纹理,第一张是纹理的颜色图(即普通的一张纹理),第二张是保存法线信息的纹理. 也可以用一张既有原始颜色信息又有发现信息的纹理。下面的例子给的HLSL代码用的是两张分开的。好了废话不多说我们先来看看代码:
*******************************************************************************************************************************************************************************************无敌分割线****************************************
//==============================================================
// Desc: 效果文件
//==============================================================
//--------------------------------------------------------------
// 全局变量
//--------------------------------------------------------------
float4x4 matWorldViewProj;
float4x4 matWorld;
float4 vecLightDir;
float4 vecEye;
//--------------------------------------------------------------
// 纹理采样器
//--------------------------------------------------------------
texture ColorMap;
sampler ColorMapSampler = sampler_state
{
Texture = <ColorMap>;
MinFilter = Linear;
MagFilter = Linear;
MipFilter = Linear;
};
texture BumpMap;
sampler BumpMapSampler = sampler_state
{
Texture = <BumpMap>;
MinFilter = Linear;
MagFilter = Linear;
MipFilter = Linear;
};
//--------------------------------------------------------------
// 顶点渲染器输出结构
//--------------------------------------------------------------
struct VS_OUTPUT
{
float4 Pos : POSITION;
float2 Tex : TEXCOORD0;
float3 Light: TEXCOORD1; //纹理空间中灯光方向
float3 View : TEXCOORD2; //纹理空间中法线方向
};
//--------------------------------------------------------------
// 顶点渲染器主函数
//--------------------------------------------------------------
VS_OUTPUT VS(float4 Pos : POSITION, float2 Tex : TEXCOORD, float3 Normal : NORMAL, float3 Tangent : TANGENT )
{
VS_OUTPUT Out = (VS_OUTPUT)0;
//顶点坐标变换
Out.Pos = mul(Pos, matWorldViewProj);
//构建一个3x3变换矩阵 用于完成从世界空间到纹理空间的变换
float3x3 worldToTangentSpace;
worldToTangentSpace[0] = mul(Tangent, matWorld);
worldToTangentSpace[1] = mul(cross(Tangent,Normal), matWorld);
worldToTangentSpace[2] = mul(Normal, matWorld);
Out.Tex = Tex.xy;
//顶点在世界空间中的坐标
float3 PosWorld = normalize(mul(Pos, matWorld));
//计算纹理空间中灯光方向向量
float3 Light = vecLightDir - PosWorld;
Out.Light.xyz = mul(worldToTangentSpace, Light);
//计算纹理空间中的观察方向
float3 Viewer = vecEye - PosWorld;
Out.View = mul(worldToTangentSpace, Viewer);
return Out;
}
//--------------------------------------------------------------
//像素渲染器输出结构
//--------------------------------------------------------------
struct PS_OUTPUT
{
float4 Color : COLOR;
};
//--------------------------------------------------------------
// Desc: 像素渲染器主函数
//--------------------------------------------------------------
PS_OUTPUT PS(float2 Tex: TEXCOORD0, float3 Light : TEXCOORD1,
float3 View : TEXCOORD2, float3 Att : TEXCOORD3)
{
PS_OUTPUT Out_ps = (PS_OUTPUT)0;
//颜色纹理采样
float4 color = tex2D(ColorMapSampler, Tex);
//凹凸纹理采样, 得到的是法线向量, 用于代替顶点法线进行光照计算
float3 bumpNormal = 2 * (tex2D(BumpMapSampler, Tex) - 0.5);
//标准化纹理空间中的灯光方向和观察方向
float3 LightDir = normalize(Light);
float3 ViewDir = normalize(View);
//根据凹凸贴图的发线进行漫反射光照计算
float4 diff = saturate(dot(bumpNormal, LightDir));
//计算自身遮蔽阴影项
float shadow = saturate(4 * diff);
//计算镜面反射强度
float3 Reflect = normalize(2 * diff * bumpNormal - LightDir);
float4 spec = min(pow(saturate(dot(Reflect, ViewDir)), 15), color.a);
//混合纹理颜色和光照颜色, 得到每个像素的最终颜色
Out_ps.Color = 0.2 * color + (shadow * (color * diff + spec) );
return Out_ps;
}
//--------------------------------------------------------------
// Desc: 技术
//--------------------------------------------------------------
technique TShader
{
pass P0
{
VertexShader = compile vs_2_0 VS();
PixelShader = compile ps_2_0 PS();
}
}
*******************************************************************************************************************************************************************************************无敌分割线****************************************
注释的比较清楚,但还有几个难点我们挑出来看看.
第一个是要完成一个世界空间到纹理坐标空间的变换。代码提取出来如下
float3x3 worldToTangentSpace;
worldToTangentSpace[0] = mul(Tangent, matWorld);
worldToTangentSpace[1] = mul(cross(Tangent,Normal), matWorld);
worldToTangentSpace[2] = mul(Normal, matWorld);
这几个乘法分别是什么意思呢?如果线性代数基础好的话肯定不难理解……可不幸的是我的线性代数都忘的差不多了,因此我是这样来类比的。我们知道Tangent 和Normal分别是在主程序中计算好的模型的切向量和法向量,那个cross的意义也就很明显了,建立了一个纹理所在模型上的三维坐标系,即纹理空间。 那么是怎么变换的呢?我们假设matWorld是Identity矩阵。Tangent 和 Normal 和 另外那个与他们俩正交的向量就是标准的X-Y-Z坐标系,那么分别用代码中的乘法乘出来不就正好是在该XYZ基坐标下的变换吗!? 因此可以不严谨的推出,用纹理空间的三个基分别做一个Identity变换,得到的正是在该纹理空间下的变换。
又因为Tangent这套基最终在三维空间里还要做一个世界变换。因此我们的纹理空间还要乘上一个世界变换得到在相应世界坐标系下的纹理空间…………说的有点乱。但自己看着还是感觉蛮清晰的。对不住各位看客了…………第一个难点到此结束。
第二个难点在这里,代码提出来看看先:
float3 bumpNormal = 2 * (tex2D(BumpMapSampler, Tex) - 0.5);
这里对原来的从凹凸纹理采样出来的法线进行了适当的变换(偏移-0.5个单位和缩放2倍)。进行这样的变换是必须的,因为凹凸纹理中的法线是以无符号纹理格式存储的,期中每个值都在【0,1】区间内,而存在这样的限制主要是为了兼容老式硬件。因此,必须将这些法向量还原到有符号区间去。【-1,1】。 (书上原话,至于为什么要这么存储数据,为什么这样就能兼容老式硬件暂不深究…………)。
剩下在ps里面都是一些基础的光照模型的计算代码,有一个自身阴影的计算:
float shadow = saturate(4 * diff);
通过漫反射成分减少镜面反射的成分,粗略的达到一个自身阴影的效果。
转帖