第九章 Normal Mapping and Displacement Mapping

来源:互联网 发布:js attachevent 编辑:程序博客网 时间:2024/04/29 17:17

第九章 Normal Mapping and Displacement Mapping

本章主要讲述两种图形学技术,支持在不增加objects的poly primitive的情况下,在场景中增加更多的细节。第一种是normal mapping,通过创建一些“fake” geometry(虚设的多边形图元)模拟光照作用。第二种是displacement mapping,根据纹理数据moving vertices actually(与“fake”相对应,这里指真实的移动)来创建凹凸不平的表面。

Normal Mapping(法线贴图)

前面几章,已经讨论了specualr maps,environment maps以及transparency maps,这些texture maps都提供了附加的数据信息。Specular maps中的数据用于限制specular highlight,environment maps则包含了用于reflective surfaces的colors,transparency maps用于控制output-merge阶段的alpha blending。这些额外的信息由每一个pixel提供,比仅由每一个vertex提供,具有更高的精度。同样 ,一个normal map也是对每一个pixel都提供表面的法向量数量,该额外的数据可以应用于多种技术上。
Normal maps的其中一个应用是虚构一个凹凸表面的细节,比如石墙。可以使用足够的geometry来模拟这中凹凸不平的墙面,使用的vertices越多,得到的细节也越多。这样就可以在场景中更好的处理光照,远离光源的石块就会显示为较暗的区域。但是,增加geometry会导致计算成本也增大。相反,对于一个含有少量poly geometry的object(即使是一个flat plane),使用normal map方法,也可以模拟与大量增加geometry情况下同样的光照效果。这种应用就称为normal mapping。

Normal Maps

图9.1中显示了用于一面石墙的color map(左图)和normal map(右图)。相对于color map,normal map看起来有点奇怪。虽然可以把norml map显示出来,但是normal map中存储的是3D directions vectors(3维的方向向量)。对normal map进行采样时,从RGB通道中得到的结果表示方向向量的x,y,z分量。这些法向量可用于一些效果的计算,比如diffuse lighting。

图9.1 A color map (left) and normal map (right) for a stone wall. (Textures by Nick
Zuccarello, Florida Interactive Entertainment Academy.)
RGB texture的每一个channels都存储一个unsigned 8-bit值(unsigned char),该类型的数值范围为[0, 255]。但是一个规范化的方向向量,对应的xyz各个分量值范围是[-1, 1]。因此,法线向量存储到texture之前必须先转换到范围[0, 255],对texture进行采样的时候必须再转换到范围[-1, 1]。可以使用如下的公式,把浮点型向量从范围[-1, 1]转换到范围[0, 255]:

f(x) = (0.5x + 0.5) * 255

再使用下面的方程式变换回来:

实际工作中,一般会使用图像处理软件(比如Adobe Photoshop)把一个normal map编码成RGB texture格式。但是采样texture时需要在shader中手动计算,把数据从范围[0, 255]转换回[-1, 1]。在采样过程中已经执行了浮点除法(除以255)操作,所以得到的采样结果在范围[0, 1]之间。因此,只需要使用下面的方程函数把范围[0, 1]转换到范围[1, 1]:

f(x) = 2x − 1

或者,也可以使用16位或32位浮点型数值作为normal maps的texture格式,这样可以产生更好的细节效果,但会牺牲一些性能。


Tangent Space(切空间)

一般使用每一个vertex的法线来计算diffuse,同样也可以使用每一个pixel的法线。但是法线必须与light处于同一个坐标空间。对于per-vertex法线,由object space提供。但是normal maps的法线值处于tangent space。
Tangent space(或者texture space)是一个相对于纹理的坐标系,由三个相互正交的向量确定:surface normal向量,tangent向量以及binormal向量。图9.2描述了这三个向量。

图9.2 An illustration of tangent space. (Texture by Nick Zuccarello, Florida Interactive Entertainment Academy.)

其中normal向量,N,是一个vertex的表面法向量。Tangent向量,T,与表面的法线垂直,与指向texture的u轴方向。Binormal向量,B,则指向texture的v轴方向。
可以使用这三个向量创建一个TBN(tangent, binormal, normal)变换矩阵,如下所示:

可以使用这个矩阵把向量由tangent space变换到object space中。但是,由于light vector通常是在world space中,因此需要把从normal map中采样得到的noraml从tangent space变换到world space。或者换一种方式,直接使用已经处于world space中的向量创建TBN矩阵。

注意
可以把normals直接编码到world space中,就可以省去从tangent space到world space的变换。但是,使用这些normals的objects只能保持静止不动(不能执行变换)。另外,这些normals也不能简单地在多个ojbects之间重用(因为这些normals不能再进行变换)。

TBN矩阵有一个非常值得关注的特性,该矩阵由三个orthonormal向量(相互正交的单位向量)构成,并形成了一个正交基(定义了一个坐标系)。也就说该矩阵是一个正交矩阵,而正交矩阵的逆等于其转置。因此,把一个向量从object或者world space变换回tangent space(inverse mapping反向映射),只需要把该向量与TBN矩阵的转置相乘即可。另外,根据TBN矩阵的orthonormal性质,如果已经知道了任意两个向量,就可以推导出第三个向量。通常情况下,normal和tangent向量与geometry一起存储,而binormal向量(在vertex shader中)由这两个向量进行cross product计算得出。执行这种计算是对GPU运算和数据传输(在CPU和GPU之间)高成本之间的一种折衷的方法。

A Normal Mapping Effect

根据上面所讨论的方法,列表9.1列出了一种normal mapping effect的代码。
列表9.1 NormalMapping.fx
#include "include\\Common.fxh"cbuffer CBufferPerFrame{    float4 AmbientColor : AMBIENT <        string UIName =  "Ambient Light";        string UIWidget = "Color";    > = {1.0f, 1.0f, 1.0f, 1.0f};        float4 LightColor : COLOR <        string Object = "LightColor0";        string UIName =  "Light Color";        string UIWidget = "Color";    > = {1.0f, 1.0f, 1.0f, 1.0f};    float3 LightDirection : DIRECTION <        string Object = "DirectionalLight0";        string UIName =  "Light Direction";        string Space = "World";    > = {0.0f, 0.0f, -1.0f};    float3 CameraPosition : CAMERAPOSITION < string UIWidget="None"; >;}cbuffer CBufferPerObject{    float4x4 WorldViewProjection : WORLDVIEWPROJECTION < string UIWidget="None"; >;    float4x4 World : WORLD < string UIWidget="None"; >;        float4 SpecularColor : SPECULAR <        string UIName =  "Specular Color";        string UIWidget = "Color";    > = {1.0f, 1.0f, 1.0f, 1.0f};    float SpecularPower : SPECULARPOWER <        string UIName =  "Specular Power";        string UIWidget = "slider";        float UIMin = 1.0;        float UIMax = 255.0;        float UIStep = 1.0;    > = {25.0f};}Texture2D ColorTexture <    string ResourceName = "default_color.dds";    string UIName =  "Color Texture";    string ResourceType = "2D";>;Texture2D NormalMap <    string ResourceName = "default_bump_normal.dds";    string UIName =  "Normap Map";    string ResourceType = "2D";>;SamplerState TrilinearSampler{    Filter = MIN_MAG_MIP_LINEAR;    AddressU = WRAP;    AddressV = WRAP;};RasterizerState DisableCulling{    CullMode = NONE;};/************* Data Structures *************/struct VS_INPUT{    float4 ObjectPosition : POSITION;    float2 TextureCoordinate : TEXCOORD;    float3 Normal : NORMAL;    float3 Tangent : TANGENT;};struct VS_OUTPUT{    float4 Position : SV_Position;    float3 Normal : NORMAL;    float3 Tangent : TANGENT;    float3 Binormal : BINORMAL;    float2 TextureCoordinate : TEXCOORD0;    float3 LightDirection : TEXCOORD1;    float3 ViewDirection : TEXCOORD2;};/************* Vertex Shader *************/VS_OUTPUT vertex_shader(VS_INPUT IN){    VS_OUTPUT OUT = (VS_OUTPUT)0;        OUT.Position = mul(IN.ObjectPosition, WorldViewProjection);        OUT.Normal = normalize(mul(float4(IN.Normal, 0), World).xyz);    OUT.Tangent = normalize(mul(float4(IN.Tangent, 0), World).xyz);    OUT.Binormal = cross(OUT.Normal, OUT.Tangent);    OUT.TextureCoordinate = get_corrected_texture_coordinate(IN.TextureCoordinate);    OUT.LightDirection = normalize(-LightDirection);        float3 worldPosition = mul(IN.ObjectPosition, World).xyz;    float3 viewDirection = CameraPosition - worldPosition;    OUT.ViewDirection = normalize(viewDirection);            return OUT;}/************* Pixel Shader *************/float4 pixel_shader(VS_OUTPUT IN) : SV_Target{    float4 OUT = (float4)0;        float3 sampledNormal = (2 * NormalMap.Sample(TrilinearSampler, IN.TextureCoordinate).xyz) - 1.0; // Map normal from [0..1] to [-1..1]    float3x3 tbn = float3x3(IN.Tangent, IN.Binormal, IN.Normal);        sampledNormal = mul(sampledNormal, tbn); // Transform normal to world space        float3 viewDirection = normalize(IN.ViewDirection);    float4 color = ColorTexture.Sample(TrilinearSampler, IN.TextureCoordinate);    float3 ambient = get_vector_color_contribution(AmbientColor, color.rgb);        LIGHT_CONTRIBUTION_DATA lightContributionData;    lightContributionData.Color = color;    lightContributionData.Normal = sampledNormal;    lightContributionData.ViewDirection = viewDirection;    lightContributionData.LightDirection = float4(IN.LightDirection, 1);    lightContributionData.SpecularColor = SpecularColor;    lightContributionData.SpecularPower = SpecularPower;    lightContributionData.LightColor = LightColor;    float3 light_contribution = get_light_contribution(lightContributionData);        OUT.rgb = ambient + light_contribution;    OUT.a = 1.0f;        return OUT;}/************* Techniques *************/technique10 main10{    pass p0    {        SetVertexShader(CompileShader(vs_4_0, vertex_shader()));        SetGeometryShader(NULL);        SetPixelShader(CompileShader(ps_4_0, pixel_shader()));                    SetRasterizerState(DisableCulling);    }}

Normal Mapping Preamble

在该effect中,首先使用了一个ambient light,specular highlight以及一个directional light。新增了一个用于表示normal map的Texture2D对象,VS_INPUT结构体包含一个surface normal和一个tangent vector(处于object space中)。VS_OUPUT中新增了表示tangent和binormal的成员,在传递给下一个管线阶段之间,需要变换到world space中。

Normal Mapping Vertex and Pixel Shader

在vertex shader中,先把normal和tangent向量变换到world space中,然后计算这两个向量的cross product得到binormal向量。在pixel shader,先对normal map进行采样,再把采样得到的normal向量由范围[0, 1]转换到范围[-1, 1]。然后创建TBN矩阵,并用该矩阵把采样的normal向量变换到world space中。当normal变换到world space中之后,接下来的光照计算与之前的完全一样。

注意
如果你发现vertex shader是一个性能瓶颈,可以在输入参数中直接提供bionormal(与surface normal和tangent一起)。这是为了平衡vertex shader性能和图形总线中数据传输成本的一种折衷方法。更普遍的情况是,图形管线成为了瓶颈。

Normal Mapping Output

图9.3显示了在一个带有stone wall纹理的plane上,使用normal mapping effect的输出结果。左图中使用了图9.1中的normal map。而右图中,使用了一个normal map但是没有任何效果。两幅图中,ambient light都是禁用的,direcitonal light是纯白色,full-intensity(强度值为1.0),specular highlight的power值为100,intensity值为0.35。

图9.3 NormalMapping.fx applied to a plane with a stone wall texture using a normal
map (left) and without a normal map (right). (Textures by Nick Zuccarello, Florida Interactive
Entertainment Academy.)
2 0
原创粉丝点击