@ -0,0 +1,566 @@
@@ -0,0 +1,566 @@
|
||||
# 屏幕后期处理效果 |
||||
|
||||
在渲染完整个场景之后,得到的屏幕图像进行了一系列操作,最终实现各种屏幕特效,如Bloom,SSAO等等。 |
||||
|
||||
要实现屏幕后期处理的基础是,必须得到渲染后的屏幕图像,即抓取屏幕,而Unity为我们提供了这样一个方便的接口---OnRenderImage函数。 |
||||
|
||||
## 写在前面 |
||||
|
||||
简单介绍几个常用名词。 |
||||
|
||||
### 0.1 UV |
||||
|
||||
U和V分别是图片在显示器水平、垂直方向上的坐标,取值范围是0~1。通常是从左往右,从下往上从0开始依次递增。 |
||||
|
||||
## 1.基础类 |
||||
|
||||
在进行屏幕后期处理之前,我们需要检查一系列条件是否满足,例如当前平台是否支持渲染纹理跟屏幕特效。是否支持当前使用的shader等。为此,我们创建了一个用于屏幕后期处理效果的基类。在实现各种屏幕特效时,我们只需继承基础类。在实现派生类中不同操作即可。 |
||||
|
||||
```csharp |
||||
using UnityEngine; |
||||
using System.Collections; |
||||
|
||||
[ExecuteInEditMode] |
||||
[RequireComponent (typeof(Camera))] |
||||
public class PostEffectsBase : MonoBehaviour { |
||||
|
||||
// 开始检查 |
||||
protected void CheckResources() { |
||||
bool isSupported = CheckSupport(); |
||||
|
||||
if (isSupported == false) { |
||||
NotSupported(); |
||||
} |
||||
} |
||||
|
||||
// 在检查是否支持屏幕后期shader的时候调用 |
||||
protected bool CheckSupport() { |
||||
if (SystemInfo.supportsImageEffects == false || SystemInfo.supportsRenderTextures == false) { |
||||
Debug.LogWarning("This platform does not support image effects or render textures."); |
||||
return false; |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
// 在不支持屏幕后期shader的时候调用。 |
||||
protected void NotSupported() { |
||||
enabled = false; |
||||
} |
||||
|
||||
protected void Start() { |
||||
CheckResources(); |
||||
} |
||||
|
||||
// 在需要通过该屏幕后期shader创建相应材质的时候调用。 |
||||
protected Material CheckShaderAndCreateMaterial(Shader shader, Material material) { |
||||
if (shader == null) { |
||||
return null; |
||||
} |
||||
|
||||
if (shader.isSupported && material && material.shader == shader) |
||||
return material; |
||||
|
||||
if (!shader.isSupported) { |
||||
return null; |
||||
} |
||||
else { |
||||
material = new Material(shader); |
||||
material.hideFlags = HideFlags.DontSave; |
||||
if (material) |
||||
return material; |
||||
else |
||||
return null; |
||||
} |
||||
} |
||||
} |
||||
``` |
||||
|
||||
### 基础类使用案例(继承PostEffectBase) |
||||
|
||||
```csharp |
||||
using System.Collections; |
||||
using System.Collections.Generic; |
||||
using UnityEngine; |
||||
|
||||
public class ImageEffectGeneral : PostEffectsBase{ |
||||
public Shader scannerShader; |
||||
private Material scannerMaterial; |
||||
public Material material |
||||
{ |
||||
get |
||||
{ |
||||
// 检查是否生成对应材质 |
||||
scannerMaterial = CheckShaderAndCreateMaterial(scannerShader, scannerMaterial); |
||||
return scannerMaterial; |
||||
} |
||||
} |
||||
//定义相关数据调整 |
||||
public string dataTags = ""; |
||||
public float dataScale = 1.0f; |
||||
public new void Start() |
||||
{ |
||||
//如果需要提前规定Camera获取深度纹理或者法线纹理,需要提前声明 |
||||
//GetComponent<Camera>().depthTextureMode = DepthTextureMode.DepthNormals; |
||||
} |
||||
|
||||
void OnRenderImage(RenderTexture src, RenderTexture dest) |
||||
{ |
||||
//GetComponent<Camera>().depthTextureMode = DepthTextureMode.DepthNormals; |
||||
if (material != null) |
||||
{ |
||||
//需要传入的数据,需要在Shader中定义好对应变量名称,作为传入依据。 |
||||
material.SetFloat(dataTags, dataScale); |
||||
Graphics.Blit(src, dest, material); |
||||
} |
||||
else |
||||
{ |
||||
Graphics.Blit(src, dest); |
||||
} |
||||
} |
||||
} |
||||
``` |
||||
|
||||
## 2.使用渲染纹理 |
||||
|
||||
在Unity中,获取深度纹理是非常简单的,直接在脚本中设置摄像机的depthTextureMode,就可以获取对应的纹理数据。 |
||||
|
||||
深度纹理获取 |
||||
|
||||
```csharp |
||||
GetComponent<Camera>().depthTextureMode = DepthTextureMode.Depth; |
||||
``` |
||||
|
||||
法线纹理+深度纹理获取: |
||||
|
||||
```csharp |
||||
GetComponent<Camera>().depthTextureMode = DepthTextureMode.DepthNormals; |
||||
``` |
||||
|
||||
#### 深度纹理采样 |
||||
|
||||
当在Shader中访问到深度纹理 _CameraDepthTexture 后,我们就可以使用当前像素的纹理坐标对它进行采样。绝大多数情况下,我们直接使用tex2D函数采样即可,但在某些平台(例如PS3或者PSP)上,我们需要一些特殊处理。Unity为我们提供了一个统一的宏,SAMPLE_Depth_Texture,用来处理这些由于平台差异造成的问题。 |
||||
|
||||
#### 法线纹理采样 |
||||
|
||||
用以下语句进行的法线采样,是范围在-1~1之间的水平&垂直法线值,如果需要展示成法线贴图,需要手动对normal进行归一化处理 normal = 0.5*normal+0.5 |
||||
|
||||
```cpp |
||||
fixed3 normal = DecodeViewNormalStereo(tex2D(_CameraDepthNormalsTexture,i.uv)); |
||||
``` |
||||
|
||||
# |
||||
|
||||
# 屏幕后期处理效果实例 |
||||
|
||||
## 1.uv应用:扭曲 |
||||
|
||||
通过重新计算uv,采用黑白图来对uv进行重新赋值,可以产生扭曲的效果。 |
||||
|
||||
```cpp |
||||
Shader "Hidden/ImageDistort" |
||||
{ |
||||
Properties |
||||
{ |
||||
_MainTex ("Texture", 2D) = "white" {} |
||||
_DistortTex ("Distort Texture", 2D) = "white" {} |
||||
_DistortScale ("Distort Scale",Range(0,1)) = 1.0 |
||||
} |
||||
SubShader |
||||
{ |
||||
// No culling or depth |
||||
Cull Off ZWrite Off ZTest Always |
||||
|
||||
Pass |
||||
{ |
||||
CGPROGRAM |
||||
#pragma vertex vert |
||||
#pragma fragment frag |
||||
|
||||
#include "UnityCG.cginc" |
||||
|
||||
sampler2D _MainTex; |
||||
sampler2D _DistortTex; |
||||
float _DistortScale; |
||||
|
||||
struct appdata |
||||
{ |
||||
float4 vertex : POSITION; |
||||
float2 uv : TEXCOORD0; |
||||
}; |
||||
|
||||
struct v2f |
||||
{ |
||||
float2 uv : TEXCOORD0; |
||||
float4 vertex : SV_POSITION; |
||||
}; |
||||
|
||||
v2f vert (appdata v) |
||||
{ |
||||
v2f o; |
||||
o.vertex = UnityObjectToClipPos(v.vertex); |
||||
o.uv = v.uv; |
||||
return o; |
||||
} |
||||
|
||||
fixed4 frag (v2f i) : SV_Target |
||||
{ |
||||
//i.uv = abs(0.5-i.uv); |
||||
fixed4 noise = tex2D(_DistortTex,i.uv); |
||||
i.uv = i.uv + (0.5-noise.r) * _DistortScale*0.1; |
||||
fixed4 col = tex2D(_MainTex, i.uv); |
||||
// just invert the colors |
||||
//col.rgb = 1 - col.rgb; |
||||
return col; |
||||
} |
||||
ENDCG |
||||
} |
||||
} |
||||
} |
||||
``` |
||||
|
||||
使用时,可以参考上述中的,基础类使用案例,编写对应的cs脚本并挂在摄像机上,通过调整相关参数来观看对应效果 |
||||
|
||||
## 2.深度纹理应用:扫描效果探究 |
||||
|
||||
深度图的每一个像素值表示场景中某点与摄像机的距离。 |
||||
|
||||
是指将从图像采集器到场景中各点的距离(深度)作为像素值的图像,它直接反映了景物可见表面的几何形状。 |
||||
|
||||
shader中,深度图的获取可以通过以下代码进行 |
||||
|
||||
```cpp |
||||
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv); |
||||
float linearDepth = Linear01Depth(depth); |
||||
fixed4 col = fixed4(linearDepth,linearDepth,linearDepth,1); |
||||
``` |
||||
|
||||
当通过纹理采样SAMPLE_DEPTH_TEXTURE之后,得到的深度值往往是非线性的。然而,我们的计算过程中通常是需要线性的深度值,也就是说,我们需要把投影后的深度值变换到线性空间下。 |
||||
|
||||
最终结果如下,上图为原始场景,下图为场景中提取的深度图(通过Linear01Depth解码) |
||||
|
||||
0表示该点和摄像机处于同一位置,1表示该点位于视锥体的远裁剪平面上(摄像机Near=0.3 Far=50) |
||||
|
||||
![avatar](./img/ImageEffect/DepthTexture1.png) |
||||
|
||||
LinearEyeDepth负责把深度纹理的采样结果转换到视角空间下的深度值 |
||||
|
||||
LinearEyeDepth解码,此时的摄像机远裁剪平面与近裁剪平面并不会对结果产生影响,通过调节参数_DepthParam,看对应的变化区间(下图为_DepthParam为10的样子,即黑的部分是离镜头10m以内的,白色部分是离镜头11m以外的,模糊的部分为距离10-11m之间的值) |
||||
|
||||
```cpp |
||||
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv); |
||||
float linearDepth = LinearEyeDepth(depth); |
||||
float diff = linearDepth-_DepthParam; |
||||
``` |
||||
|
||||
![avatar](./img/ImageEffect/DepthTexture3.png) |
||||
|
||||
使用floor函数,对上述的值进行取整,并对采样值归一化,可以得到比较尖锐化的边缘,此时深度在11m范围内的部分会全部变成黑色。 |
||||
|
||||
```cpp |
||||
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv); |
||||
float linearDepth = LinearEyeDepth(depth); |
||||
float diff = linearDepth-_DepthParam; |
||||
diff = saturate(floor(diff)); |
||||
``` |
||||
|
||||
![avatar](./img/ImageEffect/DepthTexture4.png) |
||||
|
||||
通过负数取值归一化,可对采样值进行反色,让黑的部分变白,白的部分变黑。 |
||||
|
||||
```cpp |
||||
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv); |
||||
float linearDepth = LinearEyeDepth(depth); |
||||
float diff = linearDepth-_DepthParam; |
||||
diff = saturate(-diff); |
||||
``` |
||||
|
||||
![avatar](./img/ImageEffect/DepthTexture5.png) |
||||
|
||||
上述操作中,如果不进行归一化(取值范围限制在0-1之间)的话,黑的部分可能会产生负数,白的部分可能会超过1,最终可能计算不出正确结果。 |
||||
|
||||
将上述两张图按加算合起来,得到一个按深度扫描的效果雏形 |
||||
|
||||
```cpp |
||||
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv); |
||||
float linearDepth = LinearEyeDepth(depth); |
||||
float diff = linearDepth-_DepthParam; |
||||
diff = saturate(floor(diff))+saturate(-diff); |
||||
``` |
||||
|
||||
![avatar](./img/ImageEffect/DepthTexture6.png) |
||||
|
||||
得到这部分后,因为归一化前面已经做过了,我们仅需要对上述结果进行反色处理(1-diff),再混合上原背景(对原图像进行插值处理,此时我们用到前面所提到的插值函数lerp),就可以看到对应的扫描效果了。 |
||||
|
||||
```cpp |
||||
fixed4 mainTex = tex2D(_MainTex, i.uv); |
||||
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv); |
||||
float linearDepth = LinearEyeDepth(depth.r); |
||||
fixed4 col = fixed4(linearDepth,linearDepth,linearDepth,1); |
||||
float diff = linearDepth-_DepthParam; |
||||
fixed4 border = 1-(saturate(floor(diff))+saturate(-diff*0.5)); |
||||
fixed4 final = lerp(mainTex,border*_DepthColor,border); |
||||
return final; |
||||
``` |
||||
|
||||
![avatar](./img/ImageEffect/DepthTexture7.png) |
||||
|
||||
下面提供完整代码 |
||||
|
||||
ExampleScannerImage.shader |
||||
|
||||
```cpp |
||||
Shader "Hidden/ExampleScannerImage" |
||||
{ |
||||
Properties |
||||
{ |
||||
_MainTex ("Texture", 2D) = "white" {} |
||||
_DepthParam ("DepthParam",Float) = 1.0 |
||||
_DepthColor("DepthColor",Color) = (1,1,1,1) |
||||
} |
||||
SubShader |
||||
{ |
||||
// No culling or depth |
||||
Cull Off ZWrite Off ZTest Always |
||||
|
||||
Pass |
||||
{ |
||||
CGPROGRAM |
||||
#pragma vertex vert |
||||
#pragma fragment frag |
||||
#include "UnityCG.cginc" |
||||
|
||||
sampler2D _MainTex; |
||||
sampler2D _CameraDepthTexture; |
||||
float _DepthParam; |
||||
fixed4 _DepthColor; |
||||
|
||||
struct appdata |
||||
{ |
||||
float4 vertex : POSITION; |
||||
float2 uv : TEXCOORD0; |
||||
}; |
||||
|
||||
struct v2f |
||||
{ |
||||
float2 uv : TEXCOORD0; |
||||
float4 vertex : SV_POSITION; |
||||
}; |
||||
|
||||
v2f vert (appdata v) |
||||
{ |
||||
v2f o; |
||||
o.vertex = UnityObjectToClipPos(v.vertex); |
||||
o.uv = v.uv; |
||||
return o; |
||||
} |
||||
|
||||
fixed4 frag (v2f i) : SV_Target |
||||
{ |
||||
fixed4 mainTex = tex2D(_MainTex, i.uv); |
||||
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv); |
||||
float linearDepth = LinearEyeDepth(depth.r); |
||||
float diff = linearDepth-_DepthParam; |
||||
fixed4 border = 1-(saturate(floor(diff))+saturate(-diff*0.5)); |
||||
fixed4 final = lerp(mainTex,border*_DepthColor,border); |
||||
return final; |
||||
} |
||||
ENDCG |
||||
} |
||||
} |
||||
} |
||||
``` |
||||
|
||||
PostEffects.cs(参考本篇开头) |
||||
|
||||
ImageScannerEffect.cs |
||||
|
||||
```csharp |
||||
using System.Collections; |
||||
using System.Collections.Generic; |
||||
using UnityEngine; |
||||
|
||||
public class ImageScannerEffect : PostEffectsBase |
||||
{ |
||||
public Shader scannerShader; |
||||
private Material scannerMaterial; |
||||
public Material material |
||||
{ |
||||
get |
||||
{ |
||||
// 检查是否生成对应材质 |
||||
scannerMaterial = CheckShaderAndCreateMaterial(scannerShader, scannerMaterial); |
||||
return scannerMaterial; |
||||
} |
||||
} |
||||
//定义相关数据调整 |
||||
public float dataScale = 1.0f; |
||||
public Color dataColor = Color.white; |
||||
|
||||
void OnRenderImage(RenderTexture src, RenderTexture dest) |
||||
{ |
||||
//GetComponent<Camera>().depthTextureMode = DepthTextureMode.DepthNormals; |
||||
if (material != null) |
||||
{ |
||||
material.SetFloat("_DepthParam", dataScale); |
||||
material.SetColor("_DepthColor", dataColor); |
||||
Graphics.Blit(src, dest, material); |
||||
} |
||||
else |
||||
{ |
||||
Graphics.Blit(src, dest); |
||||
} |
||||
} |
||||
} |
||||
``` |
||||
|
||||
使用时,将ImageScannerEffect.cs拖拽至主摄像机中,并且把ExampleScannerImage.shader 这个文件拖至Scanner Shader选项卡中去。 |
||||
|
||||
录制动画,调节脚本中DataScale选项依次增大,完成相关扫描特效的制作。 |
||||
|
||||
可以根据自己喜好,调整相关颜色等操作。 |
||||
|
||||
添加图片遮罩,根据法线纹理生成网格纹理等等我们后续会接着讲。 |
||||
|
||||
![avatar](./img/ImageEffect/DepthTexture8.gif) |
||||
|
||||
## 3.纹理附加:扫描效果探究(续) |
||||
|
||||
在上图中,我们由于为对插值处理进行相关优化,所以在尾部会出现一些黑色的部分,这些黑色的部分是因为原本的图像是呈现黑色的。 |
||||
|
||||
这次我们使用一张附加纹理,使得扫描效果更具科技感一些 |
||||
|
||||
这次是我们使用的附加纹理原图 |
||||
|
||||
![avatar](./img/ImageEffect/net7.jpg) |
||||
|
||||
在使用上述图片之前,我们需要了解uv转换函数TRANSFORM_TEX |
||||
|
||||
TRANSFORM_TEX方法比较简单,就是将模型顶点的uv和Tiling、Offset两个变量进行运算,计算出实际显示用的定点uv。 |
||||
|
||||
如果对_MaskTex这个纹理进行uv转换,必须声明变量_MaskTex_ST(float4类型) |
||||
|
||||
实际调整的时候,_MaskTex_ST.xy代表Tilling(缩放程度),_MaskTex_ST.zw代表Offset(偏移程度) |
||||
|
||||
我们在声明变量的时候,沿用_MaskTex的同时下方添加一个ST的声明即可 |
||||
|
||||
```cpp |
||||
sampler2D _MaskTex; |
||||
float4 _MaskTex_ST; |
||||
float _MaskTexTillY; |
||||
``` |
||||
|
||||
片元着色器 |
||||
|
||||
```cpp |
||||
_MaskTex_ST.y = _MaskTexTillY; |
||||
i.uv = TRANSFORM_TEX(i.uv, _MaskTex); |
||||
fixed4 maskTex = tex2D(_MaskTex,i.uv); |
||||
``` |
||||
|
||||
在附加纹理时,可以通过两种方式进行,一种是附加法,一种是累乘法 |
||||
|
||||
我们这里使用了带颜色的附加法进行(因为包含了黑色,原本是黑色的部分就不会变化) |
||||
|
||||
同样的,我们在原本的基础上使用了_DepthColor.a 作为底图的影响程度,这样可以淡化尾部的黑色部分。 |
||||
|
||||
```cpp |
||||
fixed4 final = lerp(mainTex,saturate(border*_DepthColor+mainTex*_DepthColor.a+maskTex*_DepthColor),saturate(border)); |
||||
``` |
||||
|
||||
改写完shader后,在cs脚本中添加以下内容即可 |
||||
|
||||
```csharp |
||||
public Texture dataTex; |
||||
public float tillY; |
||||
``` |
||||
|
||||
OnRenderImage函数中 |
||||
|
||||
```csharp |
||||
material.SetTexture("_MaskTex", dataTex); |
||||
material.SetFloat("_MaskTexTillY", tillY); |
||||
``` |
||||
|
||||
shader完整代码 |
||||
|
||||
```c |
||||
Shader "Hidden/ExampleScannerImage" |
||||
{ |
||||
Properties |
||||
{ |
||||
_MainTex ("Texture", 2D) = "white" {} |
||||
_MaskTex("Mask Texture", 2D) = "white" {} |
||||
_MaskTexTillY("Till Y",Float) = 1.0 |
||||
_DepthParam ("DepthParam",Float) = 1.0 |
||||
_DepthColor("DepthColor",Color) = (1,1,1,1) |
||||
_DepthLength ("DepthLength",Float) = 1.0 |
||||
} |
||||
SubShader |
||||
{ |
||||
// No culling or depth |
||||
Cull Off ZWrite Off ZTest Always |
||||
|
||||
Pass |
||||
{ |
||||
CGPROGRAM |
||||
#pragma vertex vert |
||||
#pragma fragment frag |
||||
#include "UnityCG.cginc" |
||||
|
||||
sampler2D _MainTex; |
||||
sampler2D _CameraDepthTexture; |
||||
sampler2D _MaskTex; |
||||
float4 _MaskTex_ST; |
||||
float _MaskTexTillY; |
||||
float _DepthParam; |
||||
fixed4 _DepthColor; |
||||
float _DepthLength; |
||||
|
||||
struct appdata |
||||
{ |
||||
float4 vertex : POSITION; |
||||
float2 uv : TEXCOORD0; |
||||
}; |
||||
|
||||
struct v2f |
||||
{ |
||||
float2 uv : TEXCOORD0; |
||||
float4 vertex : SV_POSITION; |
||||
}; |
||||
|
||||
v2f vert (appdata v) |
||||
{ |
||||
v2f o; |
||||
o.vertex = UnityObjectToClipPos(v.vertex); |
||||
o.uv = v.uv; |
||||
return o; |
||||
} |
||||
|
||||
fixed4 frag (v2f i) : SV_Target |
||||
{ |
||||
fixed4 mainTex = tex2D(_MainTex, i.uv); |
||||
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv); |
||||
float linearDepth = LinearEyeDepth(depth.r); |
||||
float diff = linearDepth-_DepthParam; |
||||
fixed4 border = 1-(saturate(floor(diff))+saturate(-diff*_DepthLength)); |
||||
|
||||
_MaskTex_ST.y = _MaskTexTillY; |
||||
i.uv = TRANSFORM_TEX(i.uv, _MaskTex); |
||||
fixed4 maskTex = tex2D(_MaskTex,i.uv); |
||||
|
||||
fixed4 final = lerp(mainTex,saturate(border*_DepthColor+mainTex*_DepthColor.a+maskTex*_DepthColor),saturate(border)); |
||||
return final; |
||||
} |
||||
ENDCG |
||||
} |
||||
} |
||||
} |
||||
|
||||
``` |
||||
|
||||
具体效果截图 |
||||
|
||||
![avatar](./img/ImageEffect/DepthTexture9.gif) |
||||
|
||||
此时,我们的扫描效果就已经比上一个阶段高出一个档次了 |
@ -0,0 +1,532 @@
@@ -0,0 +1,532 @@
|
||||
# 前言 |
||||
|
||||
本编程指南仅尽可能的列出编写一个shader的参考 |
||||
|
||||
# |
||||
|
||||
# shader编写结构 |
||||
|
||||
## 1.定义Properties(方便在创建材质的时候可以传入贴图等数据) |
||||
|
||||
## 2.编写SubShader主题 |
||||
|
||||
## 2.1【Tags】 |
||||
|
||||
## 2.2【RenderSetUp】 |
||||
|
||||
### 2.2.1【LOD】渲染深度(详情参考Unity LOD 远景渲染部分) |
||||
|
||||
## 2.3【GrabPass】【UsePass】【Pass】【Pass】(第二个或者其他) |
||||
|
||||
----------------------案例Pass-------------------- |
||||
【RenderSetUp】 |
||||
CGPROGRAM |
||||
//编译顶点着色器 |
||||
#pragma vertex vert |
||||
//编译片元着色器 |
||||
#pragma fragment frag |
||||
|
||||
### 2.3.1 其他引用以及其它编译指令 |
||||
|
||||
### 2.3.2 声明参数 |
||||
|
||||
### 2.3.3 设置应用数据->顶点着色器结构体a2v,appdata(a2v为app to vert简写) |
||||
|
||||
### 2.3.4 设置顶点->片源着色器结构体v2f(v2f为vert to fragment简写) |
||||
|
||||
### 2.3.5 编写顶点着色器渲染方法 |
||||
|
||||
v2f vert (a2v v){ |
||||
v2f o; |
||||
//着色器内容 |
||||
return o; |
||||
} |
||||
|
||||
### 2.3.5 编写片源着色器渲染方法 |
||||
|
||||
fixed4 frag (v2f i) : SV_Target{ |
||||
fixed4 tex = ...; |
||||
//着色器内容 |
||||
return tex; |
||||
} |
||||
ENDCG |
||||
|
||||
--------------------------------------------------- |
||||
|
||||
## 3.设置Fallback,如果显卡无法运行上面的SubShader,则执行FallBack预设进行渲染 |
||||
|
||||
# 1.Properties(参考书籍P30) |
||||
|
||||
-------------------------------------------------------------------------- |
||||
|
||||
### 传入数据常用类型 |
||||
|
||||
## Int |
||||
|
||||
_Int("Int",Int)=2 |
||||
|
||||
## Float |
||||
|
||||
_Float("Float",Float)=1.0 |
||||
|
||||
## Range |
||||
|
||||
_Range("Range",Range(0.0,5.0))=1.0 |
||||
|
||||
## Color |
||||
|
||||
_Color("Color",Color)=(1,1,1,1) |
||||
|
||||
## Vector |
||||
|
||||
_Vector("Vector",Vector)=(1,1,1,1) |
||||
|
||||
## 2D |
||||
|
||||
_MainTex ("Texture", 2D) = "white" {} |
||||
ps:内置纹理名称包含white、black、gray、bump等等 |
||||
|
||||
## Cube |
||||
|
||||
_Cubemap ("Environment Cubemap", Cube) = "_Skybox" {} |
||||
|
||||
## 3D |
||||
|
||||
_3D ("3D", 3D) = "black" {} |
||||
-------------------------------------------------------------------------- |
||||
|
||||
# 2.Tags(参考网站:https://gameinstitute.qq.com/community/detail/121997) |
||||
|
||||
-------------------------------------------------------------------------- |
||||
|
||||
### 渲染类型声明(包含渲染顺序) |
||||
|
||||
Tags的书写方法为键值对,可多可少,不需要全部声明,未声明时按默认值 |
||||
Tags { "RenderType"="Opaque" "Queue"="Transparent" } |
||||
|
||||
# 2.1(SubShader)Tags |
||||
|
||||
## key:Queue |
||||
|
||||
渲染顺序,括号内为优先度,值越小越先被渲染 |
||||
|
||||
## value |
||||
|
||||
Background(1000) |
||||
Geometry(2000)【默认】 |
||||
AlphaTest(2450) |
||||
Transparent(3000) |
||||
Overlay(4000) |
||||
可以自定义值例如"Queue"="3100" |
||||
|
||||
## key:RenderType |
||||
|
||||
渲染类型 |
||||
|
||||
## value |
||||
|
||||
Opaque-----------------不透明(法线、自发光、反射、地形Shader) |
||||
Transparent------------半透明(透明、粒子、字体、地形添加通道Shader) |
||||
TransparentCutout------遮罩透明(透明裁切、双通道植物Shader) |
||||
Background-------------天空盒Shader |
||||
Overlay----------------GUI纹理、光晕、闪光Shader |
||||
TreeOpaque-------------地形引擎——树皮 |
||||
TreeTransparentCutout--地形引擎——树叶 |
||||
TreeBillboard----------地形引擎——公告牌(始终面向摄像机)式树木 |
||||
Grass------------------地形引擎——草 |
||||
GrassBillboard---------地形引擎——公告牌(始终面向摄像机)式草 |
||||
|
||||
## key:DisableBatching |
||||
|
||||
是否禁用Batch(打包、合并) |
||||
|
||||
## value |
||||
|
||||
True-------------------禁用 |
||||
False------------------不禁用(默认) |
||||
LODFading--------------当LOD fade开启的时候禁用,一般用在树木上面 |
||||
|
||||
## key:ForceNoShadowCasting |
||||
|
||||
是否强制不投射阴影,当这个值为True的时候,使用这个Shader的对象便不会投射阴影。 |
||||
|
||||
## key:IgnoreProjector |
||||
|
||||
无视投影器,当这个值为True的时候,对象便不受投射器影响。 |
||||
|
||||
## key:CanUseSpriteAtlas |
||||
|
||||
可使用精灵集,当这个值为False的时候,不能使用精灵集。 |
||||
|
||||
## key:PreviewType |
||||
|
||||
材质的预览形式,默认显示为球体,可以使用Plane(2D平面)或Skybox(天空盒) |
||||
|
||||
# 2.2(Pass)Tags |
||||
|
||||
## key:LightMode |
||||
|
||||
光照模式 |
||||
|
||||
## value |
||||
|
||||
Always------------------总是渲染,不使用光照 |
||||
ForwardBase-------------用于前向渲染,使用环境光、主平行光、顶点/SH(球谐函数)光照以及光照贴图 |
||||
ForwardAdd--------------用于前向渲染,额外使用每像素光,每个光照一个通道 |
||||
Deferred----------------用于延迟着色,渲染G-Buffer |
||||
ShadowCaster------------渲染对象的深度到阴影贴图或者深度纹理 |
||||
PrepassBase-------------用于(旧版)延迟光照,渲染法线和高光指数 |
||||
PrepassFinal------------用于(旧版)延迟光照,合并贴图、光照和自发光来渲染最终色彩 |
||||
Vertex------------------当对象不受光照贴图影响的时候,用来渲染(旧版)顶点发光。使用所有的顶点光照 |
||||
VertexLMRGBM------------当对象接受光照贴图影响的时候,用来渲染(旧版)顶点发光。适用于使用RGBM编码光照贴图的平台(PC&主机) |
||||
VertexLM----------------当对象接受光照贴图影响的时候,用来渲染(旧版)顶点发光。适用于使用double-LDR编码光照贴图的平台(移动平台) |
||||
|
||||
## key:PassFlags |
||||
|
||||
标志渲染管线如何传递数据给通道 |
||||
|
||||
## value |
||||
|
||||
OnlyDirectional---------只有主平行光、环境光和光照探测器的数据会传递给通道。仅用于LightMode为ForwardBase |
||||
|
||||
## key:RequireOptions |
||||
|
||||
标志通道至于在某些外部条件满足时才会被渲染 |
||||
SoftVegetation----------当Quality Setting中的Soft Vegetation选项被开启时,才会渲染通道 |
||||
|
||||
# 3.RenderSetUp(以下写在CGPROGRAM之前)(参考书籍P31) |
||||
|
||||
-------------------------------------------------------------------------- |
||||
|
||||
## 3.1 Cull |
||||
|
||||
Cull Back | Front | Off |
||||
设置剔除模式:剔除背面/正面/关闭剔除 |
||||
|
||||
## 3.2 ZTest |
||||
|
||||
ZTest Less Greater | LEqual | GEqual | Equal | NotEqual | Always |
||||
设置深度测试时使用的函数 |
||||
|
||||
## 3.3 ZWrite |
||||
|
||||
ZWrite On | Off |
||||
开启/关闭深度写入 |
||||
|
||||
## 3.4 Blend |
||||
|
||||
BlendOp (Op) |
||||
设置混合操作 |
||||
Blend (SrcFactor) (DstFactor) |
||||
Blend (SrcFactor) (DstFactor),(SrcFactorA) (DstFactorA) |
||||
开启并设置混合模式(透明材质用) |
||||
例如:Blend SrcAlpha OneMinusSrcAlpha |
||||
|
||||
其中SrcFactor为源颜色乘以的混合因子 |
||||
DstFactor为目标颜色乘以的混合因子 |
||||
两者相加存入颜色缓冲区 |
||||
|
||||
Blend混合操作表(参考书籍P174) |
||||
----------------------------------------- |
||||
|
||||
Add | 源颜色和目标颜色相加 |
||||
Sub | 源颜色减去目标颜色 |
||||
RevSub | 目标颜色减去源颜色 |
||||
Min | 使用目标颜色跟源颜色中较小的值 |
||||
Max | 使用源颜色跟目标颜色中较大的值 |
||||
|
||||
Blend混合因子表(参考书籍P174) |
||||
----------------------------------------- |
||||
|
||||
One | 1 |
||||
Zero | 0 |
||||
SrcColor | 源颜色值 |
||||
SrcAlpha | 源颜色的透明度值 |
||||
DstColor | 目标颜色值 |
||||
DstAlpha | 目标颜色的透明度值 |
||||
OneMinusSrcColor | 1-源颜色值 |
||||
OneMinusSrcAlpha | 1-源颜色的透明值 |
||||
OneMinusDstColor | 1-目标颜色值 |
||||
OneMinusDstAlpha | 1-目标颜色的透明度值 |
||||
|
||||
常用混合类型 |
||||
----------------------------------------- |
||||
|
||||
正常--------| Blend SrcAlpha OneMinusSrcAlpha |
||||
柔向相加----| Blend OneMinusDstColor One |
||||
正片叠底----| Blend DstColor Zero |
||||
两倍相乘----| Blend DstColor SrcColor |
||||
变暗--------| BlendOp Min |
||||
------------| Blend One One |
||||
变亮--------| BlendOp Max |
||||
------------| Blend One One |
||||
滤色--------| Blend OneMinusDstColor One |
||||
线性减淡----| Blend One One |
||||
|
||||
# 4.Pass |
||||
|
||||
-------------------------------------------------------------------------- |
||||
|
||||
渲染所使用的通道,可以使用抓取屏幕图像的Pass--GrabPass |
||||
以及引用其他shader的Pass--UsePass |
||||
以及自己编写的Pass |
||||
|
||||
# 5.vert & frag |
||||
|
||||
-------------------------------------------------------------------------- |
||||
|
||||
顶点着色器,编写案例如下 |
||||
需要前置结构体a2v |
||||
输出结构体v2f |
||||
(应用->顶点坐标->顶点着色器->输出顶点) |
||||
v2f vert (a2v v){ |
||||
v2f o; |
||||
//着色器内容 |
||||
return o; |
||||
} |
||||
|
||||
需要前置结构体v2f |
||||
输出着色器颜色值fixed4 |
||||
|
||||
(顶点->片元着色器->输出颜色) |
||||
|
||||
fixed4 frag (v2f i) : SV_Target{ |
||||
fixed4 tex = ...; |
||||
//着色器内容 |
||||
return tex; |
||||
} |
||||
|
||||
## 5.0 结构体语义(参考书籍P110) |
||||
|
||||
### a2v------从应用阶段传递模型数据给顶点着色器 |
||||
|
||||
| 语义 | 描述 | 类型 | |
||||
| ----------- | ------------------------------------------ | ------------- | |
||||
| POSITION | 模型空间的顶点位置 | float4 | |
||||
| NORMAL | 顶点法线 | float3 | |
||||
| TANGENT | 顶点切线 | float3 | |
||||
| TEXCOORD0-7 | 该顶点的纹理坐标,可以定义TEXCOORD0,TEXCOORD1表示第一组或者第二组 | float2&float4 | |
||||
| COLOR | 顶点颜色 | fixed4&float4 | |
||||
|
||||
### v2f------从顶点着色器传递数据给片元着色器 |
||||
|
||||
| 语义 | 描述 | 类型 | |
||||
| ----------- | ----------------- | ------ | |
||||
| SV_POSITION | 裁剪空间中的顶点坐标 | float4 | |
||||
| COLOR0 | 通常用于输出第一组顶点颜色,非必需 | fixed4 | |
||||
| COLOR1 | 通常用于输出第二组顶点颜色,非必需 | fixed4 | |
||||
| TEXCOORD0-7 | 用于输出纹理坐标0-7 | 任意 | |
||||
|
||||
### SV_Target------片元着色器输出时用的语义 |
||||
|
||||
输出值将会存储到渲染目标(Render Target)中。 |
||||
|
||||
### VPOS/WPOS------片元着色器传递屏幕坐标的语义 |
||||
|
||||
类型为float4,xy为屏幕空间中的坐标,z为近裁剪平面处于远裁剪平面处的分量,范围0-1。对于w,如果摄像机投影类型为正交投影,w恒为1,如果使用透视投影,w的取值范围为[1/Near,1/Far],Near为近裁剪平面处于摄像机的距离,Far为远裁剪平面处于摄像机的距离。 |
||||
|
||||
## 5.1 变换矩阵(参考书籍P87) |
||||
|
||||
----------------------------------------- |
||||
|
||||
### UNITY_MATRIX_MVP(取模现在被UnityObjectToClipPos(v.vertex)所替代) |
||||
|
||||
当前模型的观察投影矩阵,顶点/方向矢量 模型空间->裁剪空间 |
||||
|
||||
### UNITY_MATRIX_MV |
||||
|
||||
当前的模型观察矩阵,顶点/方向矢量 模型空间->观察空间 |
||||
|
||||
### UNITY_MATRIX_V |
||||
|
||||
当前的观察矩阵,顶点/方向矢量 世界空间->观察空间 |
||||
|
||||
### UNITY_MATRIX_P |
||||
|
||||
当前的投影矩阵,顶点/方向矢量 观察空间->裁剪空间 |
||||
|
||||
### UNITY_MATRIX_VP |
||||
|
||||
当前的观察投影矩阵,顶点/方向矢量 世界空间->裁剪空间 |
||||
|
||||
### UNITY_MATRIX_T_MV |
||||
|
||||
UNITY_MATRIX_MV的转置矩阵 |
||||
|
||||
### UNITY_MATRIX_IT_MV |
||||
|
||||
UNITY_MATRIX_MV的逆转置矩阵,用于将发现从模型空间变换到观察空间,也可用于得到 |
||||
UNITY_MATRIX_MV的逆矩阵 |
||||
|
||||
### unity_ObjectToWorld(原为_Object2World) |
||||
|
||||
当前的模型矩阵,用于将顶点/方向矢量从模型空间变换到世界空间 |
||||
|
||||
### unity_WorldToObject(原为_World2Object) |
||||
|
||||
unity_ObjectToWorld的逆矩阵,用于将顶点/方向矢量从世界空间变换到模型空间 |
||||
|
||||
## 5.2 Unity内置的摄像机和屏幕参数(参考书籍P88) |
||||
|
||||
------------------------------------------------- |
||||
|
||||
### 【float3】 _WorldSpaceCameraPos |
||||
|
||||
该摄像机在世界空间中的位置 |
||||
|
||||
### 【float4】 _ProjectionParams |
||||
|
||||
x = 1.0 & -1.0 | y = Near | z = Far | w = 1.0 + 1.0 / Far |
||||
Near = 近裁剪平面到摄像机的距离 |
||||
Far = 远裁剪平面到摄像机的距离 |
||||
|
||||
### 【float4】 _ScreenParams |
||||
|
||||
x = width | y = height | z = 1.0 + 1.0 / width | w = 1.0 + 1.0 / height |
||||
width = 该摄像机的渲染目标的像素宽度 |
||||
height = 该摄像机的渲染目标的像素高度 |
||||
|
||||
### 【float4】 _ZBufferParams |
||||
|
||||
x = 1 - Far / Near | y = Far / Near | z = x / Far | w = y / Far |
||||
该变量用于线性化Z缓存中的深度值 |
||||
|
||||
### 【float4】unity_OrthoParams |
||||
|
||||
x = width | y = height | z = 无定义 | w = 1.0(该摄像机为正交摄像机) & 0.0(该摄像机为透视摄像机) |
||||
width = 正交投影摄像机的宽度 |
||||
height = 正交投影摄像机的高度 |
||||
|
||||
### 【float4x4】unity_CameraProjection |
||||
|
||||
该摄像机的投影矩阵 |
||||
|
||||
### 【float4x4】unity_CameraInvProjection |
||||
|
||||
该摄像机的投影矩阵的逆矩阵 |
||||
|
||||
### 【float4[6]】unity_CameraWorldClipPlanes[6] |
||||
|
||||
该摄像机的6个裁剪平面在世界空间下的等式 |
||||
按如下顺序:左、右、上、下、近、远裁剪平面 |
||||
|
||||
## 5.3 UnityCG.cginc常用的帮助函数(参考书籍P137) |
||||
|
||||
------------------------------------------------- |
||||
|
||||
### 【float3】 WorldSpaceViewDir(float4 v) |
||||
|
||||
输入:模型空间中的顶点位置 |
||||
返回:世界空间红从该点到摄像机的观察方向。 |
||||
ps:内部实现使用了UnityWorldSpaceViewDir函数 |
||||
|
||||
### 【float3】 UnityWorldSpaceViewDir(float4 v) |
||||
|
||||
输入:世界空间中的顶点位置 |
||||
返回:世界空间中从该点到摄像机的观察方向。 |
||||
|
||||
### 【float3】 ObjectSpaceViewDir(float4 v) |
||||
|
||||
输入:一个模型空间中的顶点位置 |
||||
返回:模型空间中该点到摄像机的观察方向。 |
||||
|
||||
### 【float3】 WorldSpaceLightDir(float4 v)【仅用于前向渲染】【未被归一化】 |
||||
|
||||
输入:一个模型空间中的顶点位置 |
||||
返回:世界空间中从该点到光源的光照方向。 |
||||
ps:内部实现使用了UnityWorldSpaceLightDir函数 |
||||
|
||||
### 【float3】 UnityWorldSpaceLightDir(float4 v)【仅用于前向渲染】【未被归一化】 |
||||
|
||||
输入:世界空间中的顶点位置 |
||||
返回:世界空间中该点到光源的光照方向 |
||||
|
||||
### 【float3】 ObjSpaceLightDir(float4 v)【仅用于前向渲染】【未被归一化】 |
||||
|
||||
输入:一个模型空间中的顶点位置 |
||||
返回:模型空间中从该点到光源的光照方向 |
||||
|
||||
### 【float3】 UnityObjectToWorldNormal(float3 normal) |
||||
|
||||
把法线方向从模型空间转换到世界空间中 |
||||
|
||||
### 【float3】 UnityObjectToWorldDir(float3 dir) |
||||
|
||||
把方向矢量从模型空间变换到世界空间中 |
||||
|
||||
### 【float3】 UnityWorldToObjectDir(float3 dir) |
||||
|
||||
把方向矢量从世界空间变换到模型空间中 |
||||
|
||||
# 6.关于光照 |
||||
|
||||
## 6.1 Unity渲染路径 |
||||
|
||||
------------------------------------------------------------ |
||||
|
||||
在tag-LightMode里设置,详情见本文2.2 |
||||
|
||||
## 6.2 Unity内置光照变量和函数 |
||||
|
||||
------------------------------------------------------------ |
||||
|
||||
### 【float4】 _LightColor0 |
||||
|
||||
该Pass处理的逐像素光源的颜色 |
||||
|
||||
### 【float4】 _WorldSpaceLightPos0 |
||||
|
||||
该Pass处理的逐像素光源的位置。 |
||||
如果该光源是平行光,_WorldSpaceLightPos0.w=0 |
||||
如果是其他类型光,_WorldSpaceLightPos0.w=1 |
||||
|
||||
### 【float4x4】 _LightMatrix0 |
||||
|
||||
从世界空间到光源空间的变换矩阵。可以用于采样cookie和光照衰减纹理 |
||||
|
||||
### 【float4】 unity_4LightPosX0 unity_4LightPosY0 unity_4LightPosZ0 |
||||
|
||||
仅用于BasePass |
||||
前四个非重要的点光源在世界空间中的位置 |
||||
|
||||
### 【float4】 unity_4LightPosX0 unity_4LightPosY0 unity_4LightPosZ0 |
||||
|
||||
仅用于BasePass |
||||
前四个非重要的点光源在世界空间中的位置 |
||||
|
||||
### 【float4】 unity_4LightAtten0 |
||||
|
||||
仅用于BasePass |
||||
前四个非重要的点光源的衰减因子 |
||||
|
||||
### 【half4[4]】 unity_LightColor |
||||
|
||||
仅用于BasePass |
||||
前四个非重要的点光源的颜色 |
||||
|
||||
## 6.3 Unity前向渲染可以使用的内置光照参数 |
||||
|
||||
------------------------------------------------------------ |
||||
|
||||
以下函数仅用于前向渲染才可使用。 |
||||
|
||||
### 【float3】 WorldSpaceLightDir(float4 v)【未被归一化】 |
||||
|
||||
输入:一个模型空间中的顶点位置 |
||||
返回:世界空间中从该点到光源的光照方向。 |
||||
ps:内部实现使用了UnityWorldSpaceLightDir函数 |
||||
|
||||
### 【float3】 UnityWorldSpaceLightDir(float4 v)【未被归一化】 |
||||
|
||||
输入:一个世界空间中的顶点位置 |
||||
返回:世界空间中从该点到光源的光照方向。 |
||||
|
||||
### 【float3】 ObjSpaceLightDir(float4 v)【未被归一化】 |
||||
|
||||
输入:一个模型空间中的顶点位置 |
||||
返回:模型空间中从该点到光源的光照方向。 |
||||
|
||||
### 【float3】 Shade4PointLights(...) |
||||
|
||||
输入:已经打包进矢量的光照数据,参考6.2 |
||||
返回:计算逐顶点光照。 |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 754 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 226 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 564 KiB |
After Width: | Height: | Size: 757 KiB |
After Width: | Height: | Size: 2.2 MiB |
After Width: | Height: | Size: 80 KiB |
After Width: | Height: | Size: 2.0 MiB |
After Width: | Height: | Size: 2.5 MiB |
After Width: | Height: | Size: 6.8 KiB |
After Width: | Height: | Size: 8.5 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 4.8 KiB |
@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
|
||||
# 法线贴图 |
||||
|
||||
法线贴图是一种用于表现平面倾斜度的数据。 |
||||
|
||||
法线的取值范围是一个半球,如下图所示,处于半球边缘的任意点都可以代表法线的朝向。 |
||||
|
||||
法线贴图实际是将每个点的法线方向,按照颜色rgba的方式存储。其中r代表x轴旋转度,g代表y轴旋转度,b实际是轴本身的旋转,几乎无意义所以基本恒为1。 |
||||
|
||||
由于颜色值rgba属性中,不能为负值,取值范围为0-1之间的四组数值。所以将0.5作为垂直表面的法线基准值。故无凹凸表现的平面颜色值为(0.5,0.5,1,1);即为图中蓝点。 |
||||
|
||||
![avatar](./img/normalMapPosition.png) |
||||
|
||||
图1 将坐标转换成颜色的图解 |
||||
|
||||
由于法线贴图不包含海拔信息,故向右凸起等于向左凹陷,反之向左凸起等于向右凹陷。比如下方图片旋转180度,感受一下是不是就像是凹陷了一样。 |
||||
|
||||
![avatar](./img/bumpTest4.png) |
||||
|
||||
图2 实际的法线贴图(包含一个凸出的八边形,如果旋转180度,则是凹陷的八边形) |
||||
|
||||
# UnpackNormal函数应用 |
||||
|
||||
上一节讲了,由于法线贴图不能包含负值,所以在UnityShader的使用过程中,需要对法线进行一次变换,使之范围从原来的【 0 ~ 1 】转换为【 -1 ~ 1 】 |
||||
|
||||
故而 UnpackNormal ( packednormal ) = packednormal.xyz * 2 – 1 |
||||
|
||||
图2 执行完该方法后,会变成图3所示的样子 |
||||
|
||||
![avatar](./img/UnpackNormal.png) |
||||
|
||||
图3 UpackNormal 方法执行后 |
||||
|
||||
到这里可能会有人奇怪了,为什么左方跟下方都是一样的颜色。这个方法执行之后,实际上黑的部分是负数,负数的颜色是没法正确表现的,故变成了黑色。我们进行一次abs绝对值函数处理,就能看到对应颜色啦。 |
||||
|
||||
![avatar](./img/abs_UnpackNormal.png) |
||||
|
||||
图4 abs绝对值处理 |
@ -0,0 +1,195 @@
@@ -0,0 +1,195 @@
|
||||
# 卡通水体的制作 |
||||
|
||||
话不多说,先上效果图 |
||||
|
||||
![avatar](./img/ToonWaterShader2.gif) |
||||
|
||||
该水体编写时,用到了GrabPass抓取屏幕,深度纹理,法线变换,菲涅尔反射,噪声纹理,以及顶点动画相关的知识 |
||||
|
||||
本篇中,仅解释编写原理,源码会在最后方放出,后续会不定期更新文档。 |
||||
|
||||
## 水体部分 |
||||
|
||||
##### 1.透明物体的制作 |
||||
|
||||
众所周知,水是大自然中透明的物体之一,首先我们需要做一个透明的物体。 |
||||
|
||||
这时,我们可以用到GrabPass抓取屏幕与透明度混合两种方式编写。 |
||||
|
||||
但由于透明度混合无法对后面的物体产生更改,故无法很好的模拟折射效果。 |
||||
|
||||
GrabPass抓取到图像后,经过变换获取到的视口空间中的坐标,从而模拟透明图像,这种方式做出来的透明图像,可以进行更多的操作,比如uv变换等等,可以很方便的模拟折射效果。 |
||||
|
||||
##### 2.使用法线贴图 |
||||
|
||||
##### 3.模拟菲涅尔反射 |
||||
|
||||
##### 4.模拟折射 |
||||
|
||||
## 浪花部分 |
||||
|
||||
##### 1.深度纹理描边 |
||||
|
||||
##### 2.泛光特效 |
||||
|
||||
##### 3.使用噪声纹理模拟浪花 |
||||
|
||||
## |
||||
|
||||
## 通过顶点动画让水体动起来 |
||||
|
||||
##### 1.sin函数顶点变换 |
||||
|
||||
##### 2.时间参数获取 |
||||
|
||||
## |
||||
|
||||
## 全部代码 |
||||
|
||||
```c |
||||
//卡通水体 |
||||
//深度纹理与菲涅尔反射的综合应用 |
||||
Shader "Unlit/ToonWaterShader" |
||||
{ |
||||
Properties |
||||
{ |
||||
// 水体颜色 |
||||
_WaterColor ("WaterColor", COLOR) = (1,1,1,1) |
||||
// 浪花深度 |
||||
_DepthScale("Depth Scale",Float) = 0.1 |
||||
// 波浪噪声纹理 |
||||
_WaveNoise("WaveTexture",2D) = "white" {} |
||||
// 菲涅尔反射颜色 |
||||
_ReflectColor ("Reflect Color",Color) = (0, 0, 1, 1) |
||||
// 浅水区颜色 |
||||
_DepthColor ("Depth Color",Color) = (0, 0, 1, 1) |
||||
// 菲涅尔反射幅度 |
||||
_FresnelScale ("Fresnel Scale", Range(0, 1)) = 1 |
||||
// 水体法线 |
||||
_BumpMap ("Normal Map", 2D) = "bump" {} |
||||
// 法线影响度 |
||||
_BumpScale("Bump Scale" ,Range(0,1)) = 1 |
||||
} |
||||
SubShader |
||||
{ |
||||
Tags { "RenderType"="Opaque" "Queue"="Transparent" } |
||||
LOD 100 |
||||
//抓取屏幕图像,存在名为_RefractionTex的变量中 |
||||
GrabPass { "_RefractionTex" } |
||||
Pass |
||||
{ |
||||
CGPROGRAM |
||||
#pragma vertex vert |
||||
#pragma fragment frag |
||||
|
||||
#include "UnityCG.cginc" |
||||
// 声明抓取的变量 |
||||
sampler2D _RefractionTex; |
||||
float4 _RefractionTex_TexelSize; |
||||
// 声明深度图 |
||||
sampler2D _CameraDepthTexture; |
||||
//声明Properties各项变量 |
||||
sampler2D _WaveNoise; |
||||
float4 _WaveNoise_ST; |
||||
float _DepthScale; |
||||
fixed4 _WaterColor; |
||||
fixed4 _ReflectColor; |
||||
fixed _FresnelScale; |
||||
fixed4 _DepthColor; |
||||
|
||||
sampler2D _BumpMap; |
||||
float4 _BumpMap_ST; |
||||
float _BumpScale; |
||||
|
||||
//定义顶点着色器结构体 |
||||
struct appdata |
||||
{ |
||||
float4 vertex : POSITION; |
||||
float2 uv : TEXCOORD0; |
||||
float4 tangent : TANGENT; |
||||
float3 normal : NORMAL; |
||||
}; |
||||
//定义片元着色器结构体 |
||||
struct v2f |
||||
{ |
||||
float2 uv : TEXCOORD0; |
||||
float4 vertex : SV_POSITION; |
||||
float4 scrPos : TEXCOORD1; |
||||
float3 worldPos : TEXCOORD2; |
||||
fixed3 worldNormal : TEXCOORD3; |
||||
fixed3 worldViewDir : TEXCOORD4; |
||||
//TBN矩阵 |
||||
half3 wNormal : TEXCOORD5; |
||||
half3 wTangent : TEXCOORD6; |
||||
half3 wBitangent : TEXCOORD7; |
||||
}; |
||||
|
||||
v2f vert (appdata v) |
||||
{ |
||||
v2f o; |
||||
//定义偏移参数,根据时间变换顶点,使x轴顶点按照sin曲线运动 |
||||
float4 offset; |
||||
offset.xyzw = float4(0.0,0.0, 0.0, 0.0); |
||||
//offset.x = sin(_Frequency * _Time.y + v.vertex.x * _InvWaveLength + v.vertex.y * _InvWaveLength + v.vertex.z * _InvWaveLength) * _Magnitude; |
||||
offset.x = cos(_Time.y + v.vertex.z)*0.3; |
||||
o.vertex = UnityObjectToClipPos(v.vertex+offset); |
||||
// |
||||
o.uv = v.uv; |
||||
o.scrPos = ComputeGrabScreenPos(o.vertex); |
||||
o.worldNormal = UnityObjectToWorldNormal(v.normal); |
||||
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; |
||||
o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos); |
||||
|
||||
//计算TBN矩阵所用的三组变量。 |
||||
o.wTangent = UnityObjectToWorldDir(v.tangent.xyz); |
||||
o.wNormal = UnityObjectToWorldNormal(v.normal); |
||||
// compute bitangent from cross product of normal and tangent |
||||
// 通过计算法线与切线的叉积,得到二次切线bitangent,叉积*切线方向 |
||||
// half tangentSign = v.tangent.w * unity_WorldTransformParams.w; |
||||
// output the tangent space matrix |
||||
half tangentSign = v.tangent.w; |
||||
o.wBitangent = cross(o.wNormal, o.wTangent) * tangentSign; |
||||
return o; |
||||
} |
||||
|
||||
fixed4 frag (v2f i) : SV_Target |
||||
{ |
||||
fixed3 worldNormal = normalize(i.worldNormal); |
||||
fixed3 worldViewDir = normalize(i.worldViewDir); |
||||
|
||||
half3 tnormal = UnpackNormal(tex2D(_BumpMap, i.uv)); |
||||
float3x3 TBNMatrix = float3x3(i.wTangent,i.wBitangent,i.wNormal); |
||||
worldNormal = mul(tnormal,TBNMatrix); |
||||
//half3 worldViewDir = UnityWorldSpaceViewDir(i.worldPos); |
||||
half3 worldRefra = refract(worldViewDir,worldNormal,_BumpScale); |
||||
float2 offset = worldRefra.xy; |
||||
//i.scrPos.xy = offset * i.scrPos.z + i.scrPos.xy; |
||||
|
||||
// sample the texture |
||||
//fixed4 refrCol = tex2D(_RefractionTex,i.uv); |
||||
fixed4 refrCol = tex2D(_CameraDepthTexture, i.scrPos.xy/i.scrPos.w); |
||||
i.scrPos.xy = offset * i.scrPos.z + i.scrPos.xy; |
||||
fixed4 refrColScreen = tex2D(_RefractionTex,i.scrPos.xy/i.scrPos.w); |
||||
|
||||
fixed4 WaveNoise = tex2D(_WaveNoise,i.uv); |
||||
|
||||
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv); |
||||
float linearDepth = LinearEyeDepth(refrCol.r); |
||||
float diff = linearDepth - i.scrPos.w; |
||||
fixed4 intersect = fixed4(1,1,1,1)-fixed4(diff*_DepthScale,diff*_DepthScale,diff*_DepthScale,1); |
||||
fixed4 border = floor(saturate(intersect+WaveNoise)*2); |
||||
|
||||
fixed4 depthColor = (1-saturate(diff)) * _DepthColor; |
||||
|
||||
fixed4 reflection = _ReflectColor; |
||||
fixed fresnel = _FresnelScale + (1 - _FresnelScale) * pow(1 - dot(worldViewDir, worldNormal), 5)*_BumpScale; |
||||
fixed4 diffuse = _WaterColor*refrColScreen+depthColor+border; |
||||
|
||||
fixed4 colorfinal = lerp(diffuse, reflection, saturate(fresnel)); |
||||
return colorfinal; |
||||
} |
||||
ENDCG |
||||
} |
||||
} |
||||
} |
||||
``` |