diff --git a/data/examples/shaders/transition/spin-blur.effect b/data/examples/shaders/transition/spin-blur.effect new file mode 100644 index 00000000..b4a3ec53 --- /dev/null +++ b/data/examples/shaders/transition/spin-blur.effect @@ -0,0 +1,340 @@ +// Copyright 2021 Michael Fabian Dirks +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holder nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +//------------------------------------------------------------------------------ +// Configuration +//------------------------------------------------------------------------------ +#define IS_TRANSITION + +//------------------------------------------------------------------------------ +// Defines +//------------------------------------------------------------------------------ +// Variations of PI/TAU +#define PI 3.141592653 +#define TAU 6.283185307 +#define PIm2 6.283185307 +#define PIb2 1.570796326 +#define PIb4 0.785398163 + +// Phi/Φ = Golden Ratio +#define PHI 1.61803398874989484820459 + +// e (Eulers Constant) +#define EULERS_CONSTANT 2,7182818284590452353602874713527 + +// Degrees <-> Radians Conversion +#define TO_RAD(x) (x * 0.017453292) +#define TO_DEG(x) (x * 57.295779513) + +//------------------------------------------------------------------------------ +// Uniforms +//------------------------------------------------------------------------------ +uniform float4x4 ViewProj< + bool automatic = true; +>; + +uniform float4 Time< + bool automatic = true; +>; + +uniform float4 ViewSize< + bool automatic = true; +>; + +// Filter Support +#ifdef IS_FILTER +uniform texture2d InputA< + bool automatic = true; +>; +#endif + +// Transition Support +#ifdef IS_TRANSITION +uniform texture2d InputA< + bool automatic = true; +>; + +uniform texture2d InputB< + bool automatic = true; +>; + +uniform float TransitionTime< + bool automatic = true; +>; + +uniform int2 TransitionSize< + bool automatic = true; +>; +#endif + +uniform int RandomSeed< + bool automatic = true; +>; + +uniform float4x4 Random< + bool automatic = true; +>; + +//------------------------------------------------------------------------------ +// Structures +//------------------------------------------------------------------------------ +struct VertexInformation { + float4 position : POSITION; + float4 texcoord0 : TEXCOORD0; +}; + +//------------------------------------------------------------------------------ +// Samplers +//------------------------------------------------------------------------------ +sampler_state PointRepeatSampler { + Filter = Point; + AddressU = Repeat; + AddressV = Repeat; +}; +sampler_state LinearRepeatSampler { + Filter = Linear; + AddressU = Repeat; + AddressV = Repeat; +}; + +sampler_state PointMirrorSampler { + Filter = Point; + AddressU = Mirror; + AddressV = Mirror; +}; +sampler_state LinearMirrorSampler { + Filter = Linear; + AddressU = Mirror; + AddressV = Mirror; +}; + +sampler_state PointClampSampler { + Filter = Point; + AddressU = Clamp; + AddressV = Clamp; +}; +sampler_state LinearClampSampler { + Filter = Linear; + AddressU = Clamp; + AddressV = Clamp; +}; + +//------------------------------------------------------------------------------ +// Functions +//------------------------------------------------------------------------------ +VertexInformation DefaultVertexShader(VertexInformation vtx) { + vtx.position = mul(float4(vtx.position.xyz, 1.0), ViewProj); + return vtx; +}; + +float2 rotate2d(float2 v, float angle) { + float s = sin(angle); + float c = cos(angle); + float2x2 m = float2x2(c, -s, s, c); + return mul(m, v); +}; + +//------------------------------------------------------------------------------ +// Options +//------------------------------------------------------------------------------ + +uniform float2 _400_Offset< + string name = "Rotation Center"; + string field_type = "slider"; + string suffix = "%"; + float2 minimum = {-50., -50.}; + float2 maximum = {50., 50.}; + float2 scale = {.01, .01}; + float2 step = {.01, .01}; +> = {0., 0.}; + +uniform float _500_Rotation< + string name = "Rotation"; + string field_type = "slider"; + string suffix = "°"; + float minimum = -180.; + float maximum = 180.; + float scale = -1.; + float step = .01; +> = 90.; + +uniform float _600_BlurRotation< + string name = "Blur Rotation"; + string field_type = "slider"; + string suffix = "°"; + float minimum = -90.; + float maximum = 90.; + float scale = 1.; + float step = .01; +> = 30.; + +uniform float _700_TransitionRange< + string name = "Transition Range"; + string field_type = "slider"; + string suffix = "%"; + float minimum = 0.; + float maximum = 100.; + float scale = .01; + float step = .01; +> = 10.; + +uniform int _900_MaximumBlurSamples< + string name = "Max. Blur Samples"; + string description = "The maximum of samples to use for the Blur effect. Higher number looks nicer, but has way higher GPU usage."; + string field_type = "slider"; + string suffix = ""; + int minimum = 4; + int maximum = 128; + int scale = 1; + int step = 1; +> = 8; + +uniform bool _990_MirrorInsteadOfRepeat< + string name = "Mirror instead of Repeat"; +> = true; + +//------------------------------------------------------------------------------ +// Effect +//------------------------------------------------------------------------------ +// Edge is 50% progress +// Rotates n° forward/backward, then switches to B, then rotates back to zero in the other direction. +// While rotated, blurs around the same axis. +// Starts slow, speeds up to 100%, then slows down again. Looks like sine curve. + +float2 uv_to_xy(float2 uv) { + return ((uv - float2(.5, .5)) + _400_Offset) * TransitionSize.xy; +} + +float2 xy_to_uv(float2 xy) { + return ((xy / TransitionSize.xy) - _400_Offset) + float2(.5, .5); +} + +float4 SampleTexture(texture2d tex, float2 uv) { + if (_990_MirrorInsteadOfRepeat) { + return tex.Sample(LinearMirrorSampler, uv); + } else { + return tex.Sample(LinearRepeatSampler, uv); + } +} + +float4 sample_with_blur(texture2d tex, float2 xy, float max_angle, uint max_steps) { + float angle_step = max_angle / float(max_steps); + + float4 final = SampleTexture(tex, xy_to_uv(xy)); + for (uint step = 1; step <= max_steps; step++) { + float angle = angle_step * float(step); + float2 xy_p = rotate2d(xy, TO_RAD(angle)); + float2 xy_n = rotate2d(xy, TO_RAD(-angle)); + + final += SampleTexture(tex, xy_to_uv(xy_p)); + final += SampleTexture(tex, xy_to_uv(xy_n)); + } + final /= (max_steps * 2u + 1u); + + return final; +} + +float4 DefaultPixelShader(VertexInformation vtx) : TARGET { + // Precalculate some important information. + float2 uv = vtx.texcoord0.xy; + // - UV offset towards the "center". + float2 uv_offset = ((uv - float2(.5, .5)) + _400_Offset); + float2 xy = uv_to_xy(uv); + // - Distance to the "center" from the offset coordinates. + float distance_to_center = distance(uv_offset, float2(.0, 0.)); + // - Maximum Blur Angle + float max_blur_angle = _600_BlurRotation; + max_blur_angle *= sin(distance_to_center * PIb2); + max_blur_angle *= cos((TransitionTime * 2. + 1. ) * PI) * .5 + .5; + // - Maximum number of samples for blurring. + uint max_blur_samples = uint(_900_MaximumBlurSamples); // TODO: Calculate this value? + + // Calculate the angle of the effect, this goes to _500_Rotation over 0-50%, + // then goes back in reverse in order to give the illusion of a continuous + // motion. + float angle_a = sin((TransitionTime * 2.) * PIb2 - PIb2) * _500_Rotation + _500_Rotation; + float angle_b = sin((TransitionTime * 2. - 1.) * PIb2) * _500_Rotation - _500_Rotation ; + + // Generate the rotated XY position for sampling. + float2 xy_a = rotate2d(xy, TO_RAD(angle_a)); + float2 xy_b = rotate2d(xy, TO_RAD(angle_b)); + + // Calculate the weight of A and B. + // - Convert the transition point to a range from -1 to 1. + float weight_a = (TransitionTime - .5) * 2.; // We don't actually want this range, but it is easier to work with. + // - Offset it by the transition range. + weight_a += _700_TransitionRange; + // - Divide it by the transition range times two. + weight_a /= _700_TransitionRange * 2.; + // - Clamp it back to a range from 0 to 1. + weight_a = 1. - (cos((clamp(weight_a, 0., 1.) + 1.) * PI) * .5 + .5); // Curve + //weight_a = 1. - clamp(weight_a, 0., 1.); // Alternative linear + // - The weight for the B side is the one-minus of the A side. + float weight_b = 1. - weight_a; + + // Store some immediate information. + float4 color_a = float4(0., 0., 0., 0.); + float4 color_b = float4(0., 0., 0., 0.); + + // Only sample A and B if they are needed. + if (weight_a >= 0.001) { + color_a = sample_with_blur(InputA, xy_a, max_blur_angle, max_blur_samples); + } + if (weight_b >= 0.001) { + color_b = sample_with_blur(InputB, xy_b, max_blur_angle, max_blur_samples); + } + + // Return a blended color. + return lerp(color_a, color_b, weight_b); +}; + +technique Draw +{ + pass + { + vertex_shader = DefaultVertexShader(vtx); + pixel_shader = DefaultPixelShader(vtx); + }; +}; + +technique Version1_Normal +{ + pass + { + vertex_shader = DefaultVertexShader(vtx); + pixel_shader = DefaultPixelShader(vtx); + }; +}; + + +technique Version1_Mirror +{ + pass + { + vertex_shader = DefaultVertexShader(vtx); + pixel_shader = DefaultPixelShader(vtx); + }; +};