16
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-07-21 21:49:19 +02:00

add refraction support for materials; two pass rain shader

This commit is contained in:
Wls50
2025-11-23 23:45:06 +01:00
committed by Hirek
parent 90538c8878
commit ff50fdf110
17 changed files with 614 additions and 145 deletions

View File

@@ -0,0 +1,15 @@
Texture2D<float> g_DepthTexture : register(t0);
RWTexture2D<float> g_Output : register(u0);
sampler depth_sampler : register(s0);
[numthreads(8, 8, 1)]
void main(uint3 PixCoord : SV_DispatchThreadID) {
uint2 dimensions;
g_Output.GetDimensions(dimensions.x, dimensions.y);
float2 co = float2(PixCoord.xy) / float2(dimensions);
float4 depths = g_DepthTexture.GatherRed(depth_sampler, co);
g_Output[PixCoord.xy] = min(min(depths.x, depths.y), min(depths.z, depths.w));
}

View File

@@ -32,13 +32,19 @@ cbuffer VertexConstants : register(b0) {
#include "manul/draw_constants.hlsli"
#ifdef NO_JITTER
#define PROJECTION g_Projection
#else
#define PROJECTION g_JitteredProjection
#endif
VertexOutput main(in VertexInput vs_in) {
VertexOutput result;
float4x3 model_view = GetModelView();
float4x3 model_view_history = GetModelViewHistory();
float3 view_space_position = mul(float4(vs_in.m_Position, 1.), model_view).xyz;
result.m_TexCoord = vs_in.m_TexCoord;
result.m_PositionSV = mul(g_JitteredProjection, float4(view_space_position, 1.));
result.m_PositionSV = mul(PROJECTION, float4(view_space_position, 1.));
#ifndef PREPASS
result.m_Normal = mul(float4(vs_in.m_Normal, 0.), model_view).xyz;
result.m_Position = view_space_position;

View File

@@ -48,6 +48,7 @@ struct PixelInput {
#include "sky.hlsli"
Texture2D<float> g_GbufferDepth : register(t12);
Texture2D<float3> g_LitScene : register(t13);
#endif
void MaterialPass(inout MaterialData material);
@@ -61,12 +62,14 @@ PixelOutput main(in PixelInput ps_in) {
material.m_Tangent = ps_in.m_Tangent.xyz;
material.m_Bitangent = ps_in.m_Tangent.w * cross(ps_in.m_Normal, ps_in.m_Tangent.xyz);
material.m_TexCoord = ps_in.m_TexCoord;
material.m_ScreenCoord = uv;
material.m_PixelCoord = ps_in.m_PositionSV.xy;
material.m_PositionNDC = ps_in.m_PositionCS / ps_in.m_PositionCS.w;
material.m_MaterialAlbedoAlpha = float4(1., 1., 1., 1.);
material.m_MaterialEmission = float3(0., 0., 0.);
material.m_MaterialParams = float4(0., .5, 1., .5); // Metalness.Roughness.Occlusion.Specular
material.m_MaterialNormal = float3(0., 0., 1.);
material.m_RefractionOffset = float2(0., 0.);
MaterialPass(material);
material.m_MaterialAlbedoAlpha.rgb = saturate(material.m_MaterialAlbedoAlpha.rgb);
material.m_MaterialEmission = max(material.m_MaterialEmission, 0.);
@@ -95,6 +98,12 @@ PixelOutput main(in PixelInput ps_in) {
ps_out.m_Motion = (ps_in.m_HistoryPositionCS.xy / ps_in.m_HistoryPositionCS.w) - (ps_in.m_PositionCS.xy / ps_in.m_PositionCS.w);
ps_out.m_Motion = ps_out.m_Motion * float2(.5, -.5);
#endif
#if (PASS & FORWARD_LIGHTING) && defined(REFRACTION)
float3 scene_color = g_LitScene.Sample(g_SkySampler, material.m_ScreenCoord + material.m_RefractionOffset * float2(1., -1.));
ps_out.m_Color.rgb += (1. - ps_out.m_Color.a) * scene_color;
ps_out.m_Color.a = 1.;
#endif
return ps_out;
}

View File

@@ -9,12 +9,14 @@ struct MaterialData {
float3 m_Bitangent;
float3 m_Normal;
float2 m_TexCoord;
float2 m_ScreenCoord;
uint2 m_PixelCoord;
float4 m_PositionNDC;
float4 m_MaterialAlbedoAlpha;
float3 m_MaterialEmission;
float4 m_MaterialParams; // Metalness.Roughness.Occlusion.Specular
float3 m_MaterialNormal;
float2 m_RefractionOffset;
};
#endif

View File

@@ -12,6 +12,7 @@ cbuffer DrawConstants : register(b2) {
float4 g_WiperPos;
float4 g_WiperTimerOut;
float4 g_WiperTimerReturn;
float g_VerticalFov;
}
float2 PixelToCS(in float2 pixel, in float2 size) {

View File

@@ -141,6 +141,10 @@ shaders:
hint: color
default: white
no_filter: true
rain:
binding: 3
hint: linear
default: system/raindrops_buffer
masked_shadow_texture: diffuse
source: ps_windshield_rain
utility:
@@ -165,6 +169,14 @@ shaders:
entrypoint: main
definitions:
PREPASS: 1
windshield_rain_anim:
source: ps_windshield_rain_anim
target: pixel
entrypoint: main
max_depth_4x4:
source: cs_downsample_depth
target: compute
entrypoint: main
# Contact shadows
# TODO Depth conversion is broken since converting to reversed depth buffer
contact_shadows:
@@ -181,6 +193,12 @@ shaders:
source: default_vertex
target: vertex
entrypoint: main
default_vertex_no_jitter:
source: default_vertex
target: vertex
entrypoint: main
definitions:
NO_JITTER: 1
default_prepass_vertex:
source: default_vertex
target: vertex

View File

@@ -1,7 +1,8 @@
#define REFRACTION 1
#include "manul/math.hlsli"
#include "manul/material.hlsli"
#include "manul/color_transform.hlsli"
#include "manul/random.hlsli"
sampler diffuse_sampler : register(s0);
sampler raindrop_sampler : register(s1);
@@ -9,163 +10,76 @@ sampler wipermask_sampler : register(s2);
Texture2D<float4> diffuse : register(t0);
Texture2D<float4> raindropsatlas : register(t1);
Texture2D<float4> wipermask : register(t2);
Texture2D<float> rain : register(t3);
float4 getDropTex(float choice, float2 uv) {
float2 offset;
if (choice < .25) offset = float2(0.0, 0.0);
else if (choice < .5) offset = float2(0.5, 0.0);
else if (choice < .75) offset = float2(0.0, 0.5);
else offset = float2(0.5, 0.5);
return raindropsatlas.Sample(raindrop_sampler, offset + uv * 0.5);
// Project the surface gradient (dhdx, dhdy) onto the surface (n, dpdx, dpdy)
float3 CalculateSurfaceGradient(float3 n, float3 dpdx, float3 dpdy, float dhdx, float dhdy)
{
float3 r1 = cross(dpdy, n);
float3 r2 = cross(n, dpdx);
return (r1 * dhdx + r2 * dhdy) / dot(dpdx, r1);
}
float GetMixFactor(in float2 co, out float side);
// Move the normal away from the surface normal in the opposite surface gradient direction
float3 PerturbNormal(float3 n, float3 dpdx, float3 dpdy, float dhdx, float dhdy)
{
return normalize(n - CalculateSurfaceGradient(n, dpdx, dpdy, dhdx, dhdy));
}
// Calculate the surface normal using screen-space partial derivatives of the height field
float3 CalculateSurfaceNormal(float3 position, float3 normal, float2 gradient)
{
float3 dpdx = ddx(position);
float3 dpdy = ddy(position);
float dhdx = gradient.x;
float dhdy = gradient.y;
return PerturbNormal(normal, dpdx, dpdy, dhdx, dhdy);
}
void MaterialPass(inout MaterialData material) {
#if PASS & FORWARD_LIGHTING
const float specular_intensity = 1.;
const float wobble_strength = .002;
const float wobble_speed = 30.;
MaterialData material_glass = material;
float4 tex_color = diffuse.Sample(diffuse_sampler, material.m_TexCoord);
if (tex_color.a < .01) discard;
uint2 size;
rain.GetDimensions(size.x, size.y);
float droplet_distance = rain.Sample(raindrop_sampler, material.m_ScreenCoord);
float droplet_distance_x = rain.Sample(raindrop_sampler, material.m_ScreenCoord, int2(1, 0));
float droplet_distance_y = rain.Sample(raindrop_sampler, material.m_ScreenCoord, int2(0, 1));
float2 gradient = float2(droplet_distance_x - droplet_distance, droplet_distance_y - droplet_distance);
material_glass.m_MaterialAlbedoAlpha.xyz = 0.;
material_glass.m_MaterialNormal = material.m_Normal;
material_glass.m_MaterialParams.g = .2;
float3 normal = CalculateSurfaceNormal(material_glass.m_Position, material_glass.m_Normal, gradient * -.005);
material_glass.m_MaterialNormal = normal;
float cosTheta = saturate(dot(-normalize(material_glass.m_Position), normal));
material.m_MaterialAlbedoAlpha.a = lerp(.1, FresnelSchlickRoughness(cosTheta, .04, 0.), smoothstep(0., .15, droplet_distance));
float2 rainCoord = material.m_TexCoord;
float gridSize = ceil(200.);
float3 normal_world = mul((float3x3)g_InverseModelView, material_glass.m_MaterialNormal);
const float numDrops = 20000.;
const float cycleDuration = 4.;
float4 glass_lit;
ApplyMaterialLighting(glass_lit, material_glass, material_glass.m_PixelCoord);
float squareMin = .5 / gridSize;
float squareMax = 1.2 / gridSize;
material.m_MaterialEmission = glass_lit * smoothstep(0., .15, droplet_distance) * saturate(normal_world.y * .5 + .5);
float2 cell = floor(rainCoord * gridSize);
material.m_MaterialAlbedoAlpha.xyz = 0.;
material.m_RefractionOffset = normal.xy * (.005 / (length(material.m_Position) * tan(.5 * g_VerticalFov))) * smoothstep(0., .15, droplet_distance);
float3 dropLayer = 0.;
float dropMaskSum = 0.;
float glass_opacity = FresnelSchlickRoughness(saturate(dot(-normalize(material.m_Position), material.m_Normal)), .2, 0.);
material.m_MaterialEmission = lerp(material.m_MaterialEmission, 0., glass_opacity);
material.m_MaterialAlbedoAlpha.a = lerp(material.m_MaterialAlbedoAlpha.a, 1., glass_opacity);
material.m_MaterialParams.g = .05;
material.m_MaterialNormal = material.m_Normal;
// Grid of 9 droplets in immediate neighbourhood
[unroll]
for (int oy = -1; oy <= 1; ++oy) {
[unroll]
for (int ox = -1; ox <= 1; ++ox) {
float2 neighborCell = cell + float2(ox, oy);
float2 neighborCenter = (neighborCell + .5) / gridSize;
float side;
float mixFactor = GetMixFactor(neighborCenter, side);
uint seed = Hash(uint3(neighborCell, side));
if(mixFactor < RandF(seed)) {
continue;
}
// Show a percentage of droplets given by rain intensity param
float activationSeed = RandF(seed);
if (activationSeed > g_RainParams.x)
continue; // kropla nieaktywna
// Randomly modulate droplet center & size
float2 dropCenter = (neighborCell + float2(RandF(seed), RandF(seed))) / gridSize;
float squareSize = lerp(squareMin, squareMax, RandF(seed));
float lifeTime = g_Time + RandF(seed) * cycleDuration;
float phase = frac(lifeTime / cycleDuration);
float active = saturate(1. - phase);
// Gravity influence (TODO add vehicle speed & wind here!)
float gravityStart = .5;
float gravityPhase = smoothstep(gravityStart, 1., phase);
float dropMass = lerp(.3, 1.2, RandF(seed));
float gravitySpeed = .15 * dropMass;
float2 gravityOffset = float2(0., gravityPhase * gravitySpeed * phase);
// Random wobble
bool hasWobble = (RandF(seed) < .10);
float2 wobbleOffset = 0.;
if (hasWobble && gravityPhase > 0.) {
float intensity = sin(g_Time * wobble_speed + RandF(seed) * 100.) * wobble_strength * gravityPhase;
wobbleOffset = float2(intensity, 0.);
}
float2 slideOffset = gravityOffset + wobbleOffset;
// Flatten droplets influenced by gravity
float flattenAmount = smoothstep(0.1, 0.5, gravityPhase);
float flattenX = lerp(1.0, 0.4, flattenAmount);
float stretchY = lerp(1.0, 1.6, flattenAmount);
// Droplet local position & mask
float2 diff = (rainCoord + slideOffset) - dropCenter;
diff.x *= 1.0 / flattenX;
diff.y *= 1.0 / stretchY;
float mask = smoothstep(squareSize * 0.5, squareSize * 0.45, max(abs(diff.x), abs(diff.y)));
if (mask > .001) {
float2 localUV = (diff + squareSize * 0.5) / squareSize;
float choice = RandF(seed);
float4 dropTex = getDropTex(choice, localUV);
float sharpAlpha = smoothstep(0.3, 0.9, dropTex.a);
float colorLuma = length(dropTex.rgb);
float alphaRange = smoothstep(0.1, 0.3, colorLuma);
float blackAlpha = lerp(0.25, 0.85, alphaRange);
dropLayer += dropTex.rgb * sharpAlpha * active * blackAlpha * mask;
dropMaskSum += sharpAlpha * active * blackAlpha * mask;
}
}
}
float3 finalMix = dropLayer;
float alphaOut = clamp(dropMaskSum, 0.0, 1.0);
material.m_MaterialAlbedoAlpha = float4(finalMix, alphaOut);
{ // Overlay windshield texture with alpha
material.m_MaterialAlbedoAlpha.xyz = lerp(material.m_MaterialAlbedoAlpha.xyz, tex_color.xyz, tex_color.a);
material.m_MaterialAlbedoAlpha.a = lerp(material.m_MaterialAlbedoAlpha.a, 1., tex_color.a);
material.m_MaterialEmission.xyz = lerp(material.m_MaterialEmission.xyz, 0., tex_color.a);
material.m_MaterialParams.g = lerp(material.m_MaterialParams.g, float4(0., .5, 1., .5), tex_color.a);
}
#endif
}
#if PASS & FORWARD_LIGHTING
float GetMixFactor(in float2 co, out float side) {
float4 movePhase = g_WiperPos;
bool4 is_out = movePhase <= 1.;
movePhase = select(is_out, movePhase, 2. - movePhase);
float4 mask = wipermask.Sample(wipermask_sampler, co);
float4 areaMask = step(.001, mask);
float4 maskVal = select(is_out, mask, 1. - mask);
float4 wipeWidth = smoothstep(1., .9, movePhase) * .25;
float4 cleaned = smoothstep(movePhase - wipeWidth, movePhase, maskVal) * areaMask;
float4 side_v = step(maskVal, movePhase);
cleaned *= side_v;
side_v = select(is_out, 1. - side_v, side_v);
// "regeneration", raindrops gradually returning after wiper pass:
float4 regenPhase = saturate((g_Time - lerp(g_WiperTimerOut, g_WiperTimerReturn, side_v) - .2) / g_RainParams.y);
side_v = lerp(0., 1. - side_v, areaMask);
float4 factor_v = lerp(1., regenPhase * (1. - cleaned), areaMask);
side = 0.;
float out_factor = 1.;
// Find out the wiper blade that influences given grid cell the most
[unroll]
for(int i = 0; i < 4; ++i)
{
bool is_candidate = factor_v[i] < out_factor;
out_factor = select(is_candidate, factor_v[i], out_factor);
side = select(is_candidate, side_v[i], side);
}
return out_factor;
}
#endif

View File

@@ -0,0 +1,162 @@
#include "manul/draw_constants.hlsli"
#include "manul/random.hlsli"
#include "manul/view_data.hlsli"
struct PixelInput {
float3 m_Position : Position;
float3 m_Normal : Normal;
float2 m_TexCoord : TexCoord;
float4 m_Tangent : Tangent;
float4 m_PositionSV : SV_Position;
float4 m_PositionCS : PositionCS;
};
sampler raindrop_sampler : register(s0);
sampler wipermask_sampler : register(s1);
Texture2D<float> raindropsatlas : register(t0);
Texture2D<float4> wipermask : register(t1);
float getDropTex(float choice, float2 uv) {
float2 offset;
if (choice < .25) offset = float2(0.0, 0.0);
else if (choice < .5) offset = float2(0.5, 0.0);
else if (choice < .75) offset = float2(0.0, 0.5);
else offset = float2(0.5, 0.5);
return raindropsatlas.Sample(raindrop_sampler, offset + uv * 0.5);
}
float GetMixFactor(in float2 co, out float side);
float main(in PixelInput input) : SV_Target0 {
const float specular_intensity = 1.;
const float wobble_strength = .002;
const float wobble_speed = 30.;
//float4 tex_color = diffuse.Sample(diffuse_sampler, material.m_TexCoord);
//if (tex_color.a < .01) discard;
float2 rainCoord = input.m_TexCoord;
float gridSize = ceil(200.);
const float numDrops = 20000.;
const float cycleDuration = 4.;
float squareMin = 1. / gridSize;
float squareMax = 2.5 / gridSize;
float2 cell = floor(rainCoord * gridSize);
float3 dropLayer = 0.;
float dropMaskSum = 0.;
float output = 0.;
// Grid of 9 droplets in immediate neighbourhood
[unroll]
for (int oy = -1; oy <= 1; ++oy) {
[unroll]
for (int ox = -1; ox <= 1; ++ox) {
float2 neighborCell = cell + float2(ox, oy);
float2 neighborCenter = (neighborCell + .5) / gridSize;
float side;
float mixFactor = GetMixFactor(neighborCenter, side);
uint seed = Hash(uint3(neighborCell, side));
if(mixFactor < RandF(seed)) {
continue;
}
// Show a percentage of droplets given by rain intensity param
float activationSeed = RandF(seed);
if (activationSeed > g_RainParams.x)
continue; // kropla nieaktywna
// Randomly modulate droplet center & size
float2 dropCenter = (neighborCell + float2(RandF(seed), RandF(seed))) / gridSize;
float squareSize = lerp(squareMin, squareMax, RandF(seed));
float lifeTime = g_Time + RandF(seed) * cycleDuration;
float phase = frac(lifeTime / cycleDuration);
float active = saturate(1. - phase);
// Gravity influence (TODO add vehicle speed & wind here!)
float gravityStart = .5;
float gravityPhase = smoothstep(gravityStart, 1., phase);
float dropMass = lerp(.3, 1.2, RandF(seed));
float gravitySpeed = .15 * dropMass;
float2 gravityOffset = float2(0., gravityPhase * gravitySpeed * phase);
// Random wobble
bool hasWobble = (RandF(seed) < .10);
float2 wobbleOffset = 0.;
if (hasWobble && gravityPhase > 0.) {
float intensity = sin(g_Time * wobble_speed + RandF(seed) * 100.) * wobble_strength * gravityPhase;
wobbleOffset = float2(intensity, 0.);
}
float2 slideOffset = gravityOffset + wobbleOffset;
// Flatten droplets influenced by gravity
float flattenAmount = smoothstep(0.1, 0.5, gravityPhase);
float flattenX = lerp(1.0, 0.4, flattenAmount);
float stretchY = lerp(1.0, 1.6, flattenAmount);
// Droplet local position & mask
float2 diff = (rainCoord + slideOffset) - dropCenter;
diff.x *= 1.0 / flattenX;
diff.y *= 1.0 / stretchY;
float mask = smoothstep(squareSize * 0.5, squareSize * 0.45, max(abs(diff.x), abs(diff.y)));
if (mask > .001) {
float2 localUV = (diff + squareSize * 0.5) / squareSize;
float choice = RandF(seed);
float dropTex = getDropTex(choice, localUV) * mask;
output = max(output, dropTex);
}
}
}
return output;
}
float GetMixFactor(in float2 co, out float side) {
float4 movePhase = g_WiperPos;
bool4 is_out = movePhase <= 1.;
movePhase = select(is_out, movePhase, 2. - movePhase);
float4 mask = wipermask.Sample(wipermask_sampler, co);
float4 areaMask = step(.001, mask);
float4 maskVal = select(is_out, mask, 1. - mask);
float4 wipeWidth = smoothstep(1., .9, movePhase) * .25;
float4 cleaned = smoothstep(movePhase - wipeWidth, movePhase, maskVal) * areaMask;
float4 side_v = step(maskVal, movePhase);
cleaned *= side_v;
side_v = select(is_out, 1. - side_v, side_v);
// "regeneration", raindrops gradually returning after wiper pass:
float4 regenPhase = saturate((g_Time - lerp(g_WiperTimerOut, g_WiperTimerReturn, side_v) - .2) / g_RainParams.y);
side_v = lerp(0., 1. - side_v, areaMask);
float4 factor_v = lerp(1., regenPhase * (1. - cleaned), areaMask);
side = 0.;
float out_factor = 1.;
// Find out the wiper blade that influences given grid cell the most
[unroll]
for(int i = 0; i < 4; ++i)
{
bool is_candidate = factor_v[i] < out_factor;
out_factor = select(is_candidate, factor_v[i], out_factor);
side = select(is_candidate, side_v[i], side);
}
return out_factor;
}