Commands/Shaders/Get-OBSClockAnalogShader.ps1
function Get-OBSClockAnalogShader { [Alias('Set-OBSClockAnalogShader','Add-OBSClockAnalogShader')] param( # Set the current_time_ms of OBSClockAnalogShader [Alias('current_time_ms')] [ComponentModel.DefaultBindingProperty('current_time_ms')] [Int32] $CurrentTimeMs, # Set the current_time_sec of OBSClockAnalogShader [Alias('current_time_sec')] [ComponentModel.DefaultBindingProperty('current_time_sec')] [Int32] $CurrentTimeSec, # Set the current_time_min of OBSClockAnalogShader [Alias('current_time_min')] [ComponentModel.DefaultBindingProperty('current_time_min')] [Int32] $CurrentTimeMin, # Set the current_time_hour of OBSClockAnalogShader [Alias('current_time_hour')] [ComponentModel.DefaultBindingProperty('current_time_hour')] [Int32] $CurrentTimeHour, # Set the hour_handle_color of OBSClockAnalogShader [Alias('hour_handle_color')] [ComponentModel.DefaultBindingProperty('hour_handle_color')] [Single[]] $HourHandleColor, # Set the minute_handle_color of OBSClockAnalogShader [Alias('minute_handle_color')] [ComponentModel.DefaultBindingProperty('minute_handle_color')] [Single[]] $MinuteHandleColor, # Set the second_handle_color of OBSClockAnalogShader [Alias('second_handle_color')] [ComponentModel.DefaultBindingProperty('second_handle_color')] [Single[]] $SecondHandleColor, # Set the outline_color of OBSClockAnalogShader [Alias('outline_color')] [ComponentModel.DefaultBindingProperty('outline_color')] [Single[]] $OutlineColor, # Set the top_line_color of OBSClockAnalogShader [Alias('top_line_color')] [ComponentModel.DefaultBindingProperty('top_line_color')] [Single[]] $TopLineColor, # Set the background_color of OBSClockAnalogShader [Alias('background_color')] [ComponentModel.DefaultBindingProperty('background_color')] [Single[]] $BackgroundColor, # Set the time_offset_hours of OBSClockAnalogShader [Alias('time_offset_hours')] [ComponentModel.DefaultBindingProperty('time_offset_hours')] [Int32] $TimeOffsetHours, # The name of the source. This must be provided when adding an item for the first time [Parameter(ValueFromPipelineByPropertyName)] [Alias('SceneItemName')] [String] $SourceName, # The name of the filter. If this is not provided, this will default to the shader name. [Parameter(ValueFromPipelineByPropertyName)] [String] $FilterName, # The inline value of the shader. This will normally be provided as a default parameter, based off of the name. [Alias('ShaderContent')] [String] $ShaderText, # If set, will force the recreation of a shader that already exists [Management.Automation.SwitchParameter] $Force, # If set, will pass thru the commands that would be sent to OBS (these can be sent at any time with Send-OBS) [Management.Automation.SwitchParameter] $PassThru, # If set, will not wait for a response from OBS (this will be faster, but will not return anything) [Management.Automation.SwitchParameter] $NoResponse, # If set, use the shader elapsed time, instead of the OBS system elapsed time [ComponentModel.DefaultBindingProperty('use_shader_elapsed_time')] [Management.Automation.SwitchParameter] $UseShaderTime ) process { $shaderName = 'clock_analog' $ShaderNoun = 'OBSClockAnalogShader' if (-not $psBoundParameters['ShaderText']) { $psBoundParameters['ShaderText'] = $ShaderText = ' //Based on https://www.shadertoy.com/view/XdKXzy uniform int current_time_ms; uniform int current_time_sec; uniform int current_time_min; uniform int current_time_hour; uniform float3 hour_handle_color = {1.0,1.0,1.0}; uniform float3 minute_handle_color = {1.0,1.0,1.0}; uniform float3 second_handle_color = {1.0,0.0,0.0}; uniform float3 outline_color = {1.0,1.0,1.0}; uniform float3 top_line_color = {1.0,0.0,0.0}; uniform float3 background_color = {.5,.5,.5}; uniform int time_offset_hours = 0; #ifndef OPENGL #define mod(x,y) (x - y * floor(x / y)) #endif // this is my first try to actually use glsl almost from scratch // so far all i''ve done is learning by doing / reading glsl docs. // this is inspired by my non glsl „elapsed_time“ projects // especially this one: https://www.gottz.de/analoguhr.htm // i will most likely use a buffer in future to calculate the elapsed_time // aswell as to draw the background of the clock only once. // tell me if thats a bad idea. // update: // screenshot: http://i.imgur.com/dF0nHDk.png // as soon as i think its in a usefull state i''ll release the source // of that particular c++ application on github. // i hope sommeone might find it usefull :D #define PI 3.141592653589793238462643383 // from https://www.shadertoy.com/view/4s3XDn <3 float ln(float2 p, float2 a, float2 b) { float2 pa = p - a; float2 ba = b - a; float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); return length(pa - ba * h); } // i think i should spend some elapsed_time reading docs in order to minimize this. // hints apreciated // (Rotated LiNe) float rln(float2 uv, float start, float end, float perc) { float inp = perc * PI * 2.0; float2 coord = float2(sin(inp), cos(inp)); return ln(uv, coord * start, coord * end); } // i need this to have an alphachannel in the output // i intend to use an optimized version of this shader for a transparent desktop widget experiment float4 mixer(float4 c1, float4 c2) { // please tell me if you think this would boost performance. // the elapsed_time i implemented mix myself it sure did reduce // the amount of operations but i''m not sure now // if (c2.a <= 0.0) return c1; // if (c2.a >= 1.0) return c2; return float4(lerp(c1.rgb, c2.rgb, c2.a), c1.a + c2.a); // in case you are curious how you could implement mix yourself: // return float4(c2.rgb * c2.a + c1.rgb * (1.0-c2.a), c1.a+c2.a); } float4 styleHandle(float4 color, float px, float dist, float3 handleColor, float width, float shadow) { if (dist <= width + shadow) { // lets draw the shadow color = mixer(color, float4(0.0, 0.0, 0.0, (1.0-pow(smoothstep(width, width + shadow, dist),0.2))*0.2)); // now lets draw the antialiased handle color = mixer(color, float4(handleColor, smoothstep(width, max(width - 3.0 * px, 0.0), dist))); } return color; } float4 mainImage(VertData v_in) : TARGET { float2 R = uv_size; // calculate the size of a pixel float px = 1.0 / R.y; // create percentages of the coordinate system float2 p = (v_in.uv * uv_size).xy / R; // center the scene and add perspective float2 uv = (2.0 * (float2(v_in.uv.x,1.0-v_in.uv.y) * uv_size) - R) / min(R.x, R.y); /*float2 uv = -1.0 + 2.0 * p.xy; // lets add perspective for mobile device support if (uv_size.x > uv_size.y) uv.x *= uv_size.x / uv_size.y; else uv.y *= uv_size.y / uv_size.x;*/ // lets scale the scene a bit down: uv *= 1.1; px *= 0.9; float width = 0.015; float dist = 1.0; float centerdist = length(uv); float4 color = image.Sample(textureSampler, v_in.uv); // background of the clock if (centerdist < 1.0 - width) color = mixer(color, float4(background_color, 0.4*(1.8-length(uv)))); float isRed = 1.0; if (centerdist > 1.0 - 12.0 * width && centerdist <= 1.1) { // minute bars for (float i = 0.0; i <= 15.0; i += 1.0) { if (mod(i, 5.0) == 0.0) { dist = min(dist, rln(abs(uv), 1.0 - 10.0 * width, 1.0 - 2.0 * width, i / 60.0)); // draw first bar red if (i == 0.0 && uv.y > 0.0) { isRed = dist; dist = smoothstep(width, max(width - 3.0 * px, 0.0), dist); color = mixer(color, float4(top_line_color, dist)); dist = 1.0; } } else { dist = min(dist, rln(abs(uv), 1.0 - 10.0 * width, 1.0 - 7.0 * width, i / 60.0)); } } // outline circle dist = min(dist, abs(1.0-width-length(uv))); // draw clock shadow if (centerdist > 1.0) color = mixer(color, float4(0.0,0.0,0.0, 0.3*smoothstep(1.0 + width*2.0, 1.0, centerdist))); // draw outline + minute bars in white color = mixer(color, float4(0.0, 0.0, 0.0, (1.0 - pow(smoothstep(width, width + 0.02, min(isRed, dist)), 0.4))*0.2)); color = mixer(color, float4(outline_color, smoothstep(width, max(width - 3.0 * px, 0.0), dist))); } if (centerdist < 1.0) { float elapsed_time = float((time_offset_hours+current_time_hour)*3600+current_time_min*60+current_time_sec) + pow(float(current_time_ms)/1000.0,16.0); // hour color = styleHandle(color, px, rln(uv, -0.05, 0.5, elapsed_time / 3600.0 / 12.0), hour_handle_color, 0.03, 0.02); // minute color = styleHandle(color, px, rln(uv, -0.075, 0.7, elapsed_time / 3600.0), minute_handle_color, 0.02, 0.02); // second color = styleHandle(color, px, min(rln(uv, -0.1, 0.9, elapsed_time / 60.0), length(uv)-0.01), second_handle_color, 0.01, 0.02); } return color; } ' } $MyVerb, $myNoun = $MyInvocation.InvocationName -split '-',2 if (-not $myNoun) { $myNoun = $myVerb $myVerb = 'Get' } switch -regex ($myVerb) { Get { $FilterNamePattern = "(?>$( if ($FilterName) { [Regex]::Escape($FilterName) } else { [Regex]::Escape($ShaderNoun -replace '^OBS' -replace 'Shader$'),[Regex]::Escape($shaderName) -join '|' } ))" if ($SourceName) { Get-OBSInput | Where-Object InputName -eq $SourceName | Get-OBSSourceFilterList | Where-Object FilterName -Match $FilterNamePattern } else { $obs.Inputs | Get-OBSSourceFilterList | Where-Object FilterName -Match $FilterNamePattern } } 'Remove' { if ($SourceName) { Get-OBSInput | Where-Object InputName -eq $SourceName | Get-OBSSourceFilterList | Where-Object FilterName -Match $FilterNamePattern | Remove-OBSSourceFilter } } '(?>Add|Set)' { $ShaderSettings = [Ordered]@{} :nextParameter foreach ($parameterMetadata in $MyInvocation.MyCommand.Parameters[@($psBoundParameters.Keys)]) { foreach ($parameterAttribute in $parameterMetadata.Attributes) { if ($parameterAttribute -isnot [ComponentModel.DefaultBindingPropertyAttribute]) { continue } $ShaderSettings[$parameterAttribute.Name] = $PSBoundParameters[$parameterMetadata.Name] if ($ShaderSettings[$parameterAttribute.Name] -is [switch]) { $ShaderSettings[$parameterAttribute.Name] = $ShaderSettings[$parameterAttribute.Name] -as [bool] } continue nextParameter } } if (-not $PSBoundParameters['FilterName']) { $filterName = $PSBoundParameters['FilterName'] = $shaderName } $ShaderFilterSplat = [Ordered]@{ ShaderSetting = $ShaderSettings FilterName = $FilterName SourceName = $SourceName } foreach ($CarryOnParameter in "PassThru", "NoResponse","Force") { if ($PSBoundParameters.ContainsKey($CarryOnParameter)) { $ShaderFilterSplat[$CarryOnParameter] = $PSBoundParameters[$CarryOnParameter] } } if (-not $script:CachedShaderFilesFromCommand) { $script:CachedShaderFilesFromCommand = @{} } if ($Home -and -not $script:CachedShaderFilesFromCommand[$shaderName]) { $MyObsPowerShellPath = Join-Path $home ".obs-powershell" $ThisShaderPath = Join-Path $MyObsPowerShellPath "$shaderName.shader" $shaderText | Set-Content -LiteralPath $ThisShaderPath $script:CachedShaderFilesFromCommand[$shaderName] = Get-Item -LiteralPath $ThisShaderPath } if ($script:CachedShaderFilesFromCommand[$shaderName]) { $ShaderFilterSplat.ShaderFile = $script:CachedShaderFilesFromCommand[$shaderName].FullName } else { $ShaderFilterSplat.ShaderText = $shaderText } if ($myVerb -eq 'Add') { Add-OBSShaderFilter @ShaderFilterSplat } else { Set-OBSShaderFilter @ShaderFilterSplat } } } } } |