martes, 29 de diciembre de 2009

FUERA DE OFICINA

Hola gente, estaré "fuera de oficina" desde hoy por, más o menos, una semana.

Para aquellos que usualmente leen este blog, gracias por su tiempo y comentarios.

No olviden chequear la nueva “Serie de Charlas de Desarrolladores” –por miembros del Equipo de Producto de XNA Game Studio, a partir de Enero de 2010!!

Nos vemos el próximo año ;)

Felíces fiestas y salúd!
~Pete

> Vínculo a la versión en inglés.

martes, 8 de diciembre de 2009

VIDEOS DEL PDC09 DISPONIBLES

Los videos que muestran todas las presentaciones del PDC09 están ahora disponibles para o bien verlos online o bien descargarlos.

En particular, para aquellos interesados en utilizar DX11 para desarrollar con .NET aplicaciones & juegos para Windows 7, miren estos videos:

Que los disfruten,
~Pete

> Vínculo a la versión en inglés.

lunes, 30 de noviembre de 2009

RAIMI QUIERE UN “ATAQUE DE PANICO”

Gente, ¿recuerdan cómo Montevideo era atacado por una horda de robots gigantes y cazas muy avanzados tecnológicamente?

Bien, de acuerdo a muchas noticias que se han publicado recientemente al rededor del globo, Sam Raimi ha contactado a los autores de este corto sorprendente a efectos de convertirlo en una película completa.

Bien hecho, muchachos! Realmente lo merecen …

Salúd, brindemos!
~Pete

> Vínculo a la versión en inglés.

miércoles, 25 de noviembre de 2009

CUIDADO CON LA XBOX 360 QUE COMPRES!

Ha pasado un poco de tiempo desde my última nota; buenol … hay una razón maravillosa para ello: la semana pasada nación my primer hijo! Así que, no tuve tiempo libre para bloguear.

Ok, ahora entremos en tema …

En las pasadas semanas deben haber escuchado y o presenciado que una nueva actualización para la XBox 360 fue recientemente liberada, trayendo consigo una cantidad de nuevas características emocionantes, como ser soporte para Twitter, Facebook y Last.fm.

También deben haber escucahdo que Microsoft suspendió unidades de memoria no oficiales.

Bien, ello no es de lo que voy a bloguear hoy!

Hay una cosa más de la que deben haber oido (y espero que no la hayan sufrido Uds. mismos): MSFT ha suspendido una plétora de consolas (modificadas) (el rumor es que el número de suspensiones se acerca a un millón de consolas).

La razón? Simple: combatir la piratería.

Ergo, si –por la razón que fuere- han pirateado el dispositivo de disco, instalado software no autorizado, creado un versión modificada de la consola, y –talvez en algúnos casos- repararla Uds. mismos del RROD con una solución casera, entonces existen posibilidades de que caigan dentro de ese número desafortunado!

Qué significa una 360 suspendida? En términos simples: en adelante, el servicio XBox Live no estará disponible para dicha consola. El dueño puede aún jugar fuera de línea (“offline”), pero olvídense de juegos Arcade e Indie.

Y puesto que el gamertag/cuenta no están suspendidos, sólo la consola, el jugador puede comprar una nueva consola para retomar acceso a esos canales. Lo que me lleva al tópico principal de esta nota:

MUCHO CUIDADO CON LA CONSOLA XBOX 360 QUE COMPRES!!!

Como resultado del proceso de suspensiones masivas, muchos dueños de 360s suspendidas están intentando vender dichas consolas a través de sitios populares de remate en línea (“online”), sosteniendo que nunca experimentaron problemas técnicos, ofreciendo una gran cantidad de juegos (pirateados), e incluso sin mencionar en primer lugar que que se trata de una consola “modeada”.

Siempre recuerden: la suspensión es sobre la consola en sí, así que por esas casualidades compran un 360 suspendida, no importa que tengan una cuenta o gamertag válido, ya que la suspensión sobre la consola no desaparecerá.

Por lo que, tengan cuidado con la 360 que elijan: verifiquen que sea nueva de paquete (sin abrir) antes de cerrar un trato.

Y no instalen software piratead: es ilegal, y eventualmente –como lo demuestra esta ola de suspensiones, se torna más caro que hacer las cosas bien desde el principio!

Vayan a lo seguro,
~Pete

> Vínculo a la versión en inglés.

viernes, 6 de noviembre de 2009

MONTEVIDEO INVADIDO POR ROBOTS GIGANTES

Navegando en la red encontré de casualidad un corto con CG muy impresionante, donda la ciudad de Montevideo, capital de Uruguay (donde vivo), es atacada repentinamente por un ejército de robots gigantes y un escuadrón muy avabzado de cazas espaciales.

El filme se titula “Atáque de Pánico! 2009” y pueden encontrar más información sobre el mismo aqui.

Felicitaciones a los productores de esta grandiosa realización!

Que la disfruten,
~Pete

[Si me preguntan, el título es algo desacertado. Yo habría elegido uno un tanto diferente para una producción tan genial. Gente, ¿qué opinan Uds.?]

> Vínculo a la versión en inglés.

REGLAS DE MULTIPLES LENGUAJES EN XBLIG

Recientemente, ha habido un poco de revuelo con respecto al uso de (símbolos de) lenguajes no soportados en los juegos “indie” de Xbox Live (en particular, símbolos del Mah-jong tradicional).

Tal discusión hizo que el Equipo de XNA publicara los siguientes dos mensajes para aclarar las cosas:

Lean ambos mensajes con atención y si tienen aún preguntas, o naveguen en los foros para obtener las respuestas.

Hasta la próxima,
~Pete

 

> Vínculo a la versión en inglés.

sábado, 24 de octubre de 2009

NUEVAS GANGAS EN XBLIG!

Desde hoy en más, los únicos precios disponibles para XBox Live Indie Games serán 80, 240 y 400 puntos (eso es en dólares americanos: uno, tres y cinco, respetivamente).

Cómo resultado de este cambio obligatorio, todos los juegos que se ofrecían por 200 puntos ahora se están vendiendo al precio de oferta de tan sólo 80 puntos … 1 dólar!

En Gamerbytes pueden encontrar información sobre algúnos de los juegos que se venden por $1 y/o pueden también visitar XBLA & XBLIG Ratings por una completa lista de los juegos ofrecidos a este precio.

Es hora de conseguir muchas gangas!!!

Que las disfruten!
~Pete

 

> Vínculo a la versión en inglés.

jueves, 22 de octubre de 2009

DOS NUEVOS EJEMPLOS EN XNA CCO

Ya deben estar al tanto de estas novedades, pero por las dudas les comento que dos grandiosos nuevos ejemplos están disponibles en el sitio web de XNA Creators Club:

Ahora bien, ¿qué hay de grandioso en ellos? Me alegro que hayan preguntado.

(I) Muestra de Extensión de Modelos Con Piel

El primer ejemplo muestra cómo extender el proyecto original de modelos con piel ("skinned models”) de forma que:

  • Puedan “mover” una parte del modelo con piel de manera independiente a la secuencia de la animación (como en este caso, un brazo y la cabeza),
  • Puedan posicionar objetos en relación a un hueso específico (en este caso, un bate), y
  • Puedan anexar una geometría de cobertura al modelo animado (en este caso, esferas).

El código es una gran fuente de conocimiento técnico para programadores respecto a la técnica de “pelaje” pero, debido a unos pocos problemas en el modelo con piel en sí, ellos también pueden aprender cuan engorrosos y puntillozos pueden llegar a ser los procesos de (no sólo modelado y desenvoltura sino también) de inclusión de un esqueleto, pelaje y animación al modelo 3D, tal que se vea ok en todas y cada una de las situaciones (esperadas) antes de salir al mercado.

Primera captura de pantalla:

Como pueden ver aquí, cuando muevan la cabeza uno de los vértices permanece (casi) inmóvil, lo cual significa que o bien no fue asignado al hueso del cuello o, si lo fue, entonces sus “proporciones” deberían reajustarse.

Segunda foto:

Parece que la parte del cinto marcada arriba con anaranjado se mueve junto con la cadera mientras que el resto del cinto se mueve con el torso. Y por ende, la animación de caminar, incluso si se está comportando como se espera, parece algo rara.

Es posible que, s¡endo la parte de programación el único/principal propósito del ejemplo en sí, ello no sea relevante para programadores, pero para independientes (“indies”) como yo que tienden a desarrollar todas las partes del juego “por sí mismos”, es un ejemplo fabuloso de algúnos de los dolores de cabeza que van a encontrar cuando traten con el lado artístico del tema.

(II) Muestra de Primitivos 3D

El segundo presenta un conjuento de clases que necesitarán para crear cubos, esferas, cilindros, roscas y la famosa tetera!

Estos primitivos se encuentran usualmente en los más famosos aplicaciones de modelado, entre otras, como 3D Studio Max o Maya.

Se pueden preguntar: ¿por qué la tetera se la considera un primitivo? Bueno, gracias al tipo de operaciones poderasas que se utilizan para crearla de manera procedural: calculos de curvas bezier! Buenísimo, realmente …

Una sugerencia para el cilindro:

En el caso que algunos de Uds. quieran crear los triángulos que conforman cada tapa del cilindro tal que se distribuyan uniformemente en torno a un vértice central, en vez de hacia un lado (como se ve arriba), entonces tendrán que reemplazar el siguiente método en la clase “CylinderPrimitive”:
/// <summary>
/// Helper method creates a triangle fan to close the ends of the cylinder.
/// </summary>
void CreateCap( int tessellation, float height, float radius, Vector3 normal )
{
  // Create cap indices.
  for ( int i = 0; i < tessellation - 2; i++ )
  {
    if ( normal.Y > 0 )
    {
      AddIndex( CurrentVertex );
      AddIndex( CurrentVertex + ( i + 1 ) % tessellation );
      AddIndex( CurrentVertex + ( i + 2 ) % tessellation );
    }
    else
    {
      AddIndex( CurrentVertex );
      AddIndex( CurrentVertex + ( i + 2 ) % tessellation );
      AddIndex( CurrentVertex + ( i + 1 ) % tessellation );
    }
  }
 
  // Create cap vertices.
  for ( int i = 0; i < tessellation; i++ )
  {
    Vector3 position = GetCircleVector( i, tessellation ) * radius +
                       normal * height;
 
    AddVertex( position, normal );
  }
}

De forma que, en cambio, se parezca a:

/// <summary>
/// Helper method that creates a cap laying out triangles around a centered vertex.
/// </summary>
void CreateCap( int tessellation, float height, float radius, Vector3 normal )
{
  // Add a vertex in the center of the cap.
  AddVertex( normal * height, normal );
 
  // Create cap indices, taking into account the centered vertex (that is why
  // we use "CurrentVertex - 1" as the first element of the corresponding index).
  for ( int i = 0; i < tessellation; i++ )
  {
    if ( normal.Y > 0 )
    {
      AddIndex( CurrentVertex - 1 );
      AddIndex( CurrentVertex + ( i + 1 ) % tessellation );
      AddIndex( CurrentVertex + ( i + 2 ) % tessellation );
    }
    else
    {
      AddIndex( CurrentVertex - 1 );
      AddIndex( CurrentVertex + ( i + 2 ) % tessellation );
      AddIndex( CurrentVertex + ( i + 1 ) % tessellation );
    }
  }
 
  // Create cap vertices.
  for ( int i = 0; i < tessellation; i++ )
  {
    Vector3 position = GetCircleVector( i, tessellation ) * radius +
                       normal * height;
 
    AddVertex( position, normal );
  }
}

Siendo el resultado:

Esto no es para nada crítico, pero por cuestiones de (una mejor) desenvoltura -e incluso de crear más “secciones” (anillos) sobre cada tapa, vía programación- 2 vértices centrales y unos pocos triángulos adicionales son útiles sin dañar a la performance.

A escribir código!
~Pete

 

> Vínculo a la versión en inglés.

jueves, 15 de octubre de 2009

VISTAZO A LA ACTUALIZACION DE LA PIZARRA DE LA 360

1UP ha publicado un video demostrando las características que vienen con la actualización de la pizarra de la consola XBox 360; esto es:

  • Twitter,
  • Facebook,
  • Last.fm, y
  • Zune Marketplace.

Pero eso no es todo! Major Nelson ha divulgado información de cómo inscribirse para obtener la previsualización de la actualización, a efectos de comenzar a disfrutar ahora mismo de las nuevas caracterísitcas.

Así que, tienes una Xbox 360? Y, el servicio de XBox Live está disponible en el país donde vives?

Si la respuesta a ambas preguntas es “sí”, entonces que diantres estás esperando?!!!

Que lo disfruten,
~Pete

 

> Vínculo a la versión en inglés.

miércoles, 14 de octubre de 2009

NUEVA CONSOLA XBOX PARA EL 2012?

Ultimamente, han habido rumores respecto al futuro próximo de la principal consola de juegos de video de MSFT.

Palabras como “XBox 720”, “X-Engine” y “Proyecto Phoenix” se han utilizado. Pero qué significan, con exactitud?

De acuerdo a este artículo, “Proyecto Phoenix” es el nombre que internamente MSFT le ha dado a lo que algúnas personas fuera de dicha Companía denominan “XBox 720”.

Ahora bien, un artículo más reciente establece que la nueva consola llegaría al mercado en el 2012, con una nueva tarjeta gráfica de ATI a efectos de mantener compatibilidad hacia atrás e impulsar mejoras de performance.

Respecto a esto último (performance), un nuevo motor “X” habría sido recientemente liberado, no sólo incluyendo un set de nuevas herramientas sino también “una forma completamente nueva de desarrollar para el sistema” –como dice este tercer arttículo).

No se cuán veraces son estas “novedades”, pero sin embargo es una lectura interesante.

Además, en mi más modesta opinión, hay mucho de las “360” aún por disfrutar, especialmente cuando se librere el Project Natal; no creen?

No cambien de canal,
~Pete

 

> Vínculo a la versión en inglés.

miércoles, 7 de octubre de 2009

EVENTO VIRTUAL: "LA NUEVA EFICIENCIA"

Hay un evento virtual, a desarrollarse hoy, muy intersante. Si hablas y/o entiendes español no puedes perdértelo!

Sigue leyendo …

Hoy, Microsoft presentará con expertos en vivo y demostraciones el nuevo concepto de eficiencia!

Algúnos de los tópicos tratados durante el evento cubren las siguientes tecnologías/productos:

  • Windows 7,
  • Windows Server 2008 R2,
  • Exchange Server 2010, y
  • Fore Front.

Así que, qué esperas? Sólo ve y regístrate para el evento cuanto antes:

http://www.lanuevaeficiencia.com/

Que lo disfrutes!
~Pete

 

> Vínculo a la versión en inglés.

viernes, 25 de septiembre de 2009

OBSERVANDO AL SOL ARDER - PARTE 2

En la parte 1 de la serie, introduje el motor de iluminación y renderizado llamado “Sunburn”, de Synapse Gaming.

Bien, ya salió la versión 1.2.4 del motor, trayendo consigo un impulso en la performance de la técnica de renderizado "hacia adelante".

Aquí encontrarán mis resultados más recientes para la demo de reflección/refracción (la cual utiliza la técnica antedicha):

1) Plataforma PC:

  • Min: 37 fps -> mirando a las tres orbes casi llegando al techo (cerca de las ventanas de arriba),
  • Max: 60 fps -> mirando a una de las ventanas de arriba, y
  • Promedio: 43 fps -> en general (a veces un poco más en los corredores, sin encarar hacia a las orbes).

2) Consola XBox 360:

  • Min: 28 fps -> mismo caso que para la plataforma PC,
  • Max: 54 fps -> idem,
  • Promedio: 32 fps -> idem.

Es importante notar que, en la demo, la escene es renderizada tres veces por cuadro: una vez para la imagen de reflexión, una vez para la imagen de refracción, y unavez para la salida final. Por qué? A efectos de permitir un comportamiento dinámico. Y qué significa? En vez de simular el efecto con fotos “estáticas”, lo que ven se actualiza y calcula en tiempo real; por lo que objetos en movimientos son captados por el proceso, refractados y reflejados.

Nuevamente, para ambas pruebas dibujé la imagen final sobre un back-buffer de alta resolución de 1280x720, pero esta vez también corrí las pruebas en la XBox 360 (ambas con una marca de tiempo variable).

Dados estos resultados, no puedo esperar a ver una versión con renderizado diferido en la XBox 360!

Ahora bien, cuán difícil es el uso de Sunburn? Averigüémoslo, bien?

I. El Código.

Continuemos utilizando la demo de “Refleccón/Refracción” para demostrar cómo se usa Sunburn. Por favor descarguen el proyecto de demostración de la sección de descargas de SG antes de seguir leyendo.

Cuando abran el proyecto en Visual Studio 2008, van a encontrar la estructura inicial usual: la clase "game" y la carpeta de contenido.

En lo que sigue, estaré explicando que incluyen, basado en el ejemplo, pero únicamente para el código relativo al motor en sí:

i. Declaraciones "Using"

Hay una buena cantidad de espacios de nombrea referenciar, pero para este ejemplo sólo concentraré mis comentarios en dos de ellas:

   1: using SynapseGaming.LightingSystem.Effects.Forward;
   2: ...
   3: using SynapseGaming.LightingSystem.Rendering.Forward;

En caso que decidan usar Renderizado Diferido, tendrán que cambiar “Forward” por “Deferred” aqui (y luego, como verán basados en el chequeo de sintaxis realizado por VS08, deberán modificar algúnas partes del código, de manera acorde).

ii. Campos

Los campos adicionales agregados a la clase "game", en comparación a la estándar, pueden separase en tres categorías: a) el sistema de iluminación, b) los miembros de la escena, y c) los miembros de la técnica.

a) basicamente, deben declarar aquí tanto los gestores de iluminación y renderizado, más los ayudantes que proverán datos específicos al sistema, como ser información del ambinente y preferencias de iluminación más detalle.

   1: LightingSystemManager lightingSystemManager;
   2: RenderManager renderManager;
   3: ...
   4: SceneState sceneState;
   5: SceneEnvironment environment;
   6: LightingSystemPreferences preferences;
   7: ...
   8: DetailPreference renderQuality = DetailPreference.Off;

b) junto con las mallas que conforman el "scenegraph", deben declarar todas las luces que se usarán en la escena más su respectivos “aparejos” (rigs); ahora bien, qué es un aparejo? Es un contenedor que guardará, organizará y ayudarña a comprartir las luces de la escena en dicha escena.

   1: ...
   2: LightRig lightRig;
   3: PointLight keyLight;
   4: PointLight fillLight;
   5: DirectionalLight sunLight;
   6: ...

c) principalmente, deben declarar los efectos de renderizado "hacia adelante" junto con los ayudantes de objetivo de renderizado (los últimos dan soporte para reflección, refracción y las usuales llamadas a la función de dibujo para renderizar en texturas).

   1: SasEffect orbEffect;
   2: SasEffect waterEffect;
   3: ...
   4: RenderTargetHelper refractionTarget;
   5: RenderTargetHelper waterReflectionTarget;

iii. Constructor

Llendo a los miembros de inicialización, deben instanciar la mayoría de los campos antedichos y establecer las preferencias de iluminación y detalle basados en la plataforma objetivo.

   1: // Load the user preferences (example - not required).
   2: preferences = new LightingSystemPreferences();
   3: #if !XBOX
   4: if (File.Exists(userPreferencesFile))
   5:     preferences.LoadFromFile(userPreferencesFile);
   6: else
   7: #endif
   8: {
   9:     preferences.EffectDetail = DetailPreference.High;
  10:     preferences.MaxAnisotropy = 1;
  11:     preferences.PostProcessingDetail = DetailPreference.High;
  12:     preferences.ShadowDetail = DetailPreference.Low;
  13:     preferences.ShadowQuality = 1.0f;
  14:     preferences.TextureQuality = DetailPreference.High;
  15:     preferences.TextureSampling = SamplingPreference.Anisotropic;
  16: }

Es interesante notar que, en caso de la plataforma PC, el ejemplo brinda un medio para seleccionar el nivel de detalle basado en el fabricante de la tarjeta gráfica y el número de modelo de la misma.

   1: // Pick the best performance options based on hardware.
   2:  VideoHardwareHelper hardware = new VideoHardwareHelper();
   3:  
   4:  if (hardware.Manufacturer == VideoHardwareHelper.VideoManufacturer.Nvidia)
   5:  {
   6:      if (hardware.ModelNumber >= 8800  hardware.ModelNumber < 1000)
   7:          renderQuality = DetailPreference.High;
   8:      else if (hardware.ModelNumber >= 7800)
   9:          renderQuality = DetailPreference.Medium;
  10:      else if (hardware.ModelNumber >= 6800)
  11:          renderQuality = DetailPreference.Low;
  12:  }
  13:  else if (hardware.Manufacturer == VideoHardwareHelper.VideoManufacturer.Ati)
  14:  {
  15:      if (hardware.ModelNumber >= 3800)
  16:          renderQuality = DetailPreference.High;
  17:      else if (hardware.ModelNumber >= 3400)
  18:          renderQuality = DetailPreference.Medium;
  19:      else if (hardware.ModelNumber >= 2600)
  20:          renderQuality = DetailPreference.Low;
  21:  }
  22:  
  23:  switch (renderQuality)
  24:  {
  25:      case DetailPreference.High:
  26:          reflectionRefractionTargetSize = 512;
  27:          reflectionRefractionTargetMultiSampleType = MultiSampleType.TwoSamples;
  28:          graphics.PreferMultiSampling = true;
  29:          break;
  30:      case DetailPreference.Medium:
  31:          reflectionRefractionTargetSize = 256;
  32:          reflectionRefractionTargetMultiSampleType = MultiSampleType.TwoSamples;
  33:          graphics.PreferMultiSampling = true;
  34:          break;
  35:      case DetailPreference.Low:
  36:          reflectionRefractionTargetSize = 128;
  37:          reflectionRefractionTargetMultiSampleType = MultiSampleType.TwoSamples;
  38:          graphics.PreferMultiSampling = false;
  39:          break;
  40:      case DetailPreference.Off:
  41:          reflectionRefractionTargetSize = 128;
  42:          reflectionRefractionTargetMultiSampleType = MultiSampleType.None;
  43:          graphics.PreferMultiSampling = false;
  44:          break;
  45:  }

Importante: puesto que el gestor de renderizado utiliza por defecto un tamaño de “página” de 2048 pixeles para mapeo de sombras, en caso de apuntar a la Xbox 360, este valor debe reducirse a 1024 pixeles para que la página encaje completamente dentro de la EDRAM de la 360, evitando así el llamado "mosaico predicado" (predicated tiling).

   1: ...
   2: renderManager.ShadowManager.PageSize = 1024;
   3: ...

iv. Cargando Contenido:

Primero, deben crear los ayudantes de objetivo de renderizado y aplicar las preferencias, en este caso, para renderizar los efectos de refracción y reflección, al ayudante que corresponda.

   1: // Create reflection / refraction targets.  Note the refraction target is using
   2: // the "Standard" type to avoid clipping as the map is used by all refractive
   3: // objects (not just one with a specific surface plane).  See the comments at the
   4: // top of the page for details on why this is done.
   5:  
   6: refractionTarget = new RenderTargetHelper(graphics, RenderTargetHelper.TargetType.Standard,
   7:     reflectionRefractionTargetSize, reflectionRefractionTargetSize, 1, SurfaceFormat.Color,
   8:     reflectionRefractionTargetMultiSampleType, 0, RenderTargetUsage.PlatformContents);
   9:  
  10: waterReflectionTarget = new RenderTargetHelper(graphics, RenderTargetHelper.TargetType.Reflection,
  11:     reflectionRefractionTargetSize, reflectionRefractionTargetSize, 1, SurfaceFormat.Color,
  12:     reflectionRefractionTargetMultiSampleType, 0, RenderTargetUsage.PlatformContents);
  13:  
  14:  
  15: // Setup the refraction and reflection preferences.  These preferences are
  16: // set to a lower quality than the main scene's rendering to increase performance
  17: // and because reflection / refraction distortions from the normal map will
  18: // hide the quality.
  19:  
  20: refractionPreferences = new LightingSystemPreferences();
  21: refractionPreferences.EffectDetail = DetailPreference.Low;
  22: refractionPreferences.MaxAnisotropy = 0;
  23: refractionPreferences.PostProcessingDetail = DetailPreference.Low;
  24: refractionPreferences.ShadowDetail = DetailPreference.Low;
  25: refractionPreferences.ShadowQuality = 0.25f;
  26: refractionPreferences.TextureSampling = SamplingPreference.Trilinear;
  27:  
  28: refractionTarget.ApplyPreferences(refractionPreferences);
  29: waterReflectionTarget.ApplyPreferences(refractionPreferences);

Luego deberán leer del disco los valores que especifican cómo configurar los efectos a fin de utilizarlos como materiales.

   1: // Load the custom materials / effects used by the additional reflection / refraction
   2: // rendering pass.  These materials both use the same FX file with different material options.
   3:  
   4: orbEffect = Content.Load<SasEffect>("Effects/Orb");
   5: waterEffect = Content.Load<SasEffect>("Effects/Water");

El contenido de los archivos “.mat” originales es cómo sigue:

//-----------------------------------------------
// Synapse Gaming - SunBurn Lighting System
// Exported from the SunBurn material editor
//-----------------------------------------------
 
Locale: en-US
 
AffectsRenderStates: False
BlendColor: 0.6 0.6 0.4
BlendColorAmount: 0
BumpAmount: 0.017
BumpTexture: ""
DoubleSided: False
EffectFile: "ReflectionRefraction.fx"
Invariant: False
ReflectAmount: 0.5
ReflectTexture: ""
RefractTexture: ""
ShadowGenerationTechnique: ""
Technique: "Technique1"
Tint: 0.8627451 0.9254902 0.9647059
Transparency: 0.5
TransparencyMapParameterName: "ReflectTexture"
TransparencyMode: None

A continuación, cargan la estructura del aparejo de luz, el cual declara cada luz y su respectiva configuración y recorren dicha estructura para instanciar y configurar a cada luz.

   1: // LightRigs contain many lights and light groups.
   2: lightRig = Content.Load<LightRig>("Lights/Lights");
   3:  
   4: // Need to find the lights for later performance adjustments.
   5: foreach (ILightGroup group in lightRig.LightGroups)
   6: {
   7:     foreach (ILight light in group.Lights)
   8:     {
   9:         if (light is PointLight)
  10:         {
  11:             PointLight pointlight = light as PointLight;
  12:  
  13:             if (pointlight.Name == "FillLight")
  14:                 fillLight = pointlight;
  15:             else if (pointlight.Name == "KeyLight")
  16:                 keyLight = pointlight;
  17:         }
  18:         else if (light is DirectionalLight)
  19:         {
  20:             DirectionalLight dirlight = light as DirectionalLight;
  21:  
  22:             if (dirlight.Name == "Sun")
  23:                 sunLight = dirlight;
  24:         }
  25:     }
  26: }

El contenido del archivo “.rig” original es como sigue:

<root>
  <LightRig>
    <LightGroups>
      <GroupList>
        <item_0>
          <LightGroup>
            <Name>EnvLighting</Name>
            <ShadowType>SceneLifeSpanObjects</ShadowType>
            <Position>
              <Vector3>
                <X>0</X>
                <Y>0</Y>
                <Z>0</Z>
              </Vector3>
            </Position>
            <Radius>0</Radius>
            <ShadowQuality>0.5</ShadowQuality>
            <ShadowPrimaryBias>1</ShadowPrimaryBias>
            <ShadowSecondaryBias>0.2</ShadowSecondaryBias>
            <ShadowPerSurfaceLOD>True</ShadowPerSurfaceLOD>
            <ShadowGroup>False</ShadowGroup>
            <Lights>
              <LightList>
                <item_0>
                  <AmbientLight>
                    <Name>Ambient Lighting</Name>
                    <Enabled>True</Enabled>
                    <DiffuseColor>
                      <Vector3>
                        <X>1</X>
                        <Y>0.6431373</Y>
                        <Z>0.04313726</Z>
                      </Vector3>
                    </DiffuseColor>
                    <Intensity>0.3</Intensity>
                  </AmbientLight>
                </item_0>
                <item_1>
                  <DirectionalLight>
                    <Name>Sun</Name>
                    <Enabled>True</Enabled>
                    <DiffuseColor>
                      <Vector3>
                        <X>1</X>
                        <Y>0.972549</Y>
                        <Z>0.772549</Z>
                      </Vector3>
                    </DiffuseColor>
                    <Intensity>2.6</Intensity>
                    <ShadowType>AllObjects</ShadowType>
                    <Direction>
                      <Vector3>
                        <X>-0.5012565</X>
                        <Y>-0.8552828</Y>
                        <Z>-0.1312759</Z>
                      </Vector3>
                    </Direction>
                    <ShadowQuality>2</ShadowQuality>
                    <ShadowPrimaryBias>1.3</ShadowPrimaryBias>
                    <ShadowSecondaryBias>0.01</ShadowSecondaryBias>
                    <ShadowPerSurfaceLOD>True</ShadowPerSurfaceLOD>
                  </DirectionalLight>
                </item_1>
              </LightList>
            </Lights>
          </LightGroup>
        </item_0>
        <item_1>
          <LightGroup>
            <Name>OrbLighting</Name>
            <ShadowType>SceneLifeSpanObjects</ShadowType>
            <Position>
              <Vector3>
                <X>0</X>
                <Y>0</Y>
                <Z>0</Z>
              </Vector3>
            </Position>
            <Radius>0</Radius>
            <ShadowQuality>0.5</ShadowQuality>
            <ShadowPrimaryBias>1</ShadowPrimaryBias>
            <ShadowSecondaryBias>0.2</ShadowSecondaryBias>
            <ShadowPerSurfaceLOD>True</ShadowPerSurfaceLOD>
            <ShadowGroup>False</ShadowGroup>
            <Lights>
              <LightList>
                <item_0>
                  <PointLight>
                    <Name>FillLight</Name>
                    <Enabled>True</Enabled>
                    <DiffuseColor>
                      <Vector3>
                        <X>0.3803922</X>
                        <Y>0.8313726</Y>
                        <Z>0.9411765</Z>
                      </Vector3>
                    </DiffuseColor>
                    <Intensity>3.8</Intensity>
                    <FillLight>True</FillLight>
                    <FalloffStrength>0</FalloffStrength>
                    <ShadowType>AllObjects</ShadowType>
                    <Position>
                      <Vector3>
                        <X>25.83315</X>
                        <Y>10.99056</Y>
                        <Z>-75.42744</Z>
                      </Vector3>
                    </Position>
                    <Radius>46</Radius>
                    <ShadowQuality>0</ShadowQuality>
                    <ShadowPrimaryBias>1</ShadowPrimaryBias>
                    <ShadowSecondaryBias>0.2</ShadowSecondaryBias>
                    <ShadowPerSurfaceLOD>True</ShadowPerSurfaceLOD>
                  </PointLight>
                </item_0>
                <item_1>
                  <PointLight>
                    <Name>KeyLight</Name>
                    <Enabled>True</Enabled>
                    <DiffuseColor>
                      <Vector3>
                        <X>0.4627451</X>
                        <Y>0.8980392</Y>
                        <Z>1</Z>
                      </Vector3>
                    </DiffuseColor>
                    <Intensity>0.6</Intensity>
                    <FillLight>False</FillLight>
                    <FalloffStrength>0</FalloffStrength>
                    <ShadowType>AllObjects</ShadowType>
                    <Position>
                      <Vector3>
                        <X>25.83315</X>
                        <Y>10.99056</Y>
                        <Z>-75.42744</Z>
                      </Vector3>
                    </Position>
                    <Radius>110</Radius>
                    <ShadowQuality>0.25</ShadowQuality>
                    <ShadowPrimaryBias>1</ShadowPrimaryBias>
                    <ShadowSecondaryBias>0.2</ShadowSecondaryBias>
                    <ShadowPerSurfaceLOD>True</ShadowPerSurfaceLOD>
                  </PointLight>
                </item_1>
              </LightList>
            </Lights>
          </LightGroup>
        </item_1>
      </GroupList>
    </LightGroups>
  </LightRig>
</root>

Finalmente, cargan los datos que configuran el ambiente.

   1: // Load the scene settings.
   2: environment = Content.Load<SceneEnvironment>("Environment/Environment");

Siendo el contenido de los archivos “.env” originales el siguiente:

<root>
  <SceneEnvironment>
    <VisibleDistance>500</VisibleDistance>
    <FogEnabled>True</FogEnabled>
    <FogColor>
      <Vector3>
        <X>0</X>
        <Y>0</Y>
        <Z>0</Z>
      </Vector3>
    </FogColor>
    <FogStartDistance>70</FogStartDistance>
    <FogEndDistance>200</FogEndDistance>
    <ShadowFadeStartDistance>500</ShadowFadeStartDistance>
    <ShadowFadeEndDistance>5000</ShadowFadeEndDistance>
    <ShadowCasterDistance>5000</ShadowCasterDistance>
    <BloomAmount>2</BloomAmount>
    <BloomThreshold>0.7</BloomThreshold>
    <ExposureAmount>1</ExposureAmount>
    <DynamicRangeTransitionMaxScale>4.5</DynamicRangeTransitionMaxScale>
    <DynamicRangeTransitionMinScale>0.5</DynamicRangeTransitionMinScale>
    <DynamicRangeTransitionTime>0.5</DynamicRangeTransitionTime>
  </SceneEnvironment>
</root>

Las tareas restantes son las usuales con excepción de la relacionada al campo local del tipo “EffectBatchHelper”.

   1: ...
   2: EffectBatchHelper batcher = new EffectBatchHelper();
   3: batcher.CollapseEffects(scene);
   4: ...

Para qué sirve? Ayuda a crear lotes de effectos, analizando los efectos usados por cada modelo en la escena. O en otras palabras, colapsa "materiales" similares a fin de optimizar las llamadas de dibujo.

v. Actualizando:

La única cosa especial a notar aquí tiene que ver con el efecto de agua, ya que en cada llamada de actualización se calcula la textura de normales a establecer para lograr el efecto de animación en la superficie del agua.

   1: // Apply the current water animation "frame" to the water effects.
   2: for (int p = 0; p < water.MeshParts.Count; p++)
   3: {
   4:     ModelMeshPart part = water.MeshParts[p];
   5:     if (part.Effect is LightingEffect)
   6:         (part.Effect as LightingEffect).NormalMapTexture = waternormalmapframe;
   7: }

Más allá de ello, no hay nada adicional que comentar porque todos los gestores son automáticamente actualizados por la propia instancia de la clase "game".

vi. Dibujando:

Cómo dije al comienzo de este artículo, el juego renderiza la textura de refracción, luego la de reflección y finalmente la salida principal.

A fin del lograr este objetivo, primero deben configurar el estado de la escena.

   1: // Setup the scene state.
   2: sceneState.BeginFrameRendering(view, projection, gameTime, environment);
   3: ...

Entonces, para cada mapa “especial” (en este caso, en order, los de refracción y reflexión), seleccionan el objetivo de renderizado, qué luces están activas y que objetos afectan, y dibujan la escena.

   1: //-------------------------------------------
   2: // Generate the refraction map.
   3:  
   4: // Adjust the reflection / refraction lighting based on performance.
   5: if (renderQuality == DetailPreference.High)
   6: {
   7:     keyLight.Enabled = true;
   8:     keyLight.ShadowType = ShadowType.AllObjects;
   9:     fillLight.Enabled = true;
  10:     fillLight.ShadowType = ShadowType.AllObjects;
  11:     sunLight.Enabled = true;
  12: }
  13: else
  14: {
  15:     keyLight.Enabled = false;
  16:     keyLight.ShadowType = ShadowType.None;
  17:     fillLight.Enabled = true;
  18:     fillLight.ShadowType = ShadowType.None;
  19:     sunLight.Enabled = true;
  20: }
  21:  
  22: // Add the light rig.
  23: renderManager.LightManager.SubmitLightRig(lightRig, ObjectLifeSpan.Frame);
  24:  
  25: // Begin generating the refraction map.
  26: refractionTarget.BeginFrameRendering(sceneState);
  27:  
  28: // Clear the depth buffer then render.
  29: graphics.GraphicsDevice.Clear(ClearOptions.DepthBuffer, Color.Gray, 1.0f, 0);
  30: RenderTarget(refractionTarget);
  31: refractionTarget.EndFrameRendering();

   1: //-------------------------------------------
   2: // Generate the water reflection map.
   3:  
   4: // Adjust the reflection / refraction lighting based on performance.
   5: if (renderQuality != DetailPreference.High)
   6:     sunLight.Enabled = false;
   7:  
   8: // Add the light rig.
   9: renderManager.LightManager.SubmitLightRig(lightRig, ObjectLifeSpan.Frame);
  10:  
  11: // The water reflection map includes the orbs so add them as dynamic frame objects.
  12: foreach (Orb orb in orbs)
  13:     renderManager.SubmitRenderableObject(orb.model, orb.mesh, orb.currentMeshToObject, sceneWorld, false, ObjectLifeSpan.Frame);
  14:  
  15: // Begin generating the water reflection map.
  16: waterReflectionTarget.BeginFrameRendering(sceneState, waterWorldPlane);
  17:  
  18: // Clear the depth buffer then render.
  19: graphics.GraphicsDevice.Clear(ClearOptions.DepthBuffer, Color.Gray, 1.0f, 0);
  20: RenderTarget(waterReflectionTarget);
  21: waterReflectionTarget.EndFrameRendering();

Por último, se renderiza la escena por tercera vez combinando ambos mapas previos y con ello, se obtiene la salida final de la escena.

   1: //-------------------------------------------
   2: // Render the main scene.
   3:  
   4: // Adjust the lighting based on performance.
   5: if (renderQuality == DetailPreference.High  renderQuality == DetailPreference.Medium)
   6: {
   7:     keyLight.Enabled = true;
   8:     keyLight.ShadowType = ShadowType.AllObjects;
   9:     fillLight.Enabled = true;
  10:     fillLight.ShadowType = ShadowType.AllObjects;
  11:     sunLight.Enabled = true;
  12: }
  13: else
  14: {
  15:     keyLight.Enabled = renderQuality != DetailPreference.Off;
  16:     keyLight.ShadowType = ShadowType.AllObjects;
  17:     fillLight.Enabled = true;
  18:     fillLight.ShadowType = ShadowType.None;
  19:     sunLight.Enabled = true;
  20: }
  21:  
  22: // Add the light rig.
  23: renderManager.LightManager.SubmitLightRig(lightRig, ObjectLifeSpan.Frame);
  24:  
  25: // The main rendering pass includes all objects so add the water and orbs as dynamic frame objects.
  26: foreach (Orb orb in orbs)
  27:     renderManager.SubmitRenderableObject(orb.model, orb.mesh, orb.currentMeshToObject, sceneWorld, false, ObjectLifeSpan.Frame);
  28: renderManager.SubmitRenderableObject(scene, water, waterMeshToObject, sceneWorld, false, ObjectLifeSpan.Frame);
  29:  
  30:  
  31: // Apply main scene preferences (higher quality than reflection / refraction).
  32: renderManager.ApplyPreferences(preferences);
  33:  
  34: // Begin main frame rendering.
  35: editor.BeginFrameRendering(sceneState);
  36: renderManager.BeginFrameRendering(sceneState);
  37:  
  38: // Clear the depth buffer then render.
  39: graphics.GraphicsDevice.Clear(ClearOptions.DepthBuffer, Color.Black, 1.0f, 0);
  40: renderManager.Render();
  41:  
  42:  
  43: // Setup and render reflective / refractive pass for water and orbs using additive blending.
  44: GraphicsDevice.RenderState.AlphaBlendEnable = true;
  45: GraphicsDevice.RenderState.SourceBlend = Blend.One;
  46: GraphicsDevice.RenderState.DestinationBlend = Blend.One;
  47:  
  48: foreach (Orb orb in orbs)
  49:     RenderMesh(orb.mesh, orb.currentMeshToObject * sceneWorld, sceneState, orbEffect, null, refractionTarget.GetTexture());
  50:  
  51: GraphicsDevice.RenderState.CullMode = CullMode.None;
  52:  
  53: RenderMesh(water, waterMeshToObject * sceneWorld, sceneState, waterEffect,
  54:     waterReflectionTarget.GetTexture(), refractionTarget.GetTexture());
  55:  
  56: GraphicsDevice.RenderState.AlphaBlendEnable = false;
  57: GraphicsDevice.RenderState.CullMode = CullMode.CullCounterClockwiseFace;
  58:  
  59:  
  60: // Done rendering main frame.
  61: renderManager.EndFrameRendering();
  62: editor.EndFrameRendering();

Cada paso de renderizado comienza y finaliza de manera similar a como lo hace la clase “SpriteBatch”, así que no hay sorpresa aquí.

Finalmente, terminamos con el renderizado del cuadro de la escena.

   1: sceneState.EndFrameRendering();
   2: ...

vii. En Suma:

Todas las demos prvistas incluyen una explicación detallada sobre lo que ocurre en cada ejemplo, lo que les ayuda mucho a entender la lógica detrás de los procesos del motor.

II. El Editor.

Bien pueden estarse preguntando: “cómo puedo acelerar el proceso de modelado? Si tengo que escribir a mano los archivos de materiales y demás, sería incómodo!”.

Tienen razón, pero, por fortuna el motor viene con un editor que los salva de realizar dicha tarea.

Cómo? Simple, utilizando la clase "game" provista por Synapse Gaming, Uds. pueden cambiar los materiales, y las posiciones de las luces, entre otras cosas.

Nota: a fin de utilizar el editor deberán agregar un campo más al código: un campo de tipo "LightingSystemEditor".

El siguiente video lo dice todo:

Por más videos mostrando el motor Sunburn por favor visiten este sitio:

http://www.youtube.com/user/bobthecbuilder

Bueno, eso es todo. Ahora es el turno de Uds. para probar y compartir su experiencia usando el motor Sunburn!

Vamos a la pausa,
~Pete

 

> Vínculo a la versión en inglés.