Skip to content

Commit

Permalink
通过Unity渲染纹理实现屏幕效果
Browse files Browse the repository at this point in the history
  • Loading branch information
linkliu committed Apr 4, 2023
1 parent c6a3331 commit 4be6b18
Show file tree
Hide file tree
Showing 10 changed files with 103 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ tags: [U3D, Shader,Cookbook,中文版]
[http://www.packtpub.com/support](http://www.packtpub.com/support)然后注册,以便通过邮件直接获取相关的代码文件。

- **注意**
**pow(arg1, arg2)**这个方法是内置的,他的功能跟数学的幂函数**power**是等价的。第一个参数是底数,第二参数是指数 。想了解更深入的了解**pow()**方法,请去看Cg语言的教程。下面这个网址提供了非常棒的资源,能让你学习更多有关着色器的知识,并且里面有Cg着色器语言的所有函数的表:
**pow(arg1, arg2)**这个方法是内建的,他的功能跟数学的幂函数**power**是等价的。第一个参数是底数,第二参数是指数 。想了解更深入的了解**pow()**方法,请去看Cg语言的教程。下面这个网址提供了非常棒的资源,能让你学习更多有关着色器的知识,并且里面有Cg着色器语言的所有函数的表:
[http://http.developer.nvidia.com/CgTutorial/cg_tutorial_appendix_e.html](http://http.developer.nvidia.com/CgTutorial/cg_tutorial_appendix_e.html)
下面的屏幕截图展示了通过材质的**检查器面板(Inspector tab)**来控制材质的属性,从而控制材质的颜色和饱和度:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Shader)**如何创建一个简单的纹理滚动效果。



3. 修改**表面函数surface function**从而修改传递给**tex2D()**函数的UV值。然后,使用内置的**_Time**变量来对UV进行循环播放的动画,这样的话当我们点击Unity中的运行按钮的时候,我们就能看到动画效果了:
3. 修改**表面函数surface function**从而修改传递给**tex2D()**函数的UV值。然后,使用内建的**_Time**变量来对UV进行循环播放的动画,这样的话当我们点击Unity中的运行按钮的时候,我们就能看到动画效果了:

```c#
void surf (Input IN, inout SurfaceOutputStandard o)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1366,7 +1366,7 @@ void surf (Input IN, inout SurfaceOutput o)
[http://www.packtpub.com/support](http://www.packtpub.com/support)然后注册,以便通过邮件直接获取相关的代码文件。
- **注意**
**pow(arg1, arg2)**这个方法是内置的,他的功能跟数学的幂函数**power**是等价的。第一个参数是底数,第二参数是指数 。想了解更深入的了解**pow()**方法,请去看Cg语言的教程。下面这个网址提供了非常棒的资源,能让你学习更多有关着色器的知识,并且里面有Cg着色器语言的所有函数的表:
**pow(arg1, arg2)**这个方法是内建的,他的功能跟数学的幂函数**power**是等价的。第一个参数是底数,第二参数是指数 。想了解更深入的了解**pow()**方法,请去看Cg语言的教程。下面这个网址提供了非常棒的资源,能让你学习更多有关着色器的知识,并且里面有Cg着色器语言的所有函数的表:
[http://http.developer.nvidia.com/CgTutorial/cg_tutorial_appendix_e.html](http://http.developer.nvidia.com/CgTutorial/cg_tutorial_appendix_e.html)
下面的屏幕截图展示了通过材质的**检查器面板(Inspector tab)**来控制材质的属性,从而控制材质的颜色和饱和度:

Expand Down Expand Up @@ -1853,7 +1853,7 @@ Shader)**如何创建一个简单的贴图滚动效果。



3. 修改**表面函数surface function**从而修改传递给**tex2D()**函数的UV值。然后,使用内置的**_Time**变量来对UV进行循环播放的动画,这样的话当我们点击Unity中的运行按钮的时候,我们就能看到动画效果了:
3. 修改**表面函数surface function**从而修改传递给**tex2D()**函数的UV值。然后,使用内建的**_Time**变量来对UV进行循环播放的动画,这样的话当我们点击Unity中的运行按钮的时候,我们就能看到动画效果了:

```c#
void surf (Input IN, inout SurfaceOutputStandard o)
Expand Down
2 changes: 1 addition & 1 deletion _posts/shader_book/2022-06-26-顶点函数.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ tags: [U3D, Shader,Cookbook,中文版]
o.vertColor = v.color;
}
```
6.最后,我们可以使用来自 **输入结构体(Input struct)**的顶点颜色数据,并且通过内置 **SurfaceOutput struct** 把数据赋值给 **o.Albedo** 参数:
6.最后,我们可以使用来自 **输入结构体(Input struct)**的顶点颜色数据,并且通过内建 **SurfaceOutput struct** 把数据赋值给 **o.Albedo** 参数:
``` c#
void surf (Input IN, inout SurfaceOutput o)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ tags: [U3D, Shader,Cookbook,中文版]
<br>

- **原理介绍**
这个着色器跟 ***实现一个玻璃效果的着色器*** 知识点中介绍的着色器很像。主要的区别就是这个着色器它时一个有动画的材质;它的扰动效果不是从法线贴图中生成的,而是通过当前的时间来计算出的一个持续的动画。用来扰动抓取纹理的UV数据的代码似乎有点复杂;让我们来理解它的效果时如何生成的。思路是用一个正弦函数来让水晃动。这个效果需要随着时间变化。为了获得这个效果,着色器产生的扭曲效果依赖于当前的时间,而这个时间可以通过内置的 **_Time** 变量获得。变量 **_Period** 决定了正弦函数的周期,意味着水波出现的有多快:
这个着色器跟 ***实现一个玻璃效果的着色器*** 知识点中介绍的着色器很像。主要的区别就是这个着色器它时一个有动画的材质;它的扰动效果不是从法线贴图中生成的,而是通过当前的时间来计算出的一个持续的动画。用来扰动抓取纹理的UV数据的代码似乎有点复杂;让我们来理解它的效果时如何生成的。思路是用一个正弦函数来让水晃动。这个效果需要随着时间变化。为了获得这个效果,着色器产生的扭曲效果依赖于当前的时间,而这个时间可以通过内建的 **_Time** 变量获得。变量 **_Period** 决定了正弦函数的周期,意味着水波出现的有多快:
<br>
`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);`
<br>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ tags: [U3D, Shader,Cookbook,中文版]
---

## 第七章 移动设备着色器适配
在接下来的两章,我们将着手于让我们写的着色器对不同的平台都有较好的性能表现。我们不会讨论任何一个特殊的平台,我们将会分解着色器内的元素,这样的话我们就可以对它们进行调整,从而让它们对于移动平台有更好的优化并且通常来说对其他任何平台来说也更高效。这些技术涵盖了从 **了解Unity提供的一些可以减少着色器内存溢出方面的内置变量****学习可以让我们的着色器代码更加高效的方法**。这一章将会包含下面的这些知识点:
在接下来的两章,我们将着手于让我们写的着色器对不同的平台都有较好的性能表现。我们不会讨论任何一个特殊的平台,我们将会分解着色器内的元素,这样的话我们就可以对它们进行调整,从而让它们对于移动平台有更好的优化并且通常来说对其他任何平台来说也更高效。这些技术涵盖了从 **了解Unity提供的一些可以减少着色器内存溢出方面的内建变量****学习可以让我们的着色器代码更加高效的方法**。这一章将会包含下面的这些知识点:
- 什么是低成本着色器
- 着色器的性能分析
- 针对移动设备修改着色器
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ tags: [U3D, Shader,Cookbook,中文版]
}
```
事实上 `SystemInfo.supportsImageEffects` 在较新的Unity版本中,是一直返回 **true** 的,这个属性已经被废弃掉了【译者述】
- 5.为了能够从Unity的渲染器中抓取 **渲染图像(Rendered Image)**我们需要利用下面这个Unity内置的 **OnRenderImage()** 方法。请输入下面的代码以便于我们能访问当前的 **渲染纹理(Render Texture)**
- 5.为了能够从Unity的渲染器中抓取 **渲染图像(Rendered Image)**我们需要利用下面这个Unity内建的 **OnRenderImage()** 方法。请输入下面的代码以便于我们能访问当前的 **渲染纹理(Render Texture)**
```c#
private void OnRenderImage(RenderTexture src, RenderTexture dest)
{
Expand Down Expand Up @@ -131,7 +131,7 @@ tags: [U3D, Shader,Cookbook,中文版]
_LuminosityAmount ("GrayScale Amount", Range(0.0, 1)) = 1.0
}
```
- 9.现在我们的着色器将使用纯CG着色器代码编写,而不是使用Unity内置的 **表面着色器(Surface Shader)** 代码。因为这样我们的屏幕效果可以得到更好的优化,因为这里我们仅仅是需要处理 **渲染纹理(Render Texture)** 的像素而已。所以我们将在着色器中创建一个新的 **通道块(Pass block)** 并且添加一些我们之前没有看过的 **#pragma** 声明:
- 9.现在我们的着色器将使用纯CG着色器代码编写,而不是使用Unity内建的 **表面着色器(Surface Shader)** 代码。因为这样我们的屏幕效果可以得到更好的优化,因为这里我们仅仅是需要处理 **渲染纹理(Render Texture)** 的像素而已。所以我们将在着色器中创建一个新的 **通道块(Pass block)** 并且添加一些我们之前没有看过的 **#pragma** 声明:
```c#
Pass
{
Expand Down Expand Up @@ -196,4 +196,97 @@ tags: [U3D, Shader,Cookbook,中文版]
当完成我们的着色器之后,返回到Unity编辑器让它编译着色器并且看看有没有遇到任何的错误。如果没有,就将这个新的着色器拖拽并且赋值给 **TestRenderImage.cs** 脚本,并且修改脚本上面的灰度变量的值。你应该可以在游戏窗口中看到游戏从有颜色变为灰色。下面的图片演示了这个屏幕效果:
![diagram](https://linkliu.github.io/game-tech-post/assets/img/shader_book/diagram86.png){: .shadow width = "90%" }
<br>
当完成了这些之后,我们就有了一个非常方便的途径去测试新的屏幕效果着色器,这样就不用反复的去编写 **屏幕效果系统(Screen Effect system** 的代码了。接下来让我们更深入的去了解一下在 **渲染纹理(Render Texture** 中都发生了什么,并且在整个过程中是如何处理它的。
当完成了这些之后,我们就有了一个非常方便的途径去测试新的屏幕效果着色器,这样就不用反复的去编写 **屏幕效果系统(Screen Effect system** 的代码了。接下来让我们更深入的去了解一下在 **渲染纹理(Render Texture** 中都发生了什么,并且在整个过程中是如何处理它的。


***
<br>

- **原理介绍**
为了完成我们的屏幕效果并且在Unity中能运行起来,我们需要创建一个脚本和一个着色器。这个脚本在Unity的编辑器上会实时刷新,它也负责从从主摄像机中捕获 **渲染纹理(Render Texture** 并且把它传到着色器中。一旦这个渲染纹理到达着色器,我们就可以使用这个着色器进行逐像素操作。
<br>
在脚本的开始运行的时候,我们进行了一些检测,这样是为了确保我们当前选择build的平台是否支持屏幕效果,是否支持我们的着色器。它们是当前平台是否支持屏幕效果和是否支持我们使用的着色器的实例。所以检查一下我们在 **Start()** 方法中所做的事情,确保如果当前平台不支持屏幕效果的时候不会有任何的报错。
<br>
当我们的游戏脚本通过了检测之后,我们就通过调用Unity内建的 **OnRenderImage()** 方法来初始化屏幕效果系统。这个方法负责抓取渲染纹理,并且通过调用 **Graphics.Blit()** 方法将它传给着色器,并且将处理好的图像返回给Unity的渲染器。你可以通过下面的两个链接找到这两个方法更详细的信息:
- **OnRenderImage** [https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnRenderImage.html](https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnRenderImage.html)
- **Graphics.Blit:** [https://docs.unity3d.com/ScriptReference/Graphics.Blit.html](https://docs.unity3d.com/ScriptReference/Graphics.Blit.html)
<br>
一旦当前的渲染纹理到达着色器后,着色器就通过 **frag()** 函数处理获取到的纹理,并且返回每一个像素最终颜色。
<br>
可以看到这很强大,对于游戏的最终渲染图像,它能给我们类似于Photoshop这种工具一样控制。这些屏幕效果就像Photoshop中的层级概念一样在摄像机中按次序的工作。当你把这些屏幕效果依次前后叠好,它们就会按照排列顺序处理。这些仅仅是屏幕效果工作过程的一个大致步骤,但也是屏幕效果系统如何工作的核心。


***
<br>

- **额外内容**
现在我们完成了一个简单的屏幕效果系统并且能够运行,让我们来了解一下我们能从Unity的渲染器中获得的一些其他的有用的信息:
![diagram](https://linkliu.github.io/game-tech-post/assets/img/shader_book/diagram87.png){: .shadow width = "90%" }
我们可以通过打开Unity内建的深度模式来让当前游戏中的所有物体都有深度效果。一旦深度效果打开,我们就可以为很多不同的效果使用深度信息。让我们来了解一下这是怎么实现的:
- 1.创建一个名为 **SceneDepth_Effect** 的着色器。然后双击打开编辑。
- 2.我们将会创建两个属性,主纹理属性和深度控制属性。深度控制属性用来控制场景深度的程度。在你的着色器中输入下面的代码:
```c#
Properties
{
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_DepthPower ("Depth Power", Range(1, 5)) = 1
}
```
- 3.接下来我们要在 **CGPROGRAM** 块中添加相应的变量。我们将添加一个叫 **_CameraDepthTexture** 的额外变量。这是Unity通过UnityCG **cginclude** 文件提供给我们的一个内建的变量。它提供了来自摄像机的深度信息:
```c#
Pass
{
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#include "UnityCG.cginc"

uniform sampler2D _MainTex;
fixed _DepthPower;
sampler2D _CameraDepthTexture;
```
- 4.我们将使用Unity提供的几个内建的函数来完成我们的 **深度着色器(depth shader**,一个是 **UNITY_SAMPLE_DEPTH()**,另一个是 **linear01Depth()**。第一个函数从变量 **_CameraDepthTexture** 中获得深度信息并且为每一个像素生成一个单精度浮点值。**Linear01Depth()** 函数确保这个单精度浮点值能控制在 [**0** - **1**]范围内,因为这个值要作为指数,因为在场景内[**0** - **1**]之间的值能确保它的位置在摄像机内【翻译不太准确,有知道的请留言】:
```c#
fixed4 frag(v2f_img i) : COLOR
{
float d = UNITY_SAMPLE_DEPTH(tex2D(_CameraDepthTexture, i.uv.xy));
d = pow(Linear01Depth(d), _DepthPower);
return d;
}
```
- 5.完成我们的着色器之后,让我们把注意力转移到我们的屏幕效果脚本上来。我们需要给脚本添加一个 **depthPower** 变量,这样我们就可以让用户在编辑器上去修改这个值:
```c#
#region Variables
public Shader curShader;
public Material curMaterial;
public float depthPower = 1.0f;
#endregion
```
- 6.我们的 **OnRenderImage()** 方法需要更新一下好让我们能够传递正确的值给着色器:
```c#
private void OnRenderImage(RenderTexture src, RenderTexture dest)
{
if (curShader != null)
{
material.SetFloat("_DepthPower", depthPower);
Graphics.Blit(src, dest, material);
}
else
{
Graphics.Blit(src, dest);
}
}
```
- 7.要完成我们的屏幕深度效果,我们需要告诉Unity让它在当前的摄像机上打开深度渲染。我们可以通过设置摄像机的 **depthTextureMode** 属性来达到目的:
```c#
private void Update()
{
Camera.main.depthTextureMode = DepthTextureMode.Depth;
depthPower = Mathf.Clamp(depthPower, 0, 5);
}
```
<br>
当代码写好后,保存你的脚本和着色器然后返回到Unity编辑器让它们完成编译。如果没有遇到什么错误,你将会看到如下图类似的结果:
![diagram](https://linkliu.github.io/game-tech-post/assets/img/shader_book/diagram88.png){: .shadow width = "90%" }
Binary file added assets/img/shader_book/diagra87.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/img/shader_book/diagram87.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/img/shader_book/diagram88.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 4be6b18

Please sign in to comment.