commit e914b91e34dbcf2723bdd5a10964709d4411b2e9 Author: ZGC Date: Thu Apr 7 14:56:18 2022 +0800 开源笔记迁移 diff --git a/ImageEffectShader屏幕后期处理解析.md b/ImageEffectShader屏幕后期处理解析.md new file mode 100644 index 0000000..8ac7ba6 --- /dev/null +++ b/ImageEffectShader屏幕后期处理解析.md @@ -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().depthTextureMode = DepthTextureMode.DepthNormals; + } + + void OnRenderImage(RenderTexture src, RenderTexture dest) + { + //GetComponent().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().depthTextureMode = DepthTextureMode.Depth; +``` + +法线纹理+深度纹理获取: + +```csharp +GetComponent().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().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) + +此时,我们的扫描效果就已经比上一个阶段高出一个档次了 diff --git a/UnityShader相关模型公式.md b/UnityShader相关模型公式.md new file mode 100644 index 0000000..3e98656 --- /dev/null +++ b/UnityShader相关模型公式.md @@ -0,0 +1,1999 @@ +# 引言 + +-------------------------------- + +结合UnityShader 编写指南中的部分编码,该文档仅提供编写思路,对常用的几个渲染模型进行了编写。 + +感谢冯乐乐的《Unity Shader入门精要》,参考书本代码的基础上,重新整理了部分代码,以及添加了些许自己的原创代码,方便各位读者查阅使用。 + +# 0.所用数学函数 + +#### + +#### mul() + +- mul(M,N):计算两个矩阵相乘 +- mul(M,v):计算矩阵和向量相乘 +- mul(v,M):计算向量和矩阵相乘 + +#### normalize() + +- normalize():Cg语言标准函数库中的函数 +- 函数功能:对向量进行归一化(按比例缩短到单位长度,方向不变) + +#### dot(A,B) 【a·b = (ax,ay,az) · (bx,by,bz) = axbx + ayby + azbz 】 + +- 功能:返回A和B的点积 +- 参数:A和B可以是标量,也可以是向量 + +#### cross(A,B)【a×b = (ax,ay,az) × (bx,by,bz) = (aybz - azby , azbx - axbz , axby - aybx) 】 + +- 功能:返回A和B的叉积 +- 参数:A和B可以是标量,也可以是向量 + +#### Saturate(x) + +- 功能:把x截取在【0,1】范围内,如果x是一个矢量,那么会对它的每一个分量进行这样的操作 +- 参数:x为总局关于操作的标量或矢量,float float2-4 + +#### step(a,x)【可用于替代部分if判断】 + +- 功能:输入两个数,如果xa或者x=a返回1。 +- 参数:x输入变量,a为参考量。 + +#### clamp(x,a,b) + +- 功能:把x截取在【a,b】范围内,如果xb,返回b; +- 如果x>a且x 世界空间 --2--> 观察空间 --3--> 裁剪空间 + +以上步骤可以按顺序进行组合,变换时,写作 mul(UNITY_MATRIX_对应矩阵 , v.vertex); + +--- + +1 = UNITY_MATRIX_M = _Object2World = unity_ObjectToWorld + +2 = UNITY_MATRIX_V + +3 = UNITY_MATRIX_P + +1+2 = UNITY_MATRIX_MV + +2+3 = UNITY_MATRIX_VP + +1+2+3 = UNITY_MATRIX_MVP = UnityObjectToClipPos(v.vertex) + +1的逆矩阵 = _World2Object = unity_WorldToObject + +1+2的转置矩阵 = UNITY_MATRIX_T_MV + +1+2的逆转置矩阵 = UNITY_MATRIX_IT_MV + +### + +### 1.6 法线变换(使用上面1) + +##### 模型法线空间--1-->世界法线空间 + +1 = (float3x3)unity_ObjectToWorld = UnityObjectToWorldNormal(v.normal) + +由于上述矩阵进行变换相乘的时候,没有进行归一化,需要对法线重新进行归一化才能得到正确的结果 + +即:normalize(mul(unity_ObjectToWorld,v.normal)); + +### + +### 1.7 屏幕坐标获取 + +###### 1.7.1 语义获取 VPOS/WPOS + +```c +fixed4 frag(float4 sp : VPOS) : SV_Target{ + //用屏幕坐标处于屏幕分辨率_ScreenParams.xy,得到视口空间中的坐标 + return fixed4(sp.xy/_ScreenParams.xy,0.0,1.0); +} +``` + +###### 1.7.2 使用ComputeScreenPos函数 + +```c +struct appdata{ + float4 vertex : POSITION; + float2 uv : TEXCOORD0; +}; + +struct v2f{ + float4 vertex : SV_POSITION; + float4 scrPos : TEXCOORD0; +}; + +v2f vert(appdata v){ + v2f o; + o.vertex = UnityObjectToClipPos(v.vertex); + o.srcPos = ComputeScreenPos(o.vertex); + return o; +} + +fixed4 frag(v2f i) : SV_target{ + float2 wcoord = (i.srcPos.xy/i.srcPos.w); + return fixed4(wcoord,0.0,1.0); +} +``` + +### 1.8 Unity顶点&片元着色器创建案例 + +右键----》Create----》shader----》UnlitShader----》起名 + +即可生成一个最简单的带默认纹理的无光照模型着色器 + +### 1.9 关于优化以及编写建议 + +1.调试时,可以采用Color的色值采样,这种方法虽然原始,但可以作为一个数据来源的依据 + +2.调试时,可以使用FrameDebugger,查看渲染事件,大概在Window菜单栏下面,具体哪个子项目视版本情况而定 + +3.关于使用不同精度值的变量 + +由于移动平台下面对GPU性能的支持不一定好,所以可以采用低精度数值的方式来对Shader进行优化。 + +float(32位),half(16位,范围-60000 ~ +60000),fixed(11位,范围-2.0 ~ +2.0) + +4.慎用if分支语句,for&while等循环语句 + +可以用step()函数来做大小与的判断 + +lerp函数() + +5.不要除以0 + +控制变量的时候尽量少用除法,尽可能用乘小数的方式进行。 + +# 2. 光照篇 + +### + +### 2.1 漫反射光照模型的实现 + +逐顶点光照(下方仅实现光照模型,如需贴图材质颜色等参数,自行相关参数添加) + +平行光法线计算+环境光 + +新建shader ,拷贝下方代码到SubShader -> Pass里 + +```c +Pass { + Tags { "LightMode"="ForwardBase" } + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + #include "Lighting.cginc" + + struct a2v { + float4 vertex : POSITION; + float3 normal : NORMAL; + }; + + struct v2f { + float4 pos : SV_POSITION; + fixed3 color : COLOR; + }; + + v2f vert(a2v v) { + v2f o; + // 模型顶点数据,从模型空间转换到裁剪空间 + o.pos = UnityObjectToClipPos(v.vertex); + + // 将法线从模型空间转换到世界空间 + // 旧:fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject)); + fixed3 worldNormal = UnityObjectToWorldNormal(v.normal); + + // 获取环境光 + // 旧:fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; + // ShadeSH9函数使用球谐光照,这里不做过多科普,感兴趣可以自行搜索,旧版是固定颜色环境光 + fixed3 ambient = ShadeSH9(fixed4(worldNormal,1)); + + // 获取世界空间中的光照方向 + fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz); + // 计算最终光照 + fixed3 diffuse = _LightColor0.rgb * saturate(dot(worldNormal, worldLight)); + // 添加环境光 + o.color = ambient + diffuse; + return o; + } + + fixed4 frag(v2f i) : SV_Target { + return fixed4(i.color, 1.0); + } + + ENDCG + } +``` + +逐像素光照(下方仅实现光照模型,如需贴图材质颜色等参数,自行相关参数添加) + +相比逐顶点,光照效果更加平滑。 + +```c +Pass { + Tags { "LightMode"="ForwardBase" } + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + #include "Lighting.cginc" + + struct a2v { + float4 vertex : POSITION; + float3 normal : NORMAL; + }; + + struct v2f { + float4 pos : SV_POSITION; + float3 worldNormal : TEXCOORD0; + }; + + v2f vert(a2v v) { + v2f o; + o.pos = UnityObjectToClipPos(v.vertex); + // 旧:o.worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject)); + o.worldNormal = UnityObjectToWorldNormal(v.normal); + return o; + } + + fixed4 frag(v2f i) : SV_Target { + // 旧:fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; + fixed3 ambient = ShadeSH9(fixed4(i.worldNormal,1)); + fixed3 worldNormal = normalize(i.worldNormal); + fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); + fixed3 diffuse = _LightColor0.rgb * saturate(dot(worldNormal, worldLightDir)); + fixed3 color = ambient + diffuse; + return fixed4(color, 1.0); + } + + ENDCG + } +``` + +半兰伯特光照模型 + +过时的经验模型,早期应用于游戏《半条命》。现在多采用球谐光照作为环境光的补正,参考上方的 ShadeSH9 函数。 + +```c +Pass { + Tags { "LightMode"="ForwardBase" } + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + #include "Lighting.cginc" + fixed4 _Diffuse; + + struct a2v { + float4 vertex : POSITION; + float3 normal : NORMAL; + }; + + struct v2f { + float4 pos : SV_POSITION; + float3 worldNormal : TEXCOORD0; + }; + + v2f vert(a2v v) { + v2f o; + o.pos = UnityObjectToClipPos(v.vertex); + o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject); + // 旧:o.worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject)); + o.worldNormal = UnityObjectToWorldNormal(v.normal); + return o; + } + + fixed4 frag(v2f i) : SV_Target { + fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; + fixed3 worldNormal = normalize(i.worldNormal); + fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); + fixed halfLambert = dot(worldNormal, worldLightDir) * 0.5 + 0.5; + fixed3 diffuse = _LightColor0.rgb * halfLambert; +                fixed3 color = ambient + diffuse; + return fixed4(color, 1.0); + } + + ENDCG + } +``` + +### 2.2 高光反射光照模型的实现 + +逐顶点高光 + +```c +Shader "TestShader/SpecularVertex-LevelOnly" { + Properties { + // 高光颜色 + _Specular ("Specular", Color) = (1, 1, 1, 1) + // 高光光泽度(反光度) + _Gloss ("Gloss", Range(8.0, 256)) = 20 + } + SubShader { + Pass { + Tags { "LightMode"="ForwardBase" } + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + #include "Lighting.cginc" + + fixed4 _Specular; + float _Gloss; + + struct a2v { + float4 vertex : POSITION; + float3 normal : NORMAL; + }; + + struct v2f { + float4 pos : SV_POSITION; + fixed3 color : COLOR; + }; + + v2f vert(a2v v) { + v2f o; + o.pos = UnityObjectToClipPos(v.vertex); + fixed3 worldNormal = UnityObjectToWorldNormal(v.normal); + fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); + // 在世界空间中获取光线反射方向 + fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal)); + // 在世界空间中获取观察方向 + fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz); + // 融合光照,高光参数生成光照颜色 + fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss); + o.color = specular; + return o; + } + + fixed4 frag(v2f i) : SV_Target { + return fixed4(i.color, 1.0); + } + + ENDCG + } + } + FallBack "Specular" +} +``` + +逐像素光照(部分代码可以参考逐顶点篇注释) + +```c +Shader "TestShader/SpecularPixel-LevelOnly" { + Properties { + _Specular ("Specular", Color) = (1, 1, 1, 1) + _Gloss ("Gloss", Range(8.0, 256)) = 20 + } + SubShader { + Pass { + Tags { "LightMode"="ForwardBase" } + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + #include "Lighting.cginc" + + fixed4 _Specular; + float _Gloss; + + struct a2v { + float4 vertex : POSITION; + float3 normal : NORMAL; + }; + + struct v2f { + float4 pos : SV_POSITION; + float3 worldNormal : TEXCOORD0; + float3 worldPos : TEXCOORD1; + }; + + v2f vert(a2v v) { + v2f o; + o.pos = UnityObjectToClipPos(v.vertex); + o.worldNormal = UnityObjectToWorldNormal(v.normal); + o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; + return o; + } + + fixed4 frag(v2f i) : SV_Target { + fixed3 worldNormal = normalize(i.worldNormal); + fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); + + fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal)); + fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz); + fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss); + return fixed4(specular, 1.0); + } + ENDCG + } + } + FallBack "Specular" +} +``` + +Blin-Phong高光模型算法 + +```c +Shader "TestShader/Blinn-PhongOnly" { + Properties { + _Specular ("Specular", Color) = (1, 1, 1, 1) + _Gloss ("Gloss", Range(8.0, 256)) = 20 + } + SubShader { + Pass { + Tags { "LightMode"="ForwardBase" } + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + #include "Lighting.cginc" + + fixed4 _Specular; + float _Gloss; + + struct a2v { + float4 vertex : POSITION; + float3 normal : NORMAL; + }; + + struct v2f { + float4 pos : SV_POSITION; + float3 worldNormal : TEXCOORD0; + float3 worldPos : TEXCOORD1; + }; + + v2f vert(a2v v) { + v2f o; + o.pos = UnityObjectToClipPos(v.vertex); + o.worldNormal = UnityObjectToWorldNormal(v.normal); + o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; + return o; + } + + fixed4 frag(v2f i) : SV_Target { + fixed3 worldNormal = normalize(i.worldNormal); + fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); + fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz); + // 在世界空间中获取half方向 + fixed3 halfDir = normalize(worldLightDir + viewDir); + // 融合高光反射参数 + fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss); + return fixed4(specular, 1.0); + } + + ENDCG + } + } + FallBack "Specular" +} +``` + +### 2.3 Unity设置LightMode实现其他光源 + +关于渲染路径,一般从摄像机的Rendering Path中设置,默认使用Graphics Settings中的数值,前向渲染。 + +LightMode的Tag参数详情见编写指南2.2(Pass)Tags + +##### 2.3.1Unity对不同光源光照处理的方式以及顺序 + +- 场景最亮的平行光总是按逐像素处理的。 + +- Render Mode 被设置成 Not Important 的光源,会按逐顶点或者SH处理 + +- Render Mode 被设置成 Important 的光源,会按逐像素处理 + +- 如果根据以上规则得到的逐像素光源数量小于 Quality Setting 中的 Pixel Light Count(逐像素光源数量),会有更多的光源以逐像素的方式进行渲染。 + +下方实现了一个多光源的前向渲染Shader。 + +```c +Shader "TestShader/ForwardRendering" { + Properties { + _Diffuse ("Diffuse", Color) = (1, 1, 1, 1) + _Specular ("Specular", Color) = (1, 1, 1, 1) + _Gloss ("Gloss", Range(8.0, 256)) = 20 + } + SubShader { + Tags { "RenderType"="Opaque" } + Pass { + // 主光源Pass + Tags { "LightMode"="ForwardBase" } + CGPROGRAM + // 保证在Base Pass中使用光照衰减等光照变量可以被正确赋值 + #pragma multi_compile_fwdbase + #pragma vertex vert + #pragma fragment frag + #include "Lighting.cginc" + + fixed4 _Diffuse; + fixed4 _Specular; + float _Gloss; + + struct a2v { + float4 vertex : POSITION; + float3 normal : NORMAL; + }; + + struct v2f { + float4 pos : SV_POSITION; + float3 worldNormal : TEXCOORD0; + float3 worldPos : TEXCOORD1; + }; + + v2f vert(a2v v) { + v2f o; + o.pos = UnityObjectToClipPos(v.vertex); + o.worldNormal = UnityObjectToWorldNormal(v.normal); + o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; + return o; + } + + fixed4 frag(v2f i) : SV_Target { + fixed3 worldNormal = normalize(i.worldNormal); + fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); + + //fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; + fixed3 ambient = ShadeSH9(fixed4(i.worldNormal,1)); + fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir)); + fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz); + fixed3 halfDir = normalize(worldLightDir + viewDir); + fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss); + fixed atten = 1.0; + return fixed4(ambient + (diffuse + specular) * atten, 1.0); + } + ENDCG + } + + Pass { + // 其他光照Pass + Tags { "LightMode"="ForwardAdd" } + // 混合模式为线性减淡(PS中常用于发光图层) + Blend One One + CGPROGRAM + // 保证在Additional Pass中访问到正确的光照变量 + #pragma multi_compile_fwdadd + #pragma vertex vert + #pragma fragment frag + #include "Lighting.cginc" + #include "AutoLight.cginc" + + fixed4 _Diffuse; + fixed4 _Specular; + float _Gloss; + + struct a2v { + float4 vertex : POSITION; + float3 normal : NORMAL; + }; + + struct v2f { + float4 pos : SV_POSITION; + float3 worldNormal : TEXCOORD0; + float3 worldPos : TEXCOORD1; + }; + + v2f vert(a2v v) { + v2f o; + o.pos = UnityObjectToClipPos(v.vertex); + o.worldNormal = UnityObjectToWorldNormal(v.normal); + o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; + return o; + } + + fixed4 frag(v2f i) : SV_Target { + fixed3 worldNormal = normalize(i.worldNormal); + #ifdef USING_DIRECTIONAL_LIGHT + // 使用平行光时,不计算衰减 + fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); + #else + // 使用其他光源时计算衰减 + fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz); + #endif + fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir)); + fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz); + fixed3 halfDir = normalize(worldLightDir + viewDir); + fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss); + #ifdef USING_DIRECTIONAL_LIGHT + // 使用其他平行光 + fixed atten = 1.0; + #else + #if defined (POINT) + // 点光源处理 + float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz; + fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL; + #elif defined (SPOT) + // 聚光源处理 + float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)); + fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL; + #else + fixed atten = 1.0; + #endif + #endif + return fixed4((diffuse + specular) * atten, 1.0); + } + ENDCG + } + } + FallBack "Specular" +} +``` + +### 2.4 投射阴影与接收阴影 + +##### 2.4.1设置Cast Shadows + +开启阴影渲染之前,必须保证光源可以投射阴影,通过Light-Shadow Type 进行设置,可以开启硬阴影投射与软阴影投射 + +##### 2.4.1设置RecevieShadows + +物体接收不接收阴影信息是由mesh renderer决定的,通过Mesh Renderer-Cast Shadows 设置成On,以开启接收阴影。 + +投射与接收阴影着色器案例: + +```c +Shader "ShaderTest/Shadow" { + Properties { + _Diffuse ("Diffuse", Color) = (1, 1, 1, 1) + _Specular ("Specular", Color) = (1, 1, 1, 1) + _Gloss ("Gloss", Range(8.0, 256)) = 20 + } + SubShader { + Tags { "RenderType"="Opaque" } + + Pass { + Tags { "LightMode"="ForwardBase" } + CGPROGRAM + #pragma multi_compile_fwdbase + #pragma vertex vert + #pragma fragment frag + #include "Lighting.cginc" + #include "AutoLight.cginc" + + fixed4 _Diffuse; + fixed4 _Specular; + float _Gloss; + + struct a2v { + float4 vertex : POSITION; + float3 normal : NORMAL; + }; + + struct v2f { + float4 pos : SV_POSITION; + float3 worldNormal : TEXCOORD0; + float3 worldPos : TEXCOORD1; + // 声明一个用于对阴影纹理采样的坐标 + SHADOW_COORDS(2) + }; + + v2f vert(a2v v) { + v2f o; + o.pos = UnityObjectToClipPos(v.vertex); + o.worldNormal = UnityObjectToWorldNormal(v.normal); + o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; + // 计算上一步中声明的阴影纹理坐标 + TRANSFER_SHADOW(o); + return o; + } + + fixed4 frag(v2f i) : SV_Target { + fixed3 worldNormal = normalize(i.worldNormal); + fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); + fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; + fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir)); + fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz); + fixed3 halfDir = normalize(worldLightDir + viewDir); + fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss); + // 混合阴影颜色 + fixed shadow = SHADOW_ATTENUATION(i); + return fixed4(ambient + (diffuse + specular) * atten * shadow, 1.0); + } + ENDCG + } + } + // 如果不设置FallBack,则该物体不会向其他物体投射阴影,得到的阴影数据也会变得不正常 + FallBack "Specular" +} +``` + +# 3. 标准纹理篇 + +###### tex2D(sampler2D,Vector2) + +从一张纹理中,对一个点进行采样的方法,返回值为一个float4的颜色值。sampler2D为贴图来源,Vector2为贴图对应的坐标。 + +###### texCUBE(samplerCUBE,Vector3) + +立方体纹理中,对点进行采样的方法,返回值为一个float4的颜色值。samplerCUBE为立方体贴图来源,Vector3为中心点指向的方向坐标(这里提一点,由于立方体纹理中是六个面组成的,所以中心点为立方体模型的中心,中心点到采样点之间会形成一个方向矢量,此矢量为Vector3的值),另外由于该变量代表方向坐标,故不需要进行归一化处理。 + +### 3.1 关于UV(纹理映射坐标): + +在美术人员建模的时候,通常会在建模软件中利用纹理展开技术把纹理映射坐标存储在每个顶点上,纹理映射坐标定义了该顶点在纹理中对应的2d坐标。 + +通常,这些坐标使用一个二维变量(u,v)来表示,其中u是横向坐标,v是纵向坐标。因此纹理映射坐标也被称为UV坐标。 + +虽然纹理的大小多种多样,但顶点UV坐标的范围通常都被归一化到【0-1】范围内。 + +### 3.2 关于纹理(贴图)资源: + +Unity支持以下格式的图片作为贴图资源:bmp,exr,gif,hdr,iff,jpg,pict,png,psd,tga,tiff。 + +### 3.3 纹理的资源的属性 + +本篇只做简要概括重要属性,其他属性详情见下方链接 + +[Unity - Manual: Texture Import Settings](https://docs.unity3d.com/Manual/class-TextureImporter.html) + +##### 1.TextureType + +声明纹理类型 + +| 类型 | 描述 | +| --------------------- | -------------------------------------------------------- | +| Default | 默认是所有纹理最常用的设置。它提供对纹理导入的大多数属性的访问。 | +| Normal Map | 作为法线贴图时,将颜色通道转换为适合实时法线贴图的格式。 | +| Editor GUI and Legacy | 如果您在任何 HUD 或 GUI 控件上使用纹理,请选择Editor GUI 和 Legacy GUI。 | +| Sprite (2D and UI) | 正在使用的纹理在2D游戏中作为一个精灵(Sprite)。 | +| Cursor | 作为一个默认光标(替代鼠标)。 | +| Cookie | 用于场景光照的基础参数缓存。 | +| Lightmap | 光照贴图,此选项支持编码为特定格式(RGBM 或 dLDR,具体取决于平台),可用于存放屏幕后期处理的贴图数据。 | +| Single Channel | 如果您只需要纹理中的一个通道,请选择单通道。 | + +##### 2.WarpMode + +该项决定了纹理坐标如果超出【0-1】这个范围,会如何平铺。 + +| 类型 | 描述 | +| ------ | ---------------------------------- | +| Repeat | 坐标超过1时,取小数部分,坐标多出的部分会重复。 | +| Clamp | 坐标截取到1,小于0的部分截取到0,坐标多出的部分会按照边缘色延伸。 | + +##### 3.FilterMode + +该项决定了当纹理由于变换产生拉伸时,将使用哪种滤波模式。 + +| 类型 | 描述 | +| ----------------- | ------------------------------------------------ | +| Point (no filter) | 不使用滤波,拉伸时,采样像素只有一个 | +| Bilinear | 线性滤波,对于每个目标像素,会找到四个邻近像素,对他们进行线性插值混合得到最终像素,会变得模糊。 | +| Trilinear | 在Bilinear的基础上,使用多级渐远纹理之间进行混合 | + +##### 4.Default-MaxSize + +如果导入的纹理大小超过了这个选项对应的设置值。那么Unity将会把该纹理缩放为这个的最大分辨率。 + +可以通过调整该选项,来优化移动以及计算性能低的设备平台使之更流畅。 + +### 3.4 纹理映射案例 + +使用纹理时,不仅要声明纹理变量本身,还要声明一个 纹理名 + _ST 格式的float4变量。 + +纹理名 + _ST:xy存放缩放值,zw存放偏移值。用于纹理坐标转换。 + +```c +Shader "TestShader/TextureOnly" { + Properties { + _MainTex ("Main Tex", 2D) = "white" {} + } + SubShader { + Pass { + Tags { "LightMode"="ForwardBase" } + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + #include "Lighting.cginc" + + sampler2D _MainTex; + float4 _MainTex_ST; + + struct a2v { + float4 vertex : POSITION; + float4 texcoord : TEXCOORD0; + }; + + struct v2f { + float4 position : SV_POSITION; + float2 uv : TEXCOORD0; + }; + + v2f vert(a2v v) { + v2f o; + o.position = UnityObjectToClipPos(v.vertex); + // 转换UV坐标贴图,转换后的样式依据贴图资源设置而改变,该方法自动_MainTex_ST变量 + o.uv = TRANSFORM_TEX(v.texcoord, _MainTex); + return o; + } + + fixed4 frag(v2f i) : SV_Target { + fixed4 c = tex2D(_MainTex, i.uv); + return fixed4(c.rgb, 1.0); + } + ENDCG + } + } + FallBack "Diffuse" +} +``` + +### 3.5 法线纹理映射案例 + +在介绍法线映射之前,要提到凹凸映射的两种贴图方案,一种是高度贴图,一种是法线贴图。 + +高度纹理用于模拟表面位移,亮的部分能遮挡暗的部分,该纹理的颜色用于表达表面海拔高度,颜色越亮值越高。 + +法线纹理直接储存表面法线,从而使光照信息按照法线贴图提供的数据进行计算。 + +在使用法线纹理之前,必须了解切线空间的存在意义,想了解TBN矩阵的可以参阅以下文章 + +https://zhuanlan.zhihu.com/p/139593847 + +切线空间就是,反应这个模型空间坐标相对应纹理坐标相的变换坡度。引自 + +http://www.cnitblog.com/wjk98550328/archive/2010/04/15/35112.html + +法线贴图的颜色值代表的含义 + +法线贴图的rgb颜色中,r代表横向法线,y代表纵向法线。法线贴图的具体图解,可详见另一篇法线贴图文档部分。里面有详细的法线贴图数据映射关系。 + +```c +Shader "TestShader/MineBumpTestShader" +{ + Properties { + _BumpMap("Normal Map", 2D) = "bump" {} + } + SubShader + { + Pass + { + Tags{"LightMode"="ForwardBase"} + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + #include "Lighting.cginc" + #include "UnityCG.cginc" + + sampler2D _BumpMap; + + struct a2v{ + float4 vertex : POSITION; + float3 normal : NORMAL; + float4 tangent : TANGENT; + float2 uv : TEXCOORD0; + }; + + struct v2f { + float3 worldPos : TEXCOORD0; + float2 uv : TEXCOORD1; + float4 pos : SV_POSITION; + half3 wNormal : TEXCOORD2; + half3 wTangent : TEXCOORD3; + half3 wBitangent : TEXCOORD4; + }; + + // 顶点着色器现在也需要一个逐顶点的切线向量。 + // 在Unity中,切线是一个四维的向量,w分向量用于表现二次切线向量的方向 + // 我们仍然需要贴图来配合(翻译自Unity Manual 法线贴图篇) + v2f vert (a2v v) + { + v2f o; + o.pos = UnityObjectToClipPos(v.vertex); + o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; + o.uv = v.uv; + o.wTangent = UnityObjectToWorldDir(v.tangent.xyz); + o.wNormal = UnityObjectToWorldNormal(v.normal); + // compute bitangent from cross product of normal and tangent + // 通过计算法线与切线的叉积,得到二次切线bitangent,以此来导出切线空间矩阵 + // 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 + { + // 依据法线贴图,将rgba的范围(原【0-1】)规范到【-1~1】之间 + half3 tnormal = UnpackNormal(tex2D(_BumpMap, i.uv)); + // 上文提到的TBN矩阵,用于将法线方向从切线空间转换到世界空间中。 + float3x3 TBNMatrix = float3x3(i.wTangent,i.wBitangent,i.wNormal); + // 根据解包后的法线数据,算出世界空间下的法线方向。 + half3 worldNormal = mul(tnormal,TBNMatrix); + // 灯光方向 + half3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); + // 计算球谐光照,反应环境光(读者可以尝试只输出这个变量看看效果) + fixed3 ambient = ShadeSH9(fixed4(worldNormal,1)); + // 计算主光源 + fixed3 diffuse = _LightColor0.rgb * saturate(dot(worldNormal, worldLightDir)); + // 融合环境光与主光源(需要添加高光反射的,去前面高光反射代码摘抄并在下方加算即可) + fixed4 c = fixed4(ambient+diffuse,1); + return c; + } + ENDCG + } + } +} +``` + +### 3.6 渐变纹理 + +用于渲染风格化的物体时,可以采用渐变纹理的方式,得到处理之后的阴影。比如卡通风格的渲染,可以采用一张二分图或者三分图当做渐变纹理,来进行渲染。 + +笔者准备了两张渐变纹理用于做下方shader的素材,详见img/gradient.jpg + +```c +Shader "TestShader/RampTexture" { + Properties { + _Color ("Color Tint", Color) = (1, 1, 1, 1) + // 渐变贴图声明 + _RampTex ("Ramp Tex", 2D) = "white" {} + _SpeRampTex ("Spe Ramp Tex", 2D) = "white" {} + _Specular ("Specular", Color) = (1, 1, 1, 1) + _Gloss ("Gloss", Range(8.0, 256)) = 20 + } + SubShader { + Pass { + Tags { "LightMode"="ForwardBase" } + + CGPROGRAM + + #pragma vertex vert + #pragma fragment frag + + #include "Lighting.cginc" + + fixed4 _Color; + sampler2D _RampTex; + float4 _RampTex_ST; + sampler2D _SpeRampTex; + float4 _SpeRampTex_ST; + fixed4 _Specular; + float _Gloss; + + struct a2v { + float4 vertex : POSITION; + float3 normal : NORMAL; + float4 texcoord : TEXCOORD0; + }; + + struct v2f { + float4 pos : SV_POSITION; + float3 worldNormal : TEXCOORD0; + float3 worldPos : TEXCOORD1; + float2 uv : TEXCOORD2; + }; + + v2f vert(a2v v) { + v2f o; + o.pos = UnityObjectToClipPos(v.vertex); + o.worldNormal = UnityObjectToWorldNormal(v.normal); + o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; + o.uv = TRANSFORM_TEX(v.texcoord, _RampTex); + return o; + } + + fixed4 frag(v2f i) : SV_Target { + fixed3 worldNormal = normalize(i.worldNormal); + fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); + + // 可根据需求调整环境光照 + fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; + //fixed3 ambient = ShadeSH9(fixed4(worldNormal,1)); + // 计算半兰伯特光照 + fixed lambert = dot(worldNormal, worldLightDir); + fixed halfLambert = 0.5 * lambert + 0.5; +                 + fixed3 diffuseColor = tex2D(_RampTex, fixed2(halfLambert, halfLambert)).rgb * _Color.rgb; + diffuseColor = fixed3(diffuseColor.r,diffuseColor.g,diffuseColor.b); + fixed3 diffuse = _LightColor0.rgb * diffuseColor; + + fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos)); + fixed3 halfDir = normalize(worldLightDir + viewDir); + fixed3 specular = pow(max(0, dot(worldNormal, halfDir)), _Gloss); + specular = tex2D(_SpeRampTex, fixed2(specular.r, specular.r)).rgb/2 * _LightColor0.rgb * _Specular.rgb; + return fixed4(ambient + diffuse + specular, 1.0); + } + + ENDCG + } + } + FallBack "Specular" +} +``` + +### 3.7 遮罩纹理 + +遮罩纹理允许我们保护某些区域,使它们免于某些修改 + +使用遮罩纹理的流程:通过采样得到遮罩纹理的纹素值,然后使用其中的某个(或者某几个)通道的值(比如texture.r)来与某种表面属性进项相乘,这样,当该通道的值为0时,可以保护表面不受该属性的影响。 + +遮罩纹理在剔除某个数据的需求中起着至关重要的作用(例如更真实的砖瓦缝隙不受高光光照影响) + +下方为一个应用于高光反射的遮罩纹理着色器。 + +```c +Shader "TestShader/MaskTexture" { + Properties { + _Color ("Color Tint", Color) = (1, 1, 1, 1) + _MainTex ("Main Tex", 2D) = "white" {} + _BumpMap ("Normal Map", 2D) = "bump" {} + _BumpScale("Bump Scale", Float) = 1.0 + _SpecularMask ("Specular Mask", 2D) = "white" {} + _SpecularScale ("Specular Scale", Range(0.0,1.0)) = 1.0 + _Specular ("Specular", Color) = (1, 1, 1, 1) + _Gloss ("Gloss", Range(8.0, 256)) = 20 + } + SubShader { + Pass { + Tags { "LightMode"="ForwardBase" } + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + + #include "Lighting.cginc" + + fixed4 _Color; + sampler2D _MainTex; + float4 _MainTex_ST; + sampler2D _BumpMap; + float _BumpScale; + sampler2D _SpecularMask; + float _SpecularScale; + fixed4 _Specular; + float _Gloss; + + struct a2v { + float4 vertex : POSITION; + float3 normal : NORMAL; + float4 tangent : TANGENT; + float4 texcoord : TEXCOORD0; + }; + + struct v2f { + float4 pos : SV_POSITION; + float2 uv : TEXCOORD0; + float3 lightDir: TEXCOORD1; + float3 viewDir : TEXCOORD2; + }; + + v2f vert(a2v v) { + v2f o; + o.pos = UnityObjectToClipPos(v.vertex); + o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw; + TANGENT_SPACE_ROTATION; + o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz; + o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz; + return o; + } + + fixed4 frag(v2f i) : SV_Target { + fixed3 tangentLightDir = normalize(i.lightDir); + fixed3 tangentViewDir = normalize(i.viewDir); + fixed3 tangentNormal = UnpackNormal(tex2D(_BumpMap, i.uv)); + tangentNormal.xy *= _BumpScale; + tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy))); + fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb; + fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo; + fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir)); + fixed3 halfDir = normalize(tangentLightDir + tangentViewDir); + // 获取r通道下的遮罩数据值 + fixed specularMask = tex2D(_SpecularMask, i.uv).r * _SpecularScale; + // 混合遮罩数据 + fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentNormal, halfDir)), _Gloss) * specularMask; + return fixed4(ambient + diffuse + specular, 1.0); + } + ENDCG + } + } + FallBack "Specular" +} +``` + +## 立方体纹理 + +在图形学中,立方体纹理是环境映射的一种实现方法。环境映射可以模拟物体周围的环境。 + +立方体纹理一共包含了6张正方形图像,六张图像边缘连续,组成一个立方体。 + +立方体纹理广泛应用于天空盒,反射折射模型中,等等。 + +### 3.8 反射 + +反射有两种方式实现,一种是获取天空盒的立方体纹理,另一种是直接使用立方体纹理(总之都采用了对应的立方体纹理) + +##### 天空盒反射(摘自Unity Manual) + +```c +Shader "Unlit/SkyReflection" +{ + SubShader + { + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + #include "UnityCG.cginc" + + struct v2f { + half3 worldRefl : TEXCOORD0; + float4 pos : SV_POSITION; + }; + + v2f vert (float4 vertex : POSITION, float3 normal : NORMAL) + { + v2f o; + o.pos = UnityObjectToClipPos(vertex); + // compute world space position of the vertex + float3 worldPos = mul(_Object2World, vertex).xyz; + // compute world space view direction + float3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos)); + // world space normal + float3 worldNormal = UnityObjectToWorldNormal(normal); + // world space reflection vector + o.worldRefl = reflect(-worldViewDir, worldNormal); + return o; + } + + fixed4 frag (v2f i) : SV_Target + { + // sample the default reflection cubemap, using the reflection vector + half4 skyData = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, i.worldRefl); + // decode cubemap data into actual color + half3 skyColor = DecodeHDR (skyData, unity_SpecCube0_HDR); + // output it! + fixed4 c = 0; + c.rgb = skyColor; + return c; + } + ENDCG + } + } +} +``` + +##### 立方体纹理反射 + +```c +Shader "TestShader/Reflection" { + Properties { + _Color ("Color Tint", Color) = (1, 1, 1, 1) + _ReflectColor ("Reflection Color", Color) = (1, 1, 1, 1) + _ReflectAmount ("Reflect Amount", Range(0, 1)) = 1 + _Cubemap ("Reflection Cubemap", Cube) = "_Skybox" {} + } + SubShader { + Tags { "RenderType"="Opaque" "Queue"="Geometry"} + + Pass { + Tags { "LightMode"="ForwardBase" } + Cull Off + CGPROGRAM + #pragma multi_compile_fwdbase + #pragma vertex vert + #pragma fragment frag + #include "Lighting.cginc" + #include "AutoLight.cginc" + + fixed4 _Color; + fixed4 _ReflectColor; + fixed _ReflectAmount; + samplerCUBE _Cubemap; + + struct a2v { + float4 vertex : POSITION; + float3 normal : NORMAL; + }; + + struct v2f { + float4 pos : SV_POSITION; + float3 worldPos : TEXCOORD0; + fixed3 worldNormal : TEXCOORD1; + fixed3 worldViewDir : TEXCOORD2; + fixed3 worldRefl : TEXCOORD3; + SHADOW_COORDS(4) + }; + + v2f vert(a2v v) { + v2f o; + o.pos = UnityObjectToClipPos(v.vertex); + o.worldNormal = UnityObjectToWorldNormal(v.normal); + o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; + o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos); + // 计算世界空间下的反射参数 + o.worldRefl = reflect(-o.worldViewDir, o.worldNormal); + // 投射阴影 + TRANSFER_SHADOW(o); + return o; + } + + fixed4 frag(v2f i) : SV_Target { + fixed3 worldNormal = normalize(i.worldNormal); + fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); + fixed3 worldViewDir = normalize(i.worldViewDir); + // 设置环境光颜色 + fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; + //fixed3 ambient = ShadeSH9(fixed4(worldNormal,1)); + // 计算漫反射光照 + fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir)); + // 使用世界空间下的反射方向来映射立方体纹理 + fixed3 reflection = texCUBE(_Cubemap, i.worldRefl).rgb * _ReflectColor.rgb; + // 混合阴影颜色 + UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos); + // 混合最终颜色 + fixed3 color = ambient + lerp(diffuse, reflection, _ReflectAmount) * atten; + return fixed4(color, 1.0); + } + ENDCG + } + } + FallBack "Reflective/VertexLit" +} +``` + +### 3.9 折射 + +当光线从一种介质斜射进入另一种介质时,传播方向一般会发生改变。 + +斯涅尔定律:n1 sinθ1 = n2 sinθ2 + +其中 n1、n2分别是两个介质的折射率。当光从介质1沿着和表面法线夹角为θ1的方向斜射入介质2时,我们可以使用上述公式计算折射光线与法线的夹角θ2 + +下方案例均仅模拟一次折射 + +##### 使用立方体纹理进行折射模拟 + +```c +Shader "TestShader/Refraction" { + Properties { + _Color ("Color Tint", Color) = (1, 1, 1, 1) + _RefractColor ("Refraction Color", Color) = (1, 1, 1, 1) + _RefractAmount ("Refraction Amount", Range(0, 1)) = 1 + _RefractRatio ("Refraction Ratio", Range(0.1, 1)) = 0.5 + _Cubemap ("Refraction Cubemap", Cube) = "_Skybox" {} + } + SubShader { + Tags { "RenderType"="Opaque" "Queue"="Geometry"} + Pass { + Tags { "LightMode"="ForwardBase" } + + CGPROGRAM + + #pragma multi_compile_fwdbase + #pragma vertex vert + #pragma fragment frag + + #include "Lighting.cginc" + #include "AutoLight.cginc" + + fixed4 _Color; + fixed4 _RefractColor; + float _RefractAmount; + fixed _RefractRatio; + samplerCUBE _Cubemap; + + struct a2v { + float4 vertex : POSITION; + float3 normal : NORMAL; + }; + + struct v2f { + float4 pos : SV_POSITION; + float3 worldPos : TEXCOORD0; + fixed3 worldNormal : TEXCOORD1; + fixed3 worldViewDir : TEXCOORD2; + fixed3 worldRefr : TEXCOORD3; + SHADOW_COORDS(4) + }; + + v2f vert(a2v v) { + v2f o; + o.pos = UnityObjectToClipPos(v.vertex); + o.worldNormal = UnityObjectToWorldNormal(v.normal); + o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; + o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos); + // Compute the refract dir in world space + o.worldRefr = refract(-normalize(o.worldViewDir), normalize(o.worldNormal), _RefractRatio); + TRANSFER_SHADOW(o); + + return o; + } + + fixed4 frag(v2f i) : SV_Target { + fixed3 worldNormal = normalize(i.worldNormal); + fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); + fixed3 worldViewDir = normalize(i.worldViewDir); + fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; + fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir)); + // Use the refract dir in world space to access the cubemap + fixed3 refraction = texCUBE(_Cubemap, i.worldRefr).rgb * _RefractColor.rgb; + UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos); + // Mix the diffuse color with the refract color + fixed3 color = ambient + lerp(diffuse, refraction, _RefractAmount) * atten; + return fixed4(color, 1.0); + } + ENDCG + } + } + FallBack "Reflective/VertexLit" +} +``` + +##### 使用渲染纹理进行折射模拟 + +亲手写的,附带法线纹理的转换(使用TBN矩阵),使用了上述的计算公式,渲染纹理详情见下章。效果酷似实体玻璃 + +```c +Shader "Unlit/TestRefractShader" +{ + Properties + { + _MainTex ("Texture", 2D) = "white" {} + _BumpMap ("Normal Map", 2D) = "bump" {} + _GlassColor ("Glass Color" ,COLOR) = (1,1,1,1) + _ScaleTest("Scale Vertex" ,Range(0,1)) = 1 + _TexTint("Tex Tint",Range(0.0,3.0)) = 0.0 + } + SubShader + { + Tags { "RenderType"="Opaque" "Queue"="Transparent" } + LOD 100 + GrabPass { "_RefractionTex2" } + Cull Off + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + // make fog work + //#pragma multi_compile_fog + + #include "UnityCG.cginc" + sampler2D _RefractionTex2; + float4 _RefractionTex2_ST; + sampler2D _BumpMap; + float4 _BumpMap_ST; + sampler2D _MainTex; + float4 _MainTex_ST; + float4 _GlassColor; + float _TexTint; + float _ScaleTest; + + + struct a2v + { + float4 vertex : POSITION; + float3 normal : NORMAL; + float4 tangent : TANGENT; + float2 uv : TEXCOORD0; + }; + + struct v2f + { + float4 vertex : SV_POSITION; + float4 scrPos : TEXCOORD0; + float2 uv : TEXCOORD1; + half3 wNormal : TEXCOORD2; + half3 wTangent : TEXCOORD3; + half3 wBitangent : TEXCOORD4; + float3 worldPos : TEXCOORD5; + UNITY_FOG_COORDS(1) + }; + + + + v2f vert (a2v v) + { + v2f o; + o.vertex = UnityObjectToClipPos(v.vertex); + //o.uv = TRANSFORM_TEX(v.uv, _MainTex); + //o.vertex = float4(o.vertex.x+(1+sin(o.vertex.x+_ScaleTest)),o.vertex.y,o.vertex.z,o.vertex.w); + o.scrPos = ComputeGrabScreenPos(o.vertex); + o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; + o.uv = v.uv; + 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 + { + fixed4 tex = tex2D(_MainTex, i.uv); + half3 tnormal = UnpackNormal(tex2D(_BumpMap, i.uv)); + float3x3 TBNMatrix = float3x3(i.wTangent,i.wBitangent,i.wNormal); + half3 worldNormal = mul(tnormal,TBNMatrix); + half3 worldViewDir = UnityWorldSpaceViewDir(i.worldPos); + + half3 worldRefra = refract(worldViewDir,worldNormal,_ScaleTest); + + fixed4 texTint = _TexTint*fixed4(i.vertex.z,i.vertex.z,i.vertex.z,1); + // sample the texture + float2 offset = worldRefra.xy; + i.scrPos.xy = offset * i.scrPos.z + i.scrPos.xy; + //i.scrPos.xy = tex.xy * i.scrPos.z + i.scrPos.xy; + fixed4 refrCol = tex2D(_RefractionTex2, i.scrPos.xy/i.scrPos.w); + fixed4 col = tex2D(_RefractionTex2, i.uv); + return refrCol*_GlassColor+tex*texTint; + //return tex*_TexTint; + } + ENDCG + } + } +} +``` + +### 3.10 菲涅尔反射 + +真实世界的菲涅尔等式是非常复杂的,实时渲染中,通常会使用一些近似公式来计算。 + +##### Schlick菲涅尔近似等式 + +F (v,n) = F0 + ( 1 - F0 ) ( 1 - v · n ) ^ 5 + +##### Empricial菲涅尔近似等式 ( bias,scale,power是控制项 ) + +F (v,n) = max( 0 , min ( 1, bias + scale * ( 1 - v · n ) ^ power) ) + +schlick菲涅尔示例 + +```c +Shader "Unlit/TestFresnelShader" +{ + Properties { + _Color ("Color Tint", Color) = (0, 0, 0, 1) + _ReflectColor ("Reflect Color",Color) = (0, 0, 1, 1) + _FresnelScale ("Fresnel Scale", Range(0, 1)) = 0.5 + } + SubShader { + Tags { "RenderType"="Opaque" "Queue"="Geometry"} + + Pass { + Tags { "LightMode"="ForwardBase" } + CGPROGRAM + #pragma multi_compile_fwdbase + #pragma vertex vert + #pragma fragment frag + + #include "UnityCG.cginc" + + fixed4 _Color; + fixed4 _ReflectColor; + fixed _FresnelScale; + + struct a2v { + float4 vertex : POSITION; + float3 normal : NORMAL; + }; + + struct v2f { + float4 pos : SV_POSITION; + float3 worldPos : TEXCOORD0; + fixed3 worldNormal : TEXCOORD1; + fixed3 worldViewDir : TEXCOORD2; + }; + + v2f vert(a2v v) { + v2f o; + o.pos = UnityObjectToClipPos(v.vertex); + o.worldNormal = UnityObjectToWorldNormal(v.normal); + o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; + o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos); + return o; + } + + fixed4 frag(v2f i) : SV_Target { + fixed3 worldNormal = normalize(i.worldNormal); + fixed3 worldViewDir = normalize(i.worldViewDir); + fixed3 ambient = ShadeSH9(fixed4(i.worldNormal,1)); + fixed3 reflection = _ReflectColor.rgb; + fixed fresnel = _FresnelScale + (1 - _FresnelScale) * pow(1 - dot(worldViewDir, worldNormal), 5); + fixed3 diffuse = _Color.rgb; + fixed3 color = ambient + lerp(diffuse, reflection, saturate(fresnel)); + return fixed4(color, 1.0); + } + ENDCG + } + } + FallBack "Reflective/VertexLit" +} +``` + +Empricial 菲涅尔示例 + +```c +Shader "Unlit/TestFresnelShader" +{ + Properties { + _Color ("Color Tint", Color) = (0, 0, 0, 1) + _ReflectColor ("Reflect Color",Color) = (0, 0, 1, 1) + _FresnelScale ("Fresnel Scale", Range(0, 1)) = 1 + _FresnelBias ("Bias", Range(0, 1)) = 0 + _FresnelPower ("Power", Range(0, 5)) = 3 + } + SubShader { + Tags { "RenderType"="Opaque" "Queue"="Geometry"} + Pass { + Tags { "LightMode"="ForwardBase" } + CGPROGRAM + #pragma multi_compile_fwdbase + #pragma vertex vert + #pragma fragment frag + + #include "UnityCG.cginc" + + fixed4 _Color; + fixed4 _ReflectColor; + fixed _FresnelScale; + float _FresnelBias; + int _FresnelPower; + + struct a2v { + float4 vertex : POSITION; + float3 normal : NORMAL; + }; + + struct v2f { + float4 pos : SV_POSITION; + float3 worldPos : TEXCOORD0; + fixed3 worldNormal : TEXCOORD1; + fixed3 worldViewDir : TEXCOORD2; + }; + + v2f vert(a2v v) { + v2f o; + o.pos = UnityObjectToClipPos(v.vertex); + o.worldNormal = UnityObjectToWorldNormal(v.normal); + o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; + o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos); + return o; + } + + fixed4 frag(v2f i) : SV_Target { + fixed3 worldNormal = normalize(i.worldNormal); + fixed3 worldViewDir = normalize(i.worldViewDir); + fixed3 ambient = ShadeSH9(fixed4(i.worldNormal,1)); + fixed3 reflection = _ReflectColor.rgb; + //fixed fresnel = _FresnelScale + (1 - _FresnelScale) * pow(1 - dot(worldViewDir, worldNormal), 5); + fixed fresnel = max(0,min(1,_FresnelBias+_FresnelScale * pow(1 - dot(worldViewDir, worldNormal),_FresnelPower))); + fixed3 diffuse = _Color.rgb; + fixed3 color = ambient + lerp(diffuse, reflection, saturate(fresnel)); + return fixed4(color, 1.0); + } + ENDCG + } + } + FallBack "Reflective/VertexLit" +} +``` + +# 4. 系统内置纹理篇 + +## 渲染纹理 + +现代的GPU允许我们吧整个三维场景渲染到一个中间缓冲中,即渲染目标纹理。 + +与之相关的是 多重渲染目标 这种技术指的是GPU允许我们吧场景同事渲染到多个渲染目标纹理中,不再需要为每个渲染目标纹理单独渲染完整的场景。延迟渲染就是使用多重渲染目标的一个应用。 + +unity为渲染目标纹理定义了一种专门的纹理类型----渲染纹理。在Unity中使用渲染纹理通常有两种方式: + +1.在Project面板下创建一个RenderTexture,通过场景中的Camera的渲染目标设置成该渲染纹理。 + +2.屏幕后处理时,使用GrabPass命令或OnRenderImage函数来获取当前屏幕图像。 + +### 4.1 使用RenderTexture + +1.在Project面板下,右键 -> Create -> Render Texture ,并且起对应的名字 + +2.在Hierarchy面板下,右键创建一个Camera,并把Target Texture设置成上一步创建的Render Texture + +3.创建一个Material,使用第一步创建的RenderTexture作为主颜色纹理。 + +4.在场景中的物体上赋予该Material,查看效果。 + +### 4.2 使用GrabPass + +下方使用了两行代码完成了屏幕的抓取,分别是 + +GrabPass{}、fixed4 refrCol = tex2D(_RefractionTex, i.uv); + +但此时我们会发现,抓取屏幕会陷入一个连续抓取的死循环中,这种错误的结果需要进行相关处理后才能使用。设置渲染顺序为Transparent,可以使得抓取屏幕时,确保其他不透明物体已经被渲染到屏幕上了,屏幕抓取不会陷入连续抓取的死循环中。 + +```c +Shader "Unlit/GrabPassShader1" +{ + Properties {} + SubShader + { + Tags { "RenderType"="Opaque" "Queue"="Transparent" } + LOD 100 + // 在当前位置声明Grabpass,如果不使用名字的话,每一个使用该 + // shader的物体都会单独进行一次昂贵的屏幕抓取操作。 + // 使用名字的话,Unity只会执行一次抓取工作,但也意味着所有物体都会 + // 使用一张屏幕图像,大多数情况下是够用的。 + // GrabPass {} + GrabPass { "_RefractionTex" } + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + #include "UnityCG.cginc" + sampler2D _RefractionTex; + float4 _RefractionTex_TexelSize; + + 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 + { + // sample the texture + // 将抓取到的图像展示出来 + fixed4 refrCol = tex2D(_RefractionTex, i.uv); + return refrCol; + } + ENDCG + } + } +} +``` + +### 4.3 使用 GrabPass 实现透明效果 + +ComputeGrabScreenPos(vertex) + +该函数与1.7中的ComputeScreenPos函数基本类似,最大的不同是针对平台差异造成的采样坐标问题进行了处理。 + +使用上述函数获得片元在屏幕上的像素位置时,通常需要两个步骤: + +第一步,把ComputeScreenPos的结果保存到scrPos中。 + +第二步,用scrPos.xy除以scrPos.w得到视口空间中的坐标。 + +```c +Shader "Unlit/GrabPassShader2" +{ + Properties + { + _Color ("Texture", COLOR) = (1,1,1,1) + } + SubShader + { + Tags { "RenderType"="Opaque" "Queue"="Transparent" } + LOD 100 + GrabPass { "_RefractionTex" } + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + + #include "UnityCG.cginc" + + fixed4 _Color; + sampler2D _RefractionTex; + float4 _RefractionTex_TexelSize; + + struct appdata + { + float4 vertex : POSITION; + float2 uv : TEXCOORD0; + }; + + struct v2f + { + float2 uv : TEXCOORD0; + float4 vertex : SV_POSITION; + float4 scrPos : TEXCOORD1; + }; + + v2f vert (appdata v) + { + v2f o; + o.vertex = UnityObjectToClipPos(v.vertex); + o.uv = v.uv; + o.scrPos = ComputeGrabScreenPos(o.vertex); + return o; + } + + fixed4 frag (v2f i) : SV_Target + { + // sample the texture + //fixed4 refrCol = tex2D(_RefractionTex,i.uv); + fixed4 refrCol = tex2D(_RefractionTex,i.scrPos.xy/i.scrPos.w); + return refrCol+_Color*0.2; + } + ENDCG + } + } +} +``` + +如果想修改画面使之产生相应的扭曲效果,以及通过透明图像产生折射效果,可以参考3.9折射篇,使用渲染纹理模拟折射效果其中的代码。部分爆炸特效的空气扭曲,可以通过法线纹理+折射模型实现的。 + +## 深度纹理 + +深度纹理实际就是一张渲染纹理,只不过它里面存储的像素值不是颜色值,而是一个高精度的深度值。 + +深度纹理中的深度值范围是(0,1),而且通常是非线性分布的。 + +获取深度纹理是非常简单的,可以通过下面的代码来获取深度纹理 + +camera.depthTextureMode = DepthTextureMode.Depth; + +在shader中,我们仅仅需要声明以下变量便可使用 + +sampler2D _CameraDepthTexture; + +如果想查看深度纹理,可以使用FrameDebugger + +Window -> Analysis ->FrameDebugger 中,Open可以查看渲染细节。 + +下方是一个深度纹理获取后,将深度纹理变为可视化的渲染器示例 + +```c +Shader "Unlit/DepthShader1" +{ + Properties + { + _DepthScale("DepthData",Float) = 0.1 + } + SubShader + { + Tags { "RenderType"="Opaque" "Queue"="Transparent" } + LOD 100 + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + + #include "UnityCG.cginc" + + sampler2D _CameraDepthTexture; + float _DepthScale; + + struct appdata + { + float4 vertex : POSITION; + float2 uv : TEXCOORD0; + }; + + struct v2f + { + float2 uv : TEXCOORD0; + float4 vertex : SV_POSITION; + float4 scrPos : TEXCOORD1; + }; + + v2f vert (appdata v) + { + v2f o; + o.vertex = UnityObjectToClipPos(v.vertex); + o.uv = v.uv; + o.scrPos = ComputeGrabScreenPos(o.vertex); + return o; + } + + fixed4 frag (v2f i) : SV_Target + { + // sample the texture + fixed4 refrCol = tex2D(_CameraDepthTexture, i.scrPos.xy/i.scrPos.w); + 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 = saturate(intersect); + return border; + } + ENDCG + } + } +} +``` + +深度纹理的进阶应用,可以参考本人在同项目中的另一篇文章,卡通水体着色器全解析。 + +由于法线纹理的获取涉及到c#代码,后续会放在屏幕后期处理中详细讲解。 + +# 5. 顶点公式篇 + +### 5.1 广告牌技术 + +广告牌技术会根据视角方向来旋转一个被纹理着色的多边形,使得多边形看起来总是面对着摄像机。广告牌技术被用于很多应用,例如闪光,烟雾,护盾,云朵,草地等等。 + +广告牌技术的本质就是构建旋转矩阵,而我们知道一个变换矩阵需要3个基向量。广告牌技术使用的基向量通常就是表面法线、指向上的方向、以及指向有的方向。除此之外,还需要制定一个锚点,这个锚点在旋转过程中是固定不变的,以此来确定多边形在空间中的位置。 + +#### 广告牌技术计算过程 + +初始计算得到目标的表面法线和指向上的方向,而两者往往是不垂直的。但是,两者其中之一是固定的,例如模拟草丛时,我们希望广告牌指向上的方向永远是(0,1,0),而法线方向应该随视角变化;当模拟粒子效果时,我们希望广告牌的法线方向是固定的,即总是指向视角方向,指向上的方向则可以发生变化。 + +我们假设法线方向是固定的,首先,我们根据初始的表面法线和指向上的方向来计算出目标方向的指向右的方向。通过叉积操作 + +right = up × normal + +对其归一化后,再由法线方向和指向右的方向计算出正交的指向上的方向即可 + +up‘ = normal × right + +至此,我们就可以得到用于旋转的3个正交基了(right,up,normal)。实现代码如下 + +```c +Shader "TestShader/Billboard" { + Properties { + _MainTex ("Main Tex", 2D) = "white" {} + _Color ("Color Tint", Color) = (1, 1, 1, 1) + _VerticalBillboarding ("Vertical Restraints", Range(0, 1)) = 1 + } + SubShader { + // 需要取消对该shader的批处理操作,"DisableBatching"="True" + Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "DisableBatching"="True"} + + Pass { + Tags { "LightMode"="ForwardBase" } + + ZWrite Off + Blend SrcAlpha OneMinusSrcAlpha + Cull Off + + CGPROGRAM + + #pragma vertex vert + #pragma fragment frag + + #include "Lighting.cginc" + // + sampler2D _MainTex; + float4 _MainTex_ST; + fixed4 _Color; + fixed _VerticalBillboarding; + + struct a2v { + float4 vertex : POSITION; + float4 texcoord : TEXCOORD0; + }; + + struct v2f { + float4 pos : SV_POSITION; + float2 uv : TEXCOORD0; + }; + + v2f vert (a2v v) { + v2f o; + float3 center = float3(0, 0, 0); + float3 viewer = mul(unity_WorldToObject,float4(_WorldSpaceCameraPos, 1)); + float3 normalDir = viewer - center; + normalDir.y =normalDir.y * _VerticalBillboarding; + normalDir = normalize(normalDir); + float3 upDir = abs(normalDir.y) > 0.999 ? float3(0, 0, 1) : float3(0, 1, 0); + float3 rightDir = normalize(cross(upDir, normalDir)); + upDir = normalize(cross(normalDir, rightDir)); + float3 centerOffs = v.vertex.xyz - center; + float3 localPos = center + rightDir * centerOffs.x + upDir * centerOffs.y + normalDir * centerOffs.z; + o.pos = UnityObjectToClipPos(float4(localPos, 1)); + o.uv = TRANSFORM_TEX(v.texcoord,_MainTex); + return o; + } + + fixed4 frag (v2f i) : SV_Target { + fixed4 c = tex2D (_MainTex, i.uv); + c.rgb *= _Color.rgb; + return c; + } + + ENDCG + } + } + FallBack "Transparent/VertexLit" +} +``` diff --git a/UnityShader编写指南.md b/UnityShader编写指南.md new file mode 100644 index 0000000..280058c --- /dev/null +++ b/UnityShader编写指南.md @@ -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 +返回:计算逐顶点光照。 diff --git a/img/2021-12-29-14-45-35-image.png b/img/2021-12-29-14-45-35-image.png new file mode 100644 index 0000000..b6b6249 Binary files /dev/null and b/img/2021-12-29-14-45-35-image.png differ diff --git a/img/ImageEffect/DepthTexture1.png b/img/ImageEffect/DepthTexture1.png new file mode 100644 index 0000000..e89a009 Binary files /dev/null and b/img/ImageEffect/DepthTexture1.png differ diff --git a/img/ImageEffect/DepthTexture2.png b/img/ImageEffect/DepthTexture2.png new file mode 100644 index 0000000..bd1d1e8 Binary files /dev/null and b/img/ImageEffect/DepthTexture2.png differ diff --git a/img/ImageEffect/DepthTexture3.png b/img/ImageEffect/DepthTexture3.png new file mode 100644 index 0000000..200be40 Binary files /dev/null and b/img/ImageEffect/DepthTexture3.png differ diff --git a/img/ImageEffect/DepthTexture4.png b/img/ImageEffect/DepthTexture4.png new file mode 100644 index 0000000..975fca0 Binary files /dev/null and b/img/ImageEffect/DepthTexture4.png differ diff --git a/img/ImageEffect/DepthTexture5.png b/img/ImageEffect/DepthTexture5.png new file mode 100644 index 0000000..8109079 Binary files /dev/null and b/img/ImageEffect/DepthTexture5.png differ diff --git a/img/ImageEffect/DepthTexture6.png b/img/ImageEffect/DepthTexture6.png new file mode 100644 index 0000000..f1a9ae0 Binary files /dev/null and b/img/ImageEffect/DepthTexture6.png differ diff --git a/img/ImageEffect/DepthTexture7.png b/img/ImageEffect/DepthTexture7.png new file mode 100644 index 0000000..1f54d43 Binary files /dev/null and b/img/ImageEffect/DepthTexture7.png differ diff --git a/img/ImageEffect/DepthTexture8.gif b/img/ImageEffect/DepthTexture8.gif new file mode 100644 index 0000000..4321af1 Binary files /dev/null and b/img/ImageEffect/DepthTexture8.gif differ diff --git a/img/ImageEffect/DepthTexture9.gif b/img/ImageEffect/DepthTexture9.gif new file mode 100644 index 0000000..cb02e97 Binary files /dev/null and b/img/ImageEffect/DepthTexture9.gif differ diff --git a/img/ImageEffect/net7.jpg b/img/ImageEffect/net7.jpg new file mode 100644 index 0000000..834d003 Binary files /dev/null and b/img/ImageEffect/net7.jpg differ diff --git a/img/ToonWaterShader.gif b/img/ToonWaterShader.gif new file mode 100644 index 0000000..a498fae Binary files /dev/null and b/img/ToonWaterShader.gif differ diff --git a/img/ToonWaterShader2.gif b/img/ToonWaterShader2.gif new file mode 100644 index 0000000..2f685b9 Binary files /dev/null and b/img/ToonWaterShader2.gif differ diff --git a/img/UnpackNormal.png b/img/UnpackNormal.png new file mode 100644 index 0000000..14b87a9 Binary files /dev/null and b/img/UnpackNormal.png differ diff --git a/img/abs_UnpackNormal.png b/img/abs_UnpackNormal.png new file mode 100644 index 0000000..70b4fe5 Binary files /dev/null and b/img/abs_UnpackNormal.png differ diff --git a/img/bumpTest4.png b/img/bumpTest4.png new file mode 100644 index 0000000..b69114c Binary files /dev/null and b/img/bumpTest4.png differ diff --git a/img/bumpTest4.tif b/img/bumpTest4.tif new file mode 100644 index 0000000..9b5ac7d Binary files /dev/null and b/img/bumpTest4.tif differ diff --git a/img/bumpTest5.png b/img/bumpTest5.png new file mode 100644 index 0000000..db930c9 Binary files /dev/null and b/img/bumpTest5.png differ diff --git a/img/bumpTest5.tif b/img/bumpTest5.tif new file mode 100644 index 0000000..5eb196d Binary files /dev/null and b/img/bumpTest5.tif differ diff --git a/img/gradient.jpg b/img/gradient.jpg new file mode 100644 index 0000000..b081bcc Binary files /dev/null and b/img/gradient.jpg differ diff --git a/img/gradient2.jpg b/img/gradient2.jpg new file mode 100644 index 0000000..388dcd9 Binary files /dev/null and b/img/gradient2.jpg differ diff --git a/img/normalMapPosition.png b/img/normalMapPosition.png new file mode 100644 index 0000000..dd0e856 Binary files /dev/null and b/img/normalMapPosition.png differ diff --git a/img/texture.png b/img/texture.png new file mode 100644 index 0000000..21bb89d Binary files /dev/null and b/img/texture.png differ diff --git a/法线贴图详解.md b/法线贴图详解.md new file mode 100644 index 0000000..51167d4 --- /dev/null +++ b/法线贴图详解.md @@ -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绝对值处理 diff --git a/深度纹理进阶应用-卡通水体着色器制作.md b/深度纹理进阶应用-卡通水体着色器制作.md new file mode 100644 index 0000000..c966464 --- /dev/null +++ b/深度纹理进阶应用-卡通水体着色器制作.md @@ -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 + } + } +} +```