这节教程的框架跟NormalMap教程的框架一样,
这次绘制立方体用了三张纹理,第一张为BaseTexture(基础纹理),第二张为NormalMap(法线贴图),第三张为SpecularMap(高光贴图)
再次简述下每张贴图的作用:
BaseTexture:作为立方体每个面的第一层最基础的纹理,最直接的外观
NormalMap:纹理像素值为凹凸隆起面的法线量,起到改善光照细节的效果.
SpecularMap:纹理像素值为照射在每个面的镜面光强度,形成一种镜面的特效.
镜面光模型的简介和公式以及镜面光和镜面贴图的关系:
前面的教程我未曾给出镜面光的计算原型,这里我具体展开,先看图:
\
其中I为入射光,r为反射光,P为反射点,E为眼睛的位置(在3D图形里也就是摄影机Camera的位置)
镜面光=镜面系数*镜面光强度
Specular=SpecularFactor*SpecualrIntensity;
这里SpecularFactor=(cosθ)^n, 即cosθ的n次方,这个θ为反射光向量r和向量pe之间夹角,当向量r和向量pe都为单位向量时,
cosθ=(r)*(pe), n为镜面幂指数,你可以任意给予其一个正整数。
说到了这里,SpecularMap(镜面贴图)的作用是什么呢?
上面说到了镜面贴图的每个像素(r,g,b,a)就存储了镜面强度SpecularIntensity,有人会问了:“可以直接指定镜面光强度,为啥还用镜面贴图?”直接指定的镜面光强度对于面上所有的像素都是一样大,而用SpecularMap可以让三角面上所有的像素接受到的镜面光强度都不一样,从而可以模拟一些直接指定镜面光无法显示的特殊的效果
一般而言,镜面贴图上面为黑色(镜面光强度为0),白色(镜面光强度最大),灰色(镜面光强度介于白色和黑色之间)
这样的话,其它的倒是没多少可以说,其它的跟上一节法线贴图教程差不多,贴出我的Shader代码
Texture2D ShaderTexture[3]; //纹理资源数组
SamplerState SampleType:register(s0); //采样方式
//VertexShader
cbuffer CBMatrix:register(b0)
{
matrix World;
matrix View;
matrix Proj;
matrix WorldInvTranpose;
};
cbuffer CBLight:register(b1)
{
float4 SpecularColor;
float4 AmbientColor;
float4 DiffuseColor;
float3 LightDirection;
float SpecularPow;
};
cbuffer CBCamera:register(b2)
{
float3 CameraPos;
float Pad;
};
struct VertexIn
{
float3 Pos:POSITION;
float2 Tex:TEXCOORD0; //多重纹理可以用其它数字
float3 Normal:NORMAL;
float3 Tangent:TANGENT;
float3 Binormal:BINORMAL;
};
struct VertexOut
{
float4 Pos:SV_POSITION;
float2 Tex:TEXCOORD0;
float3 Normal_W:NORMAL;
float3 Tangent_W:TANGENT;
float3 Binormal_W:BINORMAL;
float3 LookDirection:NORMAL1;
};
VertexOut VS(VertexIn ina)
{
VertexOut outa;
//变换坐标到齐次裁剪空间(CVV)
outa.Pos = mul(float4(ina.Pos,1.0f), World);
outa.Pos = mul(outa.Pos, View);
outa.Pos = mul(outa.Pos, Proj);
outa.Tex= ina.Tex;
//将法线量由局部空间变换到世界空间,并进行规格化
outa.Normal_W = mul(ina.Normal, (float3x3)WorldInvTranpose);
outa.Normal_W = normalize(outa.Normal_W);
//将切向量由局部空间变换到世界空间,并且进行规格化
outa.Tangent_W = mul(ina.Tangent,(float3x3)World);
outa.Tangent_W = normalize(outa.Tangent_W);
//将切向量由局部空间变换到世界空间,并且进行规格化
outa.Binormal_W = mul(ina.Binormal, (float3x3)World);
outa.Binormal_W = normalize(outa.Binormal_W);
//求出顶点到眼睛的单位向量
float3 worldPos= (float3)mul(float4(ina.Pos, 1.0f), World);
//计算出每个顶点到相机的单位向量,之后在光栅化阶段进行插值
outa.LookDirection =CameraPos.xyz - worldPos.xyz;
outa.LookDirection = normalize(outa.LookDirection);
return outa;
}
float4 PS(VertexOut outa) : SV_Target
{
float4 BasePixel;
float3 BumpNormal; //隆起法向量
float4 color;
float DiffuseFactor; //漫反射因子
float SpecularFactor; //镜面反射因子
float4 Specular; //镜面反射颜色
float4 SpecularIntensity; //镜面强度
//增加漫反射光颜色
color = AmbientColor;
//求每个像素的纹理像素颜色
BasePixel = ShaderTexture[0].Sample(SampleType, outa.Tex);
//求每个像素的隆起法向量(切线空间)
BumpNormal=(float3)ShaderTexture[1].Sample(SampleType, outa.Tex);
BumpNormal = (2.0f*BumpNormal) - 1.0f;
//-----求出TBN矩阵(已经和世界变换矩阵结合在一起)--------
float3 N = outa.Normal_W;
float3 T = outa.Tangent_W;
float3 B = outa.Binormal_W;
//将隆起法向量由切线空间变换到局部空间,再到世界空间,然后规格化
//BumpNormal= mul(BumpNormal,float3x3(T,B,N));
BumpNormal = N + BumpNormal.x*T + BumpNormal.y*B;
BumpNormal = normalize(BumpNormal);
//求出漫反射因子
float3 InvLightDirection = -LightDirection;
DiffuseFactor = saturate(dot(BumpNormal, InvLightDirection));
color += DiffuseColor*DiffuseFactor;
color = saturate(color);
//乘以基础纹理颜色
color = color*BasePixel;
//如果漫射因子为正时,漫反射光和镜面才存在意义
if (DiffuseFactor > 0)
{
//求出入射光的反射向量,此时的法线量应该为法线贴图的法向量,别把参数位置搞反了
float3 ReflectLightDir = normalize(reflect(LightDirection,BumpNormal));
//float3 ReflectLightDir = normalize(2 * DiffuseFactor*BumpNormal - InvLightDirection);
//求每个像素的镜面强度
SpecularIntensity = ShaderTexture[2].Sample(SampleType, outa.Tex);
//求出镜面反射因子
SpecularFactor = pow(saturate(dot(outa.LookDirection, ReflectLightDir)), SpecularPow);
//求出镜面颜色
Specular = SpecularFactor *SpecularIntensity;
color = color + Specular; //确实只有镜面光被计算 才加上
}
color = saturate(color);
return color;
}
先贴出没用SpecularMap的运行效果:
在贴用了SpecularMap的运行效果:
最后有三点的说的是:
(1),可能大家在Shader看到,环境光和漫反射光都用于调节基础纹理颜色,而镜面光没用于调节基础纹理颜色,模拟现实中白色闪瞎眼的效果,所以镜面光不用乘以BasePixel,而是直接相加于color.
(2),求反射光的方向得注意用的反射面法向量为法线贴图提供的法向量,而不是TBN的normal向量,此外还得小心reflect函数的一个参数为入射光,第二个参数为反射面法向量,别搞反位置了,我在这被坑了。
(3),求那个反射点到相机的向量其实有两种实现思路,第一种是像上面的shader所写,先在世界空间求出每个顶点到相机的向量,然后在光栅化阶段对向量进行插值得到世界空间三角面每个像素到相机的向量;第二种,得到每个顶点都在世界空间的坐标,然后再光栅化阶段对顶点位置插值得到三角面每个像素的世界空间位置,然后用像素的世界空间位置与相机位置相计算,得到世界空间每个像素到相机的向量。
这里两种方法得到的向量还是有些区别,毕竟插值的对象不一样,本教程这第一种方法,D3D11龙书用的是第二种方法,不过我想最终的效果应该不会相差多少。
我的源代码链接如下:
转载自原文链接, 如需删除请联系管理员。
原文链接:Directx11教程十三之SpecularMap(高光贴图),转载请注明来源!