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!
> Vínculo a la versión en inglés.