diff --git "a/_posts/shader_book/2023-04-18-\345\261\217\345\271\225\346\225\210\346\236\234\344\270\255\347\232\204\350\246\206\347\233\226\346\267\267\345\220\210\346\250\241\345\274\217.md" "b/_posts/shader_book/2023-04-18-\345\261\217\345\271\225\346\225\210\346\236\234\344\270\255\347\232\204\350\246\206\347\233\226\346\267\267\345\220\210\346\250\241\345\274\217.md" new file mode 100644 index 00000000000..9a2ba363873 --- /dev/null +++ "b/_posts/shader_book/2023-04-18-\345\261\217\345\271\225\346\225\210\346\236\234\344\270\255\347\232\204\350\246\206\347\233\226\346\267\267\345\220\210\346\250\241\345\274\217.md" @@ -0,0 +1,94 @@ +--- +category: shader +tags: [U3D, Shader,Cookbook,中文版] +--- + +## 屏幕效果中的覆盖混合模式 +对于我们最后要讲的知识点,我们将会去了解另一种混合模式,覆盖混合模式。这种模式实际上是利用了一些条件声明,这些条件声明决定了每个通道上的每个像素的最终颜色。所以,在使用这种混合模式的过程中需要编写的代码会更多一些。接下来我们看看该如何实现它。 + + +*** +
+ +- **始前准备** + 对于最后这个屏幕特效,我们需要像前面两个知识点中那样设置两个脚本(一个C#, 一个shader)。对于这个知识点,我们将使用之前使用的场景,所以我们不必创建新的场景了: + - 1.分别创建一个名为 **Overlay_ImageEffect** 的C#脚本和一个名为 **Overlay_Effect** 的着色器脚本。 + - 2.把上一个知识点中用的C#脚本代码复制到这个新的C#脚本中来。 + - 3.将上一个知识点中使用的着色器代码复制到这个新的着色器代码中来。 + - 4.将 **Overlay_ImageEffect** C#脚本挂载到主摄像机上(注意把之前的C#脚本先移除),然后在 **查看面板(Inspector)** 中将 **Overlay_Effect** 着色器拖拽到C#脚本组件的着色器变量上。 + - 5.然后分别双击C#脚本和着色器在代码编辑器上打开它们。 + + +*** +
+ +- **操作步骤** + 开始处理我们的覆盖屏幕效果,我们将需要完成着色器代码而且要运行起来没有错误。接下来我们就可以修改C#脚本用来给着色器发送正确的数据。 + - 1.首先要做的是在着色器的 **属性块(Properties block)** 中添加需要的属性。我们将使用这一章前面几个知识点中一样的一些属性: + ```c# + Properties + { + _MainTex ("Base (RGB)", 2D) = "white" {} + _BlendTex ("Blend Texture", 2D) = "white" {} + _Opacity ("Blend Opacity", Range(0, 1)) = 1 + } + ``` + - 2.接下来我们需要在 **CGPROGRAM** 代码块之内添加与属性对应的变量: + ```c# + CGPROGRAM + #pragma vertex vert_img + #pragma fragment frag + #pragma fragmentoption ARB_precision_hint_fastest + #include "UnityCG.cginc" + + uniform sampler2D _MainTex; + uniform sampler2D _BlendTex; + fixed _Opacity; + ``` + - 3.为了让 **覆盖混合效果(Overlay Blend effect)** 能起作用,我们必须要分别处理每通道中的每一个像素。为了实现这一操作,我们要编写一个自定义函数,这个函数接收一个单独通道,比如传递一个红色通道给它,并且进行覆盖操作。在着色器的变量声明下面输入下面的代码: + ```c# + fixed OverlayBlendMode(fixed basePixel, fixed blendPixel) + { + if(basePixel < 0.5) + { + return (2.0 * basePixel * blendPixel); + } + else + { + return (1.0 - 2.0 * (1.0 - basePixel) * (1.0 - blendPixel)); + } + } + ``` + - 4.最后,我们要去修改 **frag()** 函数来处理纹理的每一个通道从而进行混合操作: + ```c# + fixed4 frag(v2f_img i) : COLOR + { + //获得渲染纹理的颜色并且获取 v2f_img的uv + fixed4 renderTex = tex2D(_MainTex, i.uv); + fixed4 blendTex = tex2D(_BlendTex, i.uv); + + fixed4 blendedImage = renderTex; + blendedImage.r = OverlayBlendMode(renderTex.r, blendTex.r); + blendedImage.g = OverlayBlendMode(renderTex.g, blendTex.g); + blendedImage.b = OverlayBlendMode(renderTex.b, blendTex.b); + + //对混合模式程度进行线性插值 + renderTex = lerp(renderTex, blendedImage, _Opacity); + return renderTex; + } + ``` + - 5.当我们的着色器代码编写好后,我们的期待的效果应该起作用了。保存着色器代码并且返回到Unity编辑器让着色器编译。我们的C#脚本完全不用改并且已经设置好了。当着色器编译完成之后,你将会看到跟下图相似的一个结果: + ![diagram](https://linkliu.github.io/game-tech-post/assets/img/shader_book/diagram97.png){: .shadow width = "90%" } + + + +*** +
+ +- **原理介绍** + 我们的覆盖混合模式的确涉及到了很多更深的内容,但是如果你真的仔细剖析这些函数的话,你就会发现它是一个简单的 **multiply** 混合模式和一个简单的 **屏幕混合模式(screen blend mode)** (就是说可以拆为这两个,通过条件语句)。真的就是那样,在这个例子中,我们通过条件检测对一个像素执行不同的混合模式。 +
+ 通过这个特定的屏幕效果,当 **覆盖函数(Overlay function)** 接收到一个像素后,会检测它是否小于0.5.如果是,我们就对它执行一个修改过的 **multiply** 混合模式;如果不是,则对它执行一个修改过的 **屏幕混合模式(screen blend mode)**。对于每个通道上的每一个像素我们都执行上述操作,最终得到我们屏幕效果的RGB像素值。 +
+ 正如你所看到的,对于屏幕效果来说可以做很多的事情。这真的就取决于是什么平台和可以为屏幕效果分配多少内存。通常,这是由游戏项目的整个过程决定的,所以,玩的开心并且在屏幕效果上尽情发挥你的想象力吧。 + diff --git a/assets/img/shader_book/diagram97.png b/assets/img/shader_book/diagram97.png new file mode 100644 index 00000000000..efbcce87daf Binary files /dev/null and b/assets/img/shader_book/diagram97.png differ