第九章 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的其中一个应用是虚构一个凹凸表面的细节,比如石墙。可以使用足够的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描述了这三个向量。
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)变换矩阵,如下所示:
可以使用这三个向量创建一个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不能再进行变换)。
可以把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
列表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性能和图形总线中数据传输成本的一种折衷方法。更普遍的情况是,图形管线成为了瓶颈。
注意
如果你发现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
- 第九章 Normal Mapping and Displacement Mapping
- Bump / Normal / Displacement / Parallax Mapping
- Normal Mapping
- Normal Mapping
- Normal Mapping
- 移位贴图 Displacement Mapping
- 移位贴图 Displacement Mapping
- Displacement Mapping (Direct3D 9)
- Displacement Mapping(移位贴图)
- Normal Mapping整理资料
- Cg normal mapping
- Normal Mapping 法线贴图
- 法线贴图(Normal Mapping)
- ShaderSimpler(5) : Bump Mapping(Normal mapping)
- Tessellation(曲面细分)和Displacement Mapping
- 3DShader之移位贴图(Displacement Mapping)
- Tessellation (曲面细分) Displacement Mapping (贴图置换)
- 法线纹理(贴图)Normal Mapping
- 二叉树中和为某一值的路径
- Oracle数据库中scott用户
- 流媒体技术基础-流媒体传输协议(三)
- mongodb 分片集群 在线添加副本集实例并升级成primay主库
- Package base-default extends undefined package struts-default
- 第九章 Normal Mapping and Displacement Mapping
- 流媒体技术基础-流媒体传输协议(四)
- Java基础入门- 数组
- Codeforces Round #331 (Div. 2)【未完待续】
- 我对ltsm的学习,从rnn的问题讲起
- 关于音频文件格式
- 【代码优化】敏感字符替换为“*”
- HDU1978
- Linux学习-linux命令基础