第7章 Additional Lighting Models

第7章 Additional Lighting Models

本章在上一章光照模型的基础上再介绍point lights,spotlights以及在场景中添加多种光源。在讲解过程中,将会进一步学习HLSL和effect框架。

Point Light

场景中的一个point light就好比在你周围的某个位置有一个灯泡,灯泡的光线会辐射到四面八方。这与directional light完全相反,directional light处于无穷远,并且光线来自于同一个方向。Directional lights没有“移动光源”的概念,而point lights不存在“旋转光源”的概念。
与directional lights一样,point lights使用同样的diffuse和specular光照模型。但是point lights中,需要提供light的position,并计算light vector。Light vector的计算只是简单地用light的postion减去object的position(这两个positions都要位于world space)。图7.1描述了一个point light以及light vector的计算方法。

图7.1 An illustration of a point light and the computed light vector. (Stone wall texture by Nick
Zuccarello, Florida Interactive Entertainment Academy.)
此外,由于point lights具有特定的坐标位置,因此可以根据光源与表面之间的距离来衰减光照。光源与表面的距离越远,产生的亮度就越小。列表7.1列出了一个简单的point light effect的代码。
列表7.1 PointLight.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 LightColor : COLOR <
		string Object = "LightColor0";
		string UIName =  "Light Color";
		string UIWidget = "Color";
	> = {1.0f, 1.0f, 1.0f, 1.0f};

	float3 LightPosition : POSITION <
		string Object = "PointLight0";
		string UIName =  "Light Position";
		string Space = "World";
	> = {0.0f, 0.0f, 0.0f};

	float LightRadius <
		string UIName =  "Light Radius";
		string UIWidget = "slider";
		float UIMin = 0.0;
		float UIMax = 100.0;
		float UIStep = 1.0;
	> = {10.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";
>;

SamplerState ColorSampler
{
	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;
};

struct VS_OUTPUT
{
	float4 Position : SV_Position;
	float3 Normal : NORMAL;
	float2 TextureCoordinate : TEXCOORD0;	
	float4 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.TextureCoordinate = get_corrected_texture_coordinate(IN.TextureCoordinate);
	OUT.Normal = normalize(mul(float4(IN.Normal, 0), World).xyz); 
	
	float3 worldPosition = mul(IN.ObjectPosition, World).xyz;
	float3 lightDirection = LightPosition - worldPosition;
	OUT.LightDirection.xyz = normalize(lightDirection);
	OUT.LightDirection.w = saturate(1.0f - (length(lightDirection) / LightRadius)); // Attenuation
	
	OUT.ViewDirection = normalize(CameraPosition - worldPosition);
	
	return OUT;
}

/************* Pixel Shader *************/

float4 pixel_shader(VS_OUTPUT IN) : SV_Target
{
	float4 OUT = (float4)0;
		
	float3 normal = normalize(IN.Normal);
	float3 lightDirection = normalize(IN.LightDirection.xyz);
	float3 viewDirection = normalize(IN.ViewDirection);
	float n_dot_l = dot(normal, lightDirection);	
	float3 halfVector = normalize(lightDirection + viewDirection);
	float n_dot_h = dot(normal, halfVector);

	float4 color = ColorTexture.Sample(ColorSampler, IN.TextureCoordinate);
	float4 lightCoefficients = lit(n_dot_l, n_dot_h, SpecularPower);

	float3 ambient = get_vector_color_contribution(AmbientColor, color.rgb);
	float3 diffuse = get_vector_color_contribution(LightColor,  lightCoefficients.y * color.rgb) * IN.LightDirection.w;
	float3 specular = get_scalar_color_contribution(SpecularColor, min(lightCoefficients.z, color.w)) * IN.LightDirection.w;

	OUT.rgb = ambient + diffuse + specular;
	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);
	}
}

Point Light Preamble

与之前一样,point light effect也是在以前代码的基础上修改的,具体地说是根据使用了内置函数lit()的Blinn-Phong effect版本。CBufferPerFrame中去掉了LightDirection,新加了一个LightPosition constant。该constant表示光源在world space中的坐标位置。此外,新增了一个shader constant LightRadius,用于指定光源能够照射到的距离范围。在光照辐射半径之外的区域是没有影响的。
CBufferPerObject,ColorTexture和ColorSample对象都没有改动。同样,VS_INPUT结构体也没有变动,还是包含vertex position(in object space),纹理坐标和法向量。但是VS_OUTPUT结构中的LightDirection向量修改成了float4类型而不是之前的float3类型,而且在point light effect中会被用到;而在Blinn-Phong effect中,使用了全局的LightDirection shader constant。在这里,LightDirection中新增的分量值用于存储光源的衰减因子。光源的direciton和attenuation(方向和衰减因子)都在vertex shader计算得到。

注意
把衰减因子存储在LightDirection向量的w分量中是一种非常有效的技巧。因为w分量没有其他的作用,所以可以用于存储衰减因子,而不是占用另一个非必要的输出寄存器。而且衰减因子与LightDirection向量中xyz分量没有任何关联。

Point Light Vertex and Pixel Shader

在vertex shader中,把计算得到的light direction存储到一个float3类型的局部变量lightDirection中,然后对其规范化再赋值给输出结构体中对应的成员。接下来,使用下面的公式计算衰减因子:

由于lightDirection变量中存储了未规范化的向量,因此可以使用lightDirection向量的长度来计算得到光源和表面之前的距离。如果该距离大于或等于light的半径,得到的衰减因子就小于或等于0。调用内置函数saturate()使得最终的值不小于0。
Pixel shader部分的代码与Blinn-Phong中的基本相同,唯一的区别是,在point light effect中diffuse和specular需要乘以光源的衰减因子。

Point Light Output

图7.2显示了在一个sphere上使用point light effect的输出结果,其中使用了half-intensity(强度值为0.5)的ambient light,full-intensity(强度值为1.0)的specular highlight以及full-intensity的point light。所有lights和specular颜色都为纯白色。

图7.2 PointLight.fx applied to a sphere with a texture of Earth, using a half-intensity ambient light,
a full-intensity specular highlight, and a full-intensity point light. All light colors are pure white. To the left,
the point light is positioned farther away from the sphere; to the right, it is closer. (Texture from Reto
Stöckli, NASA Earth Observatory.)

Point Light Modifications

在图7.2中的右图中,specular highlight出现了一些误差,这是由于vertex shader在计算light direction时导致的。这种情况只有当point light与object靠近时才会出现,此时在pixels之间的light vector各不相同。图7.2显示了分别在vertex shader和pixel shader中计算light direction的输出结果。

图7.3 Comparison of the specular highlight produced with a per-vertex calculation (left) of the light
direction and a per-pixel calculation (right). (Texture from Reto Stöckli, NASA Earth Observatory.)
列表7.2 Per-Pixel Calculation of the View Direction
#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 LightColor : COLOR <
		string Object = "LightColor0";
		string UIName =  "Light Color";
		string UIWidget = "Color";
	> = {1.0f, 1.0f, 1.0f, 1.0f};

	float3 LightPosition : POSITION <
		string Object = "PointLight0";
		string UIName =  "Light Position";
		string Space = "World";
	> = {0.0f, 0.0f, 0.0f};

	float LightRadius <
		string UIName =  "Light Radius";
		string UIWidget = "slider";
		float UIMin = 0.0;
		float UIMax = 100.0;
		float UIStep = 1.0;
	> = {10.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";
>;

SamplerState ColorSampler
{
	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;
};

struct VS_OUTPUT
{
	float4 Position : SV_Position;
	float3 Normal : NORMAL;
	float2 TextureCoordinate : TEXCOORD0;	
	float3 WorldPosition : TEXCOORD1;
	float Attenuation : TEXCOORD2;
};

/************* Vertex Shader *************/

VS_OUTPUT vertex_shader(VS_INPUT IN)
{
	VS_OUTPUT OUT = (VS_OUTPUT)0;
	
	OUT.Position = mul(IN.ObjectPosition, WorldViewProjection);
	OUT.WorldPosition = mul(IN.ObjectPosition, World).xyz;
	OUT.TextureCoordinate = get_corrected_texture_coordinate(IN.TextureCoordinate);
	OUT.Normal = normalize(mul(float4(IN.Normal, 0), World).xyz);	
	
	float3 lightDirection = LightPosition - OUT.WorldPosition;
	OUT.Attenuation = saturate(1.0f - (length(lightDirection) / LightRadius)); // Attenuation	
	
	return OUT;
}

/************* Pixel Shader *************/

float4 pixel_shader(VS_OUTPUT IN) : SV_Target
{
	float4 OUT = (float4)0;
	
	float3 lightDirection = LightPosition - IN.WorldPosition;
	lightDirection = normalize(lightDirection);

	float3 viewDirection = normalize(CameraPosition - IN.WorldPosition);
	
	float3 normal = normalize(IN.Normal);	
	float n_dot_l = dot(normal, lightDirection);	
	float3 halfVector = normalize(lightDirection + viewDirection);
	float n_dot_h = dot(normal, halfVector);

	float4 color = ColorTexture.Sample(ColorSampler, IN.TextureCoordinate);
	float4 lightCoefficients = lit(n_dot_l, n_dot_h, SpecularPower);

	float3 ambient = get_vector_color_contribution(AmbientColor, color.rgb);
	float3 diffuse = get_vector_color_contribution(LightColor,  lightCoefficients.y * color.rgb) * IN.Attenuation;
	float3 specular = get_scalar_color_contribution(SpecularColor, min(lightCoefficients.z, color.w)) * IN.Attenuation;

	OUT.rgb = ambient + diffuse + specular;
	OUT.a = 1.0f;
	
	return OUT;
}

/************* Techniques *************/

technique main
{
	pass p0
	{
		VertexShader = compile vs_3_0 vertex_shader();	
		PixelShader = compile ps_3_0 pixel_shader();
	}
}

technique10 main10
{
	pass p0
	{
		SetVertexShader(CompileShader(vs_4_0, vertex_shader()));
		SetGeometryShader(NULL);
		SetPixelShader(CompileShader(ps_4_0, pixel_shader()));
			
		SetRasterizerState(DisableCulling);
	}
}



Light direction应该在world space中计算。因此,必须通过VS_OUTPUT结构体传递表面的world-space坐标。又因为vertex shader不再计算light vector,可以从VS_OUTPUT结构体中删除LightDirection成员。但是不需要把衰减因子的计算移到到pixel shader中,而且在pixel shader会产生更高的计算成本。当point light靠近object时,对light direction的影响越发明显,任何衰减都可以忽略不计。但是在vertex shader输出结构体中保留了float类型的Attenuation变量。这导致需要计算两次light direction:一次是在vertex shader,用于计算attenuation,另一次是在pixel shader计算更精确的light direction。但是这种方式比直接在pixel shader中计算attenuation的计算成本要小很多。
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值