[DirectX]Programming.Role.Playing.Games:02_05_Light

    Light,Programming.Role.Playing.Games.with.DirectX书上把点光源,聚光灯,平行光和环境光都介绍了一下,案例也只给了最简单的平行光的源码,稍微简略了一点。其实用固定管线实现都很简单,就改几个参数,但用Shader来实现的话,就复杂了,涉及到了光照衰减公式等。还是老规则,左边的是用固定管线实现的,右边是用Shader实现的。

    因为代码写的有点多,之前也有写过,所以就懒得再仔细写一遍。一个工程就包括了点光源,聚光灯,平行光3中实现,就如上图一样,通过1,2,3按键来控制。改shader比较影响效果,就只改了平行光的效果,没用lamber光照模型,用的是v社的halflamber光照模型,只计算diffuse漫反射,高光什么的再加进去,工程就有点大了,为了效果我已经加了在旋转的灯光模型和地表模型,都是用xfile加载的。

    如果想看其他的(有高光的),可以下载源码去看下,里面我加了当前要讲的工程外,还加了4个工程,一个就是下图的点光源,另外3个就包含了Gouradu模型和phong模型的工程,聚光灯实现,平行光和半球模型和光照贴图的实现。   

    上图的代码其实很简单,为了玩,又加了边缘光的算法,就得出这种效果,有点怪怪的。

    来看下渲染的代码,代码是在Draw3D上修改的:

    固定管线:

void ModelClass::Render(IDirect3DDevice9* device, float time, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, LightClass* light)
{
	D3DXMATRIX xRotationMatrix, yRotationMatrix;
	D3DXMATRIX translationMatrix;
	LightType lightType;
	D3DXVECTOR4 lightColor;
	D3DXVECTOR4 lightPos;
	D3DXVECTOR4 lightDir;
	D3DXVECTOR4 lightAtten;
	float phi, theta;
	D3DLIGHT9 light9;

	// Rotation
	::D3DXMatrixRotationX(&xRotationMatrix, D3DX_PI * 0.25f);
	::D3DXMatrixRotationY(&yRotationMatrix, -D3DX_PI * 0.25f);

	// Translate
	::D3DXMatrixTranslation(&translationMatrix, -2.0f, 5.0f, 0.0f);

	::D3DXMatrixMultiply(&worldMatrix, &worldMatrix, &xRotationMatrix);
	::D3DXMatrixMultiply(&worldMatrix, &worldMatrix, &yRotationMatrix);
	::D3DXMatrixMultiply(&worldMatrix, &worldMatrix, &translationMatrix);

	device->SetTransform(D3DTS_WORLD, &worldMatrix);
	device->SetTransform(D3DTS_VIEW, &viewMatrix);
	device->SetTransform(D3DTS_PROJECTION, &projectionMatrix);

	// Setup light
	device->SetRenderState(D3DRS_LIGHTING, true);

	lightType = light->GetLightType();
	light->GetColor(lightColor);
	light->GetPosition(lightPos);
	light->GetDirection(lightDir);
	light->GetAttenuation(lightAtten);
	phi = light->GetPhi();
	theta = light->GetTheta();

	::ZeroMemory(&light9, sizeof(D3DLIGHT9));
	switch (lightType)
	{
	case LightType::Point:
		light9.Type = D3DLIGHT_POINT;
		break;
	case LightType::Spot:
		light9.Type = D3DLIGHT_SPOT;
		break;
	case LightType::Directional:
		light9.Type = D3DLIGHT_DIRECTIONAL;
		break;
	}
	light9.Diffuse.r = lightColor.x;
	light9.Diffuse.g = lightColor.y;
	light9.Diffuse.b = lightColor.z;
	light9.Diffuse.a = lightColor.w;
	light9.Range = lightAtten.x;
	light9.Attenuation0 = lightAtten.y;
	light9.Attenuation1 = lightAtten.z;
	light9.Attenuation2 = lightAtten.w;
	light9.Position.x = lightPos.x;
	light9.Position.y = lightPos.y;
	light9.Position.z = lightPos.z;
	light9.Direction.x = lightDir.x;
	light9.Direction.y = lightDir.y;
	light9.Direction.z = lightDir.z;
	light9.Phi = phi;
	light9.Theta = theta;

	device->SetLight(0, &light9);
	device->LightEnable(0, true);

	device->SetStreamSource(0, m_vertexBuffer, 0, sizeof(VertexType));
	device->SetFVF(VERTEX_FVF);

	device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, m_vertexCount / 3);
}

    所有灯光相关数据都存在lightclass中,lightclass头文件结构:

#pragma once

#include <d3d9.h>
#include <d3dx9.h>
#include "LightShaderClass.h"
#include "Utility.h"

class LightClass
{
public:
	LightClass(LightType lightType);
	LightClass(const LightClass& other);
	~LightClass();

	bool Initialize(IDirect3DDevice9* device, TCHAR* modelFilePath, D3DXVECTOR4 lightColor, 
		float range, float attenuation0, float attenuation1, float attenuation2, float phi, float theta);
	void Shutdown();
	void Render(IDirect3DDevice9* device, float time, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix);

	void SetPosition(float x, float y, float z, float w);

	LightType GetLightType();
	void GetPosition(D3DXVECTOR4& position);
	void GetDirection(D3DXVECTOR4& direction);
	void GetColor(D3DXVECTOR4& color);
	void GetAttenuation(D3DXVECTOR4& attenuation);
	float GetPhi();
	float GetTheta();

private:
	bool InitializeMesh(IDirect3DDevice9* device, TCHAR* modelFilePath);

private:
	ID3DXMesh* m_mesh;
	LightShaderClass* m_lightShader;

	LightType m_lightType;
	D3DXVECTOR4 m_pos;
	D3DXVECTOR4 m_color;
	D3DXVECTOR4 m_atten;
	float m_phi;
	float m_theta;
};

        固定管线的实现就简单,SetRenderState(D3DRS_LIGHTING, true)函数开启灯光,然后填充D3DLIGHT9结构,没了。具体的原理还是看shader代码会比较清楚,我就放shader后。

    Shader渲染:

void ShaderModelClass::Render(IDirect3DDevice9* device, float time, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, LightClass* light)
{
	D3DXMATRIX xRotationMatrix, yRotationMatrix;
	D3DXMATRIX translationMatrix;
	D3DXMATRIX world2ObjectMatrix;
	bool result;
	UINT passMaxNum;

	// Rotation
	::D3DXMatrixRotationX(&xRotationMatrix, D3DX_PI * 0.25f);
	::D3DXMatrixRotationY(&yRotationMatrix, D3DX_PI * 0.25f);

	// Translate
	::D3DXMatrixTranslation(&translationMatrix, 2.0f, 5.0f, 0.0f);

	::D3DXMatrixMultiply(&worldMatrix, &worldMatrix, &xRotationMatrix);
	::D3DXMatrixMultiply(&worldMatrix, &worldMatrix, &yRotationMatrix);
	::D3DXMatrixMultiply(&worldMatrix, &worldMatrix, &translationMatrix);

	::D3DXMatrixInverse(&world2ObjectMatrix, nullptr, &worldMatrix);
	::D3DXMatrixTranspose(&world2ObjectMatrix, &world2ObjectMatrix);

	result = m_colorShader->Render(device, time, worldMatrix, viewMatrix, projectionMatrix, world2ObjectMatrix, light);
	if (!result)
		return;

	device->SetStreamSource(0, m_vertexBuffer, 0, sizeof(VertexType));
	device->SetFVF(VERTEX_FVF);

	m_colorShader->GetEffect()->Begin(&passMaxNum, 0);
	for (UINT pass = 0; pass < passMaxNum; ++pass)
	{
		m_colorShader->GetEffect()->BeginPass(pass);
		device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, m_vertexCount / 3);
		m_colorShader->GetEffect()->EndPass();
	}
	m_colorShader->GetEffect()->End();
}

    shader渲染代码没什么大变化,还是具体看shader代码:

float time;
float4x4 worldMatrix;
float4x4 viewMatrix;
float4x4 projectionMatrix;
float4x4 world2ObjectMatrix;
float4 lightColor;
float4 lightPos;
float4 lightDir;
float4 lightAtten;
float phi;
float theta;

struct VertexInputType
{
	float4 vertex : POSITION;
	float3 normal : NORMAL;
	float4 diffuse : COLOR;
};

struct PixelInputType
{
	float4 pos : POSITION;
	float3 normal : TEXCOORD0;
	float4 diffuse : TEXCOORD1;
	float3 worldPos : TEXCOORD2;
};

PixelInputType ColorVertexShader(VertexInputType input)
{
	PixelInputType output;

	float4x4 worldViewMaxtrix = mul(worldMatrix, viewMatrix);
	float4x4 worldViewProjectionMatrix = mul(worldViewMaxtrix, projectionMatrix);
	output.pos = mul(input.vertex, worldViewProjectionMatrix);
	output.normal = mul(input.normal, (float3x3)world2ObjectMatrix);
	output.diffuse = input.diffuse;
	output.worldPos = mul(input.vertex, worldMatrix).xyz;

	return output;
}

float4 DirectionalLightPixelShader(PixelInputType input) : COLOR
{
	float3 worldNormal = normalize(input.normal);
	float3 worldLightDir = normalize(-lightDir.xyz);

	float halfLambert = saturate(dot(worldNormal, worldLightDir)) * 0.5f + 0.5f;
	float3 diffuse = input.diffuse.xyz * lightColor.xyz * halfLambert;
	//float lambert = saturate(dot(worldNormal, worldLightDir));
	//float3 diffuse = input.diffuse.xyz * lightColor.xyz * lambert;

	float3 color = diffuse;

	return float4(color, 1.0f);
}

float4 PointLightPixelShader(PixelInputType input) : COLOR
{
	float3 worldNormal = normalize(input.normal);
	float3 worldLightDir = normalize(lightPos.xyz - input.worldPos);
	float distance = length(lightPos.xyz - input.worldPos);
	float range = lightAtten.x;
	float atten0 = lightAtten.y;
	float atten1 = lightAtten.z;
	float atten2 = lightAtten.w;

	float3 diffuse = float3(0.0f, 0.0f, 0.0f);
	if (distance < range)
	{
		diffuse = input.diffuse.xyz * lightColor.xyz * saturate(dot(worldNormal, worldLightDir));
		float attenPara = 1.0f / (atten0 + atten1 * distance + atten2 * distance * distance);
		diffuse *= attenPara;
	}

	float3 color = diffuse;

	return float4(color, 1.0f);
}

float4 SpotLightPixelShader(PixelInputType input) : COLOR
{
	float3 worldNormal = normalize(input.normal);
	float3 worldLightDir = normalize(lightPos.xyz - input.worldPos);
	float distance = length(lightPos.xyz - input.worldPos);
	float range = lightAtten.x;
	float atten0 = lightAtten.y;
	float atten1 = lightAtten.z;
	float atten2 = lightAtten.w;

	float3 diffuse = float3(0.0f, 0.0f, 0.0f);
	if (distance < range)
	{
		float tensity = saturate(dot(normalize(lightDir).xyz, -worldLightDir));
		if (tensity >= phi)
		{
			tensity = (clamp(tensity, phi, theta) - phi) / (theta - phi) * 0.5f + 0.5f;
			diffuse = input.diffuse.xyz * lightColor.xyz * saturate(dot(worldNormal, worldLightDir)) * tensity;
			float attenParam = 1.0f / (atten0 + atten1 * distance + atten2 * distance * distance);
			diffuse *= attenParam;
		}
	}

	float3 color = diffuse;

	return float4(color, 1.0f);
}

technique DirectionalLightTechnique
{
	pass pass0
	{
		VertexShader = compile vs_2_0 ColorVertexShader();
		PixelShader = compile ps_2_0 DirectionalLightPixelShader();
	}
}

technique PointLightTechnique
{
	pass pass0
	{
		VertexShader = compile vs_2_0 ColorVertexShader();
		PixelShader = compile ps_2_0 PointLightPixelShader();
	}
}

technique SpotLightTechnique
{
	pass pass0
	{
		VertexShader = compile vs_2_0 ColorVertexShader();
		PixelShader = compile ps_3_0 SpotLightPixelShader();
	}
}

    地表的渲染shader可右边Cube的几乎是一样的,不同处就在贴图上,所以也就不贴地表的shader实现了。平行光的shader代码是最简单,让我贴一个公式过来:

    原理公式:diffuse = I*cosθ;      

    diffuse:反射光线的的光强;      

    I:入射光线的光强,方向如上图所示;      

    cosθ:入射光线和该顶点法线的余弦。 

    所以,最后的数学表达式为:diffuse = I*(L*N);也就是被我注释了代码。而所谓halflambert公式则是使得背光面不会很暗,从公式上就可以看出来,cosθ的值为(-1, 1)之间,调用saturate后值为(0, 1),所以背光面就会为黑色,而halflambert公式则使得值为(0.5, 1)之间,使得背光面有了默认的亮度。

    重点开始,一开始学习DX9的固定管线的话,肯定不了解D3DLIGHT9中的衰减和聚光灯的phi及theta的用处,然后去查,发现理论讲的很清楚,但是就是不明白,直到看到shader代码后,其实真的很简单。

    先说点光源的衰减和范围,其实就是在平行光计算的基础上乘一个衰减系数,而这个衰减系数的计算公式则是1/(att0+ ATT1* D+ ATT2* D* D),D是距离,也就是PointLightPixelShader代码中标红的那一段。

    聚关灯的算法其实也可以简化为在点光源的基础上,乘一个角度衰减系数。也就是falloff参数,公式为((cosα-cosφ)/(cosθ-cosφ))falloff,就是聚光灯算法中标红的那一段,只是跟halflambert一样,把值限制在(0.5,1)之间而已,代码很清楚,就不细说了,看完代码,才能明白D3DLIGHT9中参数到底是如何参与运算的。具体的可以参考http://lengbingteng.iteye.com/blog/1767772这篇文章还有这篇多光源的http://lengbingteng.iteye.com/blog/1767772,也很不错,代码也很简单,可以仔细看下。

源码下载:下载地址

猜你喜欢

转载自blog.csdn.net/zp288105109a/article/details/80932891