Environment Mapping
Texture cubes的另一种应用是environment mapping。Environment mapping也称为reflection mapping,用于近似模拟reflective surfaces(反光表面),比如汽车上的镀了一层金属物的保险杠。
Environment mapping的处理过程比skybox稍微复杂一些,因为需要计算光线与表面的反射向量。反射向量主要由view direction(限入射角向量)和表面的法向量计算得到。具体的公式如下:
R = I – 2 * N * (I • N)
其中,I表示入射向量,N指表面的法向量。列表8.2列出了一种environment mapping effect的代码。
列表8.2 EnvironmentMapping.fx
#include "include\\Common.fxh"
/************* Resources *************/
cbuffer CBufferPerFrame
{
float4 AmbientColor : AMBIENT <
string UIName = "Ambient Light";
string UIWidget = "Color";
> = {1.0f, 1.0f, 1.0f, 0.0f};
float4 EnvColor : COLOR <
string UIName = "Environment Color";
string UIWidget = "Color";
> = {1.0f, 1.0f, 1.0f, 1.0f };
float3 CameraPosition : CAMERAPOSITION < string UIWidget="None"; >;
}
cbuffer CBufferPerObject
{
float4x4 WorldViewProjection : WORLDVIEWPROJECTION < string UIWidget="None"; >;
float4x4 World : WORLD < string UIWidget="None"; >;
float ReflectionAmount <
string UIName = "Reflection Amount";
string UIWidget = "slider";
float UIMin = 0.0;
float UIMax = 1.0;
float UIStep = 0.05;
> = {0.5f};
}
Texture2D ColorTexture <
string ResourceName = "default_color.dds";
string UIName = "Color Texture";
string ResourceType = "2D";
>;
TextureCube EnvironmentMap <
string UIName = "Environment Map";
string ResourceType = "3D";
>;
SamplerState TrilinearSampler
{
Filter = MIN_MAG_MIP_LINEAR;
};
RasterizerState DisableCulling
{
CullMode = NONE;
};
/************* Data Structures *************/
struct VS_INPUT
{
float4 ObjectPosition : POSITION;
float3 Normal : NORMAL;
float2 TextureCoordinate : TEXCOORD;
};
struct VS_OUTPUT
{
float4 Position : SV_Position;
float2 TextureCoordinate : TEXCOORD0;
float3 ReflectionVector : TEXCOORD1;
};
/************* Vertex Shader *************/
VS_OUTPUT vertex_shader(VS_INPUT IN)
{
VS_OUTPUT OUT = (VS_OUTPUT)0;
OUT.Position = mul(IN.ObjectPosition, WorldViewProjection);
OUT.TextureCoordinate = get_corrected_texture_coordinate(IN.TextureCoordinate);
float3 worldPosition = mul(IN.ObjectPosition, World).xyz;
float3 incident = normalize(worldPosition - CameraPosition);
float3 normal = normalize(mul(float4(IN.Normal, 0), World).xyz);
// Reflection Vector for cube map: R = I - 2*N * (I.N)
OUT.ReflectionVector = reflect(incident, normal);
return OUT;
}
/************* Pixel Shader *************/
float4 pixel_shader(VS_OUTPUT IN) : SV_Target
{
float4 OUT = (float4)0;
float4 color = ColorTexture.Sample(TrilinearSampler, IN.TextureCoordinate);
float3 ambient = get_vector_color_contribution(AmbientColor, color.rgb);
float3 environment = EnvironmentMap.Sample(TrilinearSampler, IN.ReflectionVector).rgb;
float3 reflection = get_vector_color_contribution(EnvColor, environment);
OUT.rgb = lerp(ambient, reflection, ReflectionAmount);
OUT.a = color.a;
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);
}
}
Environment Mapping Preamble
在这个effect代码中,CBufferPerFrame模块中包含了表示ambient color和environment color的成员。这是一个全局的AmbientColor变量,以及一个用于指定多个environment-mapped objects的独立的color/intensity变量值。对于美术人员来说这只是多一种选择。用于表示directional lighting,specular,point light或者spotlights的成员全部从CBufferPerFrame中移除了,重点是用于表示environment mapping的成员。但是,实际上前面所有的光照模型都可以与environment mapping一起使用。
CBufferPerFrame中还包含一个CameraPosition成员,用于计算入射(观察)向量。
CBufferPerObject中新增了一个成员ReflectionAmout。该变量用于在ambient和reflection color之间进行插值运算。
在environment mapping effect中提供了两个textures。其中ColorTexture是一个普通的2D纹理,用于采样表面的颜色。EnvironmentMap是一个TextureCube类型的3D纹理,包含了反光环境的数据。
最后,看一下VS_OUTPUT结构体中的ReflectionVector成员。在vertex shader计算该向量值,并用于texture cube的采样。其中的2D TextureCoordinate成员则是用于color texture的采样。
Environment Mapping Vertex Shader
与以前一样,vertex shader首先执行常用的步骤,把vertex变换到homogeneous space,再给color map的纹理坐标赋值。然后变换vertex到world space中,用于计算入射向量。接着在把表面的法向量变换到world space并进行规范化后,使用HLSL的内置函数reflect()计算反射向量。该函数的运算方法与注释中列出的数学公式一样,由于HLSL中提供了该函数,最好是直接使用内置函数。
Environment Mapping Pixel Shader
Pixel shader首先对color texture进行采样并计算ambient。然后采样environment map texture,并使用EvnColor变量的颜色和强度对environment值进行调制。最后调用HLSL的内置函数lerp()对ambient和reflection进行插值得到最终的输出颜色。使用下面公式进行线性插值:
Value = x * (1 – s) + (y*s)
其中,s是一个介于0.0和1.0之间的值,表示最终计算结果中x占有的比例以及y占用的比例。因此,对于environment mapping effect,代入该公式得:
其中ReflectionAmout变量类似于一个滑块,用于定义reflection和nonreflection颜色所占的百分比。如果ReflectionAmout值设为1.0,object的颜色就像镜面一样,反射所有关联的texture cube颜色。相反,如果该值设为0.0,object就不会反射环境纹理。
Environment Mapping Output
图8.6显示了在一个sphere上使用environment mapping effect的输出结果,其中ambient为纯白色和full-intensity(强度值为1.0)。在该effect中,使用checkerboard texture作为color map,使用图8.1中的texture cube作为environment map。左图中,reflection amount值为1.0;右图中reflection amount值为0.5。
图8.6 EnvironmentMapping.fx applied to a sphere with pure-white, full-intensity
ambient and environment light values, and reflection amounts of 1.0 (left) and 0.5 (right).
(Skybox texture by Emil Persson.)
Dynamic Environment Mapping
到目前为止,已经讨论了静态的environment maps,以及不会改变(或者说不会频繁改变)的texture cubes。静态的environment maps也能提供有趣的细节和较好性能,但如果仔细观察,会发现environment map与实际的场景并不完全相符。很多游戏使用完全不相关的cube maps,只存在理论上的颜色相似性。如果camera不能足够靠近一个反射表面,或者反射表面的形状使得反射画面足够扭曲,就没有人能够发现反射的画面与实际场景不符。但是,如果camera可以在一个反射面上放大,观察就有可能看到实现环境和reflected map之间的不一致。例如,在一个“global” environment map中通常不包含相距很近的objects。
一种解决方案是根据观察者从一区域移动到另一个区域时相应的改变environment map,使得每个区域具有不同的主题。对于同一个区域,也可以根据一天中某个时刻或天气情况改变environment map。但是最终的解决方案是在场景中从场景本身生成动态的environment maps。这种方案的实现步骤为,把camera放在一个object所处的位置,并设置camera的FOV为90-degree(包括水平和垂直方向);然后在每一帧中指向每一个坐标轴渲染场景,共渲染6次,并把这6次的输出图片组合成一个texture cube。使用这种方法,需要捕获reflected environment 场景中的每一个object。但是,要在可交互的帧率下执行这些运算,计算成本是非常大的。可以考虑只针对场景中关键的objects创建动态的texture cubes。另外,还可以使用较低的分辨率渲染这种cube maps,或者在总帧率一小部分渲染。