суббота, 11 февраля 2012 г.

XNA: динамическое создание шейдеров

В четвёртой версии XNA Microsoft предлагает создание шейдеров только на стадии компиляции, путём помещения fx-файлов в директорию ресурсов и последующей загрузки уже скомпилированного шейдера в эффекте из ресурсов.
var effect = Content.Load<Effect>( Path.Combine(Directories.ContentDirectory, "ShaderFileName"));

Если есть потребность создания шейдеров в рантайме придётся прибегнуть к громоздкой и медленной процедуре. Поэтому лучше использовать обычный способ загрузки шейдеров из ресурсов.
Далее описание динамического создания шейдера.

Для получения эффекта с шейдером его конструктору надо передать код компилированного эффекта.
var effect = new Effect( graphicsDevice, compiledEffect.GetEffectCode() );

Компилированный эффект получается в результате работы эффект процессора, которому нужно передать исходник эффекта и контекст процессора.
var compiledEffect = processor.Process( effectSource, new EffectCompilerProcessorContext() );

Эффект процессор получить легко - это просто экземпляр класса EffectProcessor.
var processor = new EffectProcessor();
Исходник эффекта уже несколько сложнее. Именно тут нужно смоделировать загрузку исходного кода шейдера из ресурса.
var effectSource = new EffectContent {
  Identity = new ContentIdentity { SourceFilename = "myshader.fx" },
  EffectCode = effectBody
};

myshader.fx - можно использовать любое имя, наличие этого файла не требуется. Видимо не стоит использовать имя уже существующего файла, последствия не проверял.
effectBody - собственно HLSL-код шейдера.
Например конвертация изображения в оттенки серого:
effectBody = @"
uniform extern texture ScreenTexture;

sampler screen = sampler_state {
  Texture = <ScreenTexture>;
};

float4 PixelShaderFunction(float2 inCoord: TEXCOORD0) : COLOR {
  float4 color = tex2D(screen, inCoord);
  color.rgb = (color.r+color.g+color.b)/3.0f;
  return color;
}

technique {
  pass P0 {
      PixelShader = compile ps_2_0 PixelShaderFunction();
  }

}";

И самый громоздкий этап создание контекста эффект-процессора.
Для этого создаём свой класс наследованный от ContentProcessorContext.
class EffectCompilerProcessorContext : ContentProcessorContext {
  public override TargetPlatform TargetPlatform { get { return TargetPlatform.Windows; } }
  public override GraphicsProfile TargetProfile { get { return GraphicsProfile.HiDef; } }
  public override string BuildConfiguration { get { return string.Empty; } }
  public override string IntermediateDirectory { get { return string.Empty; } }
  public override string OutputDirectory { get { return string.Empty; } }
  public override string OutputFilename { get { return string.Empty; } }

  public override OpaqueDataDictionary Parameters { get { return parameters; } }
  readonly OpaqueDataDictionary parameters = new OpaqueDataDictionary();

  public override ContentBuildLogger Logger { get { return logger; } }
  readonly ContentBuildLogger logger = new EffectCompilerLogger();

  public override void AddDependency( string filename ) { }
  public override void AddOutputFile( string filename ) { }

  public override T_OUTPUT Convert<T_INPUT, T_OUTPUT>( T_INPUT input, string processor_name, OpaqueDataDictionary processor_parameters ) { throw new NotImplementedException(); }
  public override T_OUTPUT BuildAndLoadAsset<T_INPUT, T_OUTPUT>( ExternalReference<T_INPUT> source_asset, string processor_name, OpaqueDataDictionary processor_parameters, string importer_name ) { throw new NotImplementedException(); }
  public override ExternalReference<T_OUTPUT> BuildAsset<T_INPUT, T_OUTPUT>( ExternalReference<T_INPUT> source_asset, string processor_name, OpaqueDataDictionary processor_parameters, string importer_name, string asset_name ) { throw new NotImplementedException(); }
}

И для него понадобится ещё один класс, логгер компилятора наследованный от ContentBuildLogger.
class EffectCompilerLogger : ContentBuildLogger {
  public override void LogMessage( string message, params object[] message_args ) { }
  public override void LogImportantMessage( string message, params object[] message_args ) { }
  public override void LogWarning( string help_link, ContentIdentity content_identity, string message, params object[] message_args ) { }
}

Использование полученного эффекта такое же как и обычного эффекта из ресурсов.
Например при старте отображения спрайтов:
spriteBatch.Begin( 0, null, null, null, null, effect  );

Источник.

Комментариев нет:

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