DirectX 9.0 (4)环境光和反射光

引言

             在上一节的DirectX 9.0 系列中,我们讲述了光和材质的交互,以及最基本的平行光漫射光模型。但是,为了使我们模拟的物体,更加的真实,我们应该还要加上其他的光彩效果才可以,这样就会使的光效果更加的丰富多彩。


环境光

             我们在上一节中,讲述的光照模型,实际上是一个局部光照模型,与之相对的是全局光照模型。全局光照模型,需要考虑当前场景中所有物体的反射和遮挡关系,有些物体会被前面的物体遮挡住光线,从而是一片漆黑的。而且,在全局光照模型中,物体之间对光的反射,会照射到其他的物体上,从而对另一个物体进行着色。在局部光照系统中,我们分别对每一个物体,单独的进行光照处理,而没有考虑物体之间交互关系了。

            为了在我们的模型中模拟出这样的效果,我们可以使用环境光来对这个物体与物体之间的交互进行模拟。环境光顾名思义就是环境所产生的光。而环境中会有多少中不同的光源,我们完全没有办法估计。所以,我们使用一个简化的环境光模型来对此进行模拟,以下是环境光源的模拟公式:

           

            这个公式的后半部分就是我们上一节中讲述的平行光漫反射模型公式,而前面的那个公式就是环境光的模拟公式,对环境光进行模拟,只要简单的指定环境光的颜色以及物体对环境光的材质属性就可以了。简单吧!!!



反射光

            如果你学过画画,那么就知道,在一个物体上,除了亮部,暗部,之外,还有一个高亮部分。这个高亮部分之所以形成,是因为,在某个特定的方向上,人眼接受到了从物体反射过来的光线,而且这个部分比其他部分的光线都要强烈,所以就形成了物体的高光部分。所以,我们这里,就将这个光称为反射光。

            从上面的描述中,我们可以看出,反射光形成的原因和人的眼睛,也就是视点,相关的。所以,这里的对平行光的反射光进行模拟的时候,需要考虑视点的位置。

             我们知道,光线照射到一个平滑的表面上的时候,就会朝着一个方向上去反射过去,但是在我们的程序中,我们的视点是一个点,而不是像眼睛一样的接受器,能够接受多个方向上的反射光,也就是说,如果我们只是接受反射到视点的光线的话,那么表现在物体上就是一个像素位置的高光而已,这样的效果肯定没有办法模拟出很好的效果出来。为此,我们需要对反射光能够被接受的范围进行定义。

             我们假设,当反射光,和被照射的点与视点形成的向量之间的夹角在某个角度范围之内,比如0-45度,那么,我们就认为,视点可以接受到这个反射光。如果对文字描述,不是很清楚,可以看下图:

             DirectX 9.0 (4)环境光和反射光_第1张图片

                上图中,I是入射光向量,R是经过反射之后的反射向量,V是从被照射顶点到视点E的向量,反射向量R与V之间的夹角为a。如果a在0-45度之内,我们就认为这个顶点的反射光线是可以被视点所看见的。

                 虽然我们是指定了某个范围内的反射光线会被视点吸收,但是,我们要明白,与向量V重合的光线应该是最亮的,而远离了向量V之后,光线的强度会越来越弱,所以为了模拟出这样的效果:在与向量V平行,即他们之间夹角为0的时候,光的强度最大,当他们之间的角度在规定范围(比如0-45)内最大的时候(比如45),这是的光照强度应该是最弱的。如果读者看过我的上片关于模拟平行光源的漫射光的强弱的话,就知道,这里的情况一致。所以,同样的,我们可以利用cos余弦定律来对这种衰弱变化进行模拟。即:

             

              读者可能会发现,在上面的公式中,只有在beta角度为90的时候,他们的光线强弱才会为0,而我们规定的范围在45度的时候,就应该是最弱的时候了。那么如何对这样的角度范围进行限制了???

              为了模拟出这样的效果,我们可以在上面的公式的结果上加上一个指数运算。我们知道,对cos余弦进行指数运算的时候,指数的值越大,cos曲线的图就会越加的收缩,这样的反应,就会导致,角度在小于90度的时候,也会出现无限接近于0的情况,所以,指数值越大,那么反射光接收范围的角度就越小。所以,最后的平行光反射光强弱的衰变公式如下所示:

          

            好了,上面就是平行光的反射光衰变公式。这里还有问题就是,向量V和向量R如何求得。

            从上面的讨论中,我们知道,向量V是从被照射顶点指向视点的一个单位向量,而R想入射光向量的反射向量。

            计算向量V很简单,我们只要简单的使用相机也就是视点的位置Ucamera减去被照射顶点的位置Wvertex,在进行归一化操作即可。

            即: V = Normalize(Ucamera - Wvertex),注意这里减法的两个顺序不能相反。

             对于反射向量,我们可以使用中学中介绍的反射公式来得到:即对于入射向量I,反射向量R应该为:

             R = I - (2*Dot(I,n))n  (这里的n就是被照射顶点的法向向量)

             如果读者对这个公式有疑问的话,可以自己手动在纸上画一些,只要简单的几部几何证明就能够得到这个公式。这里不再证明。

             好了,但是还是有一个问题,这里的入射向量,该如何做了???还记得,在我们上篇文章讨论过的,对于平行光来说,有一个光照向量,只不过,它是从顶点指向光源,而不是从光源指向顶点的。这个光照向量,这里的I也就是入射光向量,是相反的,所以只要简答使用I = -L,带入上面的公式就可以了。

            好了,至此,平行光的反射衰减公式已经讲述完毕了。

            但是,好像还差点什么?对了,我们忘了加上反射光的光源和物体对反射光材质属性了。所以最终平行光反射光公式如下所示:

           


光照公式

              好了,我们需要模拟的光照属性大概就这三种,我么来将环境光,漫射光,反射光,加在一起,作为被处理顶点最后的颜色:

             

             读者,可能会疑问,为什么要对不同的光使用不同的光源颜色和材质。恩,其实你要是喜欢的话,也可以使用一样的,但是这里并不假设他们是一样的,通过上面的公式,指定不同的光源颜色和材质,可以让程序员或者艺术家们指定不同类型的颜色和材质,从而营造出不同的效果出来。


实例解析

             好了,理论到此结束,我们还是来看看代码吧。毕竟,对于程序员来说,代码才是王道。

             在这个例子中,我使用的模型是一个茶壶。调用下面的函数来创建一个茶壶模型:

             

	//Create the teapot mesh
	HR(D3DXCreateTeapot(m_pDevice,&m_TeaportMesh,NULL));
               这里的m_TeaportMesh就是保存这个模型网格的对象指针。

               我们使用下面的函数来绘制这个茶壶:

               

void CubeDemo::draw()
{
	//Set the vertex declaration
	HR(m_pDevice->SetVertexDeclaration(VertexPN::_vertexDecl));

	//Create the world matrix
	D3DXMATRIX _worldM ;
	D3DXMatrixIdentity(&_worldM);
	
	//Set the technique
	HR(m_pEffect->SetTechnique(m_hTechnique));

	//Set the gWVP matrix
	D3DXHANDLE _hWVP = m_pEffect->GetParameterByName(0, "gWVP");
	HR(m_pEffect->SetMatrix(_hWVP, &(_worldM* m_ViewMatrix* m_ProjMatrix)));

	//Set the gWorld matrix
	HR(m_pEffect->SetMatrix(m_gWorld, &_worldM));

	//Set the gInverseTranspose
	D3DXMatrixInverse(&_worldM,NULL, &_worldM);
	D3DXMatrixTranspose(&_worldM, &_worldM);
	HR(m_pEffect->SetMatrix(m_gInverseTranspose, &_worldM));

	//Set the gPower
	static float power = 1280.0f;
	HR(m_pEffect->SetValue(m_gPower,&power,sizeof(float)));

	//Set the gMaterial
	HR(m_pEffect->SetVector(m_gMaterial,&D3DXVECTOR4(0.5, 0.7, 0.0, 1.0)));

	//Set the gLightColor
	HR(m_pEffect->SetVector(m_gLightColor, &D3DXVECTOR4(0.8, 0.8, 0.8, 1.0)));

	//Set the gLightVector
	HR(m_pEffect->SetVector(m_gLightVector, &D3DXVECTOR4(1,1,1,0)));

	//Set the gSpecularLightColor
	HR(m_pEffect->SetValue(m_gSpecularLightColor, &D3DXCOLOR(1.0,1.0,1.0,1.0),  sizeof(D3DXCOLOR)));

	//Set the gSpecularMaterial
	HR(m_pEffect->SetValue(m_gSpecularMaterial, &D3DXCOLOR(1.0,1.0,1.0,1.0), sizeof(D3DXCOLOR)));

	//Set the gAmbientLightColor
	HR(m_pEffect->SetValue(m_gAmbientLightColor, &D3DXCOLOR(0.0,0.0,1.0,0.0),sizeof(D3DXCOLOR)));

	//Set the gAmbientMaterial
	HR(m_pEffect->SetValue(m_gAmbientMaterial, &D3DXCOLOR(0.5,0.5,0.5,0.0), sizeof(D3DXCOLOR)));

	//Begin pass
	UINT _pass = 0 ;
	HR(m_pEffect->Begin(&_pass, 0));
	HR(m_pEffect->BeginPass(0));

	//Draw the primitive
	HR(m_TeaportMesh->DrawSubset(0));

	//End pass
	HR(m_pEffect->EndPass());
	HR(m_pEffect->End());
}

                上面代码中,有很多的Shader里面变量的句柄,还有Shader对象的变量,他们的获取是使用下面的公式:

void CubeDemo::createEffect()
{
	//Create error buffer
	ID3DXBuffer* _error = NULL ;
	HR(D3DXCreateBuffer(128, &_error));

	//Create the effect from a file
	HR(D3DXCreateEffectFromFile(
		m_pDevice,
		L"Ambient_Spec_Diffuse.fx",
		NULL,
		NULL,
		D3DXSHADER_DEBUG,
		NULL,
		&m_pEffect,
		&_error
		));

	//If error 
	if(_error)
	{
		MessageBoxA(NULL,(char*)_error->GetBufferPointer(),"Error", MB_OK);
		return ;
	}

	//Get the technique handle
	m_hTechnique = m_pEffect->GetTechniqueByName("DiffuseTech");
	if(m_hTechnique == NULL)
	{
		MessageBox(NULL, L"GetTechnique error", L"Error", MB_OK);
		return ;
	}

	//Get the gWVP handle
	m_gWVP = m_pEffect->GetParameterByName(0, "gWVP") ;
	if(m_gWVP == NULL)
	{
		MessageBox(NULL, L"GetParameterByName error", L"Error", MB_OK);
		return ;
	}

	//Get the gInverseTranspose
	m_gInverseTranspose = m_pEffect->GetParameterByName(0,"gInverseTranspose");
	if(m_gInverseTranspose == NULL)
	{
		MessageBox(NULL, L"GetParameterByName error", L"Error", MB_OK);
		return ;
	}

	//Get the gMaterial
	m_gMaterial  = m_pEffect->GetParameterByName(0, "gMaterial");
	if(m_gMaterial == NULL)
	{
		MessageBox(NULL, L"GetParameterByName error", L"Error", MB_OK);
		return ;
	}

	//Get the gLightColor
	m_gLightColor = m_pEffect->GetParameterByName(0, "gLightColor");
	if(m_gLightColor == NULL)
	{
		MessageBox(NULL, L"GetParameterByName error", L"Error", MB_OK);
		return ;
	}

	//Get the gLightVector
	m_gLightVector = m_pEffect->GetParameterByName(0, "gLightVector");
	if(m_gLightVector == NULL)
	{
		MessageBox(NULL, L"GetParameterByName error", L"Error", MB_OK);
		return ;
	}

	//Get the gWorld
	m_gWorld = m_pEffect->GetParameterByName(0, "gWorld");
	if(m_gWorld == NULL)
	{
		MessageBox(NULL, L"GetParameterByName error", L"Error", MB_OK);
		return ;
	}

	//Get the gEye
	m_gEye = m_pEffect->GetParameterByName(0, "gEye");
	if(m_gEye == NULL)
	{
		MessageBox(NULL, L"GetParameterByName error", L"Error", MB_OK);
		return ;
	}

	//Get the gPower
	m_gPower = m_pEffect->GetParameterByName(0, "gPower");
	if(m_gPower == NULL)
	{
		MessageBox(NULL, L"GetParameterByName error", L"Error", MB_OK);
		return ;
	}

	//Get the gSpecularLightColor
	m_gSpecularLightColor = m_pEffect->GetParameterByName(0, "gSpecularLightColor");
	if(m_gSpecularLightColor == NULL)
	{
		MessageBox(NULL, L"GetParameterByName error", L"Error", MB_OK);
		return ;
	}

	//Get the gSpecularMaterial
	m_gSpecularMaterial = m_pEffect->GetParameterByName(0, "gSpecularMaterial");
	if(m_gSpecularMaterial == NULL)
	{
		MessageBox(NULL, L"GetParameterByName error", L"Error", MB_OK);
		return ;
	}

	//Get the gAmbientLightColor
	m_gAmbientLightColor = m_pEffect->GetParameterByName(0, "gAmbientLightColor");
	if(m_gAmbientLightColor == NULL)
	{
		MessageBox(NULL, L"GetParameterByName error", L"Error", MB_OK);
		return ;
	}

	//Get the gAmbientMaterial
	m_gAmbientMaterial = m_pEffect->GetParameterByName(0, "gAmbientMaterial");
	if(m_gAmbientMaterial == NULL)
	{
		MessageBox(NULL, L"GetParameterByName error", L"Error", MB_OK);
		return ;
	}
}

             好了,最后来看看,Shader是如何编写的:

//---------------------------------------------------------------------------
// declaration	: Copyright (c), by XJ , 2014 . All right reserved .
// brief	: This file will define the Diffuse shader.
// date		: 2014 / 5 / 24
//----------------------------------------------------------------------------
uniform float4x4 gWVP ;				//这个变量将会保存世界变换矩阵*相机变换矩阵*透视投影矩阵的积
						//用这个矩阵,将点转化到裁剪空间中去

uniform float4x4 gInverseTranspose;		//这个变量将会保存世界变换矩阵的逆矩阵*转置矩阵,用来对法向量进行变换

uniform float4   gMaterial;			//这个变量用来保存顶点的材质属性,在本Demo中,将对所有的顶点使用相同的
						//材质

uniform float4   gLightColor;			//这个变量将用来保存一个平行光的颜色

uniform float3   gLightVector;			//这个变量用来保存平行光的光照向量

uniform float4x4 gWorld;			//这个变量保存世界变换,用来对模型坐标进行变换,从而计算视向量

uniform float3   gEye;				//这个变量保存视点的位置,也就是相机的位置

uniform float    gPower	;			//这个变量将会控制反射光的衰减速度

uniform float4   gSpecularLightColor;		//反射光颜色

uniform float4   gSpecularMaterial;		//物体对反射光的材质属性

uniform float4   gAmbientLightColor;	        //环境光颜色

uniform float4   gAmbientMaterial;              //物体对环境光的材质属性

//定义顶点着色的输入结构体
struct OutputVS
{
   float4 posH : POSITION0 ;
   float4 color : COLOR0 ;
};

OutputVS DiffuseVS(float3 posL: POSITION0, float3 normalL: NORMAL0)
{
	//清空OutputVS
	OutputVS outputVS = (OutputVS) 0 ;

	//对顶点的法向向量进行变换
	normalL = normalize(normalL);
	float3 normalW = mul(float4(normalL, 0.0f),
			gInverseTranspose).xyz;
	normalW = normalize(normalW);

	//根据漫反射公式:
	// Color = max(L * Normal, 0)*(LightColor*Material)
	float s = max(dot(gLightVector,normalW), 0);
	float3 diffuse = s*(gMaterial*gLightColor).rgb ;

	//根据环境光公式:
	// Color = AmbientColor * AmbientMaterial
	float3 ambient = (gAmbientLightColor * gAmbientMaterial).rgb ;

	//根据反射光公式:
	// Color = pow((max(dot(r,v),0)),p) * (SpecularLightColor*SpecularMaterial)
	float3 posW = mul(float4(posL, 1.0), gWorld).xyz ;
	float3 view = gEye - posW ;
	view = normalize(view);
	float3 ref = reflect(-gLightVector, normalW);
	float t = pow(max(dot(ref,view),0),gPower);
	float3 specular = t * (gSpecularLightColor * gSpecularMaterial).rgb ;

	//计算最后的颜色
	outputVS.color.rgb = diffuse + ambient + specular ;
	outputVS.color.a = gMaterial.a ;

	//使用gWVP将世界坐标转化为裁剪坐标
	outputVS.posH = mul(float4(posL, 1.0f), gWVP);

	//返回结果
	return outputVS ;
}// end for Vertex Shader

float4 DiffusePS(float4 c: COLOR0): COLOR
{
	return c ;
}// end for Pixel Shader

technique DiffuseTech
{
	pass P0
	{
		vertexShader = compile vs_2_0 DiffuseVS();
		pixelShader =  compile ps_2_0 DiffusePS();
	}
}

               最后,是程序运行的截图:

DirectX 9.0 (4)环境光和反射光_第2张图片


                    好了,今天就到这里,完整源代码可在这里下载Light_Formula,我们下次再见!!!

你可能感兴趣的:(图形,shader,DirectX)