Освещение

Освещение

В рамках этой и следующих статей мы будем рассматривать реализации различных моделей освещения с использованием XNA Framework и шейдеров HLSL.

В данной главе мы рассмотрим основые теоретические аспекты освещения в компьютерной графике.


В интернете можно найти достаточно много материалов по моделям освещения. Лично мне нравится следующая статья (http://steps3d.narod.ru/tutorials/lighting-tutorial.html) даже несмотря на то, что в ней содержится слишком мало теоретического материала. Зато ней можно ознакомиться с результатами работы каждого из методов.

Перед тем как переходить к реализации рассмотрим несколько базовых вопросов освещения в трехмерной графике.

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

    Можно условно разделить все объекты на те, которые излучают свет (солнце, лампочка) и на те, которые отражают свет (в принципе, все остальные объекты, их большинство) – именно такие объекты мы и будем рассматривать дальше.

    Если объект по каким-то причинам не отражает свет (например, он находится в комнате, в которую не попадают лучи света), мы не сможет его увидеть. Точно также если какая-то часть объекта не отражает свет, мы не сможет увидеть и ее.

  2. Какие пареметры влияют на освещенность объекта? Под этим вопросом имеется в виду примерно следующее: почему разные объекты по-разному освещены даже при одинаковых условиях освещения (например, находятся на одинаковом расстоянии от лампы), почему разные объекты имеют разный цвет? Можно выделить две группы параметров:
  • Параметры источники света: например, цвет. Сужествует множество различных искусственных источников света, имеющих различные цвета.
  • Параметры объекта (материал объекта): Легко заметить, что золотой слиток и, аналогичный по форме, размеру и т.д. кусок пластилина освещаются совершенно по-разному. Материал объекта очень сильно влияет на его освещенность.
  1. Как определяется освещенность объекта? Вообще это вопрос физики. Легко заметить, что освещенность каждой точки объекта зависит от следующих параметров: направления нормали в точке относительно направления освещения (направление хода лучей света), позиция наблюдателя (особенно сильно это заметно на металлических поверхностях, которые отражают больше света), удаленность от источника света и т.д. Хотя на самом деле все гораздо сложнее, а в рамках современных систем компьютерной графики делается множетсво упрощений для получения высокой производительности.

    В компьютерной графике обычно считается, что освещенность каждой точки складывается из трех компонент: фоновой (ambient), рассеянной (diffuse, диффузной) и зеркальной (specular).

    Картинка содрана со статьи, которая мне также очень нравится (http://compgraphics.info/3D/lighting/phong_reflection_model.php)


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

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

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

    Еще одна картинка:


    Введем некоторые основные обозначения:


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

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

Как мы уже поняли, освещенность объекта зависит от направления нормали. Определимся с тем как вычислять нормаль в каждой точке. Изначально нам известно направление нормали в каждой вершине.

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

Опять позаимствую немного теории из отличной статьи:

Затенение по Гуро (Gouraud shading)

В этой модели освещение не усредняется для грани, а линейно интерполируется между вершинами, поэтому для данной модели важно то, что нормали задаются в вершинах. Этот алгоритм позволяет получить гораздо более визуально-приятное изображение, чем при использовании алгоритма плоского затенения.

Сфера с затенением по Гуро, около 2000 треугольников

Сфера с затенением по Гуро, около 32000 треугольников

С другой стороны, вычислительная стоимость затенения по Гуро остается приемлемой, т.к. дорогостоящий расчёт освещения по-прежнему осуществляется в вершинах, а линейную интерполяцию можно хорошо оптимизировать. К сожалению, модель затенения по Гуро не безупречна. Блики на освещаемой поверхности при невысоком уровне детализации будут смазаны или могут вовсе «потеряться».

Смазанный блик в модели затенения Гуро

Чёткий блик

Затенение по Фонгу (Phong shading)

В этой модели между вершинами интерполируется не цвет, а нормаль. Цвет, в свою очередь, рассчитывается для каждого пикселя в отдельности. При использовании затенения по Фонгу изображение получается гораздо более качественным, чем при использовании предыдущих техник, и исчезает проблема с бликами. Но данный алгоритм требует гораздо больше вычислительных ресурсов. К примеру, при использовании модели Фонга для визуализации сцены на экране с разрешением 1024×768, понадобится рассчитать освещение для более чем 700 000 точек.

Сфера с затенением по Фонгу, около 2000 треугольников

Сфера с затенением по Фонгу, около 32000 треугольников

[http://www.compgraphics.info/3D/lighting/shading_model.php]

Резюмирая, можно заметить, что освещенность в точки можно вычислить в верщинном или пиксельном шейдере. В зависимости от этого получается немного разный результат. Конечно, лучше использовать попиксельное освещение, однако в таком случае значительно пострадает производительность.

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

Стандартный класс BasicEffect реализует обе модели закрашивания, попробуем сравнить их на примере. Для начала включим в нашу модель освещения бликовую компоненту:

basicEffect.DirectionalLight0.SpecularColor = new
Vector3(1, 1, 1);

basicEffect.SpecularPower = 24;

Получим следующее изображение:


Для того чтобы включить попиксельный режим освещения установим:

basicEffect.PreferPerPixelLighting = true;


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

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;


BasicEffect basicEffect;


TeapotPrimitive teapot;


FreeCamera camera;


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

Components.Add(light);

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

Components.Add(camera);

basicEffect = new
BasicEffect(GraphicsDevice, null);

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

}


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


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


Matrix view = camera.View;


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


// TODO: Add your drawing code here

basicEffect.World = Matrix.Identity;

basicEffect.View = view;

basicEffect.Projection = proj;

basicEffect.AmbientLightColor = new
Vector3(0.1f, 0.1f, 0.1f);

basicEffect.DirectionalLight0.Direction = Vector3.Zero-light.Position;

basicEffect.DirectionalLight0.DiffuseColor = new
Vector3(0.4f, 0, 0.2f);

basicEffect.LightingEnabled = true;

basicEffect.DirectionalLight0.Enabled = true;

basicEffect.DirectionalLight0.SpecularColor = new
Vector3(1, 1, 1);

basicEffect.SpecularPower = 24;

basicEffect.PreferPerPixelLighting = true;


for (int i = -2; i < 3; i++)

{


for (int j = -2; j < 3; j++)

{


for (int k = -2; k < 3; k++)

{


Vector3 pos = new
Vector3(i,j,k) * 2;

basicEffect.World = Matrix.CreateTranslation(pos);

basicEffect.DirectionalLight0.Direction = pos — light.Position;

teapot.Draw(basicEffect);

}

}

}

light.Draw(view, proj);


base.Draw(gameTime);

}

}

}

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

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

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

Логотип WordPress.com

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

Фотография Twitter

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

Фотография Facebook

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

Google+ photo

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

Connecting to %s