windows下自动连接openvpn的powershell脚本

windows下的openvpn自动连接powershell脚本,自动尝试指定目录下的所有.ovpn文件 powershell的脚本如下所示: $ovpn_dir = "D:\MyProject\mybat\vpnconf" $auth_file = "D:\MyProject\mybat\auth.txt" $log_file = "D:\MyProject\mybat\temp.log" foreach ($ovpn_file in Get-ChildItem -Path $ovpn_dir -Filter *.ovpn) { Write-Host "Trying $($ovpn_file.Name)" Remove-Item $log_file -ErrorAction SilentlyContinue $process = Start-Process -FilePath "C:\Program Files\OpenVPN\bin\openvpn.exe" -ArgumentList "--config $ovpn_file --auth-user-pass $auth_file" -NoNewWindow -PassThru -RedirectStandardOutput $log_file $connected = $false while (!$process.HasExited) { $content = Get-Content $log_file if ($content -match "Initialization Sequence Completed") { $connected = $true Write-Host "Connected successfully!" break } Start-Sleep -Milliseconds 1000 } if ($connected) { $inputStr = Read-Host "Type 'stop' to terminate the program" if ($inputStr.Equals("stop")) { $killProcess = Get-Process -Name *openvpn* if ($killProcess) { Stop-Process -Name "openvpn" -Force } break } } if (!$connected) { Write-Host "Configurations failed" $killProcess = Get-Process -Name *openvpn* if ($killProcess) { Stop-Process -Name "openvpn" -Force } Start-Sleep -Seconds 5 } } $ovpn_dir是你自己的配置文件目录,把所有的 .ovpn 文件放到该目录下面。 $auth_file是你的账户信息,里面有两行,第一行是你的用户名,第二行是你的密码。格式大概如下面一样,记得把your_name,your_password整个替换成你的用户名和密码。 your_name your_password $log_file是openvpn的日志输出文件,控制台会读取这个文件,然后比对字符串,看看是否已经成功连接。 openvpn的参数: --config 指定配置文件位置。 --auth-user-pass 指定用户和密码文件 --connect-timeout 10 指定timeout时间,这里表示10秒没有连上就timeout --connect-retry-max 3 指定最多尝试次数,这里尝试3次如果失败就会退出程序 ...

March 28, 2023 · 1 min · 144 words · Link

针对移动设备修改着色器

针对移动设备修改着色器 我们在优化着色器这方面已经了解了较多的技术了,现在让我们来了解如何为移动设备编写高效,高质量的着色器代码。对于我们已经写好的着色器代码,通过一些小的修改让他们能在移动设备上高速运行好事比较简单的。这里包含了使用 approxview 或者 halfasview 光照函数变量等知识内容。我们可以减少所需的纹理数量并且对所用的纹理使用更好的压缩方式。这个知识点的最后,对于移动游戏,我们将会有一个优化很好的法线贴图,高光着色器。 始前准备 在开始前,我们先创建一个新的场景并且创建一些游戏对象用来使用我们的着色器: 创建一个新的场景并且添加一个默认球体和一个方向光。 创建一个新的材质球和着色器,并且把着色器应用到材质。 最后把材质应用到场景中的球体上。 当完成上面的步骤后,你的场景看起来大概更下图差不多: 操作步骤 在这个知识点中,我们会反复斟酌着色器中各种元素从而编写一个对移动平台友好着色器: 1.首先根据所需的纹理修改着色器的 属性块(Properties block) 。在这个例子中,我们会使用一个alpha通道带有光滑纹理漫反射纹理,一张法线贴图,一个控制高光强度的滑动条。 Properties { _Diffuse ("Base (RGB) Specular Amount (A)", 2D) = "white" {} _SpecIntensity ("Specular Width", Range(0.01, 1)) = 0.5 _NormalMap ("Normal Map", 2D) = "bump" {} } 2.下一个任务是设置 #pragma 申明。这些声明会打开或者关闭 表面着色器(Surface Shader) 的一些具体特性,并且最终影响着色器的性能消耗,是高成本还是低成本。 CGPROGRAM #pragma surface surf MobileBlinnPhong exclude_path:prepass nolightmap noforwardadd halfasview 3.接着我们在 CGPROGRAM 中定义与 属性块(Properties block) 中对应的变量。这次对于高光强度这个滑动条,我们将使用 fixed 类型的变量,从而减少着色器的内存使用: sampler2D _Diffuse; sampler2D _NormalMap; fixed _SpecIntensity; 4.为了能将我们的纹理映射到游戏对象的表面,我们需要获取相应的UV。这个例子里,为了让着色器数据保持最小,我们将仅使用一个UV设置: struct Input { half2 uv_Diffuse; }; 5.这一步是要完成我们的光照函数,由于在 #pragma 声明中有了一些新的变量,所以这里我们就可以使用它们: inline fixed4 LightingMobileBlinnPhong(SurfaceOutput s, fixed3 lightDir, fixed3 halfDir, fixed atten) { fixed diff = max(0, dot(s.Normal, lightDir)); fixed nh = max(0, dot(s.Normal, halfDir)); fixed spec = pow(nh, s.Specular*128) * s.Gloss; fixed4 c; c.rgb = (s.Albedo * _LightColor0.rgb * diff + _LightColor0.rgb * spec) * (atten * 2); c.a = 0.0; return c; } 6.最后,我们需要创建 surf() 函数并且处理表面的最终颜色: void surf (Input IN, inout SurfaceOutput o) { // Albedo comes from a texture tinted by color fixed4 diffuseTex = tex2D(_Diffuse, IN.uv_Diffuse); o.Albedo = diffuseTex.rgb; o.Gloss = diffuseTex.a; o.Alpha = 0.0; o.Specular = _SpecIntensity; o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_Diffuse)); } 当完成上面的步骤后,我们完成了这个知识点的部分代码,保存你的着色器代码并且返回Unity编辑等待着色器编译完。如果没有遇到什么错误,你会获得一个类似下图的结果: 原理介绍 所以,让我们开始介绍这个着色器吧,看看它做了什么和没做什么。首先,它排除了后光线通道。也就是说如果你创建了一个连接 后渲染前通道(deferred renderer’s prepass) 的光线函数,那么它将不会使用那个特定的光线函数并且会去寻找默认的光线函数,比如我们这本书目前为止创建的那些默认的光线函数一样。 这个特定的着色器并不受Unity内部的光照贴图系统的光照映射支持。这样的话对于使用了这个着色器的游戏对象来说,可以防止着色器试图去寻找法线贴图,从而可以让着色器有更好的性能表现,因为它不用再执行光线映射检测了。 我们添加了 noforwardadd 声明,这样的话通过单个的方向光我们只要处理逐像素纹理即可。所有其他类型的光将会强制变成逐顶点光并且不会被在 surf() 函数中的任何逐像素操作所涉及。 最后,我们使用 halfasview 声明告诉Unity,我们将不会使用普通光线函数中的 viewDir 参数。取而代之的是,我们将使用 半向量(half vector) 作为视野方向并且处理我们的高光。这样的话着色器的处理将会快很多,因为它是基于逐顶点操作来完成的。当用这个着色器来模拟真实世界中的高光时,其实它并不够精确,但是对于移动设备中的视效来说,它看起来已经很不错了并且着色器也优化的更好。 这些技术可以让着色器更加高效和简洁【codewise 我不知道怎么翻译】。按照游戏的要求,根据你的游戏硬件和视觉质量要求来衡量你到底需要那些数据,最好确保只使用你需要的数据。最后,这些技术最终组成了游戏使用的那些着色器。

March 26, 2023 · 1 min · 202 words · Link

着色器的性能分析

着色器的性能分析 我们现在知道该如何减少色器可能出现的内存消耗,让我们来了解一下在场景中,如何在大量同时运行的游戏对象,着色器和脚本等包含的大量着色器中,找出有问题的着色器。要在整个游戏中去找到某一个单独的游戏对象或者着色器可能会让人有点望而却步,但是Unity给我们提供了它内建的性能分析工具。它可以让我们知道在游戏中每一帧都发生了什么,CPU和GPU资源使用情况。 通过使用性能分析工具,我们可以使用其界面创建分析作业块,来单独分析诸如着色器、几何图新和一些一般渲染项。我们可以筛选出我们要寻找的影响性能的单个游戏对象。这让我们能够在运行时观察对象执行其功能时对 CPU和GPU的影响。 让我们来看看性能分析工具的不同部分和并且学习如何调试我们的场景,当然更重要的是如何调试我们的着色器。 始前准备 为了使用性能分析器,我们要准备好一些资源并且打开我们的性能分析窗口: 1.我们就使用介绍上一个知识点时的场景,然后通过菜单 Window | Profiler 或 Ctrl + 7 打开性能分析窗口。 2.我们多复制几个球体,看看这样会对渲染有什么影响。 你会看到跟下图类似的一些东西: 操作步骤 使用性能分析工具的时候,你会在该窗口看到一些UI元素。在我们点击运行按钮前,让我们了解一下该如何从性能分析器中获取我们想要的信息: 1.首先,点击 Profiler 窗口中的 GPU Usage ,CPU Usage 和 Rendering 这几栏。你可以在窗口的左上角找到这几栏: 使用这几栏,我们可以看到跟游戏主要功能相关的不同的数据。CPU Usage 给我们展示的是我们大部分脚本在干什么,当然还有物理运算和总体渲染。GPU Usage 这一栏给我们的是光照,阴影和渲染队列等的详情信息。最后,Rendering 这一栏有每一帧中 drawcall 和游戏场景中集合体的数量这些信息。 点击其中的一栏,我们就可以把在 性能会话(profiling session) 中看到的数据类型单独分离出来分析。 2.现在,我们可以点击性能分析栏中的小颜色块然后点击编辑器的运行按钮或者用 Ctrl + P 快捷键运行场景。 这样选择性的查看,可以让我们更加深入的分析性能会话,因为这样我们可以选择我们想分析的内容。当场景运行的时候,再 GPU使用(GPU Usage) 栏中取消其他所有的颜色小块的勾选,然后留下 Opaque 这一勾选。请注意这样我们就可以知道 Opaque 渲染队列中的游戏对象再渲染中花了多长时间了: 3.性能分析窗口中另一个非常好用的功能是可以在图形窗口中进行的拖拽操作。这个操作会自动暂停你的游戏,这样你就可以更细致的分析图形中某一个具体的波峰从而找出引起性能问题的具体项了。你可以在图形区域内点击或者拖拽移动来暂停游戏,从而了解这一功能的具体效果: 4.现在让我们把目光聚焦到性能分析窗口的下半部分,你会发现当我们选中GPU那一栏的时候这里会有一个下拉选择框。我们可以把它展开从而获得更多当前激活的性能会话中的详细信息,这种情况下我们可以知道有关于当前摄像机渲染情况和花费时间的更多信息: 它能让我们全面了解Unity在某一帧中内部工作都在处理什么。在这个例子中,我们能看到场景中的球体和我们优化过的着色器在绘制到屏幕上花了大概0.14毫秒,花了7个drawcall,并且这个处理每帧花费了大概3.1%的GPU时间。通过这些类型的信息我们可以去诊断和解决跟着色器性能相关的问题。让我们准备一个测试,看看如果我们给着色器添加一个额外的纹理并且用 lerp 函数把两张漫反射纹理混合到一块会造成什么样的影响。你将会在在性能分析器中看到清晰的影响。 5.修改着色器的 属性块(Properties block) ,然后添加下面的代码,这样就可以为我们的着色器添加另一个纹理了: Properties { _MainTex ("Albedo (RGB)", 2D) = "white" {} NormalMap ("Normal Map", 2D) = "bump" {} _BlendTex ("Blend Texture", 2d) = "white" {} } 6.然后在 CGPROGRAM 中添加一个变量用来使用这个纹理: CGPROGRAM #pragma surface surf SimpleLambert exclude_path:prepass noforwardadd sampler2D _MainTex; sampler2D _NormalMap; sampler2D _BlendTex; 7.相应的我们也要去修改一下我们的 surf() 函数以便我们能将纹理和漫反射纹理混合到一块: void surf (Input IN, inout SurfaceOutput o) { // Albedo comes from a texture tinted by color fixed4 c = tex2D (_MainTex, IN.uv_MainTex); fixed4 blendTex = tex2D(_BlendTex, IN.uv_MainTex); c = lerp(c, blendTex, blendTex.r); o.Albedo = c.rgb; o.Alpha = c.a; o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_MainTex)); } 当你保存了你着色器的修改并且回到Unity编辑器后,你就可以运行我们的游戏并且看看我们的着色器增加的时间消耗。回到Unity后点击运行按钮并且在性能分析器窗口查看结果: 你可以看到场景中我们着色器渲染 Opaque 队列的时间消耗数量从 0.140 毫秒增加到了 0.179 毫秒。从添加了另一张额外的纹理后和使用了 lerp() 函数后,我们的球体的渲染时间增加了。当然这个变化非常的小,但是想象一下如果有20个着色器用不同的工作方式在不同的游戏对象上,那消耗就多了。 ...

March 23, 2023 · 1 min · 170 words · Link

在2D游戏中实现水效果的着色器

在2D游戏中实现水效果的着色器 上一个知识点介绍的玻璃着色器它的效果是静态的;它的扭曲效果永远都不会改变。只要对着色器稍加修改,就可以将它转换成一个有动画的材质,它非常的适合2D游戏中的水体特效。在这个知识点将会使用 第五章,对表面着色器中的顶点使用动画 中类似的技术: 始前准备 这个知识点基于 使用抓取通道 知识点中描述的顶点和片元着色器,因为它很依赖抓取通道。 1.创建一个新的抓取通道着色器;你可以自己写一个新的着色器或者使用 使用抓取通道 这个知识点中用到的着色器作为开始。 2.为你的着色器创建一个对应的材质球。 3.将材质球应用到一个平面几何图形中,它将用来表示2D中的水。为了让这个效果起作用,您应该在其后渲染一些东西,以便可以看到类似水的扰动效果。 4.这个知识点需要一张噪音纹理,用来获得伪随机的值。选择一个无缝的噪音纹理很重要,比如由可以铺砌的2D的 Perlin 噪音生成的噪音纹理,如下图所示的那样。这是为了确保材质应用到一个很大的游戏对象中时,不会看到有任何不连续的割裂感。为了让效果起作用,纹理需要以 Repeat 模式导入。如果你想要让你的水体效果看起来平滑和连续,那么在 导入器(Inspector) 那里要设置成 Bilinear 。这样设置能确保纹理能从着色器中正确的被采样: 操作步骤 你可以修改着色器中的代码来创建动画效果。请跟着下面的步骤走: 1.将下面的代码添加到着色器的属性块中: _NoiseTex("Noise text", 2D) = "white" {} _Colour ("Colour", Color) = (1,1,1,1) _Period ("Period", Range(0,50)) = 1 _Magnitude ("Magnitude", Range(0,0.5)) = 0.05 _Scale ("Scale", Range(0,10)) = 1 2.并且在次通道中添加与属性对应的变量 sampler2D _NoiseTex; fixed4 _Colour; float _Period; float _Magnitude; float _Scale; 3.为顶点函数定义下面 输出结构体(output structure) : struct vertOutput { float4 vertex : POSITION; fixed4 color : COLOR; float2 texcoord : TEXCOORD0; float4 worldPos : TEXCOORD1; float4 uvgrab : TEXCOORD2; }; 4.这个着色器需要知道每个片元在空间上的精确位置。为了实现这一过程,将代码 o.worldPos = mul(unity_ObjectToWorld, i.vertex); 添加到顶点函数中去: vertOutput vert(vertInput i) { vertOutput o; o.vertex = UnityObjectToClipPos(i.vertex); o.color = i.color; o.texcoord = i.texcoord; o.worldPos = mul(unity_ObjectToWorld, i.vertex); o.uvgrab = ComputeGrabScreenPos(o.vertex); return o; } 5.使用下面的片元函数: fixed4 frag(vertOutput o): COLOR { float sinT = sin(_Time.w / _Period); float2 distortion = float2( tex2D(_NoiseTex, o.worldPos.xy / _Scale + float2(sinT, 0) ).r - 0.5,tex2D(_NoiseTex, o.worldPos.xy / _Scale + float2(0, sinT) ).r - 0.5); o.uvgrab.xy += distortion * _Magnitude; fixed4 col = tex2Dproj( _GrabTexture, UNITY_PROJ_COORD(o.uvgrab)); return col * _Colour; } 6.完整代码: Shader "Custom/IWS" { Properties { _NoiseTex("Noise text", 2D) = "white" {} _Colour ("Colour", Color) = (1,1,1,1) _Period ("Period", Range(0,50)) = 1 _Magnitude ("Magnitude", Range(0,0.5)) = 0.05 _Scale ("Scale", Range(0,10)) = 1 } SubShader { Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Opaque" } GrabPass { "_GrabTexture" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" sampler2D _GrabTexture; sampler2D _NoiseTex; fixed4 _Colour; float _Period; float _Magnitude; float _Scale; struct vertOutput { float4 vertex : POSITION; fixed4 color : COLOR; float2 texcoord : TEXCOORD0; float4 worldPos : TEXCOORD1; float4 uvgrab : TEXCOORD2; }; struct vertInput { float4 vertex : POSITION; float4 color : COLOR; float2 texcoord : TEXCOORD0; }; vertOutput vert(vertInput i) { vertOutput o; o.vertex = UnityObjectToClipPos(i.vertex); o.color = i.color; o.texcoord = i.texcoord; o.worldPos = mul(unity_ObjectToWorld, i.vertex); o.uvgrab = ComputeGrabScreenPos(o.vertex); return o; } fixed4 frag(vertOutput o): COLOR { float sinT = sin(_Time.w / _Period); float2 distortion = float2 ( tex2D(_NoiseTex, o.worldPos.xy / _Scale + float2(sinT, 0) ).r - 0.5, tex2D(_NoiseTex, o.worldPos.xy / _Scale + float2(0, sinT) ).r - 0.5 ); o.uvgrab.xy += distortion * _Magnitude; fixed4 col = tex2Dproj( _GrabTexture, UNITY_PROJ_COORD(o.uvgrab)); return col * _Colour; } ENDCG } } } 原理介绍 这个着色器跟 实现一个玻璃效果的着色器 知识点中介绍的着色器很像。主要的区别就是这个着色器它时一个有动画的材质;它的扰动效果不是从法线贴图中生成的,而是通过当前的时间来计算出的一个持续的动画。用来扰动抓取纹理的UV数据的代码似乎有点复杂;让我们来理解它的效果时如何生成的。思路是用一个正弦函数来让水晃动。这个效果需要随着时间变化。为了获得这个效果,着色器产生的扭曲效果依赖于当前的时间,而这个时间可以通过内建的 _Time 变量获得。变量 _Period 决定了正弦函数的周期,意味着水波出现的有多快: float2 distortion = float2(tex2D(_NoiseTex, o.worldPos.xy / _Scale + float2(sinT, 0) ).r - 0.5,tex2D(_NoiseTex, o.worldPos.xy / _Scale + float2(0, sinT) ).r - 0.5); 这里的代码有一个问题,就是在 X 轴和 Y 轴它们的扰动是一样的;结果就是整个抓取纹理以圆形运动旋转,这看起来一点都不像水了。很显然我们需要为此添加一些随机性。 给着色器添加随即行为的最常用的方式就是添加一张噪音纹理。现在的问题就变成了找到一种方法来对纹理进行一个看似随机的采样。为了避免效果看起来有明显的正弦模式,最好的方法就是在噪声纹理的UV数据中使用正弦波作为偏移量: float sinT = sin(_Time.w / _Period); float2 distortion = float2( tex2D(_NoiseTex, o.worldPos.xy / _Scale + float2(sinT, 0) ).r - 0.5,tex2D(_NoiseTex, o.worldPos.xy / _Scale + float2(0, sinT) ).r - 0.5); 变量 _Scale 用来决定波的大小。这个方案已经很接近最终版本了,但还是有一些问题——如果水体移动,UV数据也会跟着它动然后你就会看到水波跟着材质动而不是锚定在背景上。为了解决这个问题,我们需要使用当前片元的世界坐标作为UV数据的初始位置: float sinT = sin(_Time.w / _Period); float2 distortion = float2( tex2D(_NoiseTex, o.worldPos.xy / _Scale + float2(sinT, 0) ).r - 0.5,tex2D(_NoiseTex, o.worldPos.xy / _Scale + float2(0, sinT) ).r - 0.5); o.uvgrab.xy += distortion * _Magnitude; 这种没有任何明显的移动方向的无缝扭曲效果确实让人看起来心情愉悦。 注意 正如所有的这些特效一样,没有完美的方案。这个知识点向你展示了创建类似水的扭曲的技术,但是我们鼓励你多对它进行试验,直到你找到一个符合你游戏美术风格的效果。

February 26, 2023 · 3 min · 473 words · Link

移动设备着色器适配

第七章 移动设备着色器适配 在接下来的两章,我们将着手于让我们写的着色器对不同的平台都有较好的性能表现。我们不会讨论任何一个特殊的平台,我们将会分解着色器内的元素,这样的话我们就可以对它们进行调整,从而让它们对于移动平台有更好的优化并且通常来说对其他任何平台来说也更高效。这些技术涵盖了从 了解Unity提供的一些可以减少着色器内存溢出方面的内建变量 到 学习可以让我们的着色器代码更加高效的方法 。这一章将会包含下面的这些知识点: 什么是低成本着色器 着色器的性能分析 针对移动设备修改着色器 介绍 学习如何优化着色器的艺术将会出现在你参与的任何游戏项目中。在任何产品中总有需要优化着色器的时候,或者需要用更少的纹理来产生相同的效果。作为一个技术美术或者着色器编程人员,你必须要理解这些核心的基本原理来优化你的着色器代码从而让你的游戏在提升性能表现的同时又能达到相同的视觉表现。有了这些知识也可以为你自己开始写着色器代码进行铺垫。比如,你知道使用你着色器的游戏将会运行在移动设备中,我们可以自动的设置所有的光照函数使用 half vector 作为视野方向,或者把所有的 浮点型变量类型(float variable types) 都设置成 fixed 类型 或 half 类型。前面提到的这些技术或者很多的其他技术,都可以让你的着色器在目标硬件上更加高效的运行。开始我们的着色器优化学习之旅吧。 什么是低成本着色器 我们首先问一个问题,什么是低成本的着色器,它回答起来可能有点困难因为有太多的元素可以可以让一个着色器变得更加高效了。它可以是你的变量使用的内存的大小。可以是你的着色器使用的纹理的大小。也可是一个工作良好的着色器,但是我们却只使用了相较之前一半的代码或者数据就获得了相同的视觉效果。我们将会在这个知识点中探索一些这样的技术并且会展示如何将这些技术结合起来从而让你的着色器更快更高效,并且不管是在移动设备还是在PC上都生成当今游戏中每个人都期望的高质量的视觉效果。 始前准备 在开始这个知识点之前,我们需要准备一些资源并且把它们放一块。所以让我们按照下面的几个任务来: 1.创建一个新的场景,并且在场景中添加一个球体和一个方向光。 2.创建一个新的着色器和材质球,并且把着色器应用到材质上。 3.然后把材质球应用到我们刚刚创建的球体。 4.最后,我们修改我们之前创建的着色器让它能使用漫反射纹理和法线贴图,并且创建一个自定义的光线函数。下面的代码展示的是修改后的着色器代码: Shader "Custom/MSA" { Properties { _MainTex ("Albedo (RGB)", 2D) = "white" {} _NormalMap ("Normal Map", 2D) = "bump" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 200 CGPROGRAM sampler2D _MainTex; sampler2D _NormalMap; #pragma surface surf SimpleLambert struct Input { float2 uv_MainTex; float2 uv_NormalMap; }; inline float4 LightingSimpleLambert(SurfaceOutput s, float3 lightDir, float atten) { float diff = max(0, dot(s.Normal, lightDir)); float4 c; c.rgb = s.Albedo * _LightColor0.rgb * (diff * atten * 2); c.a = s.Alpha; return c; } void surf (Input IN, inout SurfaceOutput o) { // Albedo comes from a texture tinted by color fixed4 c = tex2D (_MainTex, IN.uv_MainTex); o.Albedo = c.rgb; o.Alpha = c.a; o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap)); } ENDCG } FallBack "Diffuse" } 现在你应该有如下图所示的一个设置。下面的这个设置将让我们初步了解一些在Unity中使用表面着色器进行优化的基本概念: ...

February 26, 2023 · 2 min · 363 words · Link

实现一个玻璃效果的着色器

实现一个玻璃效果的着色器 玻璃是一个非常复杂的材质;没必要对它刚到惊讶,在第四章,向PBR中添加透明度 这个知识点中,通过行为驱动开发创建测试用例和编写场景(Creating Test Cases and Writing Scenarios for Behavior Driven Development in Symfony) 我们已经创建了一个这样的着色器来模拟它了。然而,透明度没有办法复现玻璃的扭曲效果。大部分的玻璃自身是不完美的,所以当我们再看玻璃的时候会有扭曲效果。这个知识点我们将教你如何实现这样的效果。这个效果背后的思路是使用顶点和片元着色器以及抓取通道,然后对抓取纹理做一些修改并应用到它的UV数据中,从而实现扭曲效果。你可以从下面的图中看到效果,使用的是Unity标准资源库 (Unity Standard Assets) 中的玻璃染色纹理: 始前准备 这个知识点的步骤跟前一章中的有点像: 创建一个新的顶点和片元着色器。你可以复制前一个知识点 抓取通道 的着色器作为基础。 创建一个材质,用来承载着色器。 将材质球赋值给一个 quad,也可以是其他的扁平的几何图形,用来模拟玻璃。 然后再这个模拟的玻璃后面放一些其他的游戏物体,好观察扭曲效果。 操作步骤【原书有错,下面是纠正后的步骤和代码】 我们开始编辑顶点和片元着色器: 向着色器的属性快 ( Properties block) 中添加4个属性: Properties { _MainTex("Base (RGB) Trans (A)", 2D) = "white" {} _Colour("Colour", Color) = (1,1,1,1) _BumpMap("Noise text", 2D) = "bump" {} _Magnitude("Magnitude", Range(0,1)) = 0.05 } 在Pass通道中添加下面的这些变量 sampler2D _MainTex; sampler2D _BumpMap; float _Magnitude; sampler2D _GrabTexture; fixed4 _Colour; 将下面的纹理信息添加到输入和输出结构体中: struct vertInput { float4 vertex : POSITION; float4 color : COLOR; float2 texcoord : TEXCOORD0; }; struct vertOutput { float4 vertex : SV_POSITION; float4 color : COLOR; float2 texcoord : TEXCOORD0; float4 uvgrab : TEXCOORD1; }; 将UV数据从输入结构体赋值到输出结构体中: vertOutput vert(vertInput input) { vertOutput o; o.vertex = UnityObjectToClipPos(input.vertex); o.color = input.color; o.texcoord = input.texcoord; o.uvgrab = ComputeGrabScreenPos(o.vertex); return o; } 使用下面的片元函数: half4 frag(vertOutput i) : COLOR { half4 mainColour = tex2D(_MainTex, i.texcoord); half4 bump = tex2D(_BumpMap, i.texcoord); half2 distortion = UnpackNormal(bump).rg; i.uvgrab.xy += distortion * _Magnitude; fixed4 col = tex2Dproj(_GrabTexture, UNITY_PROJ_COORD(i.uvgrab)); return col * mainColour * _Colour; } 因为这个材质是透明的,所以我们还需要在它的 SubShader 块中改变它的 标签(tags) Tags{ "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Opaque" } 接下的工作就是为玻璃设置纹理和法线贴图从而替换掉抓取纹理。 原理介绍 该着色器的核心作用是使用抓取通道来获取已经被渲染在屏幕上的东西。我们在片元函数中实现了扭曲效果的。在这里法线贴图被解析并且用来计算抓取纹理的UV数据偏移: half4 bump = tex2D(_BumpMap, i.texcoord); half2 distortion = UnpackNormal(bump).rg; i.uvgrab.xy += distortion * _Magnitude; _Magnitude 这个滑动条用来控制效果的强弱。 额外内容 这个效果非常的通用;它可以基于法线贴图,通过抓取屏幕来创建扭曲效果。如果想模拟一些更有趣的效果没理由不使用它。有很多游戏会在爆炸中或者一些科幻设备上使用扭曲效果。这个材质也可以应用到球体中,如果使用不同的法线贴图,它还可以很好的模拟爆炸中的冲击波。

February 10, 2023 · 1 min · 204 words · Link

使用抓取通道

使用抓取通道 在第四章,向PBR中添加透明度 这个知识点中,通过行为驱动开发创建测试用例和编写场景(Creating Test Cases and Writing Scenarios for Behavior Driven Development in Symfony) ,我们了解了材质是如何实现透明的。尽管一个透明材质可以在一个场景之上进行绘制,但是它不能改变在场景之下已经绘制的东西。这也意味着那些 透明着色器(Transparent Shaders) 不能创建像从玻璃或者水里看到的那些常见的扭曲效果。为了模拟它们,我们需要介绍另一种叫做 抓取通道(grab pass) 的技术。这个技术可以让我们获取到目前为止,已经绘制在屏幕上的信息,从而让我们的着色器没有限制的去使用(或者修改)它们。为了学习如何使用抓取通道,我们会创建一个材质球,来抓取它背后的渲染信息并且在屏幕上再次绘制它们。这让人感觉有点荒谬,这个材质用了一系列的操作,显示效果还是跟原来一样【作者的意思可能是在这个例子中,使用了抓取通道和没有使用的着色器,它们的显示效果是一样的】。 始前准备 这个知识点需要下面的一系列操作: 创建一个着色器,之后我们会对它进行初始化。 创建一个材质球,用来使用我们的着色器。 将材质球应用到一块扁平的几何图形上,比如Unity中的quad。然后将它放在某个物体的前面,能挡住你看后面的物体。当我们的着色器完成之后,这个quad将会变得透明。 操作步骤 为了能使用抓取通道,请你按照下面的步骤操作: 删除着色器的 属性快(Properties section) ;这个着色器将不会使用里面的任何东西。 在 SubShader 中,添加抓取通道: GrabPass{ } 在添加完抓取通道后,我们将需要添加下面这个额外的通道: Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" sampler2D _GrabTexture; struct vertInput { float4 vertex : POSITION; }; struct vertOutput { float4 vertex : POSITION; float4 uvgrab : TEXCOORD1; }; // Vertex function vertOutput vert(vertInput v) { vertOutput o; o.vertex = UnityObjectToClipPos(v.vertex); o.uvgrab = ComputeGrabScreenPos(o.vertex); return o; } // Fragment function half4 frag(vertOutput i) : COLOR { fixed4 col = tex2Dproj(_GrabTexture, UNITY_PROJ_COORD(i.uvgrab)); return col + half4(0.5,0,0,0); } ENDCG } 原理介绍 这个知识点不仅仅介绍抓取通道同时也会介绍顶点着色器和片元着色器;因此,我们必须要分析着色器的各种细节。 到目前为止,所有的着色器代码都是直接放在 SubShader 中的。这是因为我们前面的着色器只需要一个单独的通道。但我们这次需要两个。第一个就是我们的抓取通道,我们简单的通过 GrabPass{} 定义了它。剩余的代码我们放在了第二个通道中,包含在我们的 Pass 块中。 着色器中第二个通道在结构上跟我们这一章的第一个知识点中所展示没有什么不同;我们使用顶点函数 vert 来获取顶点的位置,之后我们在片元函数 frag 中给它赋予颜色。不同的地方在于方法 vert 计算了另一个重要的细节:抓取通道的UV数据。下面的代码展示了抓取通道自动创建的一个与之相关的纹理: sampler2D _GrabTexture; 为了对纹理进行采样,我们需要它的UV数据。ComputeGrabScreenPos 函数可以的返回之后要用到的数据,这样我们就能对抓取的纹理进行正确的采样。我们可以在片元着色器中用下面这行代码来完成这个操作: fixed4 col = tex2Dproj(_GrabTexture, UNITY_PROJ_COORD(i.uvgrab)); 这是对纹理进行抓取并且把它应用到屏幕正确的位置的一种标准做法。如果每一步都操作正确,这个着色器会简单的把几何图形后面已经渲染的东西简单的克隆。我们将在接下的知识点中了解到如何使用这个技术来创建水或者玻璃这样的材质。 额外内容 当你每一次使用带有 GrabPass {} 的材质球时,Unity都会把屏幕渲染到一张纹理中。这是一个非常消耗性能的操作并且限制了你在游戏中能使用的抓取通道的数量。Cg语言提供了一个稍微不同的方式: GrabPass {"TextureName"} 这行代码不仅可以让你对纹理进行取名,并且它能让所有的抓取通道叫做 TextureName 的材质球共享同一个纹理。这意味着如果你有10个材质,Unity将仅使用一个抓取通道并且让它们共享一个纹理。这个技术的主要问题是它不允许效果的叠加。如果你使用这个技术来创建玻璃,你做不到在玻璃后面再有一块玻璃的效果。

January 2, 2023 · 1 min · 146 words · Link

片元着色器和抓取通道

第六章 片元着色器和抓取通道 到目前为止,我们都在折腾 表面着色器(Surface Shaders) 。它的设计初衷是简化我们的着色器编码工作,为艺术家提供一个有意义的工具。但是如果想让我们的着色器知识更上一层楼,我们就要前往 顶点(Vertex) 和 片元(Fragment) 着色器的知识岛屿冒险啦。 在这一章节,我们将会学习下面的一些知识点: 理解顶点和片元着色器 使用抓取通道 实现一个玻璃效果的着色器 在2D游戏中实现水效果的着色器 介绍 跟 表面着色器(Surface Shaders) 相比,顶点 和 片元 着色器少了一些诸如,光是如何在物体表面反射的物理属性信息。所谓有失必有得,这样的话顶点和片元着色器就没有了物理规则的限制并且特别适合实现非真实的效果。这个章节将集中讲抓取通道的技术,这些技术可以让着色器来模拟形变效果。 理解顶点和片元着色器 理解顶点和片元着色器最好的方法就是你自己亲自创建一个。在这个知识点我们将展示如何编写一个这样的着色器,该着色器简单的将一张纹理应用到一个模型上并且通过给定的颜色进行乘积运算,效果就如同下图一样: 这里展示的着色器非常的简单,只是作为学习其他顶点和片元着色器基础。 始前准备 对于这个知识点,我们将需要一个新的着色器。我们按照下面的步骤来: 1.创建一个新的着色器。 2.创建一个新的材质并且把着色器应用于该材质。 操作步骤 在前面的所有章节中,我们总是能在 表面着色器(Surface Shaders) 的基础上进行修改。但在这里就不能再那样做了,因为表面着色器和片元着色器在结构上是不一样的。我们需要做如下的修改: 删除着色器上的所有属性,然后用下面的属性替换: Color ("Color", Color) = (1,0,0,1) // Red _MainTex ("Base texture", 2D) = "white" {} 删除 SubShader 块中的所有代码,然后用下面的代码替换: Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag half4 _Color; sampler2D _MainTex; struct vertInput { float4 pos : POSITION; float2 texcoord : TEXCOORD0; }; struct vertOutput { float4 pos : SV_POSITION; float2 texcoord : TEXCOORD0; }; vertOutput vert(vertInput input) { vertOutput o; o.pos = UnityObjectToClipPos(input.pos); o.texcoord = input.texcoord; return o; } half4 frag(vertOutput output) : COLOR { half4 mainColour = tex2D(_MainTex, output.texcoord); return mainColour * _Color; } ENDCG } 后面所有的顶点和片元着色器都会以此为基础。 ...

December 3, 2022 · 2 min · 300 words · Link

XLua框架,Unity3D,WEBGL,报错ArgumentException-Destination-array-was-not-long-enough

Unity使用XLua框架,打WEBGL包,运行时报错:ArgumentException: Destination array was not long enough 具体的错误如下: dangerArgumentException: Destination array was not long enough. Check destIndex and length, and the array's lower bounds {: .prompt-danger } 由于我们UI使用的时FGUI,所以这个错误的具体表现是: 错误表现 在编辑器中运行良好,没有任何问题 打包成WEBGL运行就报这个错,但不是每次都报错, 如果报错,那么UI逻辑是正常的,如果不报错,那么UI逻辑异常,比如按钮的绑定事件错乱 解决办法 我在XLua的Issue中,找到了大佬的解决办法。是XLua在释放资源时的Lock操作引起的。原issue地址。但是大佬的贴的代码,不能直接运行,需要稍加修改才行【大佬可能给的时伪代码】。我在这里把解决步骤归纳一下: 1.添加一个非锁互斥队列【也可以不用添加,就用系统的也行】: using System.Threading; namespace XLua { public class LockFreeQueue<T> { internal class SingleLinkNode<U> where U : T { public SingleLinkNode<U> Next; public U Item; } static private bool CAS<T>(ref T location, T comparand, T newValue) where T : class { return comparand == Interlocked.CompareExchange(ref location, newValue, comparand); } SingleLinkNode<T> head; SingleLinkNode<T> tail; int count; public int Count { get { return count; } } public bool IsEmpty { get { return count <= 0; } } public LockFreeQueue() { head = new SingleLinkNode<T>(); tail = head; count = 0; } public void Enqueue(T item) { SingleLinkNode<T> oldTail = null; SingleLinkNode<T> oldTailNext; SingleLinkNode<T> newNode = new SingleLinkNode<T>(); newNode.Item = item; bool newNodeAdded = false; while (!newNodeAdded) { oldTail = tail; oldTailNext = oldTail.Next; if (tail == oldTail) { if (oldTailNext == null) newNodeAdded = CAS<SingleLinkNode<T>>(ref tail.Next, null, newNode); else CAS<SingleLinkNode<T>>(ref tail, oldTail, oldTailNext); } } CAS<SingleLinkNode<T>>(ref tail, oldTail, newNode); Interlocked.Increment(ref count); } public bool TryDequeue(out T item) { item = default(T); SingleLinkNode<T> oldHead = null; bool haveAdvancedHead = false; while (!haveAdvancedHead) { oldHead = head; SingleLinkNode<T> oldTail = tail; SingleLinkNode<T> oldHeadNext = oldHead.Next; if (oldHead == head) { if (oldHead == oldTail) { if (oldHeadNext == null) { return false; } CAS<SingleLinkNode<T>>(ref tail, oldTail, oldHeadNext); } else { item = oldHeadNext.Item; haveAdvancedHead = CAS<SingleLinkNode<T>>(ref head, oldHead, oldHeadNext); } } } Interlocked.Decrement(ref count); return true; } public T Dequeue() { T result; if (TryDequeue(out result)) return result; return default(T); } public void Clear() { while (Count > 0) { Dequeue(); } } } } 2.修改XLua源码,添加条件编译,让WEBGL在释放引用的时候不加锁 改下面几个地方: ...

October 31, 2022 · 2 min · 376 words · Link

Unity编辑器-persistentDataPath-dataPath-streamingAssetsPath

Unity中persistentDataPath, dataPath, streamingAssetsPath在不同的平台对应的路径 为了方便以后的开发,自己结合官方资料和自己的实际开发,把上面的路径变量在不同的平台对应的正式路径总结一下 Application.persistentDataPath 官方参考:https://docs.unity3d.com/2020.3/Documentation/ScriptReference/Application-persistentDataPath.html 对app是只读的,对玩家来说读写都可以,如果是IOS或者安卓,该路径指向设备的公共目录 app更新的时候,不会删除该目录,但是用户自己是可以对该目录增删改查的 平台 指向的位置 Windows Store Apps %userprofile%\AppData\Local\Packages&lt;productname>\LocalState Windows Editor and Standalone Player %userprofile%\AppData\LocalLow&lt;companyname>&lt;productname> WebGL /idbfs/<md5 hash of data path> 该路径是URL最后一个斜杠“/”和“?”之间的字符串 Linux $XDG_CONFIG_HOME/unity3d 或者$HOME/.config/unity3d iOS /var/mobile/Containers/Data/Application/<guid>/Documents tvOS 不支持且返回空字符串 Android 通常指向/storage/emulated/0/Android/data/<packagename>/files,有的老机型可能指向SD卡的路径 Mac 指向用户的Library目录,通常该目录是隐藏的,现在Unity是指向~/Library/Application Support/company name/product name Application.dataPath 官方参考:https://docs.unity3d.com/2020.3/Documentation/ScriptReference/Application-streamingAssetsPath.html 对玩家和app都是只读的,是指设备的游戏目录,只能读取 根据不同的平台,游戏目录不一样 平台 指向位置 Unity Editor <项目路径>/Assets Mac player <path to player app bundle>/Contents iOS player <path to player app bundle>/<AppName.app>/Data Win/Linux player <游戏的可执行文件的数据目录> (请注意Linux目录是大小写敏感的,window不是) WebGL 玩家数据目录的绝对URL(没有具体的文件名) Android 通常指向APK,如果你使用的是安卓分包,那么它指向OBB(也就是说游戏数据文件都保存到了OBB文件中) Windows Store Apps 是一个指向玩家数据目录的绝对路径 注意:PC上返回的路径是用反斜杠(“\”)做分割的 ...

October 27, 2022 · 1 min · 150 words · Link