Рендеринг в расширенном динамическом диапазоне в XNA.

( «High Dynamic Range Rendering in XNA» – автор: Mahdi Khodadadi Fard, 18 October 2008 )


Что такое расширенный динамический диапазон?

Рендеринг в расширенном динамическом диапазоне (HDR rendering) — это рендеринг трехмерных сцен компьютерной графики при использовании вычислений освещения, сделанных в большем динамическом диапазоне. Видеоигры и компьютерная анимация становятся более реалистичными по сравнению с предыдущей моделью распространения света (называемой «низким динамическим диапазоном освещения» или «стандартным освещением»). Расширение динамического диапазона облегчает использование цветов вне стандартного диапазона, позволяя производить более реалистичный рендеринг типичной трехмерной сцены.

Если вы когда-нибудь проезжали тёмный туннель на автомобиле, а затем с другой стороны появлялся яркий солнечный свет, временно ослепляющий из-за разницы в световых уровнях, возникало перенасыщение, — это один из примеров, возможный с HDR. Другой пример — слепящие блики на метале.

Введение.

Динамический диапазон – это отношение наибольшего значения света к наименьшему. Текущие 8 и 16 битные целочисленные форматы используют компоненты со значениеями в диапазоне [0, 1], что не позволяет использовать значения более, чем 1, извесные как значения «сверхдиапазона». Изображение ниже илюстрирует работу расширеного динамического диапазона:

Прорендерите сцену в RenderTarget2D. Так как поверхность цели – это 4-канальный буфер со значениями с плавающей запятой, то возращаемые значения могут быть больше, чем 1.0 пиксельный шейдер, когда сцена прорендерится.

Вспомогательные классы.

Прежде, чем начинать работы с рендерингом в расширеном диапазоне, необходимо ввести вспомогательный класс, облегчающий работу с эффектами: PostProcessEffect — это базовый класс для всех классов в этой статье, работающих с 2D текстурами. Вот его реализация:

public abstract
class PostProcessEffect

{

private Quadrangle _Quadrangle = null;

internal GraphicsDevice _GraphicsDevice = null;

protected GraphicsDevice GraphicsDevice

{

get

{

return _GraphicsDevice;

}

}

protected PostProcessEffect(GraphicsDevice graphicsDevice)

{

_GraphicsDevice = graphicsDevice;

_Quadrangle = Quadrangle.Find(graphicsDevice);

}

public
abstract
void PostProcess(Texture2D sourceTexture,

RenderTarget2D result);

protected
void GetTargetDimensions(RenderTarget2D target,

out Vector2 dimentions)

{

if (target == null)

{

dimentions.X =

_GraphicsDevice.PresentationParameters.BackBufferWidth;

dimentions.Y =

_GraphicsDevice.PresentationParameters.BackBufferHeight;

}

else

{

dimentions.X = target.Width;

dimentions.Y = target.Height;

}

}

protected
void DrawQuad(Effect effect)

{

_Quadrangle.Draw(effect);

}

}

Измерение яркости.

Для начала, создайте последовательность для объекта рендеринга для измерения яркости текстур сцены.

Создайте последовательность для объекта рендеринга, где последний элемент имеет размер пикселя 1х1 и каждый последующий в три раза больше предыдущего: второй – 3х3, третий – 9х9 и так далее, пока ширина и высота последовательности для объекта меньше, чем размер текстур сцены. Для каждого элемента последовательности сохранается его средний уровень яркости, как число с плавающей запятой в красном канале для более низкого уровня и в зелёном канале для более высокого уровня. Таким образом, формат поверхности для всех прорендеренных целей будет HalfVector2, потому что этот формат будет содержать два 16-разрядных числа с плавающей запятой для красного и зеленого. Вот код для составления последовательности:

public Luminance(int width, int height, [...]) 
{ 
 [... ] 
 int chainLength = 1; 
 //width and height are source texture dimensions. 
 int startSize = (int)MathHelper.Min(width / 4, height / 4); 
 int size = 1; 
 for (size = 1; size < startSize; size *= 3) 
 { 
 chainLength++; 
 } 
 chainLength--; 
 luminanceChain = new RenderTarget2D[chainLength]; 
 //size /= 3; 
 for (int i = 0; i < chainLength; i++) 
 { 
 luminanceChain[i] = new RenderTarget2D(graphicsDevice, size, 
 size, 1, SurfaceFormat.HalfVector2); 
 size /= 3; 
 } 
}

Скопируйте данные текстуры со сцены в первую последовательность яркости и для каждого пикселя в текстуре сцены выберите 4 соседних пикселя. Для каждого из них, для их значения яркости, вычислите экспоненту из логарифма скалярного произведения RGB каждого пикселя с константой сигнала яркости. После найдите средний и максимальный сигнал яркости для этих 4 пикселей и сохраните их в красном цвете и зелёном каналах соответственно. Константа сигнала яркости равна <0.299, 0.587, 0.114>:

float2 SourceDimensions = 1; 
float2 TargetDimensions; 
static const float Offsets2x2[2] = {-0.5f, 0.5f}; 
static const float LUMINANCE = float3(0.299f, 0.587f, 0.114f); 
struct VS_OUTPUT 
{ 
 float4 Position : POSITION; 
 float2 TexCoord : TEXCOORD0; 
}; 
VS_OUTPUT Common_VS(float4 Position : POSITION, 
 float2 TexCoord : TEXCOORD0) 
{ 
 VS_OUTPUT OUT; 
 OUT.Position = Position; 
 OUT.TexCoord = TexCoord + (0.5f / TargetDimensions); 
 return OUT; 
} 
texture2D SourceTexture; 
sampler2D SourceTextureSampler = sampler_state 
{ 
 Texture = <SourceTexture>; 
 MinFilter = POINT; 
 MagFilter = POINT; 
 MipFilter = POINT; 
 MaxAnisotropy = 1; 
 AddressU = CLAMP; 
 AddressV = CLAMP; 
}; 
float4 Luminance2x2_PS(float2 texCoord : TEXCOORD0) : COLOR0 
{ 
 float average = 0.0f; 
 float maximum = -1e20; 
 float4 color = 0.0f; 
 for (int x = 0; x < 2; x++) 
 { 
 for (int y = 0; y < 2; y++) 
 { 
 float2 vOffset = float2(Offsets2x2[x], Offsets2x2[y]) 
 / SourceDimensions; 
 color = tex2D(SourceTextureSampler, texCoord + vOffset); 
 float GreyValue = dot(color.rgb, LUMINANCE); 
 maximum = max(maximum, GreyValue); 
 average += log(1e-5 + GreyValue) / 4.0f; 
 } 
 } 
 average = exp(average); 
 return float4(average, maximum, 0.0f, 1.0f); 
} 
technique Luminance 
{ 
 pass p0 
 { 
 VertexShader = compile vs_1_1 Common_VS(); 
 PixelShader = compile vs_2_0 Luminance2x2_PS(); 
 ZEnable = false; 
 ZWriteEnable = false; 
 AlphaBlendEnable = false; 
 AlphaTestEnable = false; 
 StencilEnable = false; 
 } 
}

Для последующих элементов в последовательности сигнала яркости найдите средний и максимальный сигнал яркости для этих 4 пикселей и сохраните их в красном канале и зелёном каналах для более низкого и высокого элементов последовательности соответственно. Так как каждый элемент в цепочке в 3 раза больше, чем его более низкий элемент последовательности, выберите сетку значений пикселей 3×3 для каждого пикселя и найдите среднее и максимальное значения среди этих 9 пикселей:

float2 SourceDimensions = 1; 
float2 TargetDimensions; 
static const float Offsets3x3[3] = {-0.5f, 0, 0.5f}; 
struct VS_OUTPUT 
{ 
 float4 Position : POSITION; 
 float2 TexCoord : TEXCOORD0; 
}; 
VS_OUTPUT Common_VS(float4 Position : POSITION, float2 TexCoord : TEXCOORD0) 
{ 
 VS_OUTPUT OUT; 
 OUT.Position = Position; 
 OUT.TexCoord = TexCoord + (0.5f / TargetDimensions); 
 return OUT; 
} 
texture2D SourceTexture; 
sampler2D SourceTextureSampler = sampler_state 
{ 
 Texture = <SourceTexture>; 
 MinFilter = POINT; 
 MagFilter = POINT; 
 MipFilter = POINT; 
 MaxAnisotropy = 1; 
 AddressU = CLAMP; 
 AddressV = CLAMP; 
}; 
float4 Luminance3x3_PS(float2 texCoord : TEXCOORD0) : COLOR0 
{ 
 float average = 0.0f; 
 float maximum = -1e20; 
 float4 color = 0.0f; 
 for (int x = 0; x < 3; x++) 
 { 
 for (int y = 0; y < 3; y++) 
 { 
 float2 vOffset = float2(Offsets3x3[x], Offsets3x3[y]) 
 / SourceDimensions; 
 color = tex2D(SourceTextureSampler, texCoord + vOffset); 
 average += color.r; 
 maximum = max( maximum, color.g ); 
 } 
 } 
 average /= 9.0f; 
 return float4( average, maximum, 0.0f, 1.0f ); 
} 
technique Luminance 
{ 
 pass p0 
 { 
 VertexShader = compile vs_1_1 Common_VS(); 
 PixelShader = compile ps_2_0 Luminance3x3_PS(); 
 ZEnable = false; 
 ZWriteEnable = false; 
 AlphaBlendEnable = false; 
 AlphaTestEnable = false; 
 StencilEnable = false; 
 } 
}

Здесь два предыдущих кода объединены для более легкого использования:

float2 SourceDimensions = 1; 
float2 TargetDimensions; 
static const float Offsets2x2[2] = {-0.5f, 0.5f}; 
static const float Offsets3x3[3] = {-0.5f, 0, 0.5f}; 
static const float LUMINANCE = float3(0.299f, 0.587f, 0.114f); 
struct VS_OUTPUT 
{ 
 float4 Position : POSITION; 
 float2 TexCoord : TEXCOORD0; 
}; 
VS_OUTPUT Common_VS(float4 Position : POSITION, float2 TexCoord : TEXCOORD0) 
{ 
 VS_OUTPUT OUT; 
 OUT.Position = Position; 
 OUT.TexCoord = TexCoord + (0.5f / TargetDimensions); 
 return OUT; 
} 
texture2D SourceTexture; 
sampler2D SourceTextureSampler = sampler_state 
{ 
 Texture = <SourceTexture>; 
 MinFilter = POINT; 
 MagFilter = POINT; 
 MipFilter = POINT; 
 MaxAnisotropy = 1; 
 AddressU = CLAMP; 
 AddressV = CLAMP; 
}; 
float4 Luminance2x2_PS(float2 texCoord : TEXCOORD0) : COLOR0 
{ 
 float average = 0.0f; 
 float maximum = -1e20; 
 float4 color = 0.0f; 
 for (int x = 0; x < 2; x++) 
 { 
 for (int y = 0; y < 2; y++) 
 { 
 float2 vOffset = float2(Offsets2x2[x], Offsets2x2[y]) 
 / SourceDimensions; 
 color = tex2D(SourceTextureSampler, texCoord + vOffset); 
 float GreyValue = dot(color.rgb, LUMINANCE); 
 maximum = max(maximum, GreyValue); 
 average += log(1e-5 + GreyValue) / 4.0f; 
 } 
 } 
 average = exp(average); 
 return float4(average, maximum, 0.0f, 1.0f); 
} 
float4 Luminance3x3_PS(float2 texCoord : TEXCOORD0) : COLOR0 
{ 
 float average = 0.0f; 
 float maximum = -1e20; 
 float4 color = 0.0f; 
 for (int x = 0; x < 3; x++) 
 { 
 for (int y = 0; y < 3; y++) 
 { 
 float2 vOffset = float2(Offsets3x3[x], Offsets3x3[y]) 
 / SourceDimensions; 
 color = tex2D(SourceTextureSampler, texCoord + vOffset); 
 average += color.r; 
 maximum = max( maximum, color.g ); 
 } 
 } 
 average /= 9.0f; 
 return float4( average, maximum, 0.0f, 1.0f ); 
} 
int ShaderIndex = 0; 
PixelShader PS[] = 
{ 
 compile ps_2_0 Luminance2x2_PS(), 
 compile ps_2_0 Luminance3x3_PS() 
}; 
technique Luminance 
{ 
 pass p0 
 { 
 VertexShader = compile vs_1_1 Common_VS(); 
 PixelShader = (PS[ShaderIndex]); 
 ZEnable = false; 
 ZWriteEnable = false; 
 AlphaBlendEnable = false; 
 AlphaTestEnable = false; 
 StencilEnable = false; 
 } 
}

Исходная текстура сцены:

Таблица, отображающая прохождение текстуры по последовательности сигнала яркости для среднего и максимального уровня.
Средний уровень(Красный канал)

Максимальный уровень(Зелёный канал)

public sealed class Luminance : PostProcessEffect, IDisposable 
{ 
 private Effect _Effect = null; 
 private RenderTarget2D[] luminanceChain = null; 
 private EffectParameter Parameter_TargetDimensions = null; 
 private EffectParameter Parameter_SourceDimensions = null; 
 private EffectParameter Parameter_SourceTexture = null; 
 private EffectParameter Parameter_ShaderIndex = null; 
 private Vector2 TargetDimensions; 
 private Vector2 SourceDimensions; 
 public Luminance(GraphicsDevice graphicsDevice, 
 ContentManager content, int width, int height) 
 : base(graphicsDevice) 
 { 
 _Effect = content.Load<Effect>("Effects/Luminance"); 
 Parameter_TargetDimensions = 
 _Effect.Parameters["TargetDimensions"]; 
 Parameter_SourceDimensions = 
 _Effect.Parameters["SourceDimensions"]; 
 Parameter_SourceTexture = 
 _Effect.Parameters["SourceTexture"]; 
 Parameter_ShaderIndex = 
 _Effect.Parameters["ShaderIndex"]; 
 int chainLength = 1; 
 //width and height are source texture dimensions. 
 int startSize = (int)MathHelper.Min(width / 4, height / 4); 
 int size = 1; 
 for (size = 1; size < startSize; size *= 3) 
 { 
 chainLength++; 
 } 
 chainLength--; 
 luminanceChain = new RenderTarget2D[chainLength]; 
 //size /= 3; 
 for (int i = 0; i < chainLength; i++) 
 { 
 luminanceChain[i] = new RenderTarget2D(graphicsDevice, size, 
 size, 1, SurfaceFormat.HalfVector2); 
 size /= 3; 
 } 
 } 
 private int ShaderIndex 
 { 
 get 
 { 
 return Parameter_ShaderIndex.GetValueInt32(); 
 } 
 set 
 { 
 Parameter_ShaderIndex.SetValue(value); 
 } 
 } 
 private void SetSourceTexture(Texture2D value) 
 { 
 Parameter_SourceTexture.SetValue(value); 
 if (value == null) 
 { 
 SourceDimensions.X = 1; 
 SourceDimensions.Y = 1; 
 } 
 else 
 { 
 SourceDimensions.X = value.Width; 
 SourceDimensions.Y = value.Height; 
 } 
 Parameter_SourceDimensions.SetValue(SourceDimensions); 
 } 
 public override void PostProcess(Texture2D sourceTexture, 
 RenderTarget2D result) 
 { 
 GetTargetDimensions(result, out TargetDimensions); 
 Parameter_TargetDimensions.SetValue(TargetDimensions); 
 ShaderIndex = 0; 
 _GraphicsDevice.SetRenderTarget(0, luminanceChain[0]); 
 GetTargetDimensions(luminanceChain[0], out TargetDimensions); 
 Parameter_TargetDimensions.SetValue(TargetDimensions); 
 SetSourceTexture(sourceTexture); 
 _GraphicsDevice.Clear(Color.Black); 
 DrawQuad(_Effect); 
 _GraphicsDevice.SetRenderTarget(0, null); 
 ShaderIndex = 1; 
 for (int i = 1; i < luminanceChain.Length; i++) 
 { 
 GetTargetDimensions(luminanceChain[i], out TargetDimensions); 
 Parameter_TargetDimensions.SetValue(TargetDimensions); 
 _GraphicsDevice.SetRenderTarget(0, luminanceChain[i]); 
 SetSourceTexture(luminanceChain[i - 1].GetTexture()); 
 _GraphicsDevice.Clear(Color.Black); 
 DrawQuad(_Effect); 
 _GraphicsDevice.SetRenderTarget(0, null); 
 } 
 _GraphicsDevice.SetRenderTarget(0, result); 
 GetTargetDimensions(result, out TargetDimensions); 
 Parameter_TargetDimensions.SetValue(TargetDimensions); 
 int last = luminanceChain.Length - 1; 
 SetSourceTexture(luminanceChain[last].GetTexture()); 
 _GraphicsDevice.Clear(Color.Black); 
 DrawQuad(_Effect); 
 _GraphicsDevice.SetRenderTarget(0, null); 
 } 
 void IDisposable.Dispose() 
 { 
 for (int i = 0; i < luminanceChain.Length; i++) 
 { 
 luminanceChain[i].Dispose(); 
 luminanceChain[i] = null; 
 } 
 luminanceChain = null; 
 } 
}
Реклама
Запись опубликована в рубрике Uncategorized. Добавьте в закладки постоянную ссылку.

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

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

Логотип WordPress.com

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

Фотография Twitter

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

Фотография Facebook

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

Google+ photo

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

Connecting to %s