Skip to content

Commit

Permalink
通过Unity渲染纹理实现屏幕效果
Browse files Browse the repository at this point in the history
  • Loading branch information
linkliu committed Apr 3, 2023
1 parent 4a04c36 commit c6a3331
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,164 @@ tags: [U3D, Shader,Cookbook,中文版]
- 4.最后,创建一个新的方向光,然后保存场景。

当我们把所有的资源准备好后,就等于是简单的设置好了场景,看起来就跟下图一样:
![diagram](https://linkliu.github.io/game-tech-post/assets/img/shader_book/diagram84.png){: .shadow width = "90%" }
![diagram](https://linkliu.github.io/game-tech-post/assets/img/shader_book/diagram84.png){: .shadow width = "90%" }

***
<br>

- **操作步骤**
为了让我们灰度屏幕效果能运行,我们需要一个脚本和着色器。我们将会在这里完成这新的两项并且给它们添加合适的代码从而生成我们的第一个屏幕效果。我们的首个任务就是完成C#脚本。这样可以让我们的整个系统跑起。在这之后,我们将会完成着色器的编写并且看到我们的屏幕效果。让我们通过下面的步骤完成我们的脚本和着色器:
- 1.打开 **TestRenderImage.cs** 这个脚本并且添加一些变量好让我们能保存导入的游戏对象和数据。在 **TestRenderImage** 这个类的最上面添加下面的代码:
``` c#
public class TestRenderImage : MonoBehaviour
{
#region Variables
public Shader curShader;
public float grayScaleAmount = 1.0f;
public Material curMaterial;
#endregion
#region Properties
}
```
- 2.当Unity编辑器没有运行的时候,为了让我们能够实时的编辑屏幕效果,我们需要在 **TestRenderImage** 类的声明上面添加下面这行代码:
![diagram](https://linkliu.github.io/game-tech-post/assets/img/shader_book/diagram85.png){: .shadow width = "90%" }
- 3.因为我们的屏幕效果是使用一个着色器在一个屏幕图像上进行逐像素操作,所以我们必须要创建一个材质来运行这个着色器。没有这个材质,我们就没有办法访问着色器的属性。因此,我们将创建一个C#的属性来检测材质,如果没有找到这个材质就创建一个。在第一个步骤的变量声明的下面输入下面的代码:
``` c#
#region Properties
Material material
{
get
{
if (curMaterial == null)
{
curMaterial = new Material(CurShader);
curMaterial.hideFlags = HideFlags.HideAndDontSave;
}
return curMaterial;
}
}
#endregion
```
- 4.现在我们想在脚本中设置一些检测来看看当前我们build的Unity游戏在平台上是否支持图像效果。如果在脚本开始运行的时候发现不支持,这个脚本将会被禁用掉:
```c#
private void Start()
{
if (!SystemInfo.supportsImageEffects)
{
enabled = false;
return;
}
if (curShader && !curShader.isSupported)
{
enabled = false;
}
}
```
事实上 `SystemInfo.supportsImageEffects` 在较新的Unity版本中,是一直返回 **true** 的,这个属性已经被废弃掉了【译者述】
- 5.为了能够从Unity的渲染器中抓取 **渲染图像(Rendered Image)**,我们需要利用下面这个Unity内置的 **OnRenderImage()** 方法。请输入下面的代码以便于我们能访问当前的 **渲染纹理(Render Texture)**
```c#
private void OnRenderImage(RenderTexture src, RenderTexture dest)
{
if (curShader != null)
{
material.SetFloat("_LuminosityAmount", grayScaleAmount);
Graphics.Blit(src, dest, material);
}
else
{
Graphics.Blit(src, dest);
}
}
```
- 6.我们的屏幕效果有一个叫 **grayScaleAmount** 的变量,它可以控制我们想要的灰度屏幕效果的程度。所以,在这里我们需要控制它的取值范围是[**0** - **1**],0表示没有灰度效果,1表示满程度的灰度效果。我们将会在 **Update()** 方法中进行这个操作,这意味着当脚本运行的时候会在游戏的每一帧去设置它们:
```c#
private void Update()
{
grayScaleAmount = Mathf.Clamp(grayScaleAmount, 0.0f, 1.0f);
}
```
- 7.最后,当脚本运行的时候,对于我们创建的这些对象我们需要对它们进行一些清理,这样这个脚本就完成了:
```c#
private void OnDisable()
{
if (curMaterial)
{
DestroyImmediate(curMaterial);
}
}
```
这个时候,如果编译通过后,我们可以将脚本挂在到我们的摄像机中去了。让我们把 **TestRenderImage.cs** 这个脚本挂载到我们场景中的主摄像机上。你可以在编辑器上看到 **grayScaleAmount** 这个值和一个着色器的域,但这个脚本会在控制台窗口抛出一个错误。说它丢失了一个对象实例并且有可能会运行不正常。如果你回顾第四个步骤的话,可以看到我们做了一些检测来确定我们是否有着色器和当前的平台是否支持该着色器。我们还没有给这个屏幕效果脚本一个着色器让它能正常的工作,所以 **curShader** 变量是空的,所以抛出了这个错误。所以让我们继续完成着色器来完善我们的屏幕效果系统吧。
- 8.开始着手编写我们的着色器了,我们将会修改着色器的属性块,添加一些属性,好让我们能给这个着色器发送一些数据:
``` c#
Properties
{
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_LuminosityAmount ("GrayScale Amount", Range(0.0, 1)) = 1.0
}
```
- 9.现在我们的着色器将使用纯CG着色器代码编写,而不是使用Unity内置的 **表面着色器(Surface Shader)** 代码。因为这样我们的屏幕效果可以得到更好的优化,因为这里我们仅仅是需要处理 **渲染纹理(Render Texture)** 的像素而已。所以我们将在着色器中创建一个新的 **通道块(Pass block)** 并且添加一些我们之前没有看过的 **#pragma** 声明:
```c#
Pass
{
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#include "UnityCG.cginc"
```
- 10.为了能够访问从Unity编辑器发送到着色器的数据,我们需要在 **CGPROGRAM** 中创建对应的变量:
```c#
uniform sampler2D _MainTex;
fixed _LuminosityAmount;
```
- 11.最后,就剩下去设置我们的 **像素函数(pixel function** 了,在这个例子中就是 **frag()** 函数。这也是这个屏幕效果的关键代码。这个函数将会处理 **渲染纹理(Render Texture** 的每一个像素并且给我们的 **TestRenderImage.cs** 脚本中返回一张新的图像:
```c#
fixed4 frag(v2f_img i) : COLOR
{
fixed4 renderTex = tex2D(_MainTex, i.uv);
float luminosity = 0.299 * renderTex.r + 0.587 * renderTex.g + 0.114 * renderTex.b;
fixed4 finalColor = lerp(renderTex, luminosity, _LuminosityAmount);
return finalColor;
}
```
【为了防止大家跟我一样,看的一头雾水,我在这里贴一下完整的着色器代码】:
``` c#
Shader "Custom/ImageEffect"
{
Properties
{
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_LuminosityAmount ("GrayScale Amount", Range(0.0, 1)) = 1.0
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
Pass
{
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#include "UnityCG.cginc"
uniform sampler2D _MainTex;
fixed _LuminosityAmount;

fixed4 frag(v2f_img i) : COLOR
{
fixed4 renderTex = tex2D(_MainTex, i.uv);
float luminosity = 0.299 * renderTex.r + 0.587 * renderTex.g + 0.114 * renderTex.b;
fixed4 finalColor = lerp(renderTex, luminosity, _LuminosityAmount);
return finalColor;
}
ENDCG
}
}
FallBack "Diffuse"
}
```
<br>
当完成我们的着色器之后,返回到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** 中都发生了什么,并且在整个过程中是如何处理它的。
Binary file added assets/img/shader_book/diagram85.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/diagram86.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 c6a3331

Please sign in to comment.