DRTVWR-592: (WIP) (has debug) Apply triplanar optimization technique to PBR material sampling. Use slightly different alpha ramp to hide unused materials

meow-7.2.2
Cosmic Linden 2023-10-13 10:42:29 -07:00
parent 6e949e5d63
commit 1d885181d8
2 changed files with 302 additions and 49 deletions

View File

@ -82,6 +82,19 @@ vec4 sample_and_mix_color4(float alpha1, float alpha2, float alphaFinal, Terrain
vec3 sample_and_mix_vector3(float alpha1, float alpha2, float alphaFinal, TerrainCoord texcoord, vec3[4] factors, sampler2D tex0, sampler2D tex1, sampler2D tex2, sampler2D tex3);
vec3 sample_and_mix_normal(float alpha1, float alpha2, float alphaFinal, TerrainCoord texcoord, sampler2D tex0, sampler2D tex1, sampler2D tex2, sampler2D tex3);
#if 1 // TODO: Remove
#define TERRAIN_DEBUG 1 // TODO: Remove debug
struct TerrainMix
{
vec4 weight;
int type;
#if TERRAIN_DEBUG
ivec4 usage;
#endif
};
TerrainMix _t_mix(float alpha1, float alpha2, float alphaFinal);
#endif
void main()
{
@ -125,16 +138,60 @@ void main()
// from mikktspace.com
vec3 vNt = sample_and_mix_normal(alpha1, alpha2, alphaFinal, terrain_texcoord, detail_0_normal, detail_1_normal, detail_2_normal, detail_3_normal);
float sign = vary_sign;
vec3 vN = vary_normal;
vec3 vT = vary_tangent.xyz;
vec3 vB = sign * cross(vN, vT);
vec3 vB = vary_sign * cross(vN, vT);
vec3 tnorm = normalize( vNt.x * vT + vNt.y * vB + vNt.z * vN );
tnorm *= gl_FrontFacing ? 1.0 : -1.0;
#if 0 // TODO: Remove (terrain weights visualization)
TerrainMix tm = _t_mix(alpha1, alpha2, alphaFinal);
#if 1
// Show full usage and weights
float uw = 0.3;
#if 1
#if 0
vec4 mix_usage = vec4(tm.usage);
#else
vec4 mix_usage = vec4(tm.weight);
#endif
#else
// Version with easier-to-see boundaries of weights vs usage
vec4 mix_usage = mix(vec4(tm.usage),
mix(max(vec4(0.0), sign(tm.weight - 0.01)),
max(vec4(0.0), sign(tm.weight - 0.005)),
0.5),
uw);
#endif
col.xyz = mix_usage.xyz;
col.x = max(col.x, mix_usage.w);
//col.y = 0.0;
//col.z = 0.0;
#else
// Show places where weight > usage + tolerance
float tolerance = 0.005;
vec4 weight_gt_usage = sign(
max(
vec4(0.0),
(tm.weight - (vec4(tm.usage) + vec4(tolerance)))
)
);
col.xyz = weight_gt_usage.xyz;
col.x = max(col.x, weight_gt_usage.w);
#endif
#endif
#if 0 // TODO: Remove (material channel discriminator)
//col.rgb = vec3(0.0, 1.0, 0.0);
//col.rgb = spec.rgb;
//col.rgb = (vNt + 1.0) / 2.0;
col.rgb = (tnorm + 1.0) / 2.0;
spec.rgb = vec3(1.0, 1.0, 0.0);
tnorm = vary_normal;
emissive = vec3(0);
#endif
frag_data[0] = max(vec4(col.xyz, 0.0), vec4(0)); // Diffuse
frag_data[1] = max(vec4(spec.rgb, base_color_factor_alpha), vec4(0)); // PBR linear packed Occlusion, Roughness, Metal.
frag_data[2] = max(vec4(encode_normal(tnorm), base_color_factor_alpha, GBUFFER_FLAG_HAS_PBR), vec4(0)); // normal, environment intensity, flags

View File

@ -49,11 +49,62 @@ in vec3 vary_vertex_normal;
vec3 srgb_to_linear(vec3 c);
float terrain_mix(vec4 samples, float alpha1, float alpha2, float alphaFinal)
// A relatively agressive threshold ensures that only one or two materials are used in most places
#define TERRAIN_RAMP_MIX_THRESHOLD 0.1
#define MIX_X 1 << 3
#define MIX_Y 1 << 2
#define MIX_Z 1 << 1
#define MIX_W 1 << 0
#define TERRAIN_DEBUG 1 // TODO: Remove debug
struct TerrainMix
{
return mix( mix(samples.w, samples.z, alpha2), mix(samples.y, samples.x, alpha1), alphaFinal );
vec4 weight;
int type;
#if TERRAIN_DEBUG
ivec4 usage;
#endif
};
#define TerrainMixSample vec4[4]
#define TerrainMixSample3 vec3[4]
TerrainMix _t_mix(float alpha1, float alpha2, float alphaFinal)
{
TerrainMix tm;
vec4 sample_x = vec4(1,0,0,0);
vec4 sample_y = vec4(0,1,0,0);
vec4 sample_z = vec4(0,0,1,0);
vec4 sample_w = vec4(0,0,0,1);
tm.weight = mix( mix(sample_w, sample_z, alpha2), mix(sample_y, sample_x, alpha1), alphaFinal );
tm.weight -= TERRAIN_RAMP_MIX_THRESHOLD;
ivec4 usage = max(ivec4(0), ivec4(ceil(tm.weight)));
// Prevent negative weights and keep weights balanced
tm.weight = normalize(tm.weight*vec4(usage));
tm.type = (usage.x * MIX_X) |
(usage.y * MIX_Y) |
(usage.z * MIX_Z) |
(usage.w * MIX_W);
#if TERRAIN_DEBUG // TODO: Remove debug
tm.usage = usage;
#endif
return tm;
}
float terrain_mix(vec4 samples, float alpha1, float alpha2, float alphaFinal)
{
TerrainMix tm = _t_mix(alpha1, alpha2, alphaFinal);
// Assume weights are normalized
return tm.weight.x * samples.x +
tm.weight.y * samples.y +
tm.weight.z * samples.z +
tm.weight.w * samples.w;
}
#if 0 // TODO: Decide if still needed, and if so, use _t_mix internally for weights
vec3 terrain_mix(vec3[4] samples, float alpha1, float alpha2, float alphaFinal)
{
return mix( mix(samples[3], samples[2], alpha2), mix(samples[1], samples[0], alpha1), alphaFinal );
@ -63,6 +114,25 @@ vec4 terrain_mix(vec4[4] samples, float alpha1, float alpha2, float alphaFinal)
{
return mix( mix(samples[3], samples[2], alpha2), mix(samples[1], samples[0], alpha1), alphaFinal );
}
#endif
vec4 terrain_mix(TerrainMix tm, TerrainMixSample tms)
{
// Assume weights are normalized
return tm.weight.x * tms[0] +
tm.weight.y * tms[1] +
tm.weight.z * tms[2] +
tm.weight.w * tms[3];
}
vec3 terrain_mix(TerrainMix tm, TerrainMixSample3 tms3)
{
// Assume weights are normalized
return tm.weight.x * tms3[0] +
tm.weight.y * tms3[1] +
tm.weight.z * tms3[2] +
tm.weight.w * tms3[3];
}
#if TERRAIN_PLANAR_TEXTURE_SAMPLE_COUNT == 3
// Triplanar mapping
@ -98,15 +168,12 @@ TerrainWeight _t_weight(TerrainCoord terrain_coord)
vec3 weight_signed = normalize(pow(abs(vary_vertex_normal), vec3(sharpness)));
weight_signed -= vec3(threshold);
TerrainWeight tw;
tw.weight = max(vec3(0), weight_signed);
// *NOTE: Make sure the threshold doesn't affect the materials
tw.weight = normalize(max(vec3(0), weight_signed));
ivec3 usage = ivec3(round(max(vec3(0), sign(weight_signed))));
tw.type = ((usage.x) * SAMPLE_X) |
((usage.y) * SAMPLE_Y) |
((usage.z) * SAMPLE_Z);
#if TERRAIN_DEBUG
tw.weight_signed = weight_signed;
tw.usage = usage;
#endif
return tw;
}
@ -256,59 +323,188 @@ vec4 terrain_texture_color(sampler2D tex, TerrainCoord terrain_coord)
}
#endif
// The goal of _tmix_sample and related functions is to only sample textures when necessary, ignoring if the weights are low.
// *TODO: Currently, there is much more switch..case branching than needed. This could be simplified by branching per-material rather than per-texture.
TerrainMixSample _tmix_sample(TerrainMix tm, TerrainCoord texcoord, sampler2D tex0, sampler2D tex1, sampler2D tex2, sampler2D tex3)
{
TerrainMixSample tms;
switch (tm.type & MIX_X)
{
case MIX_X:
tms[0] = terrain_texture(tex0, texcoord);
break;
default:
tms[0] = vec4(1.0, 0.0, 1.0, 1.0);
break;
}
switch (tm.type & MIX_Y)
{
case MIX_Y:
tms[1] = terrain_texture(tex1, texcoord);
break;
default:
tms[1] = vec4(1.0, 0.0, 1.0, 1.0);
break;
}
switch (tm.type & MIX_Z)
{
case MIX_Z:
tms[2] = terrain_texture(tex2, texcoord);
break;
default:
tms[2] = vec4(1.0, 0.0, 1.0, 1.0);
break;
}
switch (tm.type & MIX_W)
{
case MIX_W:
tms[3] = terrain_texture(tex3, texcoord);
break;
default:
tms[3] = vec4(1.0, 0.0, 1.0, 1.0);
break;
}
return tms;
}
TerrainMixSample _tmix_sample_color(TerrainMix tm, TerrainCoord texcoord, sampler2D tex0, sampler2D tex1, sampler2D tex2, sampler2D tex3)
{
TerrainMixSample tmix;
switch (tm.type & MIX_X)
{
case MIX_X:
tmix[0] = terrain_texture_color(tex0, texcoord);
break;
default:
tmix[0] = vec4(1.0, 0.0, 1.0, 1.0);
break;
}
switch (tm.type & MIX_Y)
{
case MIX_Y:
tmix[1] = terrain_texture_color(tex1, texcoord);
break;
default:
tmix[1] = vec4(1.0, 0.0, 1.0, 1.0);
break;
}
switch (tm.type & MIX_Z)
{
case MIX_Z:
tmix[2] = terrain_texture_color(tex2, texcoord);
break;
default:
tmix[2] = vec4(1.0, 0.0, 1.0, 1.0);
break;
}
switch (tm.type & MIX_W)
{
case MIX_W:
tmix[3] = terrain_texture_color(tex3, texcoord);
break;
default:
tmix[3] = vec4(1.0, 0.0, 1.0, 1.0);
break;
}
return tmix;
}
TerrainMixSample3 _tmix_sample_normal(TerrainMix tm, TerrainCoord texcoord, sampler2D tex0, sampler2D tex1, sampler2D tex2, sampler2D tex3)
{
TerrainMixSample3 tmix3;
switch (tm.type & MIX_X)
{
case MIX_X:
tmix3[0] = terrain_texture_normal(tex0, texcoord);
break;
default:
tmix3[0] = vec3(1.0, 0.0, 1.0);
break;
}
switch (tm.type & MIX_Y)
{
case MIX_Y:
tmix3[1] = terrain_texture_normal(tex1, texcoord);
break;
default:
tmix3[1] = vec3(1.0, 0.0, 1.0);
break;
}
switch (tm.type & MIX_Z)
{
case MIX_Z:
tmix3[2] = terrain_texture_normal(tex2, texcoord);
break;
default:
tmix3[2] = vec3(1.0, 0.0, 1.0);
break;
}
switch (tm.type & MIX_W)
{
case MIX_W:
tmix3[3] = terrain_texture_normal(tex3, texcoord);
break;
default:
tmix3[3] = vec3(1.0, 0.0, 1.0);
break;
}
return tmix3;
}
vec3 sample_and_mix_color3(float alpha1, float alpha2, float alphaFinal, TerrainCoord texcoord, vec3[4] factors, sampler2D tex0, sampler2D tex1, sampler2D tex2, sampler2D tex3)
{
vec3[4] samples;
samples[0] = terrain_texture(tex0, texcoord).xyz;
samples[1] = terrain_texture(tex1, texcoord).xyz;
samples[2] = terrain_texture(tex2, texcoord).xyz;
samples[3] = terrain_texture(tex3, texcoord).xyz;
samples[0] = srgb_to_linear(samples[0]);
samples[1] = srgb_to_linear(samples[1]);
samples[2] = srgb_to_linear(samples[2]);
samples[3] = srgb_to_linear(samples[3]);
samples[0] *= factors[0];
samples[1] *= factors[1];
samples[2] *= factors[2];
samples[3] *= factors[3];
return terrain_mix(samples, alpha1, alpha2, alphaFinal);
TerrainMix tm = _t_mix(alpha1, alpha2, alphaFinal);
TerrainMixSample tms = _tmix_sample_color(tm, texcoord, tex0, tex1, tex2, tex3);
vec3[4] tms3;
tms3[0] = tms[0].xyz;
tms3[1] = tms[1].xyz;
tms3[2] = tms[2].xyz;
tms3[3] = tms[3].xyz;
tms3[0] *= factors[0];
tms3[1] *= factors[1];
tms3[2] *= factors[2];
tms3[3] *= factors[3];
return terrain_mix(tm, tms3);
}
vec4 sample_and_mix_color4(float alpha1, float alpha2, float alphaFinal, TerrainCoord texcoord, vec4[4] factors, sampler2D tex0, sampler2D tex1, sampler2D tex2, sampler2D tex3)
{
vec4[4] samples;
samples[0] = terrain_texture_color(tex0, texcoord);
samples[1] = terrain_texture_color(tex1, texcoord);
samples[2] = terrain_texture_color(tex2, texcoord);
samples[3] = terrain_texture_color(tex3, texcoord);
samples[0] *= factors[0];
samples[1] *= factors[1];
samples[2] *= factors[2];
samples[3] *= factors[3];
return terrain_mix(samples, alpha1, alpha2, alphaFinal);
TerrainMix tm = _t_mix(alpha1, alpha2, alphaFinal);
TerrainMixSample tms = _tmix_sample_color(tm, texcoord, tex0, tex1, tex2, tex3);
tms[0] *= factors[0];
tms[1] *= factors[1];
tms[2] *= factors[2];
tms[3] *= factors[3];
return terrain_mix(tm, tms);
}
vec3 sample_and_mix_vector3(float alpha1, float alpha2, float alphaFinal, TerrainCoord texcoord, vec3[4] factors, sampler2D tex0, sampler2D tex1, sampler2D tex2, sampler2D tex3)
{
vec3[4] samples;
samples[0] = terrain_texture(tex0, texcoord).xyz;
samples[1] = terrain_texture(tex1, texcoord).xyz;
samples[2] = terrain_texture(tex2, texcoord).xyz;
samples[3] = terrain_texture(tex3, texcoord).xyz;
samples[0] *= factors[0];
samples[1] *= factors[1];
samples[2] *= factors[2];
samples[3] *= factors[3];
return terrain_mix(samples, alpha1, alpha2, alphaFinal);
TerrainMix tm = _t_mix(alpha1, alpha2, alphaFinal);
TerrainMixSample tms = _tmix_sample(tm, texcoord, tex0, tex1, tex2, tex3);
vec3[4] tms3;
tms3[0] = tms[0].xyz;
tms3[1] = tms[1].xyz;
tms3[2] = tms[2].xyz;
tms3[3] = tms[3].xyz;
tms3[0] *= factors[0];
tms3[1] *= factors[1];
tms3[2] *= factors[2];
tms3[3] *= factors[3];
return terrain_mix(tm, tms3);
}
// Returns the unpacked normal texture in range [-1, 1]
vec3 sample_and_mix_normal(float alpha1, float alpha2, float alphaFinal, TerrainCoord texcoord, sampler2D tex0, sampler2D tex1, sampler2D tex2, sampler2D tex3)
{
vec3[4] samples;
samples[0] = terrain_texture_normal(tex0, texcoord).xyz;
samples[1] = terrain_texture_normal(tex1, texcoord).xyz;
samples[2] = terrain_texture_normal(tex2, texcoord).xyz;
samples[3] = terrain_texture_normal(tex3, texcoord).xyz;
return terrain_mix(samples, alpha1, alpha2, alphaFinal);
TerrainMix tm = _t_mix(alpha1, alpha2, alphaFinal);
TerrainMixSample3 tms3 = _tmix_sample_normal(tm, texcoord, tex0, tex1, tex2, tex3);
return terrain_mix(tm, tms3);
}