Модифицированная модель Ламберта

Модифицированная модель освещения Ламберта

Иногда оказывается, что модели, освещенные по стандартной модели Ламберта, оказываются недостаточно освещенными. В статье описывается простой метод модификации модели Ламберта, который позволяет в некоторой степени избежать этой проблемы.


Напомню еще раз основные обозначения:


  • N – нормаль к поверхности
  • L – направление к источнику света
  • R – направление отраженного луча
  • V – направление к наблюдателю

Причем каждый из векторов является единичным.

А модель освещения Ламберта описывается следующей формулой:


Очевидно, что когда угол между n и l превышает значение 90 градусов, все точки становятся черными (не освещенными).

Хорошо этот эффект будет заметен если мы временно отключим ambient освещение и поменяем фон.

В шейдере:

float4 AmbientColor = float4(0.1, 0.1, 0.1, 1);

float ka = 0;

В Game1:


protected
override
void Draw(GameTime gameTime)

{

GraphicsDevice.Clear(Color.Black);


// …

}


В нашей простейшей модификации мы используем дополнительную переменную – смещение.

Таким образом, получим следующую формулу:


Изменим немного наш пиксельный шейдер (вершинный остается без изменений). Обратите внимание на то, что в шейдере мы опять используем функцию max, для того, чтобы значение диффузной освещенности не стало отрицательным.

float f;

float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0

{


// TODO: add your pixel shader code here.


float3 worldPosition = input.WorldPosition;


float3 worldNormal = normalize(input.Normal);


float4 Ambient = ka * AmbientColor;


float3 lightDirection = normalize(LightPosition — worldPosition);


float fDiffuse = (max(0, dot(worldNormal, lightDirection) + f))/(1.0+f);


float4 Diffuse = kd * fDiffuse * DiffuseColor;


return Ambient + Diffuse;

}

Значение f будем задавать из основной программы, также было бы интересно иметь возможность регулировать значение параметра с клавиатуры (я буду использовать клавиши Z и X). Выводить текущее значение параметра будем в заголовок окна (да, именно туда, где раньше было значение FPS).


KeyboardState oldState;


float f;


protected
override
void Update(GameTime gameTime)

{


// Allows the game to exit


if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)


this.Exit();


// TODO: Add your update logic here


KeyboardState state = Keyboard.GetState();


if (state.IsKeyDown(Keys.Z) && oldState.IsKeyUp(Keys.Z))

{

f = f — 0.1f;

}


if (state.IsKeyDown(Keys.X) && oldState.IsKeyUp(Keys.X))

{

f = f + 0.1f;

}

Window.Title = «f=» + f;

oldState = state;


base.Update(gameTime);

}


protected
override
void Draw(GameTime gameTime)

{

GraphicsDevice.Clear(Color.Black);


Matrix view = camera.View;


Matrix proj = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), GraphicsDevice.Viewport.AspectRatio, 0.1f, 10f);


// TODO: Add your drawing code here

effect.Parameters[«World»].SetValue(Matrix.CreateRotationY((float)gameTime.TotalGameTime.TotalSeconds));

effect.Parameters[«View»].SetValue(view);

effect.Parameters[«Projection»].SetValue(proj);

effect.Parameters[«LightPosition»].SetValue(light.Position);

effect.Parameters[«f»].SetValue(f);

teapot.Draw(effect);

light.Draw(view, proj);


base.Draw(gameTime);

}


Разумеется, такую модификацию можно будет применить и в других моделях освещения (которые мы рассмотрим дальше) при вычислении диффузного освещения (можно попробовать и ддля зеркального).

Исходный код:

Game1.cs

using System;

using System.Collections.Generic;

using System.Linq;

using Microsoft.Xna.Framework;

using Microsoft.Xna.Framework.Audio;

using Microsoft.Xna.Framework.Content;

using Microsoft.Xna.Framework.GamerServices;

using Microsoft.Xna.Framework.Graphics;

using Microsoft.Xna.Framework.Input;

using Microsoft.Xna.Framework.Media;

using Microsoft.Xna.Framework.Net;

using Microsoft.Xna.Framework.Storage;

using Primitives3D;

namespace MyGame

{


///
<summary>


/// This is the main type for your game


///
</summary>


public
class
Game1 : Microsoft.Xna.Framework.Game

{


GraphicsDeviceManager graphics;


SpriteBatch spriteBatch;


Light light;


Effect effect;


TeapotPrimitive teapot;


FreeCamera camera;


KeyboardState oldState;


float f;


public Game1()

{

graphics = new
GraphicsDeviceManager(this);

Content.RootDirectory = «Content»;

graphics.SynchronizeWithVerticalRetrace = false;

IsFixedTimeStep = false;

}


///
<summary>


/// Allows the game to perform any initialization it needs to before starting to run.


/// This is where it can query for any required services and load any non-graphic


/// related content. Calling base.Initialize will enumerate through any components


/// and initialize them as well.


///
</summary>


protected
override
void Initialize()

{


// TODO: Add your initialization logic here

Components.Add(new
FPSCounter(this));

light = new
Light(this);

light.DefaultPosition = new
Vector3(1, 1, 1);

Components.Add(light);

camera = new
FreeCamera(this, new
Vector3(0, 0, 2), Vector3.Zero);

Components.Add(camera);

teapot = new
TeapotPrimitive(GraphicsDevice);


base.Initialize();

}


///
<summary>


/// LoadContent will be called once per game and is the place to load


/// all of your content.


///
</summary>


protected
override
void LoadContent()

{


// Create a new SpriteBatch, which can be used to draw textures.

spriteBatch = new
SpriteBatch(GraphicsDevice);


// TODO: use this.Content to load your game content here

effect = Content.Load<Effect>(«light»);

}


///
<summary>


/// UnloadContent will be called once per game and is the place to unload


/// all content.


///
</summary>


protected
override
void UnloadContent()

{


// TODO: Unload any non ContentManager content here

}


///
<summary>


/// Allows the game to run logic such as updating the world,


/// checking for collisions, gathering input, and playing audio.


///
</summary>


///
<param name=»gameTime»>Provides a snapshot of timing values.</param>


protected
override
void Update(GameTime gameTime)

{


// Allows the game to exit


if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)


this.Exit();


// TODO: Add your update logic here


KeyboardState state = Keyboard.GetState();


if (state.IsKeyDown(Keys.Z) && oldState.IsKeyUp(Keys.Z))

{

f = f — 0.1f;

}


if (state.IsKeyDown(Keys.X) && oldState.IsKeyUp(Keys.X))

{

f = f + 0.1f;

}

Window.Title = «f=» + f;

oldState = state;


base.Update(gameTime);

}


///
<summary>


/// This is called when the game should draw itself.


///
</summary>


///
<param name=»gameTime»>Provides a snapshot of timing values.</param>


protected
override
void Draw(GameTime gameTime)

{

GraphicsDevice.Clear(Color.Black);


Matrix view = camera.View;


Matrix proj = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), GraphicsDevice.Viewport.AspectRatio, 0.1f, 10f);


// TODO: Add your drawing code here

effect.Parameters[«World»].SetValue(Matrix.CreateRotationY((float)gameTime.TotalGameTime.TotalSeconds));

effect.Parameters[«View»].SetValue(view);

effect.Parameters[«Projection»].SetValue(proj);

effect.Parameters[«LightPosition»].SetValue(light.Position);

effect.Parameters[«f»].SetValue(f);

teapot.Draw(effect);

light.Draw(view, proj);


base.Draw(gameTime);

}

}

}

Light.fx

float4x4 World;

float4x4 View;

float4x4 Projection;

float4 AmbientColor = float4(0.1, 0.1, 0.1, 1);

float ka = 0;

float4 DiffuseColor = float4(0.4, 0, 0, 1);

float kd = 1;

float f;

float3 LightPosition;

// TODO: add effect parameters here.

struct VertexShaderInput

{


float4 Position : POSITION0;


float3 Normal : NORMAL;


// TODO: add input channels such as texture


// coordinates and vertex colors here.

};

struct VertexShaderOutput

{


float4 Position : POSITION0;


float3 WorldPosition : TEXCOORD0;


float3 Normal : TEXCOORD1;


// TODO: add vertex shader outputs such as colors and texture


// coordinates here. These values will automatically be interpolated


// over the triangle, and provided as input to your pixel shader.

};

VertexShaderOutput VertexShaderFunction(VertexShaderInput input)

{

VertexShaderOutput output;


float3 worldNormal = normalize(mul(input.Normal, World));


float4 worldPosition = mul(input.Position, World);


float4 viewPosition = mul(worldPosition, View);

output.Position = mul(viewPosition, Projection);

output.WorldPosition = worldPosition;

output.Normal = worldNormal;


// TODO: add your vertex shader code here.


return output;

}

float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0

{


// TODO: add your pixel shader code here.


float3 worldPosition = input.WorldPosition;


float3 worldNormal = normalize(input.Normal);


float4 Ambient = ka * AmbientColor;


float3 lightDirection = normalize(LightPosition — worldPosition);


float fDiffuse = (max(0, dot(worldNormal, lightDirection) + f))/(1.0+f);


float4 Diffuse = kd * fDiffuse * DiffuseColor;


return Ambient + Diffuse;

}

technique Light

{


pass Pass1

{


// TODO: set renderstates here.


VertexShader = compile
vs_1_1 VertexShaderFunction();


PixelShader = compile
ps_2_0 PixelShaderFunction();

}

}

Реклама
Запись опубликована в рубрике Uncategorized с метками , , , , , , , . Добавьте в закладки постоянную ссылку.

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s