diff --git a/Effects/EffectHelpers.cs b/Effects/EffectHelpers.cs new file mode 100644 index 0000000..5a23251 --- /dev/null +++ b/Effects/EffectHelpers.cs @@ -0,0 +1,14 @@ +using System; + +namespace Kav +{ + [Flags] + internal enum EffectDirtyFlags + { + WorldViewProj = 1, + World = 2, + EyePosition = 4, + ShaderIndex = 8, + All = -1 + } +} diff --git a/Effects/FXB/PBREffect.fxb b/Effects/FXB/PBREffect.fxb new file mode 100644 index 0000000..9763762 Binary files /dev/null and b/Effects/FXB/PBREffect.fxb differ diff --git a/Effects/HLSL/Macros.fxh b/Effects/HLSL/Macros.fxh new file mode 100644 index 0000000..91d1702 --- /dev/null +++ b/Effects/HLSL/Macros.fxh @@ -0,0 +1,57 @@ +//----------------------------------------------------------------------------- +// Macros.fxh +// +// Microsoft XNA Community Game Platform +// Copyright (C) Microsoft Corporation. All rights reserved. +//----------------------------------------------------------------------------- + +#ifdef SM4 + + +// Macros for targetting shader model 4.0 (DX11) + +#define BEGIN_CONSTANTS cbuffer Parameters : register(b0) { +#define MATRIX_CONSTANTS }; cbuffer ProjectionMatrix : register(b1) { +#define END_CONSTANTS }; + +#define _vs(r) +#define _ps(r) +#define _cb(r) : packoffset(r) + +#define DECLARE_TEXTURE(Name, index) \ + Texture2D Name : register(t##index); \ + sampler Name##Sampler : register(s##index) + +#define DECLARE_CUBEMAP(Name, index) \ + TextureCube Name : register(t##index); \ + sampler Name##Sampler : register(s##index) + +#define SAMPLE_TEXTURE(Name, texCoord) Name.Sample(Name##Sampler, texCoord) +#define SAMPLE_CUBEMAP(Name, texCoord) Name.Sample(Name##Sampler, texCoord) +#define SAMPLE_CUBEMAP_LOD(Name, texCoord) Name.SampleLevel(Name##Sampler, texCoord.xyz, texCoord.w) + +#else + + +// Macros for targetting shader model 2.0 (DX9) + +#define BEGIN_CONSTANTS +#define MATRIX_CONSTANTS +#define END_CONSTANTS + +#define _vs(r) : register(vs, r) +#define _ps(r) : register(ps, r) +#define _cb(r) + +#define DECLARE_TEXTURE(Name, index) \ + texture2D Name; \ + sampler Name##Sampler : register(s##index) = sampler_state { Texture = (Name); }; + +#define DECLARE_CUBEMAP(Name, index) \ + textureCUBE Name; \ + sampler Name##Sampler : register(s##index) = sampler_state { Texture = (Name); }; + +#define SAMPLE_TEXTURE(Name, texCoord) tex2D(Name##Sampler, texCoord) +#define SAMPLE_CUBEMAP(Name, texCoord) texCUBE(Name##Sampler, texCoord) +#define SAMPLE_CUBEMAP_LOD(Name, texCoord) texCUBElod(Name##Sampler, texCoord) +#endif diff --git a/Effects/HLSL/PBREffect.fx b/Effects/HLSL/PBREffect.fx new file mode 100644 index 0000000..ee5ecb7 --- /dev/null +++ b/Effects/HLSL/PBREffect.fx @@ -0,0 +1,313 @@ +#include "Macros.fxh" //from FNA + +static const float PI = 3.141592653589793; + +// Samplers + +DECLARE_TEXTURE(AlbedoTexture, 0); +DECLARE_TEXTURE(NormalTexture, 1); +DECLARE_TEXTURE(EmissionTexture, 2); +DECLARE_TEXTURE(OcclusionTexture, 3); +DECLARE_TEXTURE(MetallicRoughnessTexture, 4); +DECLARE_CUBEMAP(EnvDiffuseTexture, 8); +DECLARE_TEXTURE(BrdfLutTexture, 9); +DECLARE_CUBEMAP(EnvSpecularTexture, 10); + +BEGIN_CONSTANTS + + // PBR Values + float3 AlbedoValue _ps(c0) _cb(c0); + float MetallicValue _ps(c1) _cb(c1); + float RoughnessValue _ps(c2) _cb(c2); + float AO _ps(c3) _cb(c3); + + // Light Info + float3 LightPositions[4] _ps(c4) _cb(c4); + float3 LightColors[4] _ps(c8) _cb(c8); + + float3 EyePosition _ps(c12) _cb(c12); + + float4x4 World _vs(c0) _cb(c16); + float4x4 WorldInverseTranspose _vs(c4) _cb(c20); + +MATRIX_CONSTANTS + + float4x4 WorldViewProjection _vs(c8) _cb(c0); + +END_CONSTANTS + +struct VertexShaderInput +{ + float4 Position : POSITION; + float3 Normal : NORMAL; + float2 TexCoord : TEXCOORD0; +}; + +struct PixelShaderInput +{ + float4 Position : SV_Position; + float2 TexCoord : TEXCOORD0; + float3 PositionWS : TEXCOORD1; + float3 NormalWS : TEXCOORD2; +}; + +PixelShaderInput main_vs(VertexShaderInput input) +{ + PixelShaderInput output; + + output.TexCoord = input.TexCoord; + output.PositionWS = mul(input.Position, World).xyz; + output.NormalWS = mul(input.Normal, (float3x3)WorldInverseTranspose).xyz; + output.Position = mul(input.Position, WorldViewProjection); + + return output; +} + +float3 FresnelSchlick(float cosTheta, float3 F0) +{ + return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0); +} + +float DistributionGGX(float3 N, float3 H, float roughness) +{ + float a = roughness * roughness; + float a2 = a * a; + float NdotH = max(dot(N, H), 0.0); + float NdotH2 = NdotH * NdotH; + + float num = a2; + float denom = (NdotH2 * (a2 - 1.0) + 1.0); + denom = PI * denom * denom; + + return num / denom; +} + +float GeometrySchlickGGX(float NdotV, float roughness) +{ + float r = (roughness + 1.0); + float k = (r * r) / 8.0; + + float num = NdotV; + float denom = NdotV * (1.0 - k) + k; + + return num / denom; +} + +float GeometrySmith(float3 N, float3 V, float3 L, float roughness) +{ + float NdotV = max(dot(N, V), 0.0); + float NdotL = max(dot(N, L), 0.0); + float ggx2 = GeometrySchlickGGX(NdotV, roughness); + float ggx1 = GeometrySchlickGGX(NdotL, roughness); + + return ggx1 * ggx2; +} + +// Easy trick to get tangent-normals to world-space to keep PBR code simplified. +float3 GetNormalFromMap(float3 worldPos, float2 texCoords, float3 normal) +{ + float3 tangentNormal = SAMPLE_TEXTURE(NormalTexture, texCoords).xyz * 2.0 - 1.0; + + float3 Q1 = ddx(worldPos); + float3 Q2 = ddy(worldPos); + float2 st1 = ddx(texCoords); + float2 st2 = ddy(texCoords); + + float3 N = normalize(normal); + float3 T = normalize(Q1*st2.y - Q2*st1.y); + float3 B = -normalize(cross(N, T)); + float3x3 TBN = float3x3(T, B, N); + + return normalize(mul(tangentNormal, TBN)); +} + +float4 ComputeColor( + float3 worldPosition, + float3 worldNormal, + float3 albedo, + float metallic, + float roughness +) { + float3 V = normalize(EyePosition - worldPosition); + float3 N = worldNormal; + + float3 F0 = float3(0.04, 0.04, 0.04); + F0 = lerp(F0, albedo, metallic); + + float3 Lo = float3(0.0, 0.0, 0.0); + + for (int i = 0; i < 4; i++) + { + float3 lightDir = LightPositions[i] - worldPosition; + float3 L = normalize(lightDir); + float3 H = normalize(V + L); + + float distance = length(lightDir); + float attenuation = 1.0 / (distance * distance); + float3 radiance = LightColors[i] * attenuation; + + float NDF = DistributionGGX(N, H, roughness); + float G = GeometrySmith(N, V, L, roughness); + float3 F = FresnelSchlick(max(dot(H, V), 0.0), F0); + + float3 numerator = NDF * G * F; + float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0); + float3 specular = numerator / max(denominator, 0.001); + + float3 kS = F; + float3 kD = float3(1.0, 1.0, 1.0) - kS; + + kD *= 1.0 - metallic; + + float NdotL = max(dot(N, L), 0.0); + Lo += (kD * albedo / PI + specular) * radiance * NdotL; + } + + float3 ambient = float3(0.03, 0.03, 0.03) * albedo * AO; + float3 color = ambient + Lo; + + color = color / (color + float3(1.0, 1.0, 1.0)); + float exposureConstant = 1.0 / 2.2; + color = pow(color, float3(exposureConstant, exposureConstant, exposureConstant)); + + return float4(color, 1.0); +} + +// The case where we have no texture maps for any PBR data +float4 None(PixelShaderInput input) : SV_TARGET0 +{ + return ComputeColor( + input.PositionWS, + input.NormalWS, + AlbedoValue, + MetallicValue, + RoughnessValue + ); +} + +float4 AlbedoMapPS(PixelShaderInput input) : SV_TARGET +{ + float3 albedo = pow(SAMPLE_TEXTURE(AlbedoTexture, input.TexCoord), 2.2).rgb; + + return ComputeColor( + input.PositionWS, + input.NormalWS, + albedo, + MetallicValue, + RoughnessValue + ); +} + +float4 MetallicRoughnessPS(PixelShaderInput input) : SV_TARGET +{ + float2 metallicRoughness = SAMPLE_TEXTURE(MetallicRoughnessTexture, input.TexCoord).rg; + + return ComputeColor( + input.PositionWS, + input.NormalWS, + AlbedoValue, + metallicRoughness.r, + metallicRoughness.g + ); +} + +float4 NormalPS(PixelShaderInput input) : SV_TARGET +{ + float3 normal = GetNormalFromMap(input.PositionWS, input.TexCoord, input.NormalWS); + + return ComputeColor( + input.PositionWS, + normal, + AlbedoValue, + MetallicValue, + RoughnessValue + ); +} + +float4 AlbedoMetallicRoughnessMapPS(PixelShaderInput input) : SV_TARGET +{ + float3 albedo = pow(SAMPLE_TEXTURE(AlbedoTexture, input.TexCoord), 2.2).rgb; + float2 metallicRoughness = SAMPLE_TEXTURE(MetallicRoughnessTexture, input.TexCoord).rg; + + return ComputeColor( + input.PositionWS, + input.NormalWS, + albedo, + metallicRoughness.r, + metallicRoughness.g + ); +} + +float4 AlbedoNormalPS(PixelShaderInput input) : SV_TARGET +{ + float3 albedo = pow(SAMPLE_TEXTURE(AlbedoTexture, input.TexCoord), 2.2).rgb; + float3 normal = GetNormalFromMap(input.PositionWS, input.TexCoord, input.NormalWS); + + return ComputeColor( + input.PositionWS, + normal, + albedo, + MetallicValue, + RoughnessValue + ); +} + +float4 MetallicRoughnessNormalPS(PixelShaderInput input) : SV_TARGET +{ + float2 metallicRoughness = SAMPLE_TEXTURE(MetallicRoughnessTexture, input.TexCoord).rg; + float3 normal = GetNormalFromMap(input.PositionWS, input.TexCoord, input.NormalWS); + + return ComputeColor( + input.PositionWS, + normal, + AlbedoValue, + metallicRoughness.r, + metallicRoughness.g + ); +} + +float4 AlbedoMetallicRoughnessNormalMapPS(PixelShaderInput input) : SV_TARGET +{ + float3 albedo = pow(SAMPLE_TEXTURE(AlbedoTexture, input.TexCoord), 2.2).rgb; + float2 metallicRoughness = SAMPLE_TEXTURE(MetallicRoughnessTexture, input.TexCoord).rg; + float3 normal = GetNormalFromMap(input.PositionWS, input.TexCoord, input.NormalWS); + + return ComputeColor( + input.PositionWS, + normal, + albedo, + metallicRoughness.r, + metallicRoughness.g + ); +} + +PixelShader PSArray[8] = +{ + compile ps_3_0 None(), + + compile ps_3_0 AlbedoMapPS(), + compile ps_3_0 MetallicRoughnessPS(), + compile ps_3_0 NormalPS(), + + compile ps_3_0 AlbedoMetallicRoughnessMapPS(), + compile ps_3_0 AlbedoNormalPS(), + compile ps_3_0 MetallicRoughnessNormalPS(), + + compile ps_3_0 AlbedoMetallicRoughnessNormalMapPS() +}; + +int PSIndices[8] = +{ + 0, 1, 2, 3, 4, 5, 6, 7 +}; + +int ShaderIndex = 0; + +Technique PBR +{ + Pass + { + VertexShader = compile vs_3_0 main_vs(); + PixelShader = (PSArray[PSIndices[ShaderIndex]]); + } +} diff --git a/Effects/PBREffect.cs b/Effects/PBREffect.cs new file mode 100644 index 0000000..fb321d8 --- /dev/null +++ b/Effects/PBREffect.cs @@ -0,0 +1,368 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Kav +{ + public struct PBRLight + { + public Vector3 position; + public Vector3 color; + + public PBRLight(Vector3 position, Vector3 colour) + { + this.position = position; + this.color = colour; + } + } + + public class PBRLightCollection + { + private readonly Vector3[] positions = new Vector3[4]; + private readonly Vector3[] colors = new Vector3[4]; + + readonly EffectParameter lightPositionsParam; + readonly EffectParameter lightColorsParam; + + public PBRLightCollection(EffectParameter lightPositionsParam, EffectParameter lightColorsParam) + { + this.lightPositionsParam = lightPositionsParam; + this.lightColorsParam = lightColorsParam; + } + + public PBRLight this[int i] + { + get { return new PBRLight(positions[i], colors[i]); } + set + { + positions[i] = value.position; + colors[i] = value.color; + lightPositionsParam.SetValue(positions); + lightColorsParam.SetValue(colors); + } + } + } + + public class PBREffect : Effect + { + EffectParameter worldParam; + EffectParameter worldViewProjectionParam; + EffectParameter worldInverseTransposeParam; + + EffectParameter albedoTextureParam; + EffectParameter normalTextureParam; + EffectParameter emissionTextureParam; + EffectParameter occlusionTextureParam; + EffectParameter metallicRoughnessTextureParam; + EffectParameter envDiffuseTextureParam; + EffectParameter brdfLutTextureParam; + EffectParameter envSpecularTextureParam; + + EffectParameter albedoParam; + EffectParameter metallicParam; + EffectParameter roughnessParam; + EffectParameter aoParam; + + EffectParameter eyePositionParam; + + EffectParameter shaderIndexParam; + + Matrix world = Matrix.Identity; + Matrix view = Matrix.Identity; + Matrix projection = Matrix.Identity; + PBRLightCollection pbrLightCollection; + + Vector3 albedo; + float metallic; + float roughness; + float ao; + + bool albedoTextureEnabled = false; + bool metallicRoughnessMapEnabled = false; + bool normalMapEnabled = false; + + EffectDirtyFlags dirtyFlags = EffectDirtyFlags.All; + + // FIXME: lazily set properties for performance + + public Matrix World + { + get { return world; } + set + { + world = value; + dirtyFlags |= EffectDirtyFlags.World | EffectDirtyFlags.WorldViewProj; + } + } + + public Matrix View + { + get { return view; } + set + { + view = value; + dirtyFlags |= EffectDirtyFlags.WorldViewProj | EffectDirtyFlags.EyePosition; + } + } + + public Matrix Projection + { + get { return projection; } + set + { + projection = value; + dirtyFlags |= EffectDirtyFlags.WorldViewProj; + } + } + + public PBRLightCollection Lights + { + get { return pbrLightCollection; } + internal set { pbrLightCollection = value; } + } + + public Vector3 Albedo + { + get { return albedo; } + set + { + albedo = value; + albedoParam.SetValue(albedo); + } + } + + public float Metallic + { + get { return metallic; } + set + { + metallic = value; + metallicParam.SetValue(metallic); + } + } + + public float Roughness + { + get { return roughness; } + set + { + roughness = value; + roughnessParam.SetValue(roughness); + } + } + + public float AO + { + get { return ao; } + set + { + ao = value; + aoParam.SetValue(ao); + } + } + + public Texture2D AlbedoTexture + { + get { return albedoTextureParam.GetValueTexture2D(); } + set + { + albedoTextureParam.SetValue(value); + albedoTextureEnabled = value != null; + dirtyFlags |= EffectDirtyFlags.ShaderIndex; + } + } + + public Texture2D NormalTexture + { + get { return normalTextureParam.GetValueTexture2D(); } + set + { + normalTextureParam.SetValue(value); + normalMapEnabled = value != null; + dirtyFlags |= EffectDirtyFlags.ShaderIndex; + } + } + + public Texture2D EmissionTexture + { + get { return emissionTextureParam.GetValueTexture2D(); } + set { emissionTextureParam.SetValue(value); } + } + + public Texture2D OcclusionTexture + { + get { return occlusionTextureParam.GetValueTexture2D(); } + set { occlusionTextureParam.SetValue(value); } + } + + public Texture2D MetallicRoughnessTexture + { + get { return metallicRoughnessTextureParam.GetValueTexture2D(); } + set + { + metallicRoughnessTextureParam.SetValue(value); + metallicRoughnessMapEnabled = value != null; + dirtyFlags |= EffectDirtyFlags.ShaderIndex; + } + } + + public TextureCube EnvDiffuseTexture + { + get { return envDiffuseTextureParam.GetValueTextureCube(); } + set { envDiffuseTextureParam.SetValue(value); } + } + + public Texture2D BRDFLutTexture + { + get { return brdfLutTextureParam.GetValueTexture2D(); } + set { brdfLutTextureParam.SetValue(value); } + } + + public TextureCube EnvSpecularTexture + { + get { return envSpecularTextureParam.GetValueTextureCube(); } + set { envSpecularTextureParam.SetValue(value); } + } + + public PBREffect(GraphicsDevice graphicsDevice) : base(graphicsDevice, Resources.PBREffect) + { + CacheEffectParameters(); + + pbrLightCollection = new PBRLightCollection( + Parameters["LightPositions"], + Parameters["LightColors"] + ); + } + + protected PBREffect(PBREffect cloneSource) : base(cloneSource) + { + CacheEffectParameters(); + + World = cloneSource.World; + View = cloneSource.View; + Projection = cloneSource.Projection; + + Lights = new PBRLightCollection( + Parameters["LightPositions"], + Parameters["LightColors"] + ); + + for (int i = 0; i < 4; i++) + { + Lights[i] = cloneSource.Lights[i]; + } + + AlbedoTexture = cloneSource.AlbedoTexture; + NormalTexture = cloneSource.NormalTexture; + EmissionTexture = cloneSource.EmissionTexture; + OcclusionTexture = cloneSource.OcclusionTexture; + MetallicRoughnessTexture = cloneSource.MetallicRoughnessTexture; + EnvDiffuseTexture = cloneSource.EnvDiffuseTexture; + BRDFLutTexture = cloneSource.BRDFLutTexture; + EnvSpecularTexture = cloneSource.EnvSpecularTexture; + + Albedo = cloneSource.Albedo; + Metallic = cloneSource.Metallic; + Roughness = cloneSource.Roughness; + AO = cloneSource.AO; + } + + public override Effect Clone() + { + return new PBREffect(this); + } + + protected override void OnApply() + { + if ((dirtyFlags & EffectDirtyFlags.World) != 0) + { + worldParam.SetValue(world); + + Matrix.Invert(ref world, out Matrix worldInverse); + Matrix.Transpose(ref worldInverse, out Matrix worldInverseTranspose); + worldInverseTransposeParam.SetValue(worldInverseTranspose); + + dirtyFlags &= ~EffectDirtyFlags.World; + } + + if ((dirtyFlags & EffectDirtyFlags.WorldViewProj) != 0) + { + Matrix.Multiply(ref world, ref view, out Matrix worldView); + Matrix.Multiply(ref worldView, ref projection, out Matrix worldViewProj); + worldViewProjectionParam.SetValue(worldViewProj); + + dirtyFlags &= ~EffectDirtyFlags.WorldViewProj; + } + + if ((dirtyFlags & EffectDirtyFlags.EyePosition) != 0) + { + Matrix.Invert(ref view, out Matrix inverseView); + eyePositionParam.SetValue(inverseView.Translation); + + dirtyFlags &= ~EffectDirtyFlags.EyePosition; + } + + if ((dirtyFlags & EffectDirtyFlags.ShaderIndex) != 0) + { + int shaderIndex = 0; + + if (albedoTextureEnabled && metallicRoughnessMapEnabled && normalMapEnabled) + { + shaderIndex = 7; + } + else if (metallicRoughnessMapEnabled && normalMapEnabled) + { + shaderIndex = 6; + } + else if (albedoTextureEnabled && normalMapEnabled) + { + shaderIndex = 5; + } + else if (albedoTextureEnabled && metallicRoughnessMapEnabled) + { + shaderIndex = 4; + } + else if (normalMapEnabled) + { + shaderIndex = 3; + } + else if (metallicRoughnessMapEnabled) + { + shaderIndex = 2; + } + else if (albedoTextureEnabled) + { + shaderIndex = 1; + } + + shaderIndexParam.SetValue(shaderIndex); + + dirtyFlags &= ~EffectDirtyFlags.ShaderIndex; + } + } + + void CacheEffectParameters() + { + worldParam = Parameters["World"]; + worldViewProjectionParam = Parameters["WorldViewProjection"]; + worldInverseTransposeParam = Parameters["WorldInverseTranspose"]; + + albedoTextureParam = Parameters["AlbedoTexture"]; + normalTextureParam = Parameters["NormalTexture"]; + emissionTextureParam = Parameters["EmissionTexture"]; + occlusionTextureParam = Parameters["OcclusionTexture"]; + metallicRoughnessTextureParam = Parameters["MetallicRoughnessTexture"]; + envDiffuseTextureParam = Parameters["EnvDiffuseTexture"]; + brdfLutTextureParam = Parameters["BrdfLutTexture"]; + envSpecularTextureParam = Parameters["EnvSpecularTexture"]; + + albedoParam = Parameters["AlbedoValue"]; + metallicParam = Parameters["MetallicValue"]; + roughnessParam = Parameters["RoughnessValue"]; + aoParam = Parameters["AO"]; + + eyePositionParam = Parameters["EyePosition"]; + + shaderIndexParam = Parameters["ShaderIndex"]; + } + } +} diff --git a/Resources.cs b/Resources.cs new file mode 100644 index 0000000..918ca8a --- /dev/null +++ b/Resources.cs @@ -0,0 +1,33 @@ +using System.IO; + +namespace Kav +{ + internal class Resources + { + public static byte[] PBREffect + { + get + { + if (pbrEffect == null) + { + pbrEffect = GetResource("PBREffect"); + } + return pbrEffect; + } + } + + private static byte[] pbrEffect; + + private static byte[] GetResource(string name) + { + Stream stream = typeof(Resources).Assembly.GetManifestResourceStream( + "Smuggler.Resources." + name + ".fxb" + ); + using (MemoryStream ms = new MemoryStream()) + { + stream.CopyTo(ms); + return ms.ToArray(); + } + } + } +} diff --git a/Scene.cs b/Scene.cs index 72b481e..303a582 100644 --- a/Scene.cs +++ b/Scene.cs @@ -3,22 +3,14 @@ using Microsoft.Xna.Framework.Graphics; namespace Kav { - public class Scene + public static class Renderer { - private PointLight[] pointLights = new PointLight[4]; - private List models = new List(); - - public void AddPointLight(int index, PointLight light) + public static void Render(GraphicsDevice graphicsDevice, SceneData sceneData) { - pointLights[index] = light; + Render(graphicsDevice, sceneData.Camera, sceneData.Models, sceneData.PointLights); } - public void AddModel(Model model) - { - models.Add(model); - } - - public void Render(GraphicsDevice graphicsDevice, Camera camera) + public static void Render(GraphicsDevice graphicsDevice, Camera camera, IEnumerable models, IEnumerable pointLights) { var view = camera.View; var projection = camera.Projection; @@ -41,9 +33,11 @@ namespace Kav if (meshPart.Effect is PointLightEffect pointLightEffect) { - for (int i = 0; i < pointLights.Length; i++) + int i = 0; + foreach (var pointLight in pointLights) { - pointLightEffect.PointLights[i] = pointLights[i]; + pointLightEffect.PointLights[i] = pointLight; + i++; } } diff --git a/SceneData.cs b/SceneData.cs new file mode 100644 index 0000000..a2b32a1 --- /dev/null +++ b/SceneData.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; + +namespace Kav +{ + public class SceneData + { + private readonly PointLight[] pointLights = new PointLight[4]; + private readonly List models = new List(); + + public Camera Camera { get; set; } + public IEnumerable Models { get { return models; } } + public IEnumerable PointLights { get { return pointLights; } } + + public void AddPointLight(int index, PointLight light) + { + pointLights[index] = light; + } + + public void AddModel(Model model) + { + models.Add(model); + } + } +} diff --git a/kav.csproj b/kav.csproj index d224283..cdb5cfc 100644 --- a/kav.csproj +++ b/kav.csproj @@ -10,4 +10,11 @@ + + + + Smuggler.Resources.PBREffect.fxb + + +