聚光灯+法线贴图 shader

来源:互联网 发布:网页设计和淘宝美工 编辑:程序博客网 时间:2024/05/03 23:46

前几天看了两个shader分别是聚光灯和法线贴图,于是想把这两个shader结合起来,产生手电照射潮湿的凹凸墙面效果:

本想很容易就能实现但是由于之前不理解光照模型的计算原理,所以我改起来相当费劲,经过努力终于弄明白了两者的原理,然后写出了shader:

struct Mtrl
{
 float4 ambient;
 float4 diffuse;
 float4 spec;
 float  specPower;
};

struct DirLight
{
 float4 ambient;
 float4 diffuse;
 float4 spec;
 float3 posW;
 float3 dirW; 
};

uniform extern float4x4 gWorldInv;
uniform extern float4x4 gWVP;
uniform extern float4x4 gWorld;     //世界矩阵
uniform extern Mtrl     gMtrl;
uniform extern DirLight gLight;
uniform extern float3   gEyePosW;
uniform extern texture  gTex;
uniform extern texture  gNormalMap;
uniform extern float3 gAttenuation012;  //光源衰减参数
uniform extern float  gSpotPower;    //光源聚合度,越大聚合越小

sampler TexS = sampler_state
{
 Texture = <gTex>;
 MinFilter = ANISOTROPIC;
 MaxAnisotropy = 8;
 MagFilter = LINEAR;
 MipFilter = LINEAR;
 AddressU  = WRAP;
    AddressV  = WRAP;
};

sampler NormalMapS = sampler_state
{
 Texture = <gNormalMap>;
 MinFilter = ANISOTROPIC;
 MaxAnisotropy = 8;
 MagFilter = LINEAR;
 MipFilter = LINEAR;
 AddressU  = WRAP;
    AddressV  = WRAP;
};
 
struct OutputVS
{
    float4 posH      : POSITION0;
    float3 toEyeT    : TEXCOORD0;
    float3 lightDirT : TEXCOORD1;
    float2 tex0      : TEXCOORD2;
    float4 posW      : TEXCOORD3;
};

OutputVS NormalMapVS(float3 posL      : POSITION0,
                     float3 tangentL  : TANGENT0,
                     float3 binormalL : BINORMAL0,
                     float3 normalL   : NORMAL0,
                     float2 tex0      : TEXCOORD0)
{
    // 初始化输出结构体对象
 OutputVS outVS = (OutputVS)0;
 
 //创建TBN坐标系,用来计算法线贴图
 float3x3 TBN;
 TBN[0] = tangentL;
 TBN[1] = binormalL;
 TBN[2] = normalL;
 
 // 将顶点渲染器输入的参数 传入到TBN坐标系中
 float3x3 toTangentSpace = transpose(TBN);
 
 // 将摄像机的位置转换到本地坐标系中,只需乘上世界矩阵的转置逆矩阵就可以了
 float3 eyePosL = mul(float4(gEyePosW, 1.0f), gWorldInv);
 
 // 计算从顶点到摄像机的方向向量  并转换到TBN坐标系中
 float3 toEyeL = eyePosL - posL;
 outVS.toEyeT = mul(toEyeL, toTangentSpace);
 
 // 将灯光的方向转换到本地坐标系后,再转换到TBN坐标系中
 float3 lightDirL = mul(float4(gLight.dirW, 0.0f), gWorldInv).xyz;
 outVS.lightDirT  = mul(lightDirL, toTangentSpace);
 
 // posH为经过世界、观察、投影等坐标转换的最终可以直接渲染的顶点位置,其他的则转换到世界坐标系中
 outVS.posH = mul(float4(posL, 1.0f), gWVP);
 outVS.posW = mul(float4(posL, 1.0f), gWorld);
 gLight.posW = mul(float4(gLight.posW, 1.0f), gWorld);   //将灯光坐标转换到世界坐标系
 gLight.dirW = mul(float4(gLight.dirW, 1.0f), gWorld);   //将灯方向转换到世界坐标系
 // 放大uv贴图的坐标,因为该图片使用了WRAP寻址模式,所以放大的效果是加倍纹理的uv平铺
 outVS.tex0 = tex0*3.f;
 
 // 返回输出结构体,然后将里面的内容传入到下面的像素渲染器里继续处理!
    return outVS;
}

float4 NormalMapPS(float3 toEyeT    : TEXCOORD0,
                   float3 lightDirT : TEXCOORD1,
                   float2 tex0      : TEXCOORD2,
                   float4 posW   :TEXCOORD3) : COLOR
{
 // 单位化
 toEyeT    = normalize(toEyeT);
 lightDirT = normalize(lightDirT);
 
 // 灯光向量即为灯光方向的相反
 float3 lightVecT = -lightDirT;
 
 // 对法线纹理进行采样
 float3 normalT = tex2D(NormalMapS, tex0);
 
 // 将法线参数范围从[0, 1] 转换到[-1, 1] 
    normalT = 2.0f*normalT - 1.0f;
    
 //单位化 
 normalT = normalize(normalT);
 
 // 计算镜面光向量
 float3 r = reflect(-lightVecT, normalT);
 
 // 计算将有多少镜面光射入到人眼中
 float t  = pow(max(dot(r, toEyeT), 0.0f), gMtrl.specPower);
 
 // 通过灯光向量和法线向量的夹角来决定漫反射光的强度
 float s = max(dot(lightVecT, normalT), 0.0f);
 
 // 如果漫反射强度过低则将镜面光置零
 if(s <= 0.0f)
      t = 0.0f;
 
 // 计算三个系数 spec diffuse 随时变化 ambient 保证物体总是有一定的亮度
 float3 spec = t*(gMtrl.spec*gLight.spec).rgb;
 float3 diffuse = s*(gMtrl.diffuse*gLight.diffuse).rgb;
 float3 ambient = gMtrl.ambient*gLight.ambient;
 
 // 对原纹理进行采样
 float4 texColor = tex2D(TexS, tex0);
 
 //计算锥光源衰减参数
 float d = distance(gLight.posW, posW.xyz);
 float A = gAttenuation012.x + gAttenuation012.y*d + gAttenuation012.z*d*d;
 float3 lightVector = normalize(gLight.posW - posW.xyz);
 gLight.dirW = normalize(gLight.dirW);
 // 计算聚光灯系数
 float spot = pow(max(dot(-lightVector, gLight.dirW), 0.0f), gSpotPower);

 //最终颜色

 float3 color = spot*2.f*( ambient*texColor.rgb +diffuse*texColor.rgb /A+spec); 
 // 输出颜色
    return float4(color, gMtrl.diffuse.a*texColor.a);
}

technique NormalMapTech
{
    pass P0
    {
        vertexShader = compile vs_2_0 NormalMapVS();
        pixelShader  = compile ps_2_0 NormalMapPS();
    }
}

 一定要搞清各坐标系中的计算 不要弄混。对于shader的调试也很重要,SDK中可以做到,也可以借助renderMonkey,但是我还不熟,有空研究一下,这工具会非常的方便。

原创粉丝点击