Elemar DEV

Negócios, tecnologia e desenvolvimento

Vamos aprender XNA? – Parte 10 – HLSL e iluminação básica

Olá pessoal, como estamos?!

Na parte 8, apresentei os fundamentos de HLSL. Na parte 9, mostrei como mapear texturas usando essa tecnologia. Agora, apresento conceitos fundamentais de iluminação.

Esse post, bem como os dois anteriores, apresenta conceitos avançados que implicam no domínio de alguns fundamentos de matemática para computação gráfica.

Considere a possibilidade de consultar esta série desde o início.

Até onde chegamos?!

No post anterior, mostrei como fazer mapeamento adequado de texturas. Entretanto, como ignorei questões de iluminação, chegamos apenas a um modelo “duro”. Observe:

image

Agora, vamos ver os fundamentos para aplicar os efeitos básicos de iluminação.

Ambient Lighting

Trata-se de uma tentativa de simular e reconhecer a luz emitida de outros objetos (assim como acontece no mundo real).

Quando olhamos para um objeto que não tem luz alguma sendo aplicada sobre ele, poderemos continuar o vendo o pois ele emite luz.

Crio um effect (AmbientLightEffect), com as seguintes variáveis globais para suportar luz ambiente.

float4x4 World;
float4x4 View;
float4x4 Projection;

float3 DiffuseColor = float3(1,1,1);
float3 AmbientLightColor = float3(0.1, 0.1, 0.1);

texture BasicTexture;

sampler BasicTextureSampler = sampler_state {
	texture = <BasicTexture>;
};

bool TextureEnabled = false;

Perfeito! A grande novidade até aqui é a variável para luz ambiente (assumo 0.1 como valor default).

Agora, vejamos a implementação do Pixel Shader:

float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
	float4 result = float4(DiffuseColor *
		AmbientLightColor, 1);
	
	if (TextureEnabled)
	{
		float3 output = AmbientLightColor;
		output *= tex2D(BasicTextureSampler, input.UV);
		result = float4(output, 1);
	}
	
	return result;
}

O resultado, até aqui é a nossa imagem “atenuada”

image

Lambertian directional lighting

Agora que temos luz ambiente, precisamos aplicar luz direcional. Isso adiciona alguma “definição” para nossos objetos.

A técnica mais simples disponível para isso é chamada Lambertian lighting, e é bem simples. Nessa técnica, definimos que a luz presente em uma face corresponde ao produto escalar do vetor da luz com o vetor normal da face.

Para poder executar esse cálculo, precisamos obter as normais no vertex shader para então repassarmos para o pixel shader, onde o cálculo é feito. Observe:

struct VertexShaderInput
{
	float4 Position : POSITION0;
	float2 UV : TEXCOORD0;
	float3 Normal : NORMAL0;
};

struct VertexShaderOutput
{
	float4 Position : POSITION0;
	float2 UV : TEXCOORD0;
	float3 Normal : TEXCOORD1;
};

Abaixo a alteração no nosso Vertex Shader:

VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
	VertexShaderOutput output;

	float4 worldPosition = mul(input.Position, World);
	float4 viewPosition = mul(worldPosition, View);
	output.Position = mul(viewPosition, Projection);

	output.UV = input.UV;
	output.Normal = mul(input.Normal, World); 

	return output;
}

Repare que preciso atualizar o vetor normal para considerar a “posição do mundo”.

Além disso, vamos adicionar duas novas variáveis: uma para a direção da luz, outra para a cor da luz.

float3 LightDirection = float3(1,1,1);
float3 LightColor = float(0.9, 0.9, 0.9);

Finalmente, vamos atualizar nosso pixel shader para que seja efetivado o cálclo da luz. Perceba que começo a utilizar algumas funções do HLSL. Entre elas, tenho a função normalize que garante que os componentes dos vetores fiquem restritos a –1 e 1.

O produto escalar da normal do vértice e a direção da luz é multiplicada pela cor da luz, para ser adicionada ao resultante.

Perceba que a função saturate garante que não ocorra valores negativos. Ou seja, mínimo seja 0.

float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
	float3 color = DiffuseColor;

	if (TextureEnabled)
		color *= tex2D(BasicTextureSampler, input.UV);

	float3 lighting = AmbientColor;
	float3 lightDir = normalize(LightDirection);
	float3 normal = normalize(input.Normal);

	lighting += saturate(dot(lightDir, normal)) * LightColor;

	float3 output = saturate(lighting) * color;
	
	return float4(output, 1);
}

 

image

Phong specular highlights

Nosso ambiente já está bem delineada e sombreada, mas ainda falta uma coisa: specular highlights.

Specular hightlights é o termo formal para “brilho” que vemos quando olhamos para o reflexo de uma fonte de luz na superfície de um objeto.

O processo para calcular specular highlights mais comum é conhecido como “phong shading model”. Os elementos básicos dessa equação são os vetores da direção da luz e o vetor da posição do observador. Dado o produto escalar desses dois vetores. O resultado dessa equação é amplificado por um fator adicional, conhecido como SpecularPower.

Vamos começar implementando mais alguns parâmetros:

float SpecularPower = 32;
float3 SpecularColor = float3(1,1,1);
float3 CameraPosition;

A posição da câmera será ajustada por nosso código C#. Agora, precisamos atualizar o Vertex Shader para calcular a direção entre o vértice e a câmera. Primeiro, preparamos a saída dos dados:

struct VertexShaderOutput
{
	float4 Position : POSITION0;
	float2 UV : TEXCOORD0;
	float3 Normal : TEXCOORD1;
	float3 ViewDirection: TEXCOORD2;
};

Agora, a atualização do Vertex Shader.

VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
	VertexShaderOutput output;

	float4 worldPosition = mul(input.Position, World);
	float4 viewPosition = mul(worldPosition, View);
	output.Position = mul(viewPosition, Projection);

	output.UV = input.UV;
	output.Normal = mul(input.Normal, World); 
	output.ViewDirection = worldPosition - CameraPosition;

	return output;
}

Repare que basta calcular a diferença entre a posição real do vértice e da câmera.

Agora, a implentação do Phong no Pixel Shader:

float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
	float3 color = DiffuseColor;

	if (TextureEnabled)
		color *= tex2D(BasicTextureSampler, input.UV);

	float3 lighting = AmbientColor;
	float3 lightDir = normalize(LightDirection);
	float3 normal = normalize(input.Normal);

	lighting += saturate(dot(lightDir, normal)) * LightColor;

	float3 refl = reflect(lightDir, normal);
	float3 view = normalize(input.ViewDirection);

	lighting += pow(saturate(dot(refl, view)), SpecularPower) * SpecularColor;
	
	float3 output = saturate(lighting) * color;
	
	return float4(output, 1);
}

Adicionei um método a mais no modelo para atualizar o valor do parâmetro CameraPosition. Observe:

public void UpdateCameraPosition(Vector3 cameraPos)
{
    foreach (var mesh in Model.Meshes)
        foreach (var part in mesh.MeshParts)
        {
            if (part.Effect.Parameters["CameraPosition"] != null)
                part.Effect.Parameters["CameraPosition"].SetValue(cameraPos);
        }
}

O resultado fica fácilmente percebido. Observe o brilho da nave Alegre

image

 

Lindo!

Agora sim, por hoje era isso.

Smiley de boca aberta

4 comentários em “Vamos aprender XNA? – Parte 10 – HLSL e iluminação básica

  1. Jhon Petter Marques
    24/07/2011

    hehehe,vlw dinovo,eu tava me matando para fazer isso……
    nao vejo a hora de vc mostrar como inplementar modelos animados
    tanks esperando mais tutu……

  2. Pingback: Vamos aprender XNA? – Parte 11 – HLSL e Point Light Effect « Elemar DEV

  3. Pingback: Vamos aprender XNA? – Parte 16 – Projective Texturing « Elemar DEV

Deixe um comentário

Informação

Publicado às 24/07/2011 por em Sem categoria e marcado , , .

Estatísticas

  • 921.697 hits