Próbuje obliczyć macież widoku oraz wartości near
i far
dla światła kierunkowego do cienowania.
Na razie mam taki kod:
//gdzies tam w klasie Camera
static constexpr std::array<glm::vec4, 8> NDC_FRUSTUM_CORNERS = {
glm::vec4(-1.0f, -1.0f, 0.0f, 1.0f),
glm::vec4(1.0f, -1.0f, 0.0f, 1.0f),
glm::vec4(-1.0f, 1.0f, 0.0f, 1.0f),
glm::vec4(1.0f, 1.0f, 0.0f, 1.0f),
glm::vec4(-1.0f, -1.0f, 1.0f, 1.0f),
glm::vec4(1.0f, -1.0f, 1.0f, 1.0f),
glm::vec4(-1.0f, 1.0f, 1.0f, 1.0f),
glm::vec4(1.0f, 1.0f, 1.0f, 1.0f)
};
glm::mat4 CalculateViewProj(const Camera& camera, glm::vec3 lightDirection)
{
lightDirection = -glm::normalize(lightDirection);
float shadowDistance = 10.0f;
float margin = 10.0f;
auto frustumCorners = Camera::NDC_FRUSTUM_CORNERS;
const glm::mat4 cameraInverseView = camera.getModel();
const glm::mat4 cameraInverseProjection = glm::inverse(glm::perspectiveFov(camera.fov, camera.width, camera.height, shadowDistance, 0.01f));
const glm::mat4 cameraInverseViewProjection = glm::mat4(glm::mat3(cameraInverseView)) * cameraInverseProjection;
for (auto& fc : frustumCorners) {
fc = cameraInverseViewProjection * fc;
fc /= fc.w;
}
glm::vec3 min = glm::vec3(std::numeric_limits<float>::max());
glm::vec3 max = glm::vec3(std::numeric_limits<float>::min());
for (const auto& fc : frustumCorners) {
min = glm::min(min, glm::vec3(fc));
max = glm::max(max, glm::vec3(fc));
}
const float h = glm::distance(min, max);
const float projectedRectangleSize = h * 0.5f;
const glm::mat4 lightView = ????
for (auto& fc : frustumCorners) {
fc = lightView * fc;
fc /= fc.w;
}
float lightMinZ = std::numeric_limits<float>::max();
float lightMaxZ = std::numeric_limits<float>::min();
for (const auto& fc : frustumCorners) {
lightMinZ = glm::min(lightMinZ, fc.z);
lightMaxZ = glm::max(lightMaxZ, fc.z);
}
const float z = glm::abs(lightMaxZ - lightMinZ) + margin;
glm::mat4 lightProjection = glm::orthoZO(-projectedRectangleSize, projectedRectangleSize, -projectedRectangleSize, projectedRectangleSize, z, -z);
const glm::mat4 lightSpaceMatrix = lightProjection * lightView;
return lightSpaceMatrix;
}
Nie wiem w jaki sposób poprawnie obliczyć macież lightView
Przejrzałem dużo wątków na ten temat w internecie, ale nie znalazłem niczego co pomogłoby rozwiązać mój problem.
Do tej pory, próbowałem 3 opcji
- najprostsza
const glm::mat4 lightView = glm::lookAt(lightDirection,
glm::vec3(0.0f, 0.0f, 0.0f),
glm::vec3(0.0f, 1.0f, 0.0f));
ale ten sposób nie działa dobrze w połączeniu z obliczeniem near
i far
dla ortho
. Żeby pokazały się cienie, dla testu, ustawiłem te wartości na sztywno na 10.0f
i -1.0f
const glm::mat4 lightProjection = glm::orthoZO(-projectedRectangleSize, projectedRectangleSize, -projectedRectangleSize, projectedRectangleSize, 10.0f, -1.0f);
. Cienie się teraz pokazują, ale nie jest to poprawne rozwiązanie, bo nie skaluje się to z frustum kamery.
- Obliczenie środka frustum
glm::vec3 frustumCenter = glm::vec3(0.0f);
for (const auto& fc : frustumCorners) {
frustumCenter += glm::vec3(fc);
}
frustumCenter /= 8.0f;
const glm::mat4 lightView = glm::lookAt(frustumCenter - lightDirection * -projectedRectangleSize, frustumCenter, Camera::WORLD_UP);
Gdzieś w internecie widziałem propozycje zdefiniowania widoku w ten sposób. Renderowałem scene z perspektywy światła, wygląda dobrze, ale z perspektywy głównej kamery cieni brak. Jest po prostu zwykła, oświetlona scena. Znów ustawiłem na sztywno near
i far
, cienie się pokazały.
- Obliczenie pozycji światła
glm::vec3 newCenter = camera.getPosition() + shadowDistance * 0.5f * camera.getForward();
const float pixelsSize = projectedRectangleSize / 1024;
const float invPixelSize = 1.0f / pixelsSize;
newCenter.x = std::floorf(newCenter.x * invPixelSize) * pixelsSize;
newCenter.y = std::floorf(newCenter.y * invPixelSize) * pixelsSize;
const glm::vec3 lightPosition = (shadowDistance * 0.5f + margin) * lightDirection + newCenter;
const glm::mat4 lightView = glm::lookAt(lightPosition,
lightDirection,
glm::vec3(0.0f, 1.0f, 0.0f));
W tym przypadku w ogóle cienie zmieniają swój kierunek po obrocie gównej kamery, co nie jest poprawnym działaniem.
Próbuje od tygodnia to ogarnąć i już nie mam pomysłów.
Depth buffor jest wyczyszczony na 0 ze względu na odwróconą głębie.
Sampler description:
D3D11_SAMPLER_DESC
{
.Filter = D3D11_FILTER_COMPARISON_MIN_MAG_MIP_POINT,
.AddressU = D3D11_TEXTURE_ADDRESS_BORDER,
.AddressV = D3D11_TEXTURE_ADDRESS_BORDER,
.AddressW = D3D11_TEXTURE_ADDRESS_BORDER,
.ComparisonFunc = D3D11_COMPARISON_GREATER,
.BorderColor = 1.0f,
}
depth shader:
cbuffer LightVp: register(b0)
{
row_major float4x4 lightViewProjection;
};
struct VertexInput
{
float3 position : POSITION;
nointerpolation float4 modelToWorld1 : I_MODEL_TO_WORLD_ONE;
nointerpolation float4 modelToWorld2 : I_MODEL_TO_WORLD_TWO;
nointerpolation float4 modelToWorld3 : I_MODEL_TO_WORLD_THREE;
nointerpolation float4 modelToWorld4 : I_MODEL_TO_WORLD_FOUR;
};
float4 VSMain(VertexInput input) : SV_POSITION
{
const float4x4 model = float4x4(input.modelToWorld1, input.modelToWorld2, input.modelToWorld3, input.modelToWorld4);
const float4 worldPosition = mul(float4(input.position, 1.0f), model);
return mul(worldPosition, lightViewProjection);
}
float PSMain(float4 position : SV_POSITION) : SV_Depth
{
return position.z;
}
Nie wydaje mi się, że jest tu coś źle, ale dla pewności wrzuce. Próbowałem też odpalić w ogóle bez shadera pixeli, sam vertex shader. NIe ma różnicy.
Funkcja cieniująca
Texture2D dirShadowMap : register(t0);
SamplerComparisonState g_shadowSampler : register(s0);
cbuffer LightVp: register(b0)
{
row_major float4x4 lightViewProjection;
};
float DirShadows(float3 worldPosition)
{
float4 fragPosLightSpace = mul(float4(worldPosition, 1.0f), lightViewProjection);
float3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
projCoords = projCoords * 0.5 + 0.5;
return dirShadowMap.SampleCmpLevelZero(g_shadowSampler, projCoords.xy, projCoords.z);
}
Jak moge zaimplementować to poprawnie?