汽车喷漆效果的实现

1649 篇文章 11 订阅
1623 篇文章 22 订阅


uploads/200703/22_123240_demopic.jpg

图1:双色调,悬浮微鳞片轿车油漆在Direct3D实时象素着色器的效果。

给汽车身体喷漆是一个复杂的过程,车身油漆是昂贵分层形式,往往包含染料层,搪瓷金属箔悬浮层.
由于这些接二连三油漆表面层,展示出了一种复杂的光学交互,使车看起来平滑,光泽。这了使用的
汽车模型的多边形较少,但是引入了高精度的法线贴图(访问 http://www.ati.com/developer/ 可以
获得更多关于法线贴图生成工具的信息),由于像素着色器应用在光滑变化的表面(如车盖),每
通道16位的法线贴图是比较适合的。

法线贴图解压缩

像素着色器中做的第一步就是法线贴图解压缩。因为法线是储存在表面的局部坐标系(也叫做切线空间)
,我们可以假设法线的z分量是正值。这样,我们能够将x,y分量存在纹理的两个16位的通道中,从而z通过
在像素着色器中通过公式 +sqrt(1 – x 2 – y 2 ) 算出。这样它相对于传统的8-8-8-8的法线贴图,就给了我
们更高的精度,但是只占用同样的内存。

uploads/200703/22_123523_fourpic.jpg

图2:双色调,微鳞片,后视镜上的效果图.

基础颜色

通过上述的法线解压过程,并和当前几何表面的法线合成计算后,我们产生了法线(N),
还有一个高频率归一化的噪声图(Nn),它是在表面反复重复着。这两个法线用来计算两
扰动法线,用来模拟汽车的双色调和内部微鳞片层。

噪声图如下图:
uploads/200703/22_123838_noise.jpg


uploads/200703/22_123944_layer.jpg

图3:金属微片,悬浮层,搪瓷层、染料层和表层散射效果.

这些法线,Ns 和 Nss 计算方法如下:
uploads/200703/22_124025_gongshi1.jpg

公式1,2

系数a,b,c,d是像素着色器的固定输入常数,它决定了扰动法线的分布。这些摄幅度平均宽度
决定在该地区的微型片状随时可见。两个法线都与视向量点乘来作为下列多项式的参数,它
决定了基础颜色和微鳞片层的强度系数。
uploads/200703/22_124109_gongshi2.jpg

公式3

多项式的前三项执行了两个色调中的混合。第四项添加了一个额外层:微鳞片层。常数c0,c1,c2
对应于基础颜色层,c3对应于微鳞片层。

亮漆层

渲染得最后一步就是渲染一个亮漆层,即反射一个环境贴图。
环境贴图显示如下:
uploads/200703/22_124256_cubemap.jpg


完整的HLSL像素着色器代码如下:
struct PsInput
{
float2 Tex : TEXCOORD0;
float3 Tangent : TEXCOORD1;
float3 Binormal : TEXCOORD2;
float3 Normal : TEXCOORD3;
float3 View : TEXCOORD4;
float3 SparkleTex : TEXCOORD5;
};

float4 main(PsInput i) : COLOR
{
// 取得当前的法线图
float3 vNormal = tex2D( normalMap, i.Tex );
// 缩放和偏移让其在[-1.0, 1.0]区域内:
vNormal = 2.0f * vNormal - 1.0f;

// 获得高频率扰动的法线,通过查询一个噪声图。
float3 vFlakesNormal = tex2D(microflakeNMap, i.SparkleTex);
// 别忘了转换到 [-1.0, 1.0] 区域内:
vFlakesNormal = 2 * vFlakesNormal - 1.0;

// 计算以下公式
// Np1 = ( a * Np + b * N ) / || a * Np + b * N || where a << b
//
float3 vNp1 = microflakePerturbationA * vFlakesNormal + normalPerturbation * vNormal ;

// 计算以下公式
// Np2 = ( c * Np + d * N ) / || c * Np + d * N || where c == d
float3 vNp2 = microflakePerturbation * ( vFlakesNormal + vNormal ) ;

// 因为需要与法线点乘求夹角,所以我们必须将它归一化一下
float3 vView = normalize( View );

// 把表面法线转换好世界空间中来,计算bump map的方法。
float3x3 mTangentToWorld = transpose( float3x3( Tangent, Binormal, Normal ) );
float3 vNormalWorld = normalize( mul( mTangentToWorld, vNormal ));

// 计算夹角余玄
float fNdotV = saturate(dot( vNormalWorld, vView));

// 计算出反射向量
float3 vReflection = 2 * vNormalWorld * fNdotV - vView;

// 我们需要一个gloss值来读环境图,在真实的demo中,反射效果会有轻微的模糊。
float fEnvBias = glossLevel;

// 用反射向量采样环境图。
float4 envMap = texCUBEbias( showroomMap, float4( vReflection, fEnvBias ) );

//乘以亮度值,在a通道中储存 RGBE
envMap.rgb = envMap.rgb * envMap.a;

// 再乘以一个亮度系数
envMap.rgb *= brightnessFactor;

// 将切线空间中的法线变换到世界坐标中来.
float3 vNp1World = normalize( mul( mTangentToWorld, vNp1) );

// 法线和视线点乘,得到斐涅尔系数
float fFresnel1 = saturate( dot( vNp1World, vView ));

// 将第二个法线也同样重切线空间中变换到世界坐标中来.
float3 vNp2World = normalize( mul( mTangentToWorld, vNp2 ));

// 同样的方法得到第二个斐涅尔系数.
float fFresnel2 = saturate( dot( vNp2World, vView ));

// 开始合成所有层了.
// 根据公式三
float fFresnel1Sq = fFresnel1 * fFresnel1;
float4 paintColor = fFresnel1 * paintColor0 +
fFresnel1Sq * paintColorMid +
fFresnel1Sq * fFresnel1Sq * paintColor2 +
pow( fFresnel2, 16 ) * flakeLayerColor;

// 最后与反射的环境贴图合成在一起,形成最终的结果.
float fEnvContribution = 1.0 - 0.5 * fNdotV;
float4 finalColor;
finalColor.a = 1.0;
finalColor.rgb = envMap * fEnvContribution + paintColor;
return finalColor;
}
结论
此代码所使用的算法,经验更大于真实的物理属性,所以需要不断得调整常数值,
来使最终得到一个满意的效果.
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值