Модель освещения Блинна-Фонга

Модель освещения Блинна-Фонга

В этой статье мы рассмотрим очень распространенную модель освещения – модель Блинна-Фонга. Модель освещения Блинна-Фонга в некоторой степени упрощает модель Фонга, из-за чего работает быстрее.

Модель освещения Блинна-Фонга используется во многих графических движках, также является основной моделью освещения в XNA Framework при использовании BasicEffect.

Еще раз об основных обозначениях:


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

Напомню, что в модели Фонга зеркальная компонента освещенности определялась по следующей формуле:


  • Is – зеркальная составляющая освещенности
  • ks— коэффициент зеркального освещения
  • is— интенсивность зеркального освещения
  • a — коэффициент блеска (свойство материала)

А суммарная освещенность объекта определяться как сумма всех трех компонент освещенности.

Вычисление отраженного луча R является достаточно сложной операцией (хотя на HLSL реализуется вызовом всего одной функции). В модели Блинна-Фонга вводится дополнительный вектор H, который является «медианой» угла между V и L.

Вектор H вычисляется по формуле:


А в формуле для модели освещения Фонга меняется на .

Таким образом, итоговая формула для модели освещения Блинна-Фонга имеет следующий вид:

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

Перейдем к реализации шейдера. Его исходный код остается практически без изменений, меняется лишь пиксельный шейдер:

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);


float4 Diffuse = kd * max(0, dot(worldNormal, lightDirection)) * DiffuseColor;


float3 eyeDirection = normalize(Eye — worldPosition);


float3 halfv = normalize((lightDirection + eyeDirection))    ;


float4 Specular = ks * pow(max(0, dot(worldNormal, halfv)), SpecularPower) * SpecularColor;


return Ambient + Diffuse + Specular;

}



Как видно из скриншотов, для того, чтобы добиться эффекта, похожего на модель освещения Фонга, в модели освещения Блинна-Фонга нужно устанавливать большие значения коэффициента блеска. Хотя точного совпадения невозможно добиться в общем случае.

Вначале я писал, что в XNA Framework модель Блинна-Фонга используется в качестве основной. Давайте проверим это. Для этого нужно скачать исходный код BasicEffect по адресу http://creators.xna.com/en-US/utilities/basiceffectshader.

Шейдер содержит достаточно много фунцкий, но сейчас нам нужно посмотреть лишь на функцию ComputePerPixelLights. Судя по комментариям, на вход эта функция принимает вектор в направлении на наблюдателя и вектор нормали к поверхности. Зеркальная составляющая вычисляется по формуле:

result.Specular += DirLight0SpecularColor * pow(max(0,dot(H,N)), SpecularPower);

Эта формула как раз описывает модель освещения Блинна-Фонга.

//——————————————————————————

// Compute per-pixel lighting.

// When compiling for pixel shader 2.0, the lit intrinsic uses more slots

// than doing this directly ourselves, so we don’t use the intrinsic.

// E: Eye-Vector

// N: Unit vector normal in world space

//——————————————————————————

ColorPair ComputePerPixelLights(float3 E, float3 N)

{

ColorPair result;


result.Diffuse = AmbientLightColor;

result.Specular = 0;


// Light0

float3 L = -DirLight0Direction;

float3 H = normalize(E + L);

float dt = max(0,dot(L,N));

result.Diffuse += DirLight0DiffuseColor * dt;


if (dt != 0)

result.Specular += DirLight0SpecularColor * pow(max(0,dot(H,N)), SpecularPower);

// Light1

L = -DirLight1Direction;

H = normalize(E + L);

dt = max(0,dot(L,N));

result.Diffuse += DirLight1DiffuseColor * dt;


if (dt != 0)

result.Specular += DirLight1SpecularColor * pow(max(0,dot(H,N)), SpecularPower);

// Light2

L = -DirLight2Direction;

H = normalize(E + L);

dt = max(0,dot(L,N));

result.Diffuse += DirLight2DiffuseColor * dt;


if (dt != 0)

result.Specular += DirLight2SpecularColor * pow(max(0,dot(H,N)), SpecularPower);

result.Diffuse *= DiffuseColor;

result.Diffuse += EmissiveColor;

result.Specular *= SpecularColor;


return result;

}

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

Light.fx

float4x4 World;

float4x4 View;

float4x4 Projection;

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

float ka = 0.4f;

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

float kd = 1;

float4 SpecularColor = float4(1, 1, 1, 1);

float ks = 1;

float SpecularPower = 8;

float3 LightPosition;

float3 Eye;

// 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);


float4 Diffuse = kd * max(0, dot(worldNormal, lightDirection)) * DiffuseColor;


float3 eyeDirection = normalize(Eye — worldPosition);


//float3 reflectedLight = normalize(reflect(-lightDirection, worldNormal));


float3 halfv = normalize((lightDirection + eyeDirection))    ;


float4 Specular = ks * pow(max(0, dot(worldNormal, halfv)), SpecularPower) * SpecularColor;


return Ambient + Diffuse + Specular;

}

technique Light

{


pass Pass1

{


// TODO: set renderstates here.


VertexShader = compile
vs_1_1 VertexShaderFunction();


PixelShader = compile
ps_2_0 PixelShaderFunction();

}

}

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;


DebugInfo debugInfo;


Effect effect;


TeapotPrimitive teapot;


FreeCamera camera;


KeyboardState oldState;


float specularPower = 8;


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);

debugInfo = new
DebugInfo(this);

Components.Add(debugInfo);

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))

{

specularPower = specularPower — 1f;

}


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

{

specularPower = specularPower + 1f;

}


DebugInfo.SetVariable(«specularPower», specularPower.ToString());

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[«SpecularPower»].SetValue(specularPower);

effect.Parameters[«Eye»].SetValue(camera.Position);

teapot.Draw(effect);

light.Draw(view, proj);


base.Draw(gameTime);

}

}

}

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

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

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

Логотип WordPress.com

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

Фотография Twitter

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

Фотография Facebook

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

Google+ photo

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

Connecting to %s