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

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

Модель освещения Фонга расширяет стандартную модель диффузного освещения, добавляя в нее зеркальную компоненту.

В этой статье мы разработаем шейдер освещения по модели Фонга.


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


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

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

Зеркальная компонента освещения зависит от расположения наблюдателя. В реальной жизни можно заметить, что когда в солнечный день мы смотрим на «зеркальные» объекты (металл, вода, стекло и т.д.) освещенность объекта зависит на нашей точки обзора. При определенных углах заметны яркие блики.

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

Таким образом, можно ввести некоторые дополнительные обозначения:


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

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

Можно переходить к реализации шейдера.

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

Итак, исходный код шейдера:(Вершинный шейдер не изменился)

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

float ks = 1;

float SpecularPower = 8;

float3 Eye;

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


float4 Specular = ks * pow(max(0, dot(eyeDirection, reflectedLight)), SpecularPower) * SpecularColor;


return Ambient + Diffuse + Specular;

}

В Game1 нужно задать значения параметров шейдера:

float specularPower = 8;


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

}


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

}



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

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

}

}

}

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


float4 Specular = ks * pow(max(0, dot(eyeDirection, reflectedLight)), 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();

}

}

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

Оставьте комментарий