add scene render system, add proper HDR environments, try load texture from mesh file

This commit is contained in:
2025-11-30 20:12:57 +08:00
parent 4cdd405ba9
commit 0d4024be39
62 changed files with 2302 additions and 1037 deletions

View File

@ -0,0 +1,103 @@
#type compute
#version 450 core
// Physically Based Rendering
// Copyright (c) 2017-2018 Michał Siejak
// Computes diffuse irradiance cubemap convolution for image-based lighting.
// Uses quasi Monte Carlo sampling with Hammersley sequence.
const float PI = 3.141592;
const float TwoPI = 2 * PI;
const float Epsilon = 0.00001;
const uint NumSamples = 64 * 1024;
const float InvNumSamples = 1.0 / float(NumSamples);
layout(binding=0) uniform samplerCube inputTexture;
layout(binding=0, rgba16f) restrict writeonly uniform imageCube outputTexture;
// Compute Van der Corput radical inverse
// See: http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html
float radicalInverse_VdC(uint bits)
{
bits = (bits << 16u) | (bits >> 16u);
bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
return float(bits) * 2.3283064365386963e-10; // / 0x100000000
}
// Sample i-th point from Hammersley point set of NumSamples points total.
vec2 sampleHammersley(uint i)
{
return vec2(i * InvNumSamples, radicalInverse_VdC(i));
}
// Uniformly sample point on a hemisphere.
// Cosine-weighted sampling would be a better fit for Lambertian BRDF but since this
// compute shader runs only once as a pre-processing step performance is not *that* important.
// See: "Physically Based Rendering" 2nd ed., section 13.6.1.
vec3 sampleHemisphere(float u1, float u2)
{
const float u1p = sqrt(max(0.0, 1.0 - u1*u1));
return vec3(cos(TwoPI*u2) * u1p, sin(TwoPI*u2) * u1p, u1);
}
vec3 GetCubeMapTexCoord()
{
vec2 st = gl_GlobalInvocationID.xy / vec2(imageSize(outputTexture));
vec2 uv = 2.0 * vec2(st.x, 1.0 - st.y) - vec2(1.0);
vec3 ret;
if (gl_GlobalInvocationID.z == 0) ret = vec3( 1.0, uv.y, -uv.x);
else if (gl_GlobalInvocationID.z == 1) ret = vec3( -1.0, uv.y, uv.x);
else if (gl_GlobalInvocationID.z == 2) ret = vec3( uv.x, 1.0, -uv.y);
else if (gl_GlobalInvocationID.z == 3) ret = vec3( uv.x, -1.0, uv.y);
else if (gl_GlobalInvocationID.z == 4) ret = vec3( uv.x, uv.y, 1.0);
else if (gl_GlobalInvocationID.z == 5) ret = vec3(-uv.x, uv.y, -1.0);
return normalize(ret);
}
// Compute orthonormal basis for converting from tanget/shading space to world space.
void computeBasisVectors(const vec3 N, out vec3 S, out vec3 T)
{
// Branchless select non-degenerate T.
T = cross(N, vec3(0.0, 1.0, 0.0));
T = mix(cross(N, vec3(1.0, 0.0, 0.0)), T, step(Epsilon, dot(T, T)));
T = normalize(T);
S = normalize(cross(N, T));
}
// Convert point from tangent/shading space to world space.
vec3 tangentToWorld(const vec3 v, const vec3 N, const vec3 S, const vec3 T)
{
return S * v.x + T * v.y + N * v.z;
}
layout(local_size_x=32, local_size_y=32, local_size_z=1) in;
void main(void)
{
vec3 N = GetCubeMapTexCoord();
vec3 S, T;
computeBasisVectors(N, S, T);
// Monte Carlo integration of hemispherical irradiance.
// As a small optimization this also includes Lambertian BRDF assuming perfectly white surface (albedo of 1.0)
// so we don't need to normalize in PBR fragment shader (so technically it encodes exitant radiance rather than irradiance).
vec3 irradiance = vec3(0);
for(uint i = 0; i < NumSamples; i++)
{
vec2 u = sampleHammersley(i);
vec3 Li = tangentToWorld(sampleHemisphere(u.x, u.y), N, S, T);
float cosTheta = max(0.0, dot(Li, N));
// PIs here cancel out because of division by pdf.
irradiance += 2.0 * textureLod(inputTexture, Li, 0).rgb * cosTheta;
}
irradiance /= vec3(NumSamples);
imageStore(outputTexture, ivec3(gl_GlobalInvocationID), vec4(irradiance, 1.0));
}