Monogame и HLSL - артефакты картирования точечного света и теней
Я делаю игру в Monogame. Я использую прямой рендеринг, и я решил написать шейдер, который будет вызывать молнии, используя модель затенения Blinn-phong. Я реализовал эту модель для работы с тремя типами света - направленным, точечным и точечным. После этого было время добавить тени в нашу игру. Я решил использовать Shadow Mapping с техникой Percentage Closer Filtering. Я реализовал это, но, к сожалению, есть проблема с отбрасыванием теней для точечных источников света. Я рендерил свою сцену на карту куба с точки зрения точечного источника света (шесть ViewProjectionMatrices, каждая из которых направлена в одну сторону), которую затем сравниваю с текущим визуализированным объектом. Я сталкиваюсь с двумя проблемами:
У краев плоской поверхности есть несколько странных артефактов. Есть закругленная часть поверхности, которая не затеняется, несмотря на то, что покрыта другой поверхностью. Скриншот с проблемой: Point Light Shadow Mapping Issue # 1 - обратите внимание на закругленные световые артефакты (эта серая сфера - это положение света)
Чтобы это затенение работало, у меня должен быть какой-то объект границ света, который расположен наиболее далеко от точечного источника света, где он виден усеченным светом. Если я не нарисую этот объект, это вызовет какое-то обратное затенение - я увижу свет на поверхности, в положении другого объекта, который находится за этой поверхностью. Скриншот для лучшего понимания: Point Light Shadow Mapping Issue # 2 - нет границ света - сцена не освещена, персонаж, форма которого видна, находится за стеной (опять же, серая сфера - это положение света
Вот мой код HLSL (я заменю части кода, не связанные напрямую с точечными источниками, просто кратким комментарием + "здесь"):
#define MAX_DIRECTIONAL_LIGHTS 3
#define MAX_POINT_LIGHTS 4
#define MAX_SPOT_LIGHTS 4
matrix worldMatrix;
matrix viewProjectionMatrix;
matrix currentLightVievProjectionMatrix;
float4 currentLightPosition;
float4 cameraPosition;
texture diffuseTexture;
texture normalTexture;
texture specularTexture;
texture opacityTexture;
float4 globalAmbient;
//Directional Lights related variables here
int currentPointLightsNumber;
float4 pointLightPosition[MAX_POINT_LIGHTS];
float4 pointLightAmbientColor[MAX_POINT_LIGHTS];
float4 pointLightDiffuseColor[MAX_POINT_LIGHTS];
float4 pointLightSpecularColor[MAX_POINT_LIGHTS];
float pointLightRadius[MAX_POINT_LIGHTS];
float pointLightTexelSize[MAX_POINT_LIGHTS];
matrix pointLightViewProjection0;
matrix pointLightViewProjection1;
matrix pointLightViewProjection2;
matrix pointLightViewProjection3;
texture pointLightShadowMap0;
texture pointLightShadowMap1;
texture pointLightShadowMap2;
texture pointLightShadowMap3;
//Spot lights related variables here
float materialShininessFactor;
float DepthBias = float(0.0004F);
sampler2D DiffuseMapSampler = sampler_state
{
Texture = <diffuseTexture>;
MinFilter = Anisotropic;
MagFilter = Linear;
MipFilter = Linear;
AddressU = wrap;
AddressV = wrap;
MaxAnisotropy = 16;
};
sampler2D NormalMapSampler = sampler_state
{
Texture = <normalTexture>;
MinFilter = Anisotropic;
MagFilter = Linear;
MipFilter = Linear;
AddressU = wrap;
AddressV = wrap;
MaxAnisotropy = 4;
};
sampler2D SecularMapSampler = sampler_state
{
Texture = <specularTexture>;
MinFilter = Linear;
MagFilter = Linear;
MipFilter = Linear;
AddressU = wrap;
AddressV = wrap;
};
sampler2D OpacityMapSampler = sampler_state
{
Texture = <opacityTexture>;
MinFilter = Linear;
MagFilter = Linear;
MipFilter = Linear;
AddressU = wrap;
AddressV = wrap;
};
//Directional light shadow map samplers here
samplerCUBE PointLightShadowMapSampler0 = sampler_state
{
Texture = <pointLightShadowMap0>;
MinFilter = Point;
MagFilter = Point;
MipFilter = None;
AddressU = clamp;
AddressV = clamp;
};
samplerCUBE PointLightShadowMapSampler1 = sampler_state
{
Texture = <pointLightShadowMap1>;
MinFilter = Point;
MagFilter = Point;
MipFilter = None;
AddressU = clamp;
AddressV = clamp;
};
samplerCUBE PointLightShadowMapSampler2 = sampler_state
{
Texture = <pointLightShadowMap2>;
MinFilter = Point;
MagFilter = Point;
MipFilter = None;
AddressU = clamp;
AddressV = clamp;
};
samplerCUBE PointLightShadowMapSampler3 = sampler_state
{
Texture = <pointLightShadowMap3>;
MinFilter = Point;
MagFilter = Point;
MipFilter = None;
AddressU = clamp;
AddressV = clamp;
};
//Spot light shadow map samplers here
struct BlinnPhongVertexShaderInput
{
float4 position : POSITION;
float2 textureCoordinates : TEXCOORD;
float3 normal : NORMAL;
float3 tangent : TANGENT;
float3 binormal : BINORMAL;
};
struct BlinnPhongPixelShaderInput
{
float4 position : SV_POSITION;
float4 worldPosition : TEXCOORD0;
float2 textureCoordinates : TEXCOORD1;
float4 viewDirection : TEXCOORD2;
float3 normal : TEXCOORD3;
float3 tangent : TANGENT;
float3 binormal : BINORMAL;
};
struct CreateShadowMapPixelShaderInput
{
float4 Position : POSITION;
float Depth : TEXCOORD0;
};
//Vertex shader for directional and spot lights here
CreateShadowMapPixelShaderInput CreateShadowMapForPointLightVertexShaderFunction(float4 Position : POSITION)
{
CreateShadowMapPixelShaderInput OUT;
OUT.Position = mul(Position, worldMatrix);
OUT.Depth = length(OUT.Position.xyz - currentLightPosition.xyz);
OUT.Position = mul(OUT.Position, currentLightVievProjectionMatrix);
return OUT;
}
float4 CreateShadowMapPixelShaderFunction(CreateShadowMapPixelShaderInput input) : COLOR
{
return float4(input.Depth, 0.0F, 0.0F, 0.0F);
}
BlinnPhongPixelShaderInput BlinnPhongVertexShaderFunction(BlinnPhongVertexShaderInput input)
{
BlinnPhongPixelShaderInput output;
float4 worldPosition = mul(input.position, worldMatrix);
output.position = mul(worldPosition, viewProjectionMatrix);
output.worldPosition = worldPosition;
output.textureCoordinates = input.textureCoordinates;
output.viewDirection = cameraPosition - output.worldPosition;
output.normal = mul(input.normal, (float3x3)worldMatrix);
output.tangent = mul(input.tangent, (float3x3)worldMatrix);
output.binormal = mul(input.binormal, (float3x3)worldMatrix);
return output;
}
//ShadowMapLookups for directional and spot lights here
float PointLightShadowMapLookup(samplerCUBE shadowMap, float3 shadowTexCoord, float3 offset, float ourDepth, float texelSize)
{
return (texCUBE(shadowMap, shadowTexCoord + offset * texelSize).r < ourDepth) ? 0.1f : 1.0f;
}
float4 BlinnPhongPixelShaderFunction(BlinnPhongPixelShaderInput input) : COLOR0
{
float4 color = globalAmbient;
float4 specularColor = float4(0.0F, 0.0F, 0.0F, 0.0F);
float3 V = normalize(input.viewDirection.xyz);
float3 L;
float3 H;
float NDotL;
float NDotH;
float attenuation;
float power;
float4 normalMap = tex2D(NormalMapSampler, input.textureCoordinates);
normalMap = (normalMap * 2.0F) - 1.0F;
float3 N = normalize((normalMap.x * normalize(input.tangent)) + (normalMap.y * normalize(input.binormal)) + (normalMap.z * normalize(input.normal)));
float4 specularMap;
specularMap = tex2D(SecularMapSampler, input.textureCoordinates);
float4 lightingPosition;
float2 ShadowTexCoord;
float3 PointLightShadowTexCoord;
float ourdepth;
float shadowOcclusion;
//Directional lights lightning callculations here
for (int j = 0; j < currentPointLightsNumber; ++j)
{
L = (pointLightPosition[j].xyz - input.worldPosition.xyz) / pointLightRadius[j];
attenuation = saturate(1.0F - dot(L, L));
L = normalize(L);
H = normalize(L + V);
NDotL = saturate(dot(N, L));
NDotH = saturate(dot(N, H));
power = (NDotL == 0.0F) ? 0.0F : saturate(pow(NDotH, materialShininessFactor / specularMap.a));
ourdepth = length((pointLightPosition[j].xyz - input.worldPosition.xyz) * 0.98);
PointLightShadowTexCoord = -normalize(pointLightPosition[j].xyz - input.worldPosition.xyz);
shadowOcclusion = 0.0F;
if (j == 0)
{
shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(0.0f, 0.0f, 0.0f), ourdepth, pointLightTexelSize[j]);
shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(1.0f, 0.0f, 0.0f), ourdepth, pointLightTexelSize[j]);
shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(2.0f, 0.0f, 0.0f), ourdepth, pointLightTexelSize[j]);
shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(0.0f, 1.0f, 0.0f), ourdepth, pointLightTexelSize[j]);
shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(1.0f, 1.0f, 0.0f), ourdepth, pointLightTexelSize[j]);
shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(2.0f, 1.0f, 0.0f), ourdepth, pointLightTexelSize[j]);
shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(0.0f, 2.0f, 0.0f), ourdepth, pointLightTexelSize[j]);
shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(1.0f, 2.0f, 0.0f), ourdepth, pointLightTexelSize[j]);
shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(2.0f, 2.0f, 0.0f), ourdepth, pointLightTexelSize[j]);
shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(0.0f, 0.0f, 1.0f), ourdepth, pointLightTexelSize[j]);
shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(1.0f, 0.0f, 1.0f), ourdepth, pointLightTexelSize[j]);
shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(2.0f, 0.0f, 1.0f), ourdepth, pointLightTexelSize[j]);
shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(0.0f, 1.0f, 1.0f), ourdepth, pointLightTexelSize[j]);
shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(1.0f, 1.0f, 1.0f), ourdepth, pointLightTexelSize[j]);
shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(2.0f, 1.0f, 1.0f), ourdepth, pointLightTexelSize[j]);
shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(0.0f, 2.0f, 1.0f), ourdepth, pointLightTexelSize[j]);
shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(1.0f, 2.0f, 1.0f), ourdepth, pointLightTexelSize[j]);
shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(2.0f, 2.0f, 1.0f), ourdepth, pointLightTexelSize[j]);
shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(0.0f, 0.0f, 2.0f), ourdepth, pointLightTexelSize[j]);
shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(1.0f, 0.0f, 2.0f), ourdepth, pointLightTexelSize[j]);
shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(2.0f, 0.0f, 2.0f), ourdepth, pointLightTexelSize[j]);
shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(0.0f, 1.0f, 2.0f), ourdepth, pointLightTexelSize[j]);
shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(1.0f, 1.0f, 2.0f), ourdepth, pointLightTexelSize[j]);
shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(2.0f, 1.0f, 2.0f), ourdepth, pointLightTexelSize[j]);
shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(0.0f, 2.0f, 2.0f), ourdepth, pointLightTexelSize[j]);
shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(1.0f, 2.0f, 2.0f), ourdepth, pointLightTexelSize[j]);
shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(2.0f, 2.0f, 2.0f), ourdepth, pointLightTexelSize[j]);
}
else if (j == 1)
{
//Same code for second point light
}
else if (j == 2)
{
//Same code for third point light
}
else
{
//Same code for fourth point light
}
shadowOcclusion /= 27.0F;
color += (pointLightAmbientColor[j] * attenuation) + (pointLightDiffuseColor[j] * NDotL * attenuation * shadowOcclusion);
specularColor += (pointLightSpecularColor[j] * power * attenuation * specularMap * shadowOcclusion);
}
//Spot lights lightning callculations here
color = saturate(color * tex2D(DiffuseMapSampler, input.textureCoordinates) + specularColor);
color.a = (float1)tex2D(OpacityMapSampler, input.textureCoordinates);
return color;
}
//technique for directional and spot lights shadow mapping here
technique CreateShadowMapForPointLight
{
pass Pass1
{
VertexShader = compile vs_4_0 CreateShadowMapForPointLightVertexShaderFunction();
PixelShader = compile ps_4_0 CreateShadowMapPixelShaderFunction();
}
}
technique BlinnPhong
{
pass Pass1
{
VertexShader = compile vs_4_0 BlinnPhongVertexShaderFunction();
PixelShader = compile ps_4_0 BlinnPhongPixelShaderFunction();
}
}
Я знаю, это выглядит плохо, но позвольте мне объяснить. Я не смог хранить легкие матрицы vievProjection в массиве, потому что их значения обновлялись во время выполнения, поэтому мне пришлось разделить их на отдельные матрицы для каждого источника света. Что касается текстур, было предупреждение о том, что шейдерный компилятор заставляет циклы развернуться, поэтому я просто хотел быть уверен, что все будет хорошо, поэтому я тоже разделил их. Не знаю, если вопрос Матрицы - это Monogame или HLSL.
Возвращаясь к теневым проблемам, есть ли проблема с HLSL, который я представил? По мере необходимости я предоставлю код для моих предложений, отвечающих за рендеринг карты теней.
Вот как выглядит моя кубическая карта и матрица vievProjection:
RenderTargetCube ShadowMapRenderTarget = new RenderTargetCube(GameObject.Scene.SceneManager.GameEngine.GraphicsDevice,
1024,
false,
SurfaceFormat.Single,
DepthFormat.Depth24);
ProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver2, 1.0F, 0.1F, Radius) * Matrix.CreateScale(-1, 1, 1);
public void CreateViewMatrix(Vector3 targetVector)
{
Vector3 upVector;
if (targetVector.Y > 0)
{
upVector = Vector3.Forward;
}
else if (targetVector.Y < 0)
{
upVector = Vector3.Backward;
}
else
{
upVector = Vector3.Up;
}
ViewMatrix = Matrix.CreateLookAt(GameObject.Transform.Position,GameObject.Transform.Position + targetVector, upVector);
}
public override void CreateViewProjectionMatrix()
{
ViewProjectionFrustum.Matrix = ViewMatrix * ProjectionMatrix;
}
Во время розыгрыша:
foreach (PointLight pointLight in PointLights)
{
foreach (CubeMapFace cubeMapFace in Enum.GetValues(typeof(CubeMapFace)))
{
pointLight.CreateViewMatrix(cubeMapFace.GetDirection());
pointLight.CreateViewProjectionMatrix();
SceneManager.GameEngine.GraphicsDevice.SetRenderTarget(pointLight.ShadowMapRenderTarget, cubeMapFace);
SceneManager.GameEngine.GraphicsDevice.Clear(Color.White);
foreach (DrawShadowMapDelegateType DrawComponent in ComponentsDrawShadowMapForPointLightMethods)
{
DrawComponent(pointLight);
}
}
}
Есть ли простой способ или простое объяснение, почему такие мысли случаются? Будет ли способ исправить это, или я буду вынужден попытаться реализовать двойное параболоидное картирование теней? Если да, есть ли пример реализации в hlsl, успешно связанный с monogame или xna?
Заранее спасибо за любые предложения и за ваше время.
0 ответов
Столкнулись с первой проблемой при реализации теневого отображения самостоятельно. Причина была в том, что я вычислял глубину в вершинном шейдере вместо фрагментного шейдера при рендеринге карты теней. Таким образом, если у вас есть многоугольник, перпендикулярный источнику света, каждая вершина получит одинаковую глубину. Я решил это, используя переменный вектор (положение фрагмента мира), а затем установив глубину в шейдере фрагмента. Не лучшее решение, так как это плохая практика для производительности.