Blacksite/assets/shaders/terrain_layers.wgsl
Rbanh d1a56f77cc
Some checks are pending
CI / Format, lint, test, build (push) Waiting to run
Add terrain material layer painting
2026-07-12 21:00:13 -04:00

117 lines
4.8 KiB
WebGPU Shading Language

#import bevy_pbr::{
pbr_fragment::pbr_input_from_standard_material,
pbr_functions::{alpha_discard, calculate_tbn_mikktspace},
}
#ifdef PREPASS_PIPELINE
#import bevy_pbr::{
prepass_io::{VertexOutput, FragmentOutput},
pbr_deferred_functions::deferred_output,
}
#else
#import bevy_pbr::{
forward_io::{VertexOutput, FragmentOutput},
pbr_functions::{apply_pbr_lighting, main_pass_post_lighting_processing},
}
#endif
struct TerrainLayerUniform {
base_colors: array<vec4<f32>, 4>,
properties: array<vec4<f32>, 4>,
uv_scales: vec4<f32>,
base_texture_enabled: vec4<f32>,
}
@group(#{MATERIAL_BIND_GROUP}) @binding(100) var<uniform> terrain_layers: TerrainLayerUniform;
@group(#{MATERIAL_BIND_GROUP}) @binding(101) var base_color0: texture_2d<f32>;
@group(#{MATERIAL_BIND_GROUP}) @binding(102) var base_sampler0: sampler;
@group(#{MATERIAL_BIND_GROUP}) @binding(103) var normal0: texture_2d<f32>;
@group(#{MATERIAL_BIND_GROUP}) @binding(104) var normal_sampler0: sampler;
@group(#{MATERIAL_BIND_GROUP}) @binding(105) var base_color1: texture_2d<f32>;
@group(#{MATERIAL_BIND_GROUP}) @binding(106) var base_sampler1: sampler;
@group(#{MATERIAL_BIND_GROUP}) @binding(107) var normal1: texture_2d<f32>;
@group(#{MATERIAL_BIND_GROUP}) @binding(108) var normal_sampler1: sampler;
@group(#{MATERIAL_BIND_GROUP}) @binding(109) var base_color2: texture_2d<f32>;
@group(#{MATERIAL_BIND_GROUP}) @binding(110) var base_sampler2: sampler;
@group(#{MATERIAL_BIND_GROUP}) @binding(111) var normal2: texture_2d<f32>;
@group(#{MATERIAL_BIND_GROUP}) @binding(112) var normal_sampler2: sampler;
@group(#{MATERIAL_BIND_GROUP}) @binding(113) var base_color3: texture_2d<f32>;
@group(#{MATERIAL_BIND_GROUP}) @binding(114) var base_sampler3: sampler;
@group(#{MATERIAL_BIND_GROUP}) @binding(115) var normal3: texture_2d<f32>;
@group(#{MATERIAL_BIND_GROUP}) @binding(116) var normal_sampler3: sampler;
fn layer_base(index: u32, uv: vec2<f32>) -> vec4<f32> {
if index == 0u { return textureSample(base_color0, base_sampler0, uv); }
if index == 1u { return textureSample(base_color1, base_sampler1, uv); }
if index == 2u { return textureSample(base_color2, base_sampler2, uv); }
return textureSample(base_color3, base_sampler3, uv);
}
fn layer_normal(index: u32, uv: vec2<f32>) -> vec3<f32> {
var sample = vec3<f32>(0.5, 0.5, 1.0);
if index == 0u { sample = textureSample(normal0, normal_sampler0, uv).xyz; }
if index == 1u { sample = textureSample(normal1, normal_sampler1, uv).xyz; }
if index == 2u { sample = textureSample(normal2, normal_sampler2, uv).xyz; }
if index == 3u { sample = textureSample(normal3, normal_sampler3, uv).xyz; }
return normalize(sample * 2.0 - 1.0);
}
@fragment
fn fragment(in: VertexOutput, @builtin(front_facing) is_front: bool) -> FragmentOutput {
var pbr_input = pbr_input_from_standard_material(in, is_front);
#ifdef VERTEX_UVS_A
let uv = in.uv;
#else
let uv = vec2<f32>(0.0);
#endif
#ifdef VERTEX_COLORS
var weights = max(in.color, vec4<f32>(0.0));
#else
var weights = vec4<f32>(1.0, 0.0, 0.0, 0.0);
#endif
let enabled = vec4<f32>(
terrain_layers.properties[0].w,
terrain_layers.properties[1].w,
terrain_layers.properties[2].w,
terrain_layers.properties[3].w,
);
weights *= enabled;
let total = dot(weights, vec4<f32>(1.0));
weights = select(vec4<f32>(1.0, 0.0, 0.0, 0.0), weights / total, total > 0.0001);
var color = vec4<f32>(0.0);
var normal_ts = vec3<f32>(0.0);
var metallic = 0.0;
var roughness = 0.0;
for (var index = 0u; index < 4u; index += 1u) {
let weight = weights[index];
let layer_uv = uv * terrain_layers.uv_scales[index];
let base_sample = mix(
vec4<f32>(1.0),
layer_base(index, layer_uv),
terrain_layers.base_texture_enabled[index],
);
color += base_sample * terrain_layers.base_colors[index] * weight;
let normal_enabled = terrain_layers.properties[index].z;
normal_ts += mix(vec3<f32>(0.0, 0.0, 1.0), layer_normal(index, layer_uv), normal_enabled) * weight;
metallic += terrain_layers.properties[index].x * weight;
roughness += terrain_layers.properties[index].y * weight;
}
pbr_input.material.base_color = alpha_discard(pbr_input.material, color);
pbr_input.material.metallic = clamp(metallic, 0.0, 1.0);
pbr_input.material.perceptual_roughness = clamp(roughness, 0.001, 1.0);
#ifdef VERTEX_TANGENTS
let tbn = calculate_tbn_mikktspace(pbr_input.world_normal, in.world_tangent);
let n = normalize(normal_ts);
pbr_input.N = normalize(n.x * tbn[0] + n.y * tbn[1] + n.z * tbn[2]);
#endif
#ifdef PREPASS_PIPELINE
return deferred_output(in, pbr_input);
#else
var out: FragmentOutput;
out.color = apply_pbr_lighting(pbr_input);
out.color = main_pass_post_lighting_processing(pbr_input, out.color);
return out;
#endif
}