├── .gitignore ├── Volume 01 Diffuse 漫反射光照 ├── Image │ ├── diffuse1.png │ ├── diffuse2.png │ ├── diffuse3.png │ ├── diffuse4.png │ └── diffuse5.png ├── readme.md ├── simpleDiffusePerFrag.shader └── simpleSpecular.shader ├── Volume 02 Texture 纹理的应用 ├── Image │ └── readmeImage │ │ ├── texture1.png │ │ ├── texture2.png │ │ └── texture3.png ├── MatCap.shader ├── RampTex.shader ├── normalTexture.shader ├── readme.md └── singleTexture.shader ├── Volume 03 Alpha 透明效果 ├── AlphaBlend.shader ├── AlphaT e s t.shader ├── Image │ └── readmeImage │ │ ├── AlphaBlend1.png │ │ ├── AlphaBlend2.png │ │ ├── AlphaBlend3.png │ │ ├── AlphaBlend4.png │ │ └── AlphaBlend5.png └── readme.md ├── Volume 04 ForwardRender渲染路径、阴影 ├── ForwardRendering.shader ├── Image │ └── readmeImage │ │ ├── RenderingPath0.gif │ │ └── RenderingPath2.png └── readme.md ├── Volume 05 CubeMap立方体纹理 ├── FireDistort.shader ├── Fresnel.shader ├── GlassRefraction.shader ├── Image │ └── readmeImage │ │ ├── CubeMap0.png │ │ ├── CubeMap1.png │ │ ├── CubeMap3.png │ │ ├── CubeMap4.png │ │ ├── Fire.gif │ │ └── FireNoDis.gif ├── Mirror.shader ├── Reflection.shader ├── Refraction.shader └── readme.md ├── Volume 06 VertexChange顶点变换 ├── Billboarding.shader ├── BillboardingImageSequenceAnimation.shader ├── GemoVisualNormal.shader ├── Image │ └── readmeImage │ │ ├── 005-Attack03.png │ │ ├── VertexChanged4.png │ │ ├── boom.png │ │ ├── vertexChanged0.png │ │ ├── vertexChanged1.png │ │ ├── vertexChanged2.png │ │ ├── vertexChanged5.png │ │ └── vertexChanged6.png ├── ImageSequenceAnimation.shader └── readme.md ├── Volume 07 SimplePostEffect简单屏幕后处理 ├── Bloom.shader ├── BrightnessSaturationAndContrast.shader ├── EdgeDetection.shader ├── GaussianBlur.shader ├── MotionBlur.shader ├── RadialBlurEffect.shader └── Script │ ├── Bloom.cs │ ├── BrightnessSaturationAndContrast.cs │ ├── EdgeDetection.cs │ ├── GaussianBlur.cs │ ├── MotionBlur.cs │ ├── PostEffectBase.cs │ └── RadialBlurEffect.cs ├── Volume 08 DepthNormalTexture深度和法线纹理的简单应用 ├── DepthOfField.shader ├── FogWithDepthTexture.shader ├── MotionBlurWithDepthTexture.shader ├── NDCtoWorld │ ├── NDCtoWorld0.png │ ├── NDCtoWorld1.png │ ├── NDCtoWorld2.png │ ├── NDCtoWorld3.png │ ├── NDCtoWorld4.png │ ├── NDCtoWorld5.png │ ├── NDCtoWorld6.png │ ├── NDCtoWorld7.png │ ├── NDCtoWorld8.png │ ├── NDCtoWorld9.png │ └── 从NDC坐标转换到世界坐标的数学原理.md ├── ScanLineWithDepth.shader ├── ScanLineWithRebuildWorldPos.shader ├── Script │ ├── DepthOfField.cs │ ├── FogWithDepthTexture.cs │ ├── MotionBlurWithDepthTexture.cs │ ├── ScanLineWithDepthTexture.cs │ ├── ScanLineWithRebuildWorldPos.cs │ └── ShowDepth.cs ├── ShowDepth.shader └── ShowDepthNotLinear.shader ├── Volume 09 EdgeDetection详解边缘检测 ├── BulletTimeStartWithEdgeDetection.shader ├── Script │ └── BulletTimeStartWithEdgeDetection.cs ├── edgeDetectionNormalAdnDepth.shader ├── edgeDetectionNormalAdnDepthWithFlashLight.shader ├── edgeDetectionWithMaskTex.shader └── edgeDetectionWithPostEffect.shader ├── Volume 10 Projector投影仪的原理及简单应用 ├── CustomizeProjector.shader ├── DissloveTextureWithProjector.shader ├── Scripts │ └── CustomizeProjector.cs └── StandardProjector.shader ├── Volume 11 Toon 非真实感渲染 ├── Hatching.shader └── ToonShadering.shader ├── Volume 12 ScreenFade 屏幕转场效果 ├── BreakingScreen.shader └── Scripts │ └── BreakingScreenWithAoNoKiseki.cs ├── Volume 13 PBR 基于物理的渲染 └── CustomPBR.shader ├── readme.md └── readmeImage ├── Volume01逐片元漫反射.png ├── Volume01逐顶点漫反射.png ├── Volume02-法线纹理映射.gif ├── Volume02-渐变纹理.png ├── Volume02-简单纹理映射.png ├── Volume03-透明度测试.png ├── Volume03-透明度混合.png ├── Volume04-多光源照射.png ├── Volume04-阴影的投射.png ├── Volume05-Fire.gif ├── Volume05-与天空盒的反射.png ├── Volume05-玻璃折射.gif ├── Volume07-Bloom.png ├── Volume07-motionBlur.gif ├── Volume07-原图.png ├── Volume07-边缘检测进行描边.png ├── Volume07-高斯模糊.png ├── Volume08-运动模糊.gif ├── Volume08-高度雾.gif ├── Volume09-边缘检测子弹时间.gif ├── Volume09-边缘检测转场.gif ├── projectorDisslove.gif ├── projectorDisslove2.gif ├── 不使用广告版-序列帧.gif ├── 不使用广告版.gif ├── 广告版-序列帧.gif ├── 广告版.gif └── 序列帧动画.gif /.gitignore: -------------------------------------------------------------------------------- 1 | *.meta 2 | *Test 3 | *test* 4 | Volume xx * -------------------------------------------------------------------------------- /Volume 01 Diffuse 漫反射光照/Image/diffuse1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 01 Diffuse 漫反射光照/Image/diffuse1.png -------------------------------------------------------------------------------- /Volume 01 Diffuse 漫反射光照/Image/diffuse2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 01 Diffuse 漫反射光照/Image/diffuse2.png -------------------------------------------------------------------------------- /Volume 01 Diffuse 漫反射光照/Image/diffuse3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 01 Diffuse 漫反射光照/Image/diffuse3.png -------------------------------------------------------------------------------- /Volume 01 Diffuse 漫反射光照/Image/diffuse4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 01 Diffuse 漫反射光照/Image/diffuse4.png -------------------------------------------------------------------------------- /Volume 01 Diffuse 漫反射光照/Image/diffuse5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 01 Diffuse 漫反射光照/Image/diffuse5.png -------------------------------------------------------------------------------- /Volume 01 Diffuse 漫反射光照/readme.md: -------------------------------------------------------------------------------- 1 | # 标准光照模型之漫反射 # 2 | ## 疑难解答 ## 3 | ### 1. 为什么漫反射光照结果计算时,要使用归一化的法线以及单位光源方向? ### 4 | 首先明确一点,光照的辐照度与cosΘ成正相关,cosΘ表示单位法线与光源方向的夹角,详情如下图。 5 | 6 | ![Avater](Image/diffuse5.png) 7 | 8 | 辐照度通过计算垂直于光的方向的单位面积上单位时间穿过的能量来计算,左图是光源方向垂直于物体的情况,但大多数情况,物体不会与光源垂直,此时情况如右图。 9 | 10 | 可以明显观察到右图因为光线之间距离变大(由原来的d变为d/cosθ),光线变少,即辐照度变少。 11 | 12 | 辐照度与d/cosθ成反比,故与cosθ成正比。 13 | 14 | 当计算漫反射光照结果时,需要用到cosθ,故此时使用法线与光源方向点乘获得cosθ,计算公式如下。 15 | 16 | 17 | ### 2. 为什么Pass中要有Tags{ "LightMode" = "ForwardBase" }这一行?作用是什么? ### 18 | 这一行的作用是设置该Pass的渲染路径。 19 | 20 | 渲染路径,其决定了光照是如何应用到Unity Shader中的,Unity内置光照的变量随着设置的渲染路径不同而不同。在这个简单的diffuse shader中,其用于填充_LightColor0变量以及正确的光源方向的赋值。 21 | 22 | 如果不对渲染路径进行设置,会出现错误结果,下图总结了这个错误的结果。 23 | 24 | ![Avater](Image/diffuse3.png) 25 | 26 | ### 3. 为什么要使用max(0,reuslt)来约束漫反射光照结果? ### 27 | 1.max(0,result)用于约束cosθ的结果为正数,需要注意的是,当光源处于物体(某一点)背面时,该点法线与光源方向点乘结果为负数,在最终结果diffuse+ambient中,因为diffuse是负数,会导致物体变暗。如下图所示。 28 | 29 | ![Avater](Image/diffuse1.png) 30 | 31 | 2.第二点,在《Unity Shader 入门精要》中提到: 32 | 33 | > 如果不对结果约束为正数,物体会被后面来的光照照亮。 34 | 35 | 实际测试中发现。。。。。貌似并没有这种现象的产生,在这里我推测一下,有可能是Unity版本不同导致的结果。根据乐乐大神在书中描述的现象,我不负责任的推测一下,可能是由于以前的Unity版本,在光照计算中遇到的负数全部都会自动变成其对应的正数。 36 | 37 | 下图中,使用了abs函数将光照结果取正值,可以发现物体的确被背面来的光照亮了。 38 | 39 | 关于这个第二点,只是我的一个个人的小猜测。。。因为在目前查阅的资料了都描述了“会被后面来的光照亮”这一现象。。。。但是我没有遇到,所以想可能是Unity版本的原因。如果大家知道是怎么回事的话,跪求在评论区告诉楼主ORZ超级感激不尽。 40 | 41 | ![Avater](Image/diffuse2.png) 42 | 43 | ## 参考资料 ## 44 | > 《Unity Shader入门精要》 45 | > 46 | > 【UnityShader从零开始】漫反射效果 http://gad.qq.com/article/detail/11725 -------------------------------------------------------------------------------- /Volume 01 Diffuse 漫反射光照/simpleDiffusePerFrag.shader: -------------------------------------------------------------------------------- 1 | /* 2 | 简单的逐片元漫反射光照 3 | */ 4 | Shader "Volume 01/Diffuse/Simple Diffuse Per Frag" { 5 | Properties { 6 | // 材质本身的漫反射颜色 7 | _Diffuse("Diffuse Color",Color) = (1, 1, 1, 1) 8 | } 9 | SubShader { 10 | 11 | // 设置渲染类型为不透明物体,渲染队列也按不透明物体来 12 | Tags { "RenderType" = "Opaque" "Queue" = "Geometry" } 13 | 14 | Pass { 15 | // 设置渲染路径,此处使用前向渲染路径,用于让Unity底层渲染引擎填充内置光照变量 16 | Tags{ "LightMode" = "ForwardBase" } 17 | 18 | CGPROGRAM 19 | 20 | // 定义顶点/片元着色器 21 | #pragma vertex vert 22 | #pragma fragment frag 23 | 24 | // 要使用_LightColor0(场景平行光光源颜色)变量, 25 | // 需要导入Lighting包 26 | #include "Lighting.cginc" 27 | 28 | // 材质漫反射颜色 29 | fixed4 _Diffuse; 30 | 31 | // 输入结构体,Application To Vertex 32 | struct a2v{ 33 | float4 vertex : POSITION; 34 | float3 normal : NORMAL; 35 | }; 36 | // 顶点着色器的输出结构体,Vertex To Fragment 37 | struct v2f{ 38 | float4 pos : SV_POSITION; 39 | float3 worldNormal : TEXCOORD0; 40 | float3 worldPos : TEXCOORD1; 41 | }; 42 | 43 | v2f vert(a2v v){ 44 | v2f o; 45 | 46 | // 变换顶点到裁剪空间 47 | o.pos = UnityObjectToClipPos(v.vertex); 48 | 49 | // 获得世界坐标下的法线 50 | o.worldNormal = UnityObjectToWorldNormal(v.normal); 51 | 52 | // 获得世界坐标下的顶点坐标 53 | o.worldPos = UnityObjectToWorldDir(v.vertex).xyz; 54 | 55 | return o; 56 | } 57 | 58 | fixed4 frag(v2f i) : SV_TARGET{ 59 | // 归一化法线 60 | fixed3 worldNormal = normalize(i.worldNormal); 61 | // 获得归一化光源方向(通过设置渲染路径获得) 62 | fixed3 worldDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); 63 | 64 | // 环境光 65 | fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb; 66 | 67 | // 计算漫反射光照 68 | fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0,dot(worldNormal,worldDir)); 69 | 70 | return fixed4(ambient+diffuse,1); 71 | } 72 | 73 | ENDCG 74 | } 75 | } 76 | FallBack "Diffuse" 77 | 78 | } -------------------------------------------------------------------------------- /Volume 01 Diffuse 漫反射光照/simpleSpecular.shader: -------------------------------------------------------------------------------- 1 | Shader "Volume 01/Diffuse/Simple Specular Per Frag" { 2 | Properties { 3 | // 漫反射材质颜色 4 | _Diffuse("Diffuse Color",Color) = (1, 1, 1, 1) 5 | // 高光反射材质颜色 6 | _Specular("Specular Color",Color) = (1, 1, 1, 1) 7 | // 光泽度 8 | _Gloss("Gloss",Range(8.0,256)) = 20 9 | } 10 | SubShader { 11 | 12 | Tags { "RenderType" = "Opaque" "Queue"="Geometry" } 13 | 14 | Pass { 15 | Tags { "LightMode" = "ForwardBase" } 16 | CGPROGRAM 17 | 18 | #pragma vertex vert 19 | #pragma fragment frag 20 | 21 | #include "Lighting.cginc" 22 | 23 | fixed4 _Diffuse; 24 | fixed4 _Specular; 25 | float _Gloss; 26 | 27 | struct a2v{ 28 | float4 vertex : POSITION; 29 | float3 normal : NORMAL; 30 | }; 31 | struct v2f{ 32 | float4 pos : SV_POSITION; 33 | float3 worldPos : TEXCOORD0; 34 | float3 worldNormal : TEXCOORD1; 35 | }; 36 | 37 | v2f vert(a2v v){ 38 | v2f o; 39 | o.pos = UnityObjectToClipPos(v.vertex); 40 | 41 | o.worldPos = UnityObjectToWorldDir(v.vertex).xyz; 42 | o.worldNormal = UnityObjectToWorldNormal(v.normal); 43 | 44 | return o; 45 | } 46 | 47 | fixed4 frag(v2f i):SV_TARGET{ 48 | // 归一化法线与光源方向 49 | fixed3 worldNormal = normalize(i.worldNormal); 50 | fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); 51 | 52 | // 环境光 53 | fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb; 54 | 55 | // 获得观察方向 56 | fixed3 worldViewDir = normalize(UnityWorldToViewPos(i.worldPos)); 57 | 58 | fixed3 halfDir = normalize(worldViewDir+worldLightDir); 59 | 60 | // 计算漫反射光照 61 | fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0,dot(worldNormal,worldLightDir)); 62 | 63 | // 计算高光反射 64 | fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(worldNormal,halfDir)),_Gloss); 65 | 66 | return fixed4(diffuse + specular + ambient,1.0); 67 | // return fixed4(specular,1.0); 68 | } 69 | 70 | ENDCG 71 | } 72 | } 73 | FallBack "Diffuse" 74 | 75 | } -------------------------------------------------------------------------------- /Volume 02 Texture 纹理的应用/Image/readmeImage/texture1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 02 Texture 纹理的应用/Image/readmeImage/texture1.png -------------------------------------------------------------------------------- /Volume 02 Texture 纹理的应用/Image/readmeImage/texture2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 02 Texture 纹理的应用/Image/readmeImage/texture2.png -------------------------------------------------------------------------------- /Volume 02 Texture 纹理的应用/Image/readmeImage/texture3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 02 Texture 纹理的应用/Image/readmeImage/texture3.png -------------------------------------------------------------------------------- /Volume 02 Texture 纹理的应用/MatCap.shader: -------------------------------------------------------------------------------- 1 | /* 2 | 材质捕获 3 | */ 4 | Shader "Volume 02/Texture/Material Capture" { 5 | Properties { 6 | // 材质捕获贴图 7 | _MatCapTex("MatCap Texture",2D) = "white" {} 8 | // 材质贴图 9 | _MainTex("Main Texture",2D) = "white" {} 10 | 11 | } 12 | SubShader { 13 | Tags { "RenderType"="Opaque" "Queue"="Geometry" } 14 | Pass { 15 | CGPROGRAM 16 | 17 | sampler2D _MatCapTex; 18 | 19 | #pragma vertex vert 20 | #pragma fragment frag 21 | 22 | struct a2v{ 23 | float4 vertex : POSITION; 24 | float3 normal : NORMAL; 25 | }; 26 | 27 | struct v2f{ 28 | float4 pos : SV_POSITION; 29 | float2 matcapUv : TEXCOORD0; 30 | }; 31 | 32 | v2f vert(a2v v){ 33 | v2f o; 34 | o.pos = UnityObjectToClipPos(v.vertex); 35 | 36 | // 将法线变换到观察空间 37 | float3 viewNormal = mul(UNITY_MATRIX_IT_MV,v.normal); 38 | viewNormal = normalize(viewNormal); 39 | o.matcapUv = viewNormal.xy * 0.5 + 0.5; 40 | return o; 41 | } 42 | 43 | fixed4 frag(v2f i) : SV_TARGET{ 44 | fixed4 color = tex2D(_MatCapTex,i.matcapUv); 45 | return color; 46 | } 47 | 48 | ENDCG 49 | } 50 | } 51 | FallBack "Diffuse" 52 | 53 | } -------------------------------------------------------------------------------- /Volume 02 Texture 纹理的应用/RampTex.shader: -------------------------------------------------------------------------------- 1 | /* 2 | 渐变纹理 3 | */ 4 | Shader "Volume 02/Texture/Ramp Texture" { 5 | Properties { 6 | // 用于控制材质整体颜色 7 | _Color("Color Tint",Color) = (1, 1, 1, 1) 8 | // 渐变纹理 9 | _RampTex("Ramp Tex",2D) = "white" {} 10 | // 高光反射颜色 11 | _Specular("Specular Color",Color) = (1, 1, 1, 1) 12 | // 光泽度 13 | _Gloss("_Gloss",Range(8.0,256)) = 20 14 | } 15 | SubShader { 16 | 17 | Tags{ "RenderType" = "Opaque" "Queue" = "Geometry" } 18 | 19 | Pass { 20 | Tags{ "LightMode" = "Forwardbase" } 21 | 22 | CGPROGRAM 23 | 24 | #include "UnityCg.cginc" 25 | #include "Lighting.cginc" 26 | 27 | #pragma vertex vert 28 | #pragma fragment frag 29 | 30 | fixed4 _Color; 31 | sampler2D _RampTex; 32 | float4 _RampTex_ST; 33 | fixed4 _Specular; 34 | float _Gloss; 35 | 36 | struct a2v{ 37 | float4 vertex : POSITION; 38 | float4 texcoord : TEXCOORD0; 39 | float3 normal : NORMAL; 40 | }; 41 | struct v2f{ 42 | float4 pos : SV_POSITION; 43 | float3 worldPos : TEXCOORD0; 44 | float2 uv : TEXCOORD1; 45 | float3 worldNormal : TEXCOORD2; 46 | }; 47 | 48 | v2f vert(a2v v){ 49 | v2f o; 50 | o.pos = UnityObjectToClipPos(v.vertex); 51 | 52 | // 对贴图进行位移缩放处理 53 | o.uv = TRANSFORM_TEX(v.texcoord,_RampTex); 54 | // 世界空间下的顶点位置 55 | o.worldPos = mul(unity_ObjectToWorld,v.vertex); 56 | // 世界空间下的法线位置 57 | o.worldNormal = UnityObjectToWorldNormal(v.normal); 58 | 59 | return o; 60 | } 61 | 62 | fixed4 frag(v2f i) : SV_TARGET{ 63 | // 获得归一化的世界空间下的法线 64 | fixed3 worldNormal = normalize(i.worldNormal); 65 | // 获得光源方向 66 | fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); 67 | // 获得环境光源 68 | fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; 69 | 70 | // 计算半兰伯特 71 | fixed halfLambert = 0.5 * dot(worldNormal,worldLightDir) + 0.5; 72 | 73 | // 计算漫反射颜色 74 | fixed3 diffuseColor = tex2D(_RampTex,fixed2(halfLambert,halfLambert)).rgb * _Color.rgb * _LightColor0.rgb; 75 | 76 | // 获得视角方向 77 | fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos)); 78 | 79 | fixed halfDir = normalize(worldLightDir+viewDir); 80 | 81 | // 计算高光反射 82 | fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(worldNormal,halfDir)),_Gloss); 83 | 84 | return fixed4(ambient+diffuseColor+specular,1.0); 85 | } 86 | 87 | ENDCG 88 | 89 | } 90 | } 91 | FallBack "Diffuse" 92 | 93 | } -------------------------------------------------------------------------------- /Volume 02 Texture 纹理的应用/normalTexture.shader: -------------------------------------------------------------------------------- 1 | // Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld' 2 | 3 | /* 4 | 简单运用法线映射,即,物体根据对应的法线贴图来进行光照计算 5 | */ 6 | Shader "Volume 02/Texture/Normal Texture"{ 7 | Properties{ 8 | // 主纹理 9 | _MainTex("Main Tex",2D) = "white" {} 10 | // 法线贴图 11 | _BumpTex("Bump Tex",2D) = "bump" {} 12 | // 物体的颜色控制 13 | _Color("Color Tint",Color) = (1, 1, 1, 1) 14 | // 高光反射颜色控制 15 | _Specular("Specular",Color) = (1, 1, 1, 1) 16 | // 高光反射光泽度 17 | _Gloss("Gloss",Range(8.0,256)) = 20 18 | // 凹凸程度 19 | _BumpScale("BumpScale",Float) = 1.0 20 | } 21 | SubShader{ 22 | Tags { "RenderType"="Opaque" "Queue"="Geometry" } 23 | Pass { 24 | 25 | // 设置渲染路径 26 | Tags { "LightMode" = "ForwardBase" } 27 | 28 | CGPROGRAM 29 | 30 | #pragma vertex vert 31 | #pragma fragment frag 32 | 33 | #include "Lighting.cginc" 34 | 35 | struct a2v{ 36 | float4 vertex : POSITION; 37 | float3 normal : NORMAL; 38 | float4 tangent : TANGENT; 39 | float4 texcoord : TEXCOORD0; 40 | }; 41 | struct v2f{ 42 | float4 pos : SV_POSITION; // 裁剪空间下的顶点坐标 43 | 44 | //=========================== 45 | // 切线空间 -- 世界空间 变换矩阵 46 | float4 TtoW0 : TEXCOORD0; // 变换矩阵第一行,w分量存放worldPos.x 47 | float4 TtoW1 : TEXCOORD1; // 变换矩阵第一行,w分量存放worldPos.y 48 | float4 TtoW2 : TEXCOORD2; // 变换矩阵第一行,w分量存放worldPos.z 49 | 50 | //============================ 51 | // uv坐标 52 | float2 uv : TEXCOORD3; 53 | float2 bump_uv : TEXCOORD4; 54 | }; 55 | 56 | sampler2D _MainTex; 57 | float4 _MainTex_ST; // 主纹理的缩放和位移 58 | sampler2D _BumpTex; 59 | float4 _BumpTex_ST; // 法线贴图的缩放和位移 60 | fixed4 _Color; 61 | fixed4 _Specular; 62 | float _Gloss; 63 | float _BumpScale; 64 | 65 | v2f vert(a2v v){ 66 | v2f o; 67 | o.pos = UnityObjectToClipPos(v.vertex); 68 | 69 | // 世界坐标下的顶点位置,用于光照计算 70 | // float3 worldPos = UnityObjectToWorldDir(v.vertex); 71 | float3 worldPos = mul(unity_ObjectToWorld,v.vertex).xyz; 72 | 73 | // 获得世界坐标下的法线(用于构建变换矩阵,无需归一化) 74 | float3 worldNormal = UnityObjectToWorldNormal(v.normal); 75 | // 获得世界坐标下的切线(用于构建变换矩阵,无需归一化) 76 | float3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz); 77 | // 根据法线和切线的叉积,计算副切线在世界坐标下的位置 78 | float3 worldBinNormal = cross(worldNormal,worldTangent) * v.tangent.w; 79 | 80 | //=========================== 81 | // 构建变换矩阵,按列摆放世界坐标下的切线空间的x,y,z轴 82 | o.TtoW0 = float4(worldTangent.x,worldBinNormal.x,worldNormal.x,worldPos.x); 83 | o.TtoW1 = float4(worldTangent.y,worldBinNormal.y,worldNormal.y,worldPos.y); 84 | o.TtoW2 = float4(worldTangent.z,worldBinNormal.z,worldNormal.z,worldPos.z); 85 | 86 | //=================================== 87 | // uv坐标 88 | o.uv = TRANSFORM_TEX(v.texcoord,_MainTex); 89 | o.bump_uv = TRANSFORM_TEX(v.texcoord,_BumpTex); 90 | 91 | return o; 92 | } 93 | 94 | fixed4 frag(v2f i) : SV_TARGET{ 95 | // 解析世界坐标 96 | float3 worldPos = float3(i.TtoW0.w,i.TtoW1.w,i.TtoW2.w); 97 | 98 | // 获得切线空间下的法线 99 | float3 tangentNormal = UnpackNormal(tex2D(_BumpTex,i.bump_uv)); 100 | // 根据凹凸程度缩放法线 101 | tangentNormal.xy *= _BumpScale; 102 | tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy,tangentNormal.xy))); 103 | // 将法线变换到世界坐标空间下 104 | fixed3 bump = normalize(half3( dot(i.TtoW0.xyz,tangentNormal),dot(i.TtoW1.xyz,tangentNormal),dot(i.TtoW2.xyz,tangentNormal) )); 105 | 106 | // 获得光源方向 107 | fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(worldPos)); 108 | // 对主纹理进行采样 109 | fixed3 albedo = tex2D(_MainTex,i.uv).rgb * _Color.rgb; 110 | // 计算漫反射光照 111 | fixed3 diffuse = _LightColor0.rgb * albedo * max(0,dot(worldLightDir,bump)); 112 | // 计算环境光 113 | fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo; 114 | // 计算归一化的观察方向 115 | fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos)); 116 | // 计算Blinn模型的half变量 117 | fixed3 halfDir = normalize(worldLightDir + worldViewDir); 118 | // 计算高光反射 119 | fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(bump,halfDir)),_Gloss); 120 | 121 | // 着色 122 | return fixed4(specular + ambient + diffuse , 1.0); 123 | } 124 | 125 | ENDCG 126 | } 127 | } 128 | Fallback "Diffuse" 129 | } -------------------------------------------------------------------------------- /Volume 02 Texture 纹理的应用/readme.md: -------------------------------------------------------------------------------- 1 | # 《Unity Shader 入门精要》读书笔记 之 纹理映射 # 2 | ## 参考资料 ## 3 | > 《Unity Shader 入门精要》 4 | ## 法线映射 ## 5 | ### 疑难解答 ### 6 | #### 获得世界空间下的顶点坐标 #### 7 | 这是对我以往的Bug写法的修正,在以前,我用 8 | 9 | o.worldPos = UnityObjectToWorldDir(v.vertex) 10 | 11 | 来计算在世界空间下的顶点坐标,但这是**不正确的写法!!!** 12 | 因为UnityObjectToWorldDir函数是对方向矢量进行变换(即只具有方向的向量),而这里我们要变换的是顶点并不是方向矢量。 13 | 14 | 正确的变换模型空间的顶点到世界坐标的方法还是要老老实实用矩阵乘法来完成,如下所示: 15 | 16 | o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz; 17 | unity_ObjectToWorld是模型-世界空间的变换矩阵。 18 | 19 | #### 法线贴图如何存储法线?如何在Shader中取出法线? #### 20 | 21 | **一 法线纹理如何存储法线?** 22 | 23 | 法线纹理存储的就是表面的法线的方向。由于法线方向分量在[-1,1],而像素分量为[0,1]。 24 | 25 | 所以对法线做一个映射,将其压缩为一个像素,存储到法线纹理中,映射公式如下: 26 | 27 | pixel = (normal+1) / 2 28 | 29 | **二 如何在Shader中取出法线?** 30 | 31 | 对于在Unity中标注好类型是Normal Map的贴图,可以直接使用unity内置函数UnpackNormal()来取出并解析法线纹理中的法线. 32 | 33 | 对于没有标注类型是Normal Map的贴图,可以直接手工模拟这个过程,即: 34 | 35 | normal = pixel*2 - 1 36 | 37 | #### 法线贴图中法线的方向 #### 38 | 任何不谈坐标空间的方向都是在耍流氓! 39 | 40 | 法线纹理中存储的法线有两种坐标空间,一是定义在模型空间的表面法线,在纹理中所有法线的方向都基于模型空间。所有法线所在的坐标空间是同一个坐标空间。 41 | 二是模型顶点的切线空间,此切线空间以**该顶点为坐标轴原点**,**以法线为z轴**,**以切线为x轴**,**以副切线为y轴**。所有法线所在的坐标空间都是不同的坐标空间。(都是各自顶点的坐标空间) 42 | 43 | 以切线空间来存储法线的法线纹理多半都是浅蓝色的。这是因为如果一个法线没有变化,那么他在法线纹理中存储的向量值应该(0,0,1)(法线在切线空间的x轴),他的像素值应该是(0.5,0.5,1),所以整个法线纹理会看到是浅蓝色的。 44 | 45 | 实际上,这种存储方式其实就是存储了每个点在各自的切线空间中的法线的扰动方向。 46 | 47 | **在实际运用中,最经常用到的是以切线空间来存储各个法线的方向。** 48 | 49 | #### 如何构建切线-世界空间变换矩阵 or 顶点-切线空间变换矩阵 #### 50 | 在Shader编程中,实际上从法线纹理中取出的法线坐标是该法线在当前顶点的切线空间下的坐标表示。要实际使用此法线,需要统一坐标空间进行运算。 51 | 52 | 可行的方案有两个,一是在切线空间下完成各类光照的计算,二是将得到的切线空间下的法线转变为世界空间坐标下的法线。 53 | 54 | 方案2更加灵活,在这里采用方案2来进行计算。 55 | 56 | 那么问题就转换为了,如何构建一个 **切线-世界空间变换矩阵** 来使得切线空间下的法线变换为世界坐标下的法线。下面按以下步骤构建变换矩阵。 57 | 58 | **一、** 已知切线空间的z轴(当前顶点的法线)、x轴(当前顶点的切线)、y轴(当前顶点的副切线)在世界坐标下的表示。 59 | 60 | **二、** 根据《Unity Shader 入门精要》4.6节的描述 可以将x轴、y轴、z轴的另一空间下的坐标表示按列或行摆放,形成变换矩阵。 61 | 62 | **那么究竟是按列摆放还是按行摆放呢?** 63 | 64 | ***冷静分析*** 65 | 66 | (T为切线空间,W为世界空间) 67 | 已知坐标空间T、W以及坐标空间T的3个坐标轴在坐标空间W下的表示Xt(世界空间的切线)、Yt(世界空间的副切线)、Zt(世界空间的法线),以及原点位置Ot。 68 | 69 | 给定一个点At = (a,b,c)在坐标空间T下的表示,求解点A在坐标空间W下的表示Aw。 70 | 71 | Aw = Ot + a * Xt + b * Yt + c * Zt 72 | 73 | 经过推导,其变换矩阵为: 74 | 75 | ![Avater](./Image/readmeImage/texture1.png) 76 | 77 | 故可以知道,将切线空间的x、y、z轴按列摆放即可形成变换矩阵。 78 | 79 | 分析规律,当要计算变换矩阵M(T→W)时,需要知道T的三个坐标轴在W空间下的表示。 80 | 81 | ***快速分析*** 82 | 83 | 当不知道构造变换矩阵要将x,y,z轴是按列摆放还是按行摆放时,一个快速的分析方法是,直接都试一遍。然后根据一个简单的验证方法验证这次尝试的变换矩阵是否正确。 84 | 85 | 这个验证方法就是,如果我们得到的变换矩阵(其切线-世界变换矩阵)是正确的,那么,使用这个变换矩阵去变换(1,0,0)(在T空间下x轴的坐标表示)一定会得到的值是[——Xt——],这是因为在W空间下,T空间x轴的坐标表示已经在前文给出,也就是[——Xt——]。 86 | 87 | 简单举个例子: 88 | 89 | 先尝试按行摆放来构造变换矩阵。 90 | 91 | ![Avater](./Image/readmeImage/texture2.png) 92 | 93 | 可以发现答案并不是预期的[——Xt——],说明此变换矩阵是不正确的。 94 | 95 | 在尝试按列摆放来构造变换矩阵。 96 | 97 | ![Avater](./Image/readmeImage/texture3.png) 98 | 99 | 可以发现结果正确,经过变换后的x轴坐标在世界坐标空间下表示为[——Xt——]。 100 | 101 | ## 渐变纹理 102 | 渐变纹理即一张一维的纹理,主要用来更加细微地控制物体的漫反射光照。 103 | 104 | 在实际使用中,一般使用半兰伯特光照模型的数学公式来对渐变纹理进行采样。表示式如下所示: 105 | 106 | 0.5*dot(worldNormal,worldLightDir)+0.5) 107 | 108 | ### 为什么要使用半兰伯特表达式进行采样 109 | 个人理解,这要从半兰伯特表示式的意义说起,此公式用于表示某点的辐照度(即光照强度),值域为[0,1]。 110 | 111 | 使用该公式来对纹理进行采样,就表示按照光照强度对一个一维纹理进行采样。 112 | 113 | 114 | -------------------------------------------------------------------------------- /Volume 02 Texture 纹理的应用/singleTexture.shader: -------------------------------------------------------------------------------- 1 | /* 2 | 简单运用纹理映射,也就是将一张纹理贴在物体上 3 | */ 4 | Shader "Volume 02/Texture/Single Texture" { 5 | Properties { 6 | // 主纹理 7 | _MainTex("Main Tex",2D) = "white" {} 8 | // 漫反射材质颜色 9 | _Diffuse("Diffuse Color",Color) = (1, 1, 1, 1) 10 | // 高光反射材质颜色 11 | _Specular("Specular Color",Color) = (1, 1, 1, 1) 12 | // 高光反射光泽度 13 | _Gloss("Gloss",Range(8.0,256)) = 20 14 | } 15 | SubShader { 16 | Tags { "RenderType"="Opaque" "Queue" = "Geometry" } 17 | Pass { 18 | // 设置渲染路径为前向渲染(使Unity正确填充光照变量) 19 | Tags { "LightMode" = "ForwardBase" } 20 | CGPROGRAM 21 | 22 | #pragma vertex vert 23 | #pragma fragment frag 24 | 25 | #include "Lighting.cginc" 26 | 27 | struct a2v{ 28 | float4 vertex : POSITION; 29 | float3 normal : NORMAL; 30 | float4 texcoord : TEXCOORD; 31 | }; 32 | struct v2f{ 33 | float4 pos : SV_POSITION; 34 | float2 uv : TEXCOORD0; 35 | float3 worldNormal : TEXCOORD1; 36 | float3 worldPos : TEXCOORD2; 37 | }; 38 | 39 | sampler2D _MainTex; 40 | float4 _MainTex_ST; // Scale 和 Translation 纹理的缩放和位移 41 | fixed4 _Diffuse; 42 | fixed4 _Specular; 43 | float _Gloss; 44 | 45 | v2f vert(a2v v){ 46 | v2f o; 47 | o.pos = UnityObjectToClipPos(v.vertex); 48 | o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw; // 将当前uv坐标应用纹理的缩放和位移设置 49 | o.worldNormal = UnityObjectToWorldNormal(v.normal); 50 | o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz; 51 | 52 | return o; 53 | } 54 | 55 | fixed4 frag(v2f i) : SV_TARGET{ 56 | // 对纹理进行采样,获得反射率 57 | fixed4 albedo = tex2D(_MainTex,i.uv) * _Diffuse; 58 | // 环境光 59 | fixed4 ambient = UNITY_LIGHTMODEL_AMBIENT * albedo; 60 | 61 | // 归一化法线 62 | fixed3 worldNormal = normalize(i.worldNormal); 63 | // 获得光源方向 64 | fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); 65 | // 计算漫反射光照 66 | fixed3 diffuse = albedo.rgb * max(0,dot(worldNormal,worldLightDir)); 67 | // 获得观察方向 68 | fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos)); 69 | // 计算Blinn模型的half变量,避免计算反射方向 70 | fixed3 halfDir = normalize(worldViewDir+worldLightDir); 71 | // 计算高光反射 72 | fixed3 specular = _Specular.rgb * pow(max(0,dot(worldNormal,halfDir)),_Gloss); 73 | 74 | // 着色 75 | return fixed4(ambient.rgb + diffuse + specular,1.0f); 76 | } 77 | 78 | ENDCG 79 | } 80 | } 81 | FallBack "Diffuse" 82 | 83 | } -------------------------------------------------------------------------------- /Volume 03 Alpha 透明效果/AlphaBlend.shader: -------------------------------------------------------------------------------- 1 | // 透明度混合 2 | Shader "Volume 03/Alpha/Alpha Blend" { 3 | Properties { 4 | _MainTex("Main Texture",2D) = "white" {} 5 | _Color("Color Tint",Color) = (1, 1, 1, 1) 6 | // 透明度 7 | _Alpha("Alpha Value",Range(0,1)) = 0.5 8 | } 9 | SubShader { 10 | Tags { "RenderType"="Transparent" "Queue"="Transparent" "IgnoreProjector"="True" } 11 | 12 | // 关闭深度写入 13 | ZWrite Off 14 | // 根据透明度进行混合 15 | Blend SrcAlpha OneMinusSrcAlpha 16 | 17 | Pass { 18 | // 设置渲染路径 19 | Tags { "LightMode" = "ForwardBase" } 20 | 21 | CGPROGRAM 22 | 23 | #include "UnityCG.cginc" 24 | #include "Lighting.cginc" 25 | 26 | #pragma vertex vert 27 | #pragma fragment frag 28 | 29 | sampler2D _MainTex; 30 | float4 _MainTex_ST; 31 | fixed4 _Color; 32 | fixed _Alpha; 33 | 34 | struct a2v{ 35 | float4 vertex : POSITION; 36 | float3 normal : NORMAL; 37 | float2 texcoord : TEXCOORD0; 38 | }; 39 | 40 | struct v2f{ 41 | float4 pos : SV_POSITION; 42 | float3 worldNormal : TEXCOORD0; 43 | float3 worldPos : TEXCOORD1; 44 | float2 uv : TEXCOORD2; 45 | }; 46 | 47 | v2f vert(a2v v){ 48 | v2f o; 49 | o.pos = UnityObjectToClipPos(v.vertex); 50 | o.worldNormal = UnityObjectToWorldNormal(v.normal); 51 | o.worldPos = mul(unity_ObjectToWorld,v.vertex); 52 | o.uv = TRANSFORM_TEX(v.texcoord,_MainTex); 53 | return o; 54 | } 55 | 56 | fixed4 frag(v2f i) : SV_TARGET{ 57 | // 归一化法线 58 | fixed3 worldNormal = normalize(i.worldNormal); 59 | // 归一化光源方向 60 | fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); 61 | 62 | // 计算反射率 63 | fixed4 albedo = _Color * tex2D(_MainTex,i.uv); 64 | 65 | // 计算漫反射光照 66 | fixed4 diffuse = _LightColor0 * albedo * max(0,dot(worldLightDir,worldNormal)); 67 | 68 | return fixed4(diffuse.rgb,_Alpha); 69 | } 70 | 71 | ENDCG 72 | } 73 | } 74 | FallBack "Diffuse" 75 | 76 | } -------------------------------------------------------------------------------- /Volume 03 Alpha 透明效果/AlphaT e s t.shader: -------------------------------------------------------------------------------- 1 | // 透明度测试 2 | Shader "Volume 03/Alpha/Alpha Test" { 3 | Properties { 4 | _Color("Color",Color) = (1, 1, 1, 1) 5 | _MainTex("Main Texture",2D) = "white" {} 6 | _CutoOff("Alpha CutoOff",Range(0,1)) = 0.5 7 | 8 | } 9 | SubShader { 10 | Tags { "Queue"="AlphaTest" "RenderType"="TransparentCutout" } 11 | 12 | // 双面渲染 13 | Cull Off 14 | 15 | Pass { 16 | Tags { "LightMode" = "ForwardBase" } 17 | CGPROGRAM 18 | 19 | #include "UnityCG.cginc" 20 | #include "Lighting.cginc" 21 | 22 | #pragma vertex vert 23 | #pragma fragment frag 24 | 25 | fixed4 _Color; 26 | sampler2D _MainTex; 27 | float4 _MainTex_ST; 28 | fixed _CutoOff; 29 | 30 | struct a2v{ 31 | float4 vertex : POSITION; 32 | float2 texcoord : TEXCOORD0; 33 | float3 normal : NORMAL; 34 | }; 35 | struct v2f{ 36 | float4 pos : SV_POSITION; 37 | float2 uv : TEXCOORD0; 38 | float3 worldNormal : TEXCOORD1; 39 | float3 worldPos : TEXCOORD2; 40 | }; 41 | 42 | v2f vert(a2v i){ 43 | v2f o; 44 | 45 | o.pos = UnityObjectToClipPos(i.vertex); 46 | o.uv = TRANSFORM_TEX(i.texcoord,_MainTex); 47 | o.worldNormal = UnityObjectToWorldNormal(i.normal); 48 | o.worldPos = mul(unity_ObjectToWorld,i.vertex); 49 | 50 | return o; 51 | } 52 | 53 | fixed4 frag(v2f i) : SV_TARGET{ 54 | // 归一化法线 55 | fixed3 worldNormal = normalize(i.worldNormal); 56 | // 获得光源方向 57 | fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); 58 | // 采样纹理 59 | fixed4 albedo = tex2D(_MainTex,i.uv) * _Color; 60 | 61 | // 根据图片的透明度进行透明度测试,图片透明度大于阈值则不进行裁剪 62 | clip(albedo.a - _CutoOff); 63 | 64 | // 计算漫反射光照 65 | fixed4 diffuse = albedo * _LightColor0 * max(0,dot(worldNormal,worldLightDir)); 66 | 67 | return diffuse; 68 | } 69 | 70 | ENDCG 71 | } 72 | } 73 | FallBack "Diffuse" 74 | 75 | } -------------------------------------------------------------------------------- /Volume 03 Alpha 透明效果/Image/readmeImage/AlphaBlend1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 03 Alpha 透明效果/Image/readmeImage/AlphaBlend1.png -------------------------------------------------------------------------------- /Volume 03 Alpha 透明效果/Image/readmeImage/AlphaBlend2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 03 Alpha 透明效果/Image/readmeImage/AlphaBlend2.png -------------------------------------------------------------------------------- /Volume 03 Alpha 透明效果/Image/readmeImage/AlphaBlend3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 03 Alpha 透明效果/Image/readmeImage/AlphaBlend3.png -------------------------------------------------------------------------------- /Volume 03 Alpha 透明效果/Image/readmeImage/AlphaBlend4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 03 Alpha 透明效果/Image/readmeImage/AlphaBlend4.png -------------------------------------------------------------------------------- /Volume 03 Alpha 透明效果/Image/readmeImage/AlphaBlend5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 03 Alpha 透明效果/Image/readmeImage/AlphaBlend5.png -------------------------------------------------------------------------------- /Volume 03 Alpha 透明效果/readme.md: -------------------------------------------------------------------------------- 1 | 2 | - [《Unity Shader 入门精要》读书笔记 之 透明效果](#unity-shader-入门精要读书笔记-之-透明效果) 3 | - [参考资料](#参考资料) 4 | - [透明效果](#透明效果) 5 | - [透明度测试](#透明度测试) 6 | - [透明度混合](#透明度混合) 7 | - [深度缓冲](#深度缓冲) 8 | - [深度写入](#深度写入) 9 | - [为什么半透明效果要关闭深度写入](#为什么半透明效果要关闭深度写入) 10 | - [渲染顺序](#渲染顺序) 11 | - [颜色混合(Blend)](#颜色混合blend) 12 | - [实现透明度混合的关键步骤](#实现透明度混合的关键步骤) 13 | 14 | # 《Unity Shader 入门精要》读书笔记 之 透明效果 # 15 | ## 参考资料 ## 16 | > 《Unity Shader 入门精要》 17 | ## 透明效果 18 | 在Unity中,分为两类透明效果,分别是类似镂空效果的**透明度测试**和半透明效果的**透明度混合**。 19 | ### 透明度测试 20 | 透明度测试应该可以说是伪·透明效果,因为他不能实现半透明效果,而只是对于片元进行选择性裁剪。 21 | 22 | 透明度测试的关键函数如下: 23 | 24 | clip(x) 25 | 26 | 当x < 0时,表示裁剪该片元,其原理是应用了discard关键字,上述函数可以还原成下面的代码: 27 | 28 | if(x<0) 29 | discard; 30 | 31 | ### 透明度混合 32 | 透明度混合效果实现较为复杂,用于实现半透明效果。 33 | 34 | 其实现涉及到5个概念,深度缓冲(Z-Buff)、深度写入(ZWrite)、渲染顺序(Queue)和颜色混合(Blend)和颜色缓冲。 35 | 36 | #### 深度缓冲 37 | 深度缓冲用于解决物体的可见性问题。 38 | 39 | 基于深度缓冲,对于不透明物体,不需要关注他们的渲染顺序也能将其正确的渲染到屏幕。 40 | 41 | 其原理是: 42 | 43 | > 当渲染一个片元时,需要把他的深度值和已经存在于深度缓冲中的值进行比较。 44 | > 如果他的值距离摄像机更远,那么说明这个片元不应该被渲染到屏幕上(说明有物体挡住了它);否则,此片元应该覆盖掉此时颜色缓冲中的像素值,并把它的深度值更新到深度缓冲中 45 | 46 | #### 深度写入 47 | 深度写入用于更新深度缓冲。 48 | 49 | 每次渲染一个片元的时候,将该片元深度值和深度缓冲中的值进行比较,如果当前片元离摄像机更近,那么就进行**深度写入**,将该片元的深度值更新到深度缓冲中。(这样,在深度缓冲中这个像素位置的深度值就被更新到了一个离摄像机更近的值) 50 | 51 | #### 为什么半透明效果要关闭深度写入 52 | 首先,我们是可以通过一个半透明物体看到它后面的物体的,如果没有关闭深度写入,那么在渲染半透明物体时,因为半透明物体距离摄像机近,而它后面的物体离摄像机远,所以它后面的物体会因为深度测试而被剔除,会出现半透明物体挡住了后面的物体的情况,如下图所示。 53 | 54 | ![Avater](./Image/readmeImage/AlphaBlend5.png) 55 | 56 | 只有对半透明物体关闭**深度写入**才能避免这个情况。 57 | 58 | > 但是关闭深度写入就破坏了深度缓冲的工作机制,此时渲染顺序就会变得十分重要。 59 | 60 | #### 渲染顺序 61 | 对于不透明物体,不用关注它们的渲染顺序,依靠深度缓冲,也能将他们按照正确的摆放顺序渲染到场景中。 62 | 63 | 但是,对于透明物体,渲染顺序就十分重要了,因为半透明物体的渲染需要关闭**深度写入**。 64 | 65 | **半透明物体与不透明物体的渲染顺序的问题** 66 | 67 | 举个例子,物体A(绿色立方体)是半透明物体,物体B(蓝色球体)是不透明物体,其摆放顺序如下图所示(A在前,B在后)。 68 | 69 | ![Avater](./Image/readmeImage/AlphaBlend2.png) 70 | 71 | **先渲染A物体** 72 | 73 | 如果先渲染A物体,因为A物体是半透明物体(深度写入已关闭),所以此时深度缓冲没有被更新,颜色缓冲被A物体写入,再渲染B物体,此时因为深度缓冲还没有任何有效数据,所以B物体(不透明物体,没有开启混合指令)直接写入深度缓冲和颜色缓冲(直接覆盖掉原本颜色缓冲中的值)。 74 | 75 | 最后的效果看起来就好像B物体(Queue=2000)在A物体(Queue=1999)前面一样,效果如下图所示。 76 | 77 | ![Avater](./Image/readmeImage/AlphaBlend1.png) 78 | 79 | **先渲染B物体** 80 | 81 | 如果先渲染B物体,B物体写入深度缓冲和颜色缓冲,再渲染A物体,因为A物体关闭了深度写入而没有关闭深度测试,所以它扔需要与深度缓冲中的值进行比较,发现A物体离摄像机更近,渲染A物体(根据A的透明度和颜色缓冲中B的颜色进行混合)。这样得到的就是正确的半透明效果了。 82 | 83 | **半透明物体与半透明物体的渲染顺序的问题** 84 | 85 | 半透明与不透明物体的渲染顺序错误会造成错误的结果。事实上,半透明与半透明之间的渲染顺序也同样重要。 86 | 87 | 两个半透明物体在场景中摆放顺序如下,物体A为绿色立方体,物体B为蓝色球体(A在前,B在后)。 88 | 89 | ![Avater](./Image/readmeImage/AlphaBlend3.png) 90 | 91 | **先渲染A物体** 92 | 93 | 如果先渲染A物体,因为目前深度缓冲中还没有有效数据,所以直接写入颜色缓冲,再渲染B物体,同样因为目前深度缓冲中还没有有效数据,所以直接写入颜色缓冲(同时与前面A物体所写入的颜色缓冲进行混合)。 94 | 95 | 其渲染效果就好像B物体在A物体前面一样,如下图所示。 96 | ![Avater](./Image/readmeImage/AlphaBlend4.png) 97 | 98 | **先渲染B物体** 99 | 100 | 如果先渲染B物体,因为目前深度缓冲中还没有有效数据,所以直接写入颜色缓冲,再渲染A物体,同样因为目前深度缓冲中还没有有效数据,所以直接写入颜色缓冲(同时与前面B物体所写入的颜色缓冲进行混合)。这样得到的就是 101 | 102 | **总结** 103 | 104 | 根据前面所述,因为透明度混合关闭了深度写入,所以渲染顺序变得极其重要,一般来说,规律如下: 105 | 106 | 1. 先渲染所有不透明物体 107 | 2. 根据半透明物体离摄像机的远近进行排序,先渲染离摄像机远的物体 108 | 109 | #### 颜色混合(Blend) 110 | 仔细思考,之所以可以判断一个物体是透明物体是因为它的什么特征? 111 | 112 | **个人认为**,之所以可以判断一个物体是透明物体,是因为可以透过这个物体观察它后面的情况,所以如果要渲染一个透明物体到屏幕上,就一定要知道在透明物体出现之前屏幕这片区域的颜色是怎么样的。 113 | 114 | 这就用到了颜色缓冲和混合指令,通常来说,可以根据物体的透明度来对物体本身颜色和当前像素区域颜色缓冲区的颜色进行混合,从而得到该透明物体的颜色。这就是颜色混合指令的作用。 115 | 116 | >ShaderLab的Blend指令表(来自《Unity Shader入门精要》一书) 117 | 118 | 语义 | 描述 119 | ---|--- 120 | Blned Off | 关闭混合 121 | Blend SrcFactor DstFactor | 开启混合,并设置混合因子。最终颜色为 源颜色(该片元的颜色)*SrcFactor + 目标颜色(颜色缓冲区的颜色)*DstFactor 122 | Blend SrcFactor DstFactor,SrcFactorA DstFactorA | 和上面一样,但是使用不同的因子(SrcFactorA和DstFactorA)来混合透明通道 123 | BlendOp BlendOperation | 并非是把源颜色和目标颜色相加后混合,而是使用BlendOperation对他们进行其他操作(乘、减、除) 124 | 125 | #### 实现透明度混合的关键步骤 126 | 127 | 透明度混合用于实现半透明效果,实现透明度混合需要经过下面三个关键步骤。 128 | 129 | 1. 设置渲染顺序 130 | 2. 关闭深度写入 131 | 3. 开启混合指令(Blend) 132 | 133 | -------------------------------------------------------------------------------- /Volume 04 ForwardRender渲染路径、阴影/ForwardRendering.shader: -------------------------------------------------------------------------------- 1 | // Upgrade NOTE: replaced '_LightMatrix0' with 'unity_WorldToLight' 2 | 3 | // Upgrade NOTE: replaced '_LightMatrix0' with 'unity_WorldToLight' 4 | 5 | 6 | // 前向渲染 7 | Shader "Volume 03/Forward Render/Forward Rendering" { 8 | Properties { 9 | _Color("Color Tint",Color) = (1, 1, 1, 1) 10 | _Specular("Specular Color",Color) = (1, 1, 1, 1) 11 | _Gloss("Gloss",Range(8.0,256)) = 25 12 | } 13 | SubShader { 14 | Tags { "RenderType"="Opaque" "Queue"="Geometry" } 15 | 16 | // BasePass 17 | Pass { 18 | Tags { "LightMode"="ForwardBase" } 19 | CGPROGRAM 20 | 21 | #pragma multi_compile_fwdbase 22 | 23 | #include "UnityCG.cginc" 24 | #include "Lighting.cginc" 25 | #include "AutoLight.cginc" 26 | 27 | #pragma vertex vert 28 | #pragma fragment frag 29 | 30 | fixed4 _Color; 31 | fixed4 _Specular; 32 | float _Gloss; 33 | 34 | struct a2v{ 35 | float4 vertex : POSITION; 36 | float3 normal : NORMAL; 37 | }; 38 | struct v2f{ 39 | float4 pos : SV_POSITION; 40 | float3 worldNormal : TEXCOORD0; 41 | float3 worldPos : TEXCOORD1; 42 | 43 | // 存储阴影映射纹理的数据结构 44 | SHADOW_COORDS(2) 45 | }; 46 | 47 | v2f vert(a2v v){ 48 | v2f o; 49 | o.pos = UnityObjectToClipPos(v.vertex); 50 | o.worldNormal = UnityObjectToWorldNormal(v.normal); 51 | o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz; 52 | 53 | // 填充阴影映射纹理 54 | TRANSFER_SHADOW(o); 55 | 56 | return o; 57 | } 58 | 59 | fixed4 frag(v2f i) : SV_TARGET{ 60 | fixed3 worldNormal = normalize(i.worldNormal); 61 | fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); 62 | 63 | fixed4 diffuse = _Color * _LightColor0 * max(0,dot(worldLightDir,worldNormal)); 64 | 65 | fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb; 66 | 67 | fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos)); 68 | 69 | fixed3 halfDir = normalize(worldViewDir+worldLightDir); 70 | 71 | fixed4 specular = _LightColor0 * _Specular * pow(max(0,dot(worldNormal,halfDir)),_Gloss); 72 | 73 | // 对阴影映射纹理进行采样 74 | fixed shadow = SHADOW_ATTENUATION(i); 75 | 76 | return fixed4(ambient+diffuse.rgb*shadow+specular.rgb,1); 77 | } 78 | 79 | ENDCG 80 | } 81 | 82 | // Additional Pass 83 | Pass { 84 | Tags { "LightMode" = "ForwardAdd" } 85 | 86 | Blend One One 87 | 88 | CGPROGRAM 89 | 90 | #include "UnityCG.cginc" 91 | #include "Lighting.cginc" 92 | #include "AutoLight.cginc" 93 | 94 | #pragma multi_compile_fwdadd 95 | 96 | #pragma vertex vert 97 | #pragma fragment frag 98 | 99 | fixed4 _Color; 100 | fixed4 _Specular; 101 | float _Gloss; 102 | 103 | struct a2v{ 104 | float4 vertex : POSITION; 105 | float3 normal : NORMAL; 106 | }; 107 | struct v2f{ 108 | float4 pos : SV_POSITION; 109 | float3 worldNormal : TEXCOORD0; 110 | float3 worldPos : TEXCOORD1; 111 | }; 112 | 113 | v2f vert(a2v v){ 114 | v2f o; 115 | o.pos = UnityObjectToClipPos(v.vertex); 116 | o.worldNormal = UnityObjectToWorldNormal(v.normal); 117 | o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz; 118 | 119 | return o; 120 | } 121 | 122 | fixed4 frag(v2f i) : SV_TARGET{ 123 | fixed3 worldNormal = normalize(i.worldNormal); 124 | // fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); 125 | 126 | #ifdef USING_DIRECTIONAL_LIGHT 127 | fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); 128 | #else 129 | fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz); 130 | #endif 131 | 132 | fixed4 diffuse = _Color * _LightColor0 * max(0,dot(worldLightDir,worldNormal)); 133 | 134 | #ifdef USING_DIRECTIONAL_LIGHT 135 | fixed atten = 1.0; 136 | #else 137 | #if defined (POINT) 138 | float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz; 139 | fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL; 140 | #elif defined (SPOT) 141 | float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)); 142 | fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL; 143 | #else 144 | fixed atten = 1.0; 145 | #endif 146 | #endif 147 | 148 | fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb; 149 | 150 | // fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos)); 151 | 152 | fixed3 worldViewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz); 153 | 154 | fixed3 halfDir = normalize(worldViewDir+worldLightDir); 155 | 156 | fixed4 specular = _LightColor0 * _Specular * pow(max(0,dot(worldNormal,halfDir)),_Gloss); 157 | 158 | return fixed4((diffuse.rgb+specular.rgb)*atten,1); 159 | } 160 | 161 | ENDCG 162 | } 163 | } 164 | FallBack "Diffuse" 165 | 166 | 167 | } -------------------------------------------------------------------------------- /Volume 04 ForwardRender渲染路径、阴影/Image/readmeImage/RenderingPath0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 04 ForwardRender渲染路径、阴影/Image/readmeImage/RenderingPath0.gif -------------------------------------------------------------------------------- /Volume 04 ForwardRender渲染路径、阴影/Image/readmeImage/RenderingPath2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 04 ForwardRender渲染路径、阴影/Image/readmeImage/RenderingPath2.png -------------------------------------------------------------------------------- /Volume 05 CubeMap立方体纹理/FireDistort.shader: -------------------------------------------------------------------------------- 1 | // 利用GrabPass实现火焰的热浪扭曲屏幕的效果 2 | Shader "Volume 05/GrabPass/Fire Distrotion" { 3 | Properties { 4 | // 扭曲采样方向的噪声图 5 | _NoiseTex("Noise Texture",2D) = "white" {} 6 | } 7 | SubShader { 8 | // GrabPass谨记要放到所有不透明物体之后渲染 9 | Tags {"Queue"="Transparent" "RenderType"="Opaque"} 10 | 11 | // 把屏幕图像扔到_ScreenTex里去 12 | GrabPass{ "_ScreenTex" } 13 | 14 | Pass { 15 | 16 | CGPROGRAM 17 | #include "UnityCG.cginc" 18 | 19 | #pragma vertex vert 20 | #pragma fragment frag 21 | 22 | sampler2D _NoiseTex; 23 | sampler2D _ScreenTex; 24 | 25 | struct a2v{ 26 | float4 vertex : POSITION; 27 | float2 texcoord : TEXCOORD0; 28 | }; 29 | 30 | struct v2f{ 31 | float4 pos : SV_POSITION; 32 | float2 uv : TEXCOORD0; 33 | float4 screenPos : TEXCOORD1; 34 | }; 35 | 36 | v2f vert(a2v v){ 37 | v2f o; 38 | 39 | o.pos = UnityObjectToClipPos(v.vertex); 40 | o.uv = v.texcoord; 41 | o.screenPos = ComputeGrabScreenPos(o.pos); 42 | 43 | return o; 44 | } 45 | 46 | fixed4 frag(v2f i) : SV_TARGET{ 47 | // 向噪声图采样 48 | fixed3 noise = tex2D(_NoiseTex,i.uv + _Time.xy); 49 | 50 | float2 scrPos = i.screenPos.xy/i.screenPos.w; 51 | 52 | // 根据噪声对uv进行偏移,并对屏幕坐标进行采样 53 | fixed3 color = tex2D(_ScreenTex,scrPos-noise*0.02); 54 | 55 | return fixed4(color,1.0); 56 | } 57 | 58 | ENDCG 59 | } 60 | } 61 | FallBack "Diffuse" 62 | 63 | } -------------------------------------------------------------------------------- /Volume 05 CubeMap立方体纹理/Fresnel.shader: -------------------------------------------------------------------------------- 1 | // 基于CubeMap模拟菲涅尔反射 2 | Shader "Volume 05/Cube Map/Fresnel" { 3 | Properties { 4 | _Color("Color Tint",Color) = (1, 1, 1, 1) 5 | // 菲涅尔系数,用于控制菲涅尔反射的强度 6 | _FresnelScale("Fresnel Scale",Range(0,1)) = 0.5 7 | _CubeMap("Cube Map",Cube) = "_Skybox" {} 8 | } 9 | SubShader { 10 | Tags { "RenderType"="Opaque" "Queue"="Geometry" } 11 | Pass { 12 | Tags { "LightMode"="ForwardBase" } 13 | CGPROGRAM 14 | 15 | #include "UnityCG.cginc" 16 | #include "Lighting.cginc" 17 | 18 | #pragma vertex vert 19 | #pragma fragment frag 20 | 21 | fixed4 _Color; 22 | fixed _FresnelScale; 23 | samplerCUBE _CubeMap; 24 | 25 | struct a2v{ 26 | float4 vertex : POSITION; 27 | float3 normal : NORMAL; 28 | }; 29 | 30 | struct v2f{ 31 | float4 pos : SV_POSITION; 32 | float3 worldNormal : TEXCOORD0; 33 | float3 worldPos : TEXCOORD1; 34 | }; 35 | 36 | v2f vert(a2v v){ 37 | v2f o; 38 | o.pos = UnityObjectToClipPos(v.vertex); 39 | o.worldNormal = UnityObjectToWorldNormal(v.normal); 40 | o.worldPos = mul(unity_ObjectToWorld,v.vertex); 41 | 42 | return o; 43 | } 44 | 45 | fixed4 frag(v2f i) : SV_TARGET{ 46 | // 计算归一化法线/视角方向/光源方向 47 | fixed3 worldNormal = normalize(i.worldNormal); 48 | fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos)); 49 | fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); 50 | 51 | // 计算视角方向基于法线的反射方向 52 | float3 reflection = reflect(-worldViewDir,worldNormal); 53 | 54 | // 根据反射方向对CubeMap进行采样 55 | fixed3 refelctionColor = texCUBE(_CubeMap,reflection).rgb; 56 | 57 | // 计算菲涅尔系数 58 | fixed fresnel = _FresnelScale + (1-_FresnelScale)*pow(1-dot(worldViewDir,worldNormal),5); 59 | 60 | // 计算漫反射光照 61 | fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0,dot(worldLightDir,worldNormal)); 62 | 63 | fixed3 finalColor = lerp(diffuse,refelctionColor,saturate(fresnel)); 64 | // fixed3 finalColor = (diffuse + refelctionColor)*fresnel; 65 | // fixed3 finalColor = diffuse + (refelctionColor*fresnel); 66 | 67 | return fixed4(finalColor,1.0); 68 | } 69 | 70 | ENDCG 71 | } 72 | } 73 | FallBack "Diffuse" 74 | 75 | } -------------------------------------------------------------------------------- /Volume 05 CubeMap立方体纹理/GlassRefraction.shader: -------------------------------------------------------------------------------- 1 | // 使用CubeMap和GrapPass制作玻璃效果 2 | Shader "Volume 05/Cube Map/GlassRefraction" { 3 | Properties { 4 | _MainTex("Main Texture",2D) = "white" {} 5 | // 法线纹理 6 | _BumpMap("Bump Map",2D) = "bump" {} 7 | // 立方体纹理,用于反射 8 | _CubeMap("Cube Map",Cube) = "_Skybox" {} 9 | // 折射时的扰动程度 10 | _Distorition("Distortion",Range(0,100)) = 10 11 | // 折射程度 12 | _RefracAmount("Refraction Amount",Range(0,1)) = 1.0 13 | } 14 | SubShader { 15 | // 因为要使用GrapPass,所以必须在所有不透明物体之后渲染 16 | Tags{ "Queue"="Transparent" "RenderType"="Opaque"} 17 | 18 | // GrabPass,用于对当前屏幕图像进行截图, 19 | // 并把图片输出到一张纹理中去 20 | GrabPass{ "_RefractionTex" } 21 | 22 | Pass { 23 | 24 | Tags{ "LightMode" = "ForwardBase"} 25 | 26 | CGPROGRAM 27 | 28 | #include "UnityCG.cginc" 29 | 30 | #pragma vertex vert 31 | #pragma fragment frag 32 | 33 | sampler2D _MainTex; 34 | float4 _MainTex_ST; 35 | sampler2D _BumpMap; 36 | float4 _BumpMap_ST; 37 | samplerCUBE _CubeMap; 38 | float _Distorition; 39 | fixed _RefracAmount; 40 | sampler2D _RefractionTex; 41 | float4 _RefractionTex_TexelSize; 42 | 43 | struct a2v{ 44 | float4 vertex : POSITION; 45 | float3 normal : NORMAL; 46 | float4 tangent : TANGENT; 47 | float2 texcoord : TEXCOORD0; 48 | }; 49 | 50 | struct v2f{ 51 | float4 pos : SV_POSITION; 52 | 53 | // 切线-世界 变换矩阵构造,w分量存worldPos 54 | float4 TtoW0 : TEXCOORD0; 55 | float4 TtoW1 : TEXCOORD1; 56 | float4 TtoW2 : TEXCOORD2; 57 | 58 | // 对主纹理和法线纹理进行采样的uv,xy:主纹理,zw:法线纹理 59 | float4 uv : TEXCOORD3; 60 | 61 | // 当前顶点在整个屏幕中的位置,用于对GrabPass得到的屏幕图像进行采样,该坐标未进行齐次除法 62 | float4 screenPos : TEXCOORD4; 63 | }; 64 | 65 | v2f vert(a2v v){ 66 | v2f o; 67 | o.pos = UnityObjectToClipPos(v.vertex); 68 | 69 | float3 worldPos = mul(unity_ObjectToWorld,v.vertex).xyz; 70 | float3 worldNormal = UnityObjectToWorldNormal(v.normal); 71 | float3 worldTangent = UnityObjectToWorldDir(v.tangent); 72 | // 计算世界坐标下的副切线(根据法线与切线的叉积得到)(根据切线的w分量确定副切线的方向) 73 | float3 worldBinTangent = cross(worldNormal,worldTangent) * v.tangent.w; 74 | 75 | // 构造变换矩阵 76 | o.TtoW0 = float4(worldTangent.x,worldBinTangent.x,worldNormal.x,worldPos.x); 77 | o.TtoW1 = float4(worldTangent.y,worldBinTangent.y,worldNormal.y,worldPos.y); 78 | o.TtoW2 = float4(worldTangent.z,worldBinTangent.z,worldNormal.z,worldPos.z); 79 | 80 | // 获得主纹理和法线纹理的采样坐标 81 | o.uv.xy = TRANSFORM_TEX(v.texcoord,_MainTex); 82 | o.uv.zw = TRANSFORM_TEX(v.texcoord,_BumpMap); 83 | 84 | // 得到当前顶点在整个屏幕中的位置,输入参数是裁剪空间中的顶点坐标 85 | o.screenPos = ComputeGrabScreenPos(o.pos); 86 | 87 | return o; 88 | } 89 | 90 | fixed4 frag(v2f i) : SV_TARGET{ 91 | float3 worldPos = float3(i.TtoW0.w,i.TtoW1.w,i.TtoW2.w); 92 | 93 | // 取出法线纹理中的法线(此时法线在切线空间中) 94 | float3 normal = UnpackNormal(tex2D(_BumpMap,i.uv.zw)); 95 | 96 | // 计算ScreenPos因为折射产生的偏移,通过切线空间下的法线来计算 97 | float2 offset = normal.xy * _Distorition * _RefractionTex_TexelSize.xy; 98 | i.screenPos.xy = i.screenPos.xy+offset; 99 | // 计算折射产生的颜色 100 | fixed3 refractColor = tex2D(_RefractionTex,i.screenPos.xy/i.screenPos.w).rgb; 101 | 102 | // 将法线变换到世界坐标空间下,并将其归一化 103 | fixed3 worldNormal = normalize(float3(dot(normal,i.TtoW0.xyz),dot(normal,i.TtoW1.xyz),dot(normal,i.TtoW2.xyz))); 104 | 105 | // 计算视角方向 106 | fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos)); 107 | // 计算光源方向 108 | fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(worldPos)); 109 | // 计算视角方向关于法线的反射方向 110 | float3 reflection = reflect(-worldViewDir,worldNormal); 111 | 112 | // 计算反射产生的颜色 113 | fixed3 reflectColor = texCUBE(_CubeMap,reflection).rgb; 114 | 115 | fixed3 texColor = tex2D(_MainTex,i.uv.xy); 116 | 117 | reflectColor *= texColor; 118 | 119 | fixed3 finalColor = lerp(reflectColor,refractColor*texColor,_RefracAmount); 120 | // fixed3 finalColor = reflectColor * refractColor * _RefracAmount; 121 | 122 | return fixed4(finalColor,1.0); 123 | } 124 | 125 | ENDCG 126 | } 127 | } 128 | FallBack "Diffuse" 129 | 130 | } -------------------------------------------------------------------------------- /Volume 05 CubeMap立方体纹理/Image/readmeImage/CubeMap0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 05 CubeMap立方体纹理/Image/readmeImage/CubeMap0.png -------------------------------------------------------------------------------- /Volume 05 CubeMap立方体纹理/Image/readmeImage/CubeMap1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 05 CubeMap立方体纹理/Image/readmeImage/CubeMap1.png -------------------------------------------------------------------------------- /Volume 05 CubeMap立方体纹理/Image/readmeImage/CubeMap3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 05 CubeMap立方体纹理/Image/readmeImage/CubeMap3.png -------------------------------------------------------------------------------- /Volume 05 CubeMap立方体纹理/Image/readmeImage/CubeMap4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 05 CubeMap立方体纹理/Image/readmeImage/CubeMap4.png -------------------------------------------------------------------------------- /Volume 05 CubeMap立方体纹理/Image/readmeImage/Fire.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 05 CubeMap立方体纹理/Image/readmeImage/Fire.gif -------------------------------------------------------------------------------- /Volume 05 CubeMap立方体纹理/Image/readmeImage/FireNoDis.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 05 CubeMap立方体纹理/Image/readmeImage/FireNoDis.gif -------------------------------------------------------------------------------- /Volume 05 CubeMap立方体纹理/Mirror.shader: -------------------------------------------------------------------------------- 1 | // 用RT模拟镜面特效 2 | Shader "Volume 05/Render Texture/Mirror" { 3 | Properties { 4 | // RT图 5 | _MainTex("Main Texture",2D) = "white" {} 6 | } 7 | SubShader { 8 | Pass { 9 | CGPROGRAM 10 | 11 | #include "UnityCG.cginc" 12 | 13 | #pragma vertex vert 14 | #pragma fragment frag 15 | 16 | sampler2D _MainTex; 17 | 18 | struct v2f{ 19 | float4 pos : SV_POSITION; 20 | fixed2 uv : TEXCOORD0; 21 | }; 22 | 23 | v2f vert(appdata_img v){ 24 | v2f o; 25 | o.pos = UnityObjectToClipPos(v.vertex); 26 | o.uv = v.texcoord; 27 | return o; 28 | } 29 | 30 | fixed4 frag(v2f i) : SV_TARGET{ 31 | // 镜面对称 32 | fixed2 uv = fixed2(1-i.uv.x,i.uv.y); 33 | return tex2D(_MainTex,uv); 34 | } 35 | 36 | ENDCG 37 | } 38 | } 39 | FallBack "Diffuse" 40 | 41 | } -------------------------------------------------------------------------------- /Volume 05 CubeMap立方体纹理/Reflection.shader: -------------------------------------------------------------------------------- 1 | // 基于CubeMap实现反射效果 2 | Shader "Volume 05/Cube Map/Reflection" { 3 | Properties { 4 | _Color("Color Tint",Color) = (1, 1, 1, 1) 5 | _ReflectColor("Reflection Color",Color) = (1, 1, 1, 1) 6 | // 反射程度 7 | _ReflectAmount("Reflection Amount",Range(0,1)) = 1 8 | _CubeMap("Cube Map",Cube) = "_Skybox" {} 9 | } 10 | SubShader { 11 | Tags{ "Queue"="Geometry" "RenderType"="Opaque" } 12 | Pass { 13 | Tags{ "LightMode" = "ForwardBase" } 14 | CGPROGRAM 15 | 16 | #include "UnityCG.cginc" 17 | #include "Lighting.cginc" 18 | 19 | #pragma vertex vert 20 | #pragma fragment frag 21 | 22 | fixed4 _Color; 23 | fixed4 _ReflectColor; 24 | fixed _ReflectAmount; 25 | samplerCUBE _CubeMap; 26 | 27 | struct a2v{ 28 | float4 vertex : POSITION; 29 | float3 normal : NORMAL; 30 | }; 31 | struct v2f{ 32 | float4 pos : SV_POSITION; 33 | float3 worldNormal : TEXCOORD0; 34 | float3 worldPos : TEXCOORD1; 35 | }; 36 | 37 | 38 | v2f vert(a2v v){ 39 | v2f o; 40 | o.pos = UnityObjectToClipPos(v.vertex); 41 | o.worldNormal = UnityObjectToWorldNormal(v.normal); 42 | o.worldPos = mul(unity_ObjectToWorld,v.vertex); 43 | return o; 44 | } 45 | 46 | fixed4 frag(v2f i) : SV_TARGET{ 47 | // 归一化法线/视角方向 48 | fixed3 worldNormal = normalize(i.worldNormal); 49 | fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos)); 50 | 51 | // 光源方向 52 | fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); 53 | 54 | // 计算反射方向 55 | float3 reflection = reflect(-worldViewDir,worldNormal); 56 | 57 | // 根据反射方向对CubeMap进行采样 58 | fixed4 color = texCUBE(_CubeMap,reflection) * _ReflectColor; 59 | 60 | // 计算漫反射 61 | fixed4 diffuse = _LightColor0 * _Color * max(0,dot(worldLightDir,worldNormal)); 62 | 63 | fixed3 finalColor = lerp(diffuse.rgb,color.rgb,_ReflectAmount); 64 | 65 | return fixed4(finalColor,1.0); 66 | } 67 | 68 | ENDCG 69 | } 70 | } 71 | FallBack "Diffuse" 72 | 73 | } -------------------------------------------------------------------------------- /Volume 05 CubeMap立方体纹理/Refraction.shader: -------------------------------------------------------------------------------- 1 | // 基于CubeMap实现折射效果 2 | Shader "Volume 05/Cube Map/Refraction" { 3 | Properties { 4 | _Color("Color Tint",Color) = (1, 1, 1, 1) 5 | _RefractColor("Refraction Color",Color) = (1, 1, 1, 1) 6 | // 折射程度 7 | _RefractAmount("Refraction Amount",Range(0,1)) = 1 8 | // 入射光线和折射介质的透射比 9 | _RefractRatio("Refraction Ratio",Range(0.1,1)) = 0.5 10 | _CubeMap("Cube Map",Cube) = "_Skybox" {} 11 | } 12 | SubShader { 13 | 14 | Tags{ "RenderType"="Opaque" "Queue"="Geometry" } 15 | 16 | Pass { 17 | Tags{"LightMode" = "ForwardBase"} 18 | 19 | CGPROGRAM 20 | 21 | #include "UnityCG.cginc" 22 | #include "Lighting.cginc" 23 | 24 | #pragma vertex vert 25 | #pragma fragment frag 26 | 27 | fixed4 _Color; 28 | fixed4 _RefractColor; 29 | fixed _RefractAmount; 30 | fixed _RefractRatio; 31 | samplerCUBE _CubeMap; 32 | 33 | struct a2v{ 34 | float4 vertex : POSITION; 35 | float3 normal : NORMAL; 36 | }; 37 | 38 | struct v2f{ 39 | float4 pos : SV_POSITION; 40 | float3 worldNormal : TEXCOORD0; 41 | float3 worldPos : TEXCOORD1; 42 | }; 43 | 44 | v2f vert(a2v v){ 45 | v2f o; 46 | o.pos = UnityObjectToClipPos(v.vertex); 47 | o.worldNormal = UnityObjectToWorldNormal(v.normal); 48 | o.worldPos = mul(unity_ObjectToWorld,v.vertex); 49 | 50 | return o; 51 | } 52 | 53 | fixed4 frag(v2f i) : SV_TARGET{ 54 | // 获得归一化的法线/视角方向 55 | fixed3 worldNormal = normalize(i.worldNormal); 56 | fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos)); 57 | 58 | // 计算折射方向 59 | float3 refraction = refract(-worldViewDir,worldNormal,_RefractRatio); 60 | // 根据折射方向对cubeMap进行采样 61 | fixed3 refractionColor = texCUBE(_CubeMap,refraction).rgb * _RefractColor.rgb; 62 | 63 | fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); 64 | 65 | // 计算漫反射光照 66 | fixed3 diffuse = _Color.rgb * _LightColor0.rgb * max(0,dot(worldLightDir,worldNormal)); 67 | 68 | fixed3 finalColor = lerp(diffuse,refractionColor,_RefractAmount); 69 | 70 | return fixed4(finalColor,1.0); 71 | } 72 | 73 | ENDCG 74 | } 75 | } 76 | FallBack "Diffuse" 77 | 78 | } -------------------------------------------------------------------------------- /Volume 05 CubeMap立方体纹理/readme.md: -------------------------------------------------------------------------------- 1 | 2 | [1. 《Unity Shader 入门精要》读书笔记 之 立方体纹理、GrabPass](#1-unity-shader-入门精要读书笔记-之-立方体纹理grabpass) 3 | - [1.1. 参考资料](#11-参考资料) 4 | - [1.2. 立方体纹理](#12-立方体纹理) 5 | - [1.2.1. 反射](#121-反射) 6 | - [1.2.1.1. 间接光照和直接光照](#1211-间接光照和直接光照) 7 | - [1.2.1.2. 为什么入射光线处参数选择的是单位视角方向?](#1212-为什么入射光线处参数选择的是单位视角方向) 8 | - [1.2.2. 折射](#122-折射) 9 | - [1.3. GrabPass](#13-grabpass) 10 | - [1.3.1. 概述](#131-概述) 11 | - [1.3.2. 使用方法](#132-使用方法) 12 | - [1.3.3. 使用要点](#133-使用要点) 13 | - [1.3.3.1. 如何获得当前片元所在屏幕图像的位置](#1331-如何获得当前片元所在屏幕图像的位置) 14 | - [1.3.3.2. ComputeGrabScreenPos原理](#1332-computegrabscreenpos原理) 15 | - [1.3.4. 火焰扭曲效果](#134-火焰扭曲效果) 16 | - [1.3.5. 玻璃效果](#135-玻璃效果) 17 | 18 | 19 | # 1. 《Unity Shader 入门精要》读书笔记 之 立方体纹理、GrabPass # 20 | ## 1.1. 参考资料 ## 21 | > 《Unity Shader 入门精要》 22 | > Unity Shader-反射效果 https://blog.csdn.net/puppet_master/article/details/80808486 23 | > 热空气扭曲原理 https://zhidao.baidu.com/question/582761762.html 24 | > Unity Shader-热空气扭曲效果 https://blog.csdn.net/puppet_master/article/details/70199330 25 | ## 1.2. 立方体纹理 26 | 与普通的2D纹理不同,立方体纹理包含6个图像,分别表示立方体的5个面。 27 | 28 | 当要对立方体纹理进行采样时,提供一个方向向量,该向量从立方体纹理中心出发并无限延伸,直到与立方体6个纹理之一相交,采样的结果即交点。 29 | 30 | 采样原理如下图所示: 31 | 32 | ![Avater](Image/readmeImage/CubeMap3.png) 33 | 34 | ### 1.2.1. 反射 35 | 基于立方体纹理可以方便的实现简单反射效果,该Shader的关键函数如下: 36 | 37 | float3 reflect(float3 i,float3 n) 38 | 39 | 表示计算入射光线i关于法向量n的反射方向。reflect函数的原理如下所示(注意该公式的所有向量均为单位向量): 40 | 41 | r = 2(n·l)n-l 42 | 43 | 实现反射效果的步骤如下: 44 | 45 | 1. 根据当前环境生成CubeMap贴图 46 | 2. shader根据反射方向对CubeMap进行采样 47 | 48 | 关键代码: 49 | 50 | float3 reflection = reflect(-o.worldViewDir,o.worldNormal); 51 | 52 | 其原理是: 53 | 54 | > 物体反射到摄像机中的光线方向,可以由光路可逆的原则来方向求得。也就是说,可以计算视角方向关于顶点法线的反射方向来求得如入射光线的方向。 55 | 56 | 关键代码各向量方向如下图所示: 57 | 58 | ![Avater](Image/readmeImage/CubeMap4.png) 59 | 60 | **概括一下**,**个人理解**,反射就是,当我们对一个能对周围进行反射的物体(如金属材质物体)进行观测时,可以通过反射看到它前方的物体。一个最为典型的例子就是——**镜子**,当我们观察镜子的时候,因为反射的作用,所以我们可以完全观察到我们自身。 61 | 62 | #### 1.2.1.1. 间接光照和直接光照 63 | 64 | > 反射,应该属于间接光照的范畴,而非直接光照。我们正常计算光的dot(N,L)或者dot(H,N)时计算的均为直接光照,光源出发经过物体表面该像素点反射进入眼睛的光照。然而这只是一部分,该点还可以接受来自场景中所有其他点反射的光照,如果表面光滑,则表面就可以反射周围的环境(如镜面,金属),到那时这个计算相当复杂,相当于需要在该点法线方向对应的半球空间上做积分运算才可能计算完全的间接光照,而且光线与物体碰撞后并不会消亡,而是经过反射或折射,改变方向后继续传递,相当于无限递归 65 | 原文:https://blog.csdn.net/puppet_master/article/details/80808486 66 | 67 | #### 1.2.1.2. 为什么入射光线处参数选择的是单位视角方向? 68 | 下面是个人理解。 69 | 70 | 一开始我对那行代码reflect(-o.worldViewDir,o.worldNormal)相当困惑,因为reflect函数要求填写的是入射光线,为什么这里是视角方向呢,难道不应该是worldLightDir吗? 71 | 72 | 看了上面那个关于直接光照和间接光照的博文,我觉得,之所以不使用worldLightDir作为入射光线,究其原因**是反射这里根本就不是直接光照产生的效果**,反射应该是场景中其他物体受光照后又反射到当前物体的效果(猜测)。 73 | 74 | 回到问题本身来,为什么使用视角方向作为参数,在《Unity Shader入门精要》中描述是,**根据光路可逆原则,可以根据最后反射到人眼中的光线来求得入射光线的方向**。然后根据入射光线对CubeMap进行采样模拟反射效果。 75 | 76 | ### 1.2.2. 折射 77 | 当我们从一个介质透过另一个介质观察其中的物体时,会发现其中的图像产生了扭曲的情况,这就是光线折射的表现。 78 | 79 | 举个例子,当我们看一个筷子在水中的表现时,会发现筷子未浸水的部分表现正常,浸入水的部分出现了明显的弯曲。这就是因为光在水和空气的折射率不同,光线在通过空气斜射入水中时,发生了折射现象。 80 | 81 | 折射的定义: 82 | 83 | > 当光线从一种介质(如空气)斜射入另一种介质(如玻璃)时,传播方向一般会发生改变。 84 | 85 | 概括一下,**个人理解**,折射就是,当我们观测一个光线进入其中会发生折射的物体(即具有折射特性材质的物体)时(如水面),会发现透过该物体,在该物体后方的图像被扭曲了。 86 | 87 | 折射关键函数为refract函数,其方法签名如下: 88 | 89 | fixed3 refract(fixed3 i,fixed3 n,fixed eta); 90 | 91 | 其中i为入射光线,n为入射光线的法线,eta为两个介质折射率的比值,如真空折射率为1,玻璃折射率为1.5,则折射比为1/1.5。需要注意的是,其中i和n都必须为**单位向量**。 92 | 93 | 基于CubeMap也可以快速做出折射效果,其实现步骤如下: 94 | 95 | 1. 根据当前环境生成CubeMap贴图 96 | 2. 基于refract函数计算经过折射后的向量,根据此向量对CubeMap进行采样 97 | 98 | ## 1.3. GrabPass 99 | ### 1.3.1. 概述 100 | GrabPass是Unity Shader里的一个特殊的Pass,可以用它来获取当前屏幕图像并保存到一张纹理中。 101 | 102 | **需要注意的是**,GrabPass的渲染队列要设置在所有不透明物体后面,以保证得到的屏幕图像是正确的。 103 | 104 | GrabPass一般用来做玻璃、火焰扭曲等效果。 105 | 106 | ### 1.3.2. 使用方法 107 | GrabPass通常使用方法如下: 108 | 109 | GrabPass{"_RefractionTex"} 110 | 111 | 表示的意思是将屏幕图像保存在名为_RefractionTex的二维纹理中。 112 | 113 | 应用这张二维纹理,可以比较轻松的做出诸如玻璃的的折射、火焰的热浪扭曲效果。 114 | 115 | ### 1.3.3. 使用要点 116 | 除了前面所说的**渲染队列需要格外注意**以外,使用GrabPass还有以下几个要点。 117 | 118 | #### 1.3.3.1. 如何获得当前片元所在屏幕图像的位置 119 | 使用GrabPass的一个常见目的就是,获得要渲染的物体的位置处的屏幕图像,而不是整张屏幕图像。 120 | 121 | 这样,就迎来了第一个问题,如何确定当前要渲染的片元对应的屏幕图像的位置。 122 | 123 | 在Unity Shader里面,可以用ComputeGrabScreenPos这个函数来进行计算,下面是该函数的签名。 124 | 125 | float4 ComputeGrabScreenPos(float4 clipPos) 126 | 127 | 待输入的参数是在裁剪空间下的顶点坐标,返回的结果值是**当前片元所在屏幕坐标(未进行齐次除法的屏幕坐标)**。 128 | 129 | 其一个标准的使用方法如下: 130 | 131 | // 顶点着色器 132 | v2f vert(appdata_base v){ 133 | .... 134 | o.pos = UnityObjectToClipPos(v.vertex); 135 | o.screenPos = ComputeGrabScreenPos(o.pos); 136 | ... 137 | } 138 | // 片元着色器 139 | fixed4 frag(v2f i) : SV_TARGET{ 140 | .... 141 | // z分量是深度,对于只是获取屏幕图像来说不需要用到 142 | float2 scrPos = i.screenPos.xy / i.screenPos.w; 143 | .... 144 | } 145 | 146 | 可以看到在顶点着色器中得到的屏幕坐标是没有进行齐次除法的,齐次除法要放到片元着色器中进行,这是因为片元着色器的插值效果。 147 | 148 | 一个显而易见的点是,在顶点着色器中的变量会经过插值传递到片元着色器中,而ComputeGrabScreenPos计算出来的屏幕坐标如果已经进行了齐次除法,这样就破坏了插值的过程。下面贴一段《Unity Shader入门精要》中的解释。 149 | 150 | > 如果在顶点着色器中进行这个除法,那么就需要对x/w和y/w直接进行插值,这样得到的插值结果就会不准确。这是因为我们不可以在投影空间进行插值,因为这并不是一个线性空间,而插值往往是线性的。 151 | 152 | #### 1.3.3.2. ComputeGrabScreenPos原理 153 | 下面深究一下ComputeGrabScreenPos这个函数。 154 | 155 | 首先要说明的是,在片元着色器手动进行齐次除法这个操作,相当于我们手工模拟了屏幕映射的过程。现在我们回忆一下屏幕映射是怎么一回事。 156 | 157 | 首先顶点经过MVP矩阵变换后来到裁剪空间,在裁剪空间下我们需要将顶点映射到屏幕坐标中(即屏幕映射操作),在这个过程中,需要进行齐次除法(即x、y、z分量同除于y分量),进行齐次除法后,顶点就被变换到了一个立方体空间内,此时x、y、z分量的范围是[-1,1],但是屏幕空间的范围是(0,0)~(screenWidth,screenHeight),所以我们要将这x、y分量经过 (*0.5+0.5) 的操作后变换到[0,1]的范围内。那么如何确定此时x、y分量所在屏幕的位置呢,其实就是一个缩放的过程,将缩放到[0,1]范围的x、y分量乘于屏幕分辨率,此时得到的x、y分量就是具体的屏幕某一个像素的坐标了。 158 | 159 | 下面用一个公式描述上述过程。 160 | 161 | ![Avater](Image/readmeImage/CubeMap0.png) 162 | 163 | 因为上面的文字描述稍显凌乱,下面用步骤一一写出顶点从裁剪空间到屏幕空间的一系列操作。 164 | 165 | 1. 顶点经过MVP矩阵变换到裁剪空间 166 | 2. 顶点经过齐次除法,坐标变换到NDC(归一化的设备坐标)中,在OpenGL中这个立方体的x、y、z分量范围都是[-1,1] 167 | 3. 因为x、y分量与屏幕坐标范围不匹配(一个是[-1,1],一个是[0,pixelWidth或piexlHeight]),所以将x、y分量转换到[0,1]范围内。(即*0.5+0.5操作,跟半兰伯特很像) 168 | 4. 将x、y分量各自乘于pixelWidth、pixelHeight得到这个片元(像素)所在屏幕坐标 169 | 170 | 而ComputeGrabScreenPos这个函数,实际上就是得到了没有除于w分量的屏幕映射坐标。下面看一下ComputeGrabScreenPos(实际上就是ComputeScreenPos的变种,加上了跨平台的判断条件等等)函数的源码。 171 | 172 | inline float4 ComputeGrabScreenPos (float4 pos) { 173 | #if UNITY_UV_STARTS_AT_TOP 174 | float scale = -1.0; 175 | #else 176 | float scale = 1.0; 177 | #endif 178 | float4 o = pos * 0.5f; 179 | o.xy = float2(o.x, o.y*scale) + o.w; 180 | #ifdef UNITY_SINGLE_PASS_STEREO 181 | o.xy = TransformStereoScreenSpaceTex(o.xy, pos.w); 182 | #endif 183 | o.zw = pos.zw; 184 | return o; 185 | } 186 | 187 | 其中ComputeScreenPos函数是ComputeGrabScreenPos函数的简化版,ComputeGrabScreenPos函数最主要是在ComputeScreenPos函数的基础上增加了针对平台差异造成的采样坐标问题。 188 | 189 | 为了简化问题,下面主要研究ComputeScreenPos函数。 190 | 191 | inline float4 ComputeScreenPos(float4 pos){ 192 | float4 o = pos * 0.5f; 193 | 194 | #if defined(UNITY_HALF_TEXEL_OFFSET) 195 | o.xy = float2(o.x,o.y*_ProjectionParams.x) + o.w * _ScreenParams.zw; 196 | #else 197 | o.xy = float2(o.x,o.y*_ProjectionParams.x) + o.w; 198 | #endif 199 | 200 | o.zw = pos.zw; 201 | return o; 202 | } 203 | 204 | 其中_ProjectionParams.x默认情况下是1。 205 | 206 | 上面代码的结果实际是输出了下面这种情况(图片来源于《Unity Shader入门精要》)。 207 | 208 | ![Avater](Image/readmeImage/CubeMap1.png) 209 | 210 | 至于说为啥是加上clipw/2,嗯,把Outputx和Outputy除上w分量(齐次除法)你就懂了。 211 | 212 | 到这里ComputerGrabScreenPos就了解的差不多了。。。基本上可以手写一个出来了,而如何通过当前片元位置计算它所处在的屏幕图像的位置也通过这么一个屏幕映射的操作计算出来了。 213 | 214 | ### 1.3.4. 火焰扭曲效果 215 | 首先看火焰扭曲效果在现实世界的原理: 216 | 217 | > 空气局部比较热,导致密度不同,因而对光线的折射率不同。而受热不均匀,引起空气密度的不断变化,光线通过空气时发生的折射就不断变化,就感觉扭曲了一样 218 | 219 | 在Unity中,可以通过uv偏移来模拟这种热空气扭曲的现象,基本思路如下: 220 | 221 | 1. 基于GrabPass得到屏幕图像纹理 222 | 2. 得到当前像素在屏幕上的位置 223 | 3. 基于上一步得到的位置对屏幕图像进行采样 224 | 4. 在采样时,对第二步得到的位置根据时间变量_Time进行一定的偏移(此处可以对噪声图采样来得到随机的位置偏移的效果) 225 | 226 | 效果及对比图如下所示: 227 | 228 | 左边增加了热空气扭曲效果,右边则没有 229 | 230 | ![Avater](Image/readmeImage/Fire.gif) ![Avater](Image/readmeImage/FireNoDis.gif) 231 | 232 | ### 1.3.5. 玻璃效果 233 | 在现实世界中,玻璃一般是透明材质,同时,它可以进行环境映射(即反射周围物体),因为介质的不同,透过玻璃观察物体还会发生折射现象。 234 | 235 | 根据以上特性,可以发现,使用CubeMap和GrabPass可以比较方便的制作玻璃效果。 236 | 237 | 基本思路是,基于CubeMap来制作反射效果,基于GrabPass来制作折射效果(即uv偏移,这里可以让uv沿着物体法线进行偏移) -------------------------------------------------------------------------------- /Volume 06 VertexChange顶点变换/Billboarding.shader: -------------------------------------------------------------------------------- 1 | // 广告版技术 2 | Shader "Volume 06/Time/Billboarding" { 3 | Properties { 4 | _MainTex("Main Texture",2D) = "white" {} 5 | _Color("Color Tint",Color) = (1, 1, 1, 1) 6 | // 是固定法线还是固定向上的位置,为1时,公告板up向量固定,为0时,公告板表面法线(即视角方向)固定 7 | _VerticalBillboarding("Vertical Restraints",Range(0,1)) = 1 8 | } 9 | SubShader { 10 | Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "DisableBatching"="True" } 11 | Pass { 12 | 13 | Tags { "LightMode" = "ForwardBase" } 14 | 15 | ZWrite Off 16 | Blend SrcAlpha OneMinusSrcAlpha 17 | Cull Off // 双面渲染 18 | 19 | CGPROGRAM 20 | 21 | #include "UnityCG.cginc" 22 | 23 | #pragma vertex vert 24 | #pragma fragment frag 25 | 26 | fixed4 _Color; 27 | sampler2D _MainTex; 28 | float _VerticalBillboarding; 29 | 30 | struct a2v{ 31 | float4 vertex : POSITION; 32 | float2 texcoord : TEXCOORD0; 33 | }; 34 | 35 | struct v2f{ 36 | float4 pos : SV_POSITION; 37 | float2 uv : TEXCOORD; 38 | }; 39 | 40 | v2f vert(a2v v){ 41 | v2f o; 42 | 43 | // 规定锚点 44 | float3 center = float3(0,0,0); 45 | // 获得模型空间下的视角方向 46 | float3 viewer = mul(unity_WorldToObject,float4(_WorldSpaceCameraPos,1)); 47 | 48 | 49 | // 获得表面法线 50 | float3 normalDir = viewer - center; 51 | // 根据_VerticalBillboarding判断法线是否固定方向 52 | normalDir.y = normalDir.y * _VerticalBillboarding; 53 | // 归一化法线 54 | normalDir = normalize(normalDir); 55 | 56 | // 根据法线和up的叉积获得向右的right向量 57 | float3 upDir = abs(normalDir.y) > 0.999 ? float3(0,0,1) : float3(0,1,0); 58 | float3 rightDir = normalize(cross(upDir,normalDir)); 59 | // 根据rightDir和表面法线,获得修正过后的upDir 60 | upDir = normalize(cross(normalDir,rightDir)); 61 | 62 | // 此时获得三个正交基(可以理解为顶点旋转之后的那个空间的三个坐标轴) 63 | float3 centerOffs = v.vertex.xyz - center; 64 | float3 localPos = center + rightDir*centerOffs.x + upDir*centerOffs.y+normalDir*centerOffs.z; 65 | 66 | // 顶点变换 67 | o.pos = UnityObjectToClipPos(float4(localPos,1)); 68 | o.uv = v.texcoord; 69 | 70 | return o; 71 | } 72 | 73 | fixed4 frag(v2f i) : SV_TARGET{ 74 | fixed4 c = tex2D(_MainTex,i.uv); 75 | 76 | return c; 77 | } 78 | 79 | ENDCG 80 | 81 | } 82 | } 83 | FallBack "Diffuse" 84 | 85 | } -------------------------------------------------------------------------------- /Volume 06 VertexChange顶点变换/BillboardingImageSequenceAnimation.shader: -------------------------------------------------------------------------------- 1 | // 广告版+序列帧,用于将2D动画特效转变为 伪`3D特效 2 | Shader "Volume 06/Time/Billboarding Image Sequence Animation" { 3 | Properties { 4 | _MainTex("Main Texture",2D) = "white" {} 5 | _Color("Color Tint",Color) = (1, 1, 1, 1) 6 | // 是固定法线还是固定向上的位置,为1时,公告板up向量固定,为0时,公告板表面法线(即视角方向)固定 7 | _VerticalBillboarding("Vertical Restraints",Range(0,1)) = 1 8 | // 动画播放速度 9 | _Speed("Speed",Range(0.1,10)) = 1 10 | // 序列帧图片一共多少行 11 | _RowAmount("Row Amount",Float) = 0 12 | // 序列帧图片一共多少列 13 | _ColAmount("Col Amount",Float) = 0 14 | } 15 | SubShader { 16 | Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "DisableBatching"="True" } 17 | Pass { 18 | 19 | Tags { "LightMode" = "ForwardBase" } 20 | 21 | ZWrite Off 22 | Blend SrcAlpha OneMinusSrcAlpha 23 | Cull Off // 双面渲染 24 | 25 | CGPROGRAM 26 | 27 | #include "UnityCG.cginc" 28 | 29 | #pragma vertex vert 30 | #pragma fragment frag 31 | 32 | fixed4 _Color; 33 | sampler2D _MainTex; 34 | float _VerticalBillboarding; 35 | float _Speed; 36 | float _RowAmount; 37 | float _ColAmount; 38 | 39 | struct a2v{ 40 | float4 vertex : POSITION; 41 | float2 texcoord : TEXCOORD0; 42 | }; 43 | 44 | struct v2f{ 45 | float4 pos : SV_POSITION; 46 | float2 uv : TEXCOORD; 47 | }; 48 | 49 | v2f vert(a2v v){ 50 | v2f o; 51 | 52 | // 规定锚点 53 | float3 center = float3(0,0,0); 54 | // 获得模型空间下的视角方向 55 | float3 viewer = mul(unity_WorldToObject,float4(_WorldSpaceCameraPos,1)); 56 | 57 | 58 | // 获得表面法线 59 | float3 normalDir = viewer - center; 60 | // 根据_VerticalBillboarding判断法线是否固定方向 61 | normalDir.y = normalDir.y * _VerticalBillboarding; 62 | // 归一化法线 63 | normalDir = normalize(normalDir); 64 | 65 | // 根据法线和up的叉积获得向右的right向量 66 | float3 upDir = abs(normalDir.y) > 0.999 ? float3(0,0,1) : float3(0,1,0); 67 | float3 rightDir = normalize(cross(upDir,normalDir)); 68 | // 根据rightDir和表面法线,获得修正过后的upDir 69 | upDir = normalize(cross(normalDir,rightDir)); 70 | 71 | // 此时获得三个正交基(可以理解为顶点旋转之后的那个空间的三个坐标轴) 72 | float3 centerOffs = v.vertex.xyz - center; 73 | float3 localPos = center + rightDir*centerOffs.x + upDir*centerOffs.y+normalDir*centerOffs.z; 74 | 75 | // 顶点变换 76 | o.pos = UnityObjectToClipPos(float4(localPos,1)); 77 | o.uv = v.texcoord; 78 | 79 | return o; 80 | } 81 | 82 | fixed4 frag(v2f i) : SV_TARGET{ 83 | float time = floor(_Time.y * _Speed) % (_ColAmount*_RowAmount); 84 | // 获得行列 85 | float row = floor(time/_RowAmount); 86 | float col = floor(time%_ColAmount); 87 | 88 | float2 uv = float2(i.uv.x/_ColAmount,i.uv.y/_RowAmount); 89 | uv.x += col/_ColAmount; 90 | uv.y -= row/_RowAmount; 91 | 92 | fixed4 color = tex2D(_MainTex,uv) * _Color; 93 | 94 | return color; 95 | } 96 | 97 | ENDCG 98 | 99 | } 100 | } 101 | FallBack "Diffuse" 102 | 103 | } -------------------------------------------------------------------------------- /Volume 06 VertexChange顶点变换/GemoVisualNormal.shader: -------------------------------------------------------------------------------- 1 | /* 2 | 法线可视化 3 | */ 4 | Shader "Volume 06/Vertex Change/Gemo Visual Normal" { 5 | Properties { 6 | _MainTex("Textrue",2D) = "white" {} 7 | _Mangitude("Mangitude",Float) = 1 8 | } 9 | SubShader { 10 | 11 | Tags { "Queue"="Geometry" "RenderType"="Opaque" } 12 | 13 | Pass{ 14 | CGPROGRAM 15 | 16 | #pragma vertex vert 17 | #pragma fragment frag 18 | 19 | struct a2v{ 20 | float4 vertex : POSITION; 21 | float2 texcoord : TEXCOORD0; 22 | }; 23 | 24 | struct v2f{ 25 | float4 pos : SV_POSITION; 26 | float2 uv : TEXCOORD0; 27 | }; 28 | 29 | sampler2D _MainTex; 30 | 31 | v2f vert(a2v v){ 32 | v2f o; 33 | o.pos = UnityObjectToClipPos(v.vertex); 34 | o.uv = v.texcoord; 35 | return o; 36 | } 37 | 38 | fixed4 frag(v2f i) : SV_TARGET{ 39 | return tex2D(_MainTex,i.uv); 40 | } 41 | 42 | ENDCG 43 | } 44 | 45 | Pass { 46 | CGPROGRAM 47 | 48 | #include "UnityCG.cginc" 49 | 50 | #pragma vertex vert 51 | #pragma geometry geom 52 | #pragma fragment frag 53 | 54 | struct a2v{ 55 | float4 vertex : POSITION; 56 | float4 texcoord : TEXCOORD0; 57 | float3 normal : NORMAL; 58 | }; 59 | 60 | struct v2g{ 61 | float4 vertex : SV_POSITION; 62 | float2 uv : TEXCOORD0; 63 | float3 normal : TEXCOORD1; 64 | }; 65 | 66 | struct g2f{ 67 | float2 uv : TEXCOORD0; 68 | float4 pos : SV_POSITION; 69 | fixed4 color : TEXCOORD1; 70 | }; 71 | 72 | sampler2D _MainTex; 73 | float _Mangitude; 74 | 75 | v2g vert(a2v v){ 76 | v2g o; 77 | o.vertex = v.vertex; 78 | o.uv = v.texcoord; 79 | o.normal = v.normal; 80 | return o; 81 | } 82 | 83 | [maxvertexcount(9)] 84 | void geom(triangle v2g input[3],inout LineStream outStream){ 85 | g2f o; 86 | 87 | for(int i=0;i<3;i++){ 88 | 89 | o.pos = (input[i].vertex); 90 | o.pos = UnityObjectToClipPos(o.pos); 91 | o.uv = input[i].uv; 92 | o.color = fixed4(1,1,1,1); 93 | outStream.Append(o); 94 | 95 | o.pos = input[i].vertex; 96 | o.pos.xyz += input[i].normal*_Mangitude; 97 | o.pos = UnityObjectToClipPos(o.pos); 98 | o.uv = input[i].uv; 99 | o.color = fixed4(1,1,1,1); 100 | outStream.Append(o); 101 | 102 | o.pos = (input[i].vertex); 103 | o.pos = UnityObjectToClipPos(o.pos); 104 | o.uv = input[i].uv; 105 | o.color = fixed4(1,1,1,1); 106 | outStream.Append(o); 107 | } 108 | 109 | 110 | //-------restart strip可以模拟一个primitives list 111 | outStream.RestartStrip(); 112 | } 113 | 114 | 115 | fixed4 frag(g2f i) : SV_TARGET{ 116 | return i.color; 117 | } 118 | ENDCG 119 | } 120 | } 121 | FallBack "Diffuse" 122 | 123 | } -------------------------------------------------------------------------------- /Volume 06 VertexChange顶点变换/Image/readmeImage/005-Attack03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 06 VertexChange顶点变换/Image/readmeImage/005-Attack03.png -------------------------------------------------------------------------------- /Volume 06 VertexChange顶点变换/Image/readmeImage/VertexChanged4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 06 VertexChange顶点变换/Image/readmeImage/VertexChanged4.png -------------------------------------------------------------------------------- /Volume 06 VertexChange顶点变换/Image/readmeImage/boom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 06 VertexChange顶点变换/Image/readmeImage/boom.png -------------------------------------------------------------------------------- /Volume 06 VertexChange顶点变换/Image/readmeImage/vertexChanged0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 06 VertexChange顶点变换/Image/readmeImage/vertexChanged0.png -------------------------------------------------------------------------------- /Volume 06 VertexChange顶点变换/Image/readmeImage/vertexChanged1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 06 VertexChange顶点变换/Image/readmeImage/vertexChanged1.png -------------------------------------------------------------------------------- /Volume 06 VertexChange顶点变换/Image/readmeImage/vertexChanged2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 06 VertexChange顶点变换/Image/readmeImage/vertexChanged2.png -------------------------------------------------------------------------------- /Volume 06 VertexChange顶点变换/Image/readmeImage/vertexChanged5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 06 VertexChange顶点变换/Image/readmeImage/vertexChanged5.png -------------------------------------------------------------------------------- /Volume 06 VertexChange顶点变换/Image/readmeImage/vertexChanged6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 06 VertexChange顶点变换/Image/readmeImage/vertexChanged6.png -------------------------------------------------------------------------------- /Volume 06 VertexChange顶点变换/ImageSequenceAnimation.shader: -------------------------------------------------------------------------------- 1 | Shader "Volume 06/Time/ImageSequenceAnimation" { 2 | Properties { 3 | // 序列帧图片 4 | _MainTex("Main Texture",2D) = "white" {} 5 | // 动画播放速度 6 | _Speed("Speed",Range(0.1,10)) = 1 7 | // 序列帧图片一共多少行 8 | _RowAmount("Row Amount",Float) = 0 9 | // 序列帧图片一共多少列 10 | _ColAmount("Col Amount",Float) = 0 11 | } 12 | SubShader { 13 | Tags { "RenderType"="Transparent" "Queue"="Transparent" "IgnoreProjector"="True" } 14 | Pass { 15 | ZWrite Off 16 | Blend SrcAlpha OneMinusSrcAlpha 17 | 18 | CGPROGRAM 19 | 20 | #include "UnityCG.cginc" 21 | 22 | #pragma vertex vert 23 | #pragma fragment frag 24 | 25 | sampler2D _MainTex; 26 | float _Speed; 27 | float _RowAmount; 28 | float _ColAmount; 29 | 30 | struct v2f{ 31 | float4 pos : SV_POSITION; 32 | float2 uv : TEXCOORD0; 33 | }; 34 | 35 | v2f vert(appdata_img v){ 36 | v2f o; 37 | o.pos = UnityObjectToClipPos(v.vertex); 38 | o.uv = v.texcoord; 39 | return o; 40 | } 41 | 42 | fixed4 frag(v2f i) : SV_TARGET{ 43 | float time = floor(_Time.y * _Speed) % (_ColAmount*_RowAmount); 44 | // 获得行列 45 | float row = floor(time/_RowAmount); 46 | float col = floor(time%_ColAmount); 47 | 48 | float2 uv = float2(i.uv.x/_ColAmount,i.uv.y/_RowAmount); 49 | uv.x += col/_ColAmount; 50 | uv.y -= row/_RowAmount; 51 | 52 | fixed4 color = tex2D(_MainTex,uv); 53 | 54 | return color; 55 | } 56 | 57 | ENDCG 58 | } 59 | } 60 | FallBack "Diffuse" 61 | 62 | } -------------------------------------------------------------------------------- /Volume 06 VertexChange顶点变换/readme.md: -------------------------------------------------------------------------------- 1 | # 《Unity Shader 入门精要》读书笔记 之 顶点变换与_Time变量的使用 # 2 | ## 参考资料 ## 3 | > 《Unity Shader 入门精要》 4 | > 孔杰 billboard效果的实现以及延伸(空间变换、批处理) https://zhuanlan.zhihu.com/p/29072964 5 | > 作者对广告牌锚点的解释 https://github.com/candycat1992/Unity_Shaders_Book/issues/27 6 | ## 序列帧动画 7 | > 最常见的纹理动画就是序列帧动画。序列帧动画的原理非常简单,它像放电影一样,依次播放一系列关键帧图像,当播放速度达到一定数值时,看起来就是一个连续的动画。 8 | 9 | 一般来说,2D游戏中用的序列帧动画次数比较多,一个常见的序列帧图片如下图所示(来自《Unity Shader入门精要》)。 10 | 11 | ![Avater](Image/readmeImage/boom.png) 12 | 13 | ### 实现 14 | 在Shader中实现序列帧还是比较简单的,基本思路就是利用_Time变量,随着时间的变换,不停的变化uv,导致看起来像播放动画一样。 15 | 16 | 基本思路如下: 17 | 根据序列帧的图片,我们设col为图片有多少列,row为图片有多少行,那么一共有col*row个关键帧图片。 18 | 19 | 那么在实际操作时,就需要把uv分成col*row份,也就是说,uv的范围实际上从(0,0)~(1,1)变换到了(0,0)~(1/col,1/row)。 20 | 21 | 一个比较形象的示意图如下: 22 | ![Avater](Image/readmeImage/vertexChanged0.png) 23 | 24 | ![Avater](Image/readmeImage/vertexChanged1.png) 25 | 26 | 简而言之,我们在frag Shader里得到的uv,x分量要除于col,y分量要除于row,这样,uv就转换到了正确的范围. 27 | 28 | 但是,这还不够,这样的画,uv只是在序列帧图片的第一幅图上而已,我们要把这个uv平移到我们要到的第N张图上 29 | 30 | 假设这第N张图的行数是RowN,列数是ColN,那么uv要加上(colN/col,rowN/row),这样才能正确的平移到目标图片上。 31 | 32 | 假设是第3行第四列的图上,如下图所示,可以看到要平移的部分。 33 | 34 | ![Avater](Image/readmeImage/vertexChanged2.png) 35 | 36 | 需要注意的是,这张序列帧图片关键帧的顺序是从左到右,从上到下,所以对于uv的y分量,应当减去才是向下一个关键帧前进,如果改为加法,是向上一个序列帧前进。 37 | 38 | 如上图所示,如果要到达第三行第四列的位置,那么要进行以下操作。 39 | 40 | 1. 当前uv.x /= col,当前uv.y /= row; 41 | 2. 当前uv.x += 4/col,当前uv.y -= 3/row。要不然y也可以加上(row-3)/row,不过对这个分式简化之后也可以发现其实也就是前面那个-3/row 42 | 3. 此时uv正确移动到了目标关键帧的位置 43 | 44 | ### 步骤 45 | 那么实现序列帧动画的基本步骤如下: 46 | 47 | 1. 根据_Time变量确定当前要播放第几行第几列的关键帧 48 | 2. 将当前uv根据上面的操作移动到目标关键帧的那个图像范围内 49 | 3. 根据uv对图像进行采样 50 | 51 | 这样,就能简单实现序列帧动画了。 52 | 53 | ## 广告板(Billboarding)技术 54 | 广告版技术就是让某个物体始终面向摄像机的一个技术,下面是《Unity Shader入门精要》对其的描述. 55 | 56 | > 广告牌技术会根据视角方向旋转一个被纹理着色的多边形,使得多边形看起来好像总是面对着摄像机。 57 | 58 | 广告牌技术的优势是显而易见的,对于一些2D的物体,我们只要渲染它的一个面,并让它时时刻刻都面向着摄像机,就能创造出3D的感觉,从而提升了性能。Unity3D中的粒子特效、树等就是基于广告牌技术的。 59 | 60 | 广告牌的实现有两种方式,一种是在CPU中实现,一种在Shader中实现。下面分别讲述。 61 | 62 | ### 在CPU实现广告牌技术 63 | 在CPU中实现广告牌技术还是比较简单的,那就是可以利用Transform中的LookAt方法,在Update或OnWillRenderObject中使物体时时刻刻的面向摄像机就OK了。 64 | 65 | 这种方法有一定的局限性,在孔杰的[知乎专栏](https://zhuanlan.zhihu.com/p/29072964),提到: 66 | 67 | >脚本挂在物体上,直接改变Transform组件,非常简单。但是当物体是勾选了bacthing static标记,Unity会对他们进行静态合并,经过合并后的Mesh是不能变动的,所以会失去效果。所以我们可以在Shader里面实现这个效果 68 | 69 | ### 在Shader中实现广告牌技术 70 | 在Shader中实现广告牌技术的要点就在于,要将模型的顶点旋转到面向摄像机的方向,这里说到旋转,一般第一个想到的是旋转矩阵,但是实际上这里并不需要用到旋转矩阵。 71 | 72 | 可以这样理解,旋转后的顶点是处于以一个锚点作为原点,以三个标准正交基(即互相垂直的单位向量)为坐标轴的坐标空间,而我们的目标就是将模型从模型空间变换到目标坐标空间中去。 73 | 74 | 如果知道了那三个标准正交基、锚点、当前顶点坐标,我们就能将当前顶点转换到**旋转后的坐标空间**中。 75 | 76 | #### 关于锚点 77 | 锚点可以随意设置,一般可以将其设置为模型空间的原点(0,0,0),**但是!**,如果直接将锚点设置为常量(0,0,0),那么在Shader的编写中就必须**关闭批处理(DisableBatching=true)**。 78 | 79 | 下面是来自作者issues的解释。 80 | 81 | > 因为一旦批处理了就会造成合并网格,那么多个四边形就会合并成一个整体网格里,这个网格的中心就肯定不是每个四边形应有的旋转中心(锚点)了。这就是下面右图的情况。此时每个四边形正确的锚点如红点所示,但模型原点确是绿点。 82 | > ![Avater](Image/readmeImage/vertexChanged4.png) 83 | 84 | 对于这种情况,可以用一套纹理存储模型每个顶点到其锚点的距离。此时锚点的位置就等于 顶点位置 + 该顶点到锚点的偏移值。 85 | 86 | #### 如何求得三个标准正交基 87 | 前面说到,广告牌技术关键是要让物体时刻面向摄影机,那么可以想到,可以让物体的法线方向永远固定为视角方向(当然还有另外一种是固定物体向上的方向的做法),这样玩家看到的就永远是物体的正面了。 88 | 89 | 那么,接下来可以假定向上的方向为标准正交基的Y轴,当然这个假定的方向不一定跟法线方向垂直,但是现在可以先不管这个,根据法线与向上的方向的叉积,就可以算出标准正交基的X轴,即单位指向右的方向。 90 | 91 | 将这个算出来的指向右的方向与法线叉积,即可得到正确的向上的方向。 92 | 93 | 最后,将这三个向量归一化,就得到了三个互相垂直的正交基了。 94 | 95 | 上述流程可以用下图来表示: 96 | 97 | ![Avater](Image/readmeImage/vertexChanged5.png) 98 | 99 | #### 如何求得旋转后的顶点的位置 100 | 已知锚点O(x,y,z),模型空间上一点P(x1,y1,z1),法线方向n,向上的方向up,向右的方向right,已知法线是顶点旋转后的空间的z轴,向右的方向是空间的x轴,向上的方向是空间的y轴,那么将模型空间中的一点P变换到旋转后的空间中的一点P'的公式如下: 101 | 102 | ![Avater](Image/readmeImage/vertexChanged6.png) 103 | -------------------------------------------------------------------------------- /Volume 07 SimplePostEffect简单屏幕后处理/Bloom.shader: -------------------------------------------------------------------------------- 1 | // Bloom后处理特效 2 | Shader "Volume 07/SimplePostEffect/Bloom" { 3 | Properties { 4 | _MainTex("Main Texture",2D) = "white" {} 5 | // 亮度阈值,Bloom特效根据亮度阈值提取图像中较亮区域作为Bloom图与原图混合 6 | _LuminanceThreshold("Luminance Threshold",Float) = 0.5 7 | } 8 | SubShader { 9 | 10 | // 用于提取图中较亮区域的Pass 11 | Pass { 12 | 13 | CGPROGRAM 14 | 15 | #include "UnityCG.cginc" 16 | 17 | #pragma vertex vertGetBloom 18 | #pragma fragment fragGetBloom 19 | 20 | sampler2D _MainTex; 21 | float _LuminanceThreshold; 22 | 23 | struct v2f{ 24 | float4 pos : SV_POSITION; 25 | float2 uv : TEXCOORD0; 26 | }; 27 | 28 | v2f vertGetBloom(appdata_img v){ 29 | v2f o; 30 | o.pos = UnityObjectToClipPos(v.vertex); 31 | o.uv = v.texcoord; 32 | return o; 33 | } 34 | 35 | fixed4 fragGetBloom(v2f i) : SV_TARGET{ 36 | fixed3 color = tex2D(_MainTex,i.uv); 37 | 38 | // 计算该点的亮度值 39 | fixed luminance = Luminance(color); 40 | 41 | // 提取亮度大于阈值的部分 42 | fixed val = clamp(luminance-_LuminanceThreshold,0,1.0); 43 | 44 | return fixed4(color*val,1.0); 45 | } 46 | 47 | ENDCG 48 | 49 | } 50 | 51 | // 用于混合Bloom图和原图的Pass 52 | Pass{ 53 | CGPROGRAM 54 | 55 | #include "UnityCG.cginc" 56 | 57 | #pragma vertex vertMixture 58 | #pragma fragment fragMixture 59 | 60 | sampler2D _MainTex; 61 | float _LuminanceThreshold; 62 | 63 | // 经过模糊后的Bloom图 64 | sampler2D _Bloom; 65 | 66 | struct v2f{ 67 | float4 pos : SV_POSITION; 68 | float2 uv : TEXCOORD0; 69 | }; 70 | 71 | v2f vertMixture(appdata_img v){ 72 | v2f o; 73 | o.pos = UnityObjectToClipPos(v.vertex); 74 | o.uv = v.texcoord; 75 | return o; 76 | } 77 | 78 | fixed4 fragMixture(v2f i) : SV_TARGET{ 79 | // 混合原图和Bloom图 80 | return tex2D(_MainTex,i.uv) + tex2D(_Bloom,i.uv); 81 | } 82 | 83 | ENDCG 84 | } 85 | } 86 | FallBack "Diffuse" 87 | 88 | } -------------------------------------------------------------------------------- /Volume 07 SimplePostEffect简单屏幕后处理/BrightnessSaturationAndContrast.shader: -------------------------------------------------------------------------------- 1 | // 屏幕后处理,用于调节饱和度、对比度、亮度的Shader 2 | Shader "Volume 07/SimplePostEffect/Brightness Saturation And Contrast" { 3 | Properties { 4 | // 屏幕图像 5 | _MainTex("Main Texture",2D) = "white" {} 6 | // 亮度值 7 | _Brightness("Brightness",Range(1,10)) = 1 8 | // 对比度 9 | _Contrast("Contrast",Range(0,1.0)) = 0 10 | // 饱和度 11 | _Saturation("Saturation",Range(0,1.0)) = 0 12 | } 13 | SubShader { 14 | 15 | Tags{ "RenderType"="Opaque" "Queue"="Geometry" } 16 | 17 | Pass { 18 | CGPROGRAM 19 | 20 | #include "UnityCG.cginc" 21 | 22 | #pragma vertex vert 23 | #pragma fragment frag 24 | 25 | sampler2D _MainTex; 26 | float _Brightness; 27 | float _Contrast; 28 | float _Saturation; 29 | 30 | struct v2f{ 31 | float4 pos : SV_POSITION; 32 | float2 uv : TEXCOORD0; 33 | }; 34 | 35 | v2f vert(appdata_img v){ 36 | v2f o; 37 | o.pos = UnityObjectToClipPos(v.vertex); 38 | o.uv = v.texcoord; 39 | return o; 40 | } 41 | 42 | fixed4 frag(v2f i) : SV_TARGET{ 43 | 44 | fixed3 finalColor = tex2D(_MainTex,i.uv); 45 | 46 | // 基于亮度系数调整图像颜色 47 | finalColor *= _Brightness; 48 | 49 | // 创建饱和度为0的颜色值 50 | fixed luminance = Luminance(finalColor); 51 | fixed3 luminanceColor = fixed3(luminance,luminance,luminance); 52 | finalColor = lerp(finalColor,luminanceColor,_Saturation); 53 | 54 | // 创建对比度为0的颜色值 55 | fixed3 avgColor = fixed3(0.5,0.5,0.5); 56 | // 调整对比度 57 | finalColor = lerp(finalColor,avgColor,_Contrast); 58 | 59 | return fixed4(finalColor,1.0); 60 | } 61 | 62 | ENDCG 63 | } 64 | } 65 | FallBack "Diffuse" 66 | 67 | } -------------------------------------------------------------------------------- /Volume 07 SimplePostEffect简单屏幕后处理/EdgeDetection.shader: -------------------------------------------------------------------------------- 1 | // 利用图像之间的梯度值进行的边缘检测 2 | // 主要利用sobel算子算出图像中水平和竖直方向的梯度值, 3 | // 并根据梯度值来判断当前像素点是不是边界 4 | Shader "Volume 07/SimplePostEffect/EdgeDetection" { 5 | Properties { 6 | // 屏幕图像 7 | _MainTex("Main Texture",2D) = "white" {} 8 | // 判断图像是否只有边线 9 | _EdgeOnly("Edge Only",Range(0,1.0)) = 0 10 | // 边缘颜色 11 | _EdgeColor("Edge Color",Color) = (0, 0, 0, 1) 12 | // 背景颜色 13 | _BackgroundColor("Background Color",Color) = (1, 1, 1, 1) 14 | } 15 | SubShader { 16 | Tags{ "RenderType"="Opaque" "Queue"="Geometry" } 17 | Pass { 18 | CGPROGRAM 19 | 20 | #include "UnityCG.cginc" 21 | 22 | #pragma vertex vert 23 | #pragma fragment frag 24 | 25 | sampler2D _MainTex; 26 | // 纹理的纹素,x = 1/width, y = 1/height, z = width, w = height 27 | float4 _MainTex_TexelSize; 28 | fixed _EdgeOnly; 29 | fixed4 _EdgeColor; 30 | fixed4 _BackgroundColor; 31 | 32 | struct v2f{ 33 | float4 pos : SV_POSITION; 34 | // 定义9个纹理坐标, 35 | // 分别用于采样原点、左上角、左边、左下角、上边、下边、右边、右上角、右下角这九个位置 36 | float2 uv[9] : TEXCOORD0; 37 | }; 38 | 39 | // 声明方法签名 40 | half sobel(v2f i); 41 | 42 | v2f vert(appdata_img v){ 43 | v2f o; 44 | 45 | // 把顶点变换到裁剪空间去 46 | o.pos = UnityObjectToClipPos(v.vertex); 47 | 48 | // opengl中,左下角是(0,0),右上角(1,1) 49 | 50 | // 这里的顺序很重要,书上的顺序是从左到右,从下到上 51 | // 左上角 52 | o.uv[0] = v.texcoord + _MainTex_TexelSize * half2(-1,1); 53 | // 上边 54 | o.uv[1] = v.texcoord + _MainTex_TexelSize * half2(0,1); 55 | // 右上角 56 | o.uv[2] = v.texcoord + _MainTex_TexelSize * half2(1,1); 57 | // 左边 58 | o.uv[3] = v.texcoord + _MainTex_TexelSize * half2(-1,0); 59 | // uv原点 60 | o.uv[4] = v.texcoord; 61 | // 右边 62 | o.uv[5] = v.texcoord + _MainTex_TexelSize * half2(1,0); 63 | // 左下角 64 | o.uv[6] = v.texcoord + _MainTex_TexelSize * half2(-1,-1); 65 | // 下边 66 | o.uv[7] = v.texcoord + _MainTex_TexelSize * half2(0,-1); 67 | // 右下角 68 | o.uv[8] = v.texcoord + _MainTex_TexelSize * half2(1,-1); 69 | 70 | 71 | return o; 72 | } 73 | 74 | fixed4 frag(v2f i) : SV_TARGET{ 75 | // 获得当前像素点的边缘值 76 | half edge = sobel(i); 77 | 78 | // 当前uv位置的像素值 79 | fixed4 color = tex2D(_MainTex,i.uv[4]); 80 | 81 | // 边缘值越大,该点越有可能是边界 82 | fixed4 withEdgeColor = lerp(color,_EdgeColor,edge); 83 | fixed4 onlyEdgeColor = lerp(_EdgeColor,_BackgroundColor,1-edge); 84 | 85 | return lerp(withEdgeColor,onlyEdgeColor,_EdgeOnly); 86 | } 87 | 88 | // 对当前像素位置进行卷积操作,返回该像素点的边缘值 89 | // 边缘值越大,则说明此处越有可能是边界 90 | half sobel(v2f i){ 91 | 92 | // Gx用于检测水平方向上的边界线 93 | // (注:书上的两个用于卷积的矩阵写反了) 94 | const half Gx[9] = { 95 | -1,0,1, 96 | -2,0,2, 97 | -1,0,1 98 | }; 99 | 100 | // Gy用于检测竖直方向上的边界线 101 | const half Gy[9] = { 102 | -1,-2,-1, 103 | 0, 0, 0, 104 | 1, 2, 1 105 | }; 106 | 107 | half edgeX = 0; 108 | half edgeY = 0; 109 | 110 | // 进行卷积操作,具体来说,就是将对应像素值位置(当前uv原点) 111 | // 的上下左右等9个角分别和其对应像素位置的颜色值相乘并求和 112 | for(int it=0;it<9;it++){ 113 | // fixed3 color = tex2D(_MainTex,i.uv[it]); 114 | fixed3 color = Luminance(tex2D(_MainTex,i.uv[it])); 115 | edgeX += color * Gx[it]; 116 | edgeY += color * Gy[it]; 117 | } 118 | 119 | // 返回该像素点的边缘值 120 | return (abs(edgeX) + abs(edgeY)); 121 | } 122 | 123 | ENDCG 124 | } 125 | } 126 | FallBack "Diffuse" 127 | 128 | } -------------------------------------------------------------------------------- /Volume 07 SimplePostEffect简单屏幕后处理/GaussianBlur.shader: -------------------------------------------------------------------------------- 1 | /* 简单实现高斯模糊效果 2 | 原理: 3 | 一张清晰的图片各个像素之间过渡明显,即具有突变的性质 4 | 而如果将图中每个像素取平均,图像就会变得模糊,最极端的情况是 5 | 所有像素都是所有像素的和的平均,这样就表现为一张纯色图片 6 | */ 7 | Shader "Volume 07/SimplePostEffect/Gaussian Blur" { 8 | Properties { 9 | _MainTex("Main Texture",2D) = "white" {} 10 | _BlurSize("Blur Size",Float) = 1.0 11 | } 12 | SubShader { 13 | 14 | CGINCLUDE 15 | #include "UnityCG.cginc" 16 | 17 | // 屏幕图像 18 | sampler2D _MainTex; 19 | // 屏幕图像的纹素 20 | float4 _MainTex_TexelSize; 21 | // 采样半径 22 | float _BlurSize; 23 | 24 | struct v2f{ 25 | float4 pos : SV_POSITION; 26 | float2 uv[5] : TEXCOORD0; 27 | }; 28 | 29 | // 竖直方向的高斯模糊 30 | v2f VerticalVert(appdata_img v){ 31 | v2f o; 32 | o.pos = UnityObjectToClipPos(v.vertex); 33 | 34 | // 获得从上到下的5个uv坐标 35 | 36 | // 上上 37 | o.uv[0] = v.texcoord + _MainTex_TexelSize.y * half2(0,-2) * _BlurSize; 38 | // 上 39 | o.uv[1] = v.texcoord + _MainTex_TexelSize.y * half2(0,-1) * _BlurSize; 40 | // 原点 41 | o.uv[2] = v.texcoord; 42 | // 下 43 | o.uv[3] = v.texcoord + _MainTex_TexelSize.y * half2(0,1) * _BlurSize; 44 | // 下下 45 | o.uv[4] = v.texcoord + _MainTex_TexelSize.y * half2(0,2) * _BlurSize; 46 | 47 | return o; 48 | } 49 | 50 | // 水平方向上的高斯模糊 51 | v2f HorizontalVert(appdata_img v){ 52 | v2f o; 53 | o.pos = UnityObjectToClipPos(v.vertex); 54 | 55 | // 获得从左到右5个uv坐标 56 | 57 | // 左左 58 | o.uv[0] = v.texcoord + _MainTex_TexelSize.x * half2(-2,0) * _BlurSize; 59 | // 左 60 | o.uv[1] = v.texcoord + _MainTex_TexelSize.x * half2(-1,0) * _BlurSize; 61 | // 原点 62 | o.uv[2] = v.texcoord; 63 | // 右 64 | o.uv[3] = v.texcoord + _MainTex_TexelSize.x * half2(1,0) * _BlurSize; 65 | // 右右 66 | o.uv[4] = v.texcoord + _MainTex_TexelSize.x * half2(2,0) * _BlurSize; 67 | 68 | return o; 69 | } 70 | 71 | // 高斯模糊通用片元着色器 72 | fixed4 frag(v2f i) : SV_TARGET{ 73 | 74 | // 高斯核 75 | float weight[5] = {0.0545,0.242,0.4026,0.242,0.0545}; 76 | 77 | // 进行卷积 78 | fixed3 color = fixed3(0,0,0); 79 | 80 | for(int it=0;it<5;it++){ 81 | // 对应uv位置像素颜色 82 | fixed4 texColor = tex2D(_MainTex,i.uv[it]); 83 | 84 | color += texColor.rgb * weight[it]; 85 | } 86 | 87 | return fixed4(color,1.0); 88 | } 89 | 90 | ENDCG 91 | 92 | // 对图像进行竖直方向高斯模糊的Pass 93 | Pass { 94 | CGPROGRAM 95 | 96 | #pragma vertex VerticalVert 97 | #pragma fragment frag 98 | 99 | ENDCG 100 | } 101 | 102 | // 对图像进行水平方向高斯模糊的Pass 103 | Pass { 104 | CGPROGRAM 105 | 106 | #pragma vertex HorizontalVert 107 | #pragma fragment frag 108 | 109 | ENDCG 110 | } 111 | } 112 | FallBack "Diffuse" 113 | 114 | } -------------------------------------------------------------------------------- /Volume 07 SimplePostEffect简单屏幕后处理/MotionBlur.shader: -------------------------------------------------------------------------------- 1 | Shader "Volume 07/SimplePostEffect/Motion Blur" { 2 | Properties { 3 | _MainTex("Main Texture",2D) = "white" {} 4 | } 5 | SubShader { 6 | 7 | CGINCLUDE 8 | 9 | #include "UnityCG.cginc" 10 | 11 | sampler2D _MainTex; 12 | 13 | // 运动时每帧图像的混合参数 14 | fixed _BlurAmount; 15 | 16 | struct v2f{ 17 | float4 pos : SV_POSITION; 18 | float2 uv : TEXCOORD0; 19 | }; 20 | 21 | v2f vert(appdata_img v){ 22 | v2f o; 23 | 24 | o.pos = UnityObjectToClipPos(v.vertex); 25 | o.uv = v.texcoord; 26 | 27 | return o; 28 | } 29 | 30 | // 混合运动图像的rgb通道 31 | fixed4 fragRGB(v2f i) : SV_TARGET{ 32 | return fixed4(tex2D(_MainTex,i.uv).rgb,_BlurAmount); 33 | } 34 | 35 | // 维护图像的A通道 36 | fixed4 fragA(v2f i) : SV_TARGET{ 37 | return tex2D(_MainTex,i.uv); 38 | } 39 | 40 | ENDCG 41 | 42 | // 用于混合每一帧运动图像的Pass 43 | Pass{ 44 | Blend SrcAlpha OneMinusSrcAlpha 45 | ColorMask RGB 46 | 47 | CGPROGRAM 48 | 49 | #pragma vertex vert 50 | #pragma fragment fragRGB 51 | 52 | ENDCG 53 | } 54 | 55 | // 用于维护A通道的Pass 56 | Pass{ 57 | Blend One Zero 58 | ColorMask A 59 | 60 | CGPROGRAM 61 | 62 | #pragma vertex vert 63 | #pragma fragment fragA 64 | 65 | ENDCG 66 | } 67 | 68 | } 69 | FallBack "Diffuse" 70 | 71 | } -------------------------------------------------------------------------------- /Volume 07 SimplePostEffect简单屏幕后处理/RadialBlurEffect.shader: -------------------------------------------------------------------------------- 1 | /* 2 | 后处理·径向模糊 3 | 原理: 4 | 根据屏幕上一个指定的位置,对屏幕图像进行模糊。 5 | 离该指定位置越近的点(屏幕空间下),采样面积越小 6 | 离该指定位置越远的点,采样面积越大 7 | 8 | 9 | */ 10 | Shader "Volume 07/SimplePostEffect/RadialBlurEffect" { 11 | Properties { 12 | // 屏幕图像 13 | _MainTex("Main Texture",2D) = "white" {} 14 | // 指定径向模糊的中心点(只用x、y分量,范围在[0,1]之间) 15 | _CenterPoint("Center Point",Vector) = (0.5, 0.5, 0, 0) 16 | } 17 | SubShader { 18 | Pass { 19 | CGPROGRAM 20 | 21 | #include "UnityCG.cginc" 22 | 23 | #pragma vertex vert 24 | #pragma fragment frag 25 | 26 | struct v2f{ 27 | float4 pos : SV_POSITION; 28 | float2 uv : TEXCOORD0; 29 | }; 30 | 31 | sampler2D _MainTex; 32 | float4 _MainTex_TexelSize; 33 | fixed4 _CenterPoint; 34 | 35 | 36 | v2f vert(appdata_img v){ 37 | v2f o; 38 | 39 | o.pos = UnityObjectToClipPos(v.vertex); 40 | o.uv = v.texcoord; 41 | return o; 42 | } 43 | 44 | fixed4 frag(v2f i) : SV_TARGET{ 45 | // 取上下左右中心做均值模糊 46 | float4 color = float4(0,0,0,1); 47 | // 模糊方向 48 | float2 dir = -_CenterPoint + i.uv; 49 | // 采样半径 50 | float factor = 15; 51 | for(int it=0;it<10;it++){ 52 | color += tex2D(_MainTex,i.uv - dir*factor*it*_MainTex_TexelSize); 53 | } 54 | fixed4 finalColor = color / 10; 55 | return finalColor; 56 | } 57 | 58 | ENDCG 59 | } 60 | } 61 | FallBack "Diffuse" 62 | 63 | } -------------------------------------------------------------------------------- /Volume 07 SimplePostEffect简单屏幕后处理/Script/Bloom.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | /// 4 | /// 屏幕后处理特效之——Bloom, 5 | /// 原理: 6 | /// 1. 用一个亮度阈值LuminanceThreshold来提取图像中较亮的部分, 7 | /// 2. 使用高斯模糊对Bloom图进行模糊 8 | /// 3. 将Bloom图和原图混合 9 | /// 10 | public class Bloom : PostEffectBase{ 11 | 12 | // 用于Bloom的shader 13 | public Shader bloomShader; 14 | private Material bloomMaterial; 15 | 16 | public Material BloomMaterial { 17 | get { 18 | if (bloomMaterial == null) bloomMaterial = CheckShaderAndCreateMaterial(bloomShader); 19 | return bloomMaterial; 20 | } 21 | } 22 | 23 | // 用于高斯模糊的Shader 24 | public Shader gaussianBlurShader; 25 | private Material gaussianBlurMaterial; 26 | 27 | public Material GaussianBlurMaterial { 28 | get { 29 | if (gaussianBlurMaterial == null) gaussianBlurMaterial = CheckShaderAndCreateMaterial(gaussianBlurShader); 30 | return gaussianBlurMaterial; 31 | } 32 | } 33 | 34 | // 模糊迭代次数 35 | [Range(0,5)] 36 | public int iteration = 1; 37 | 38 | // 模糊半径 39 | [Range(1,10)] 40 | public float blurSize = 1; 41 | 42 | // RT的降分辨率比例 43 | [Range(0,5)] 44 | public int downSample = 1; 45 | 46 | // Bloom亮度阈值 47 | [Range(0f,4)] 48 | public float luminanceThroshle = 0.5f; 49 | 50 | private void OnRenderImage(RenderTexture source, RenderTexture destination) { 51 | if (GaussianBlurMaterial != null && BloomMaterial != null) { 52 | 53 | gaussianBlurMaterial.SetFloat("_BlurSize",blurSize); 54 | bloomMaterial.SetFloat("_LuminanceThreshold", luminanceThroshle); 55 | 56 | // 申请两张RT用于模糊迭代 57 | RenderTexture rt1 = RenderTexture.GetTemporary(source.width>>downSample,source.height>>downSample,0,source.format); 58 | RenderTexture rt2 = RenderTexture.GetTemporary(source.width >> downSample, source.height >> downSample, 0, source.format); 59 | 60 | // 将Bloom图扔到rt1 61 | Graphics.Blit(source,rt1,bloomMaterial,0); 62 | 63 | for (int i = 0; i < iteration; i++) { 64 | // 模糊Bloom图 65 | Graphics.Blit(rt1, rt2, gaussianBlurMaterial, 0); // 竖 66 | Graphics.Blit(rt2, rt1, gaussianBlurMaterial, 1); // 横 67 | } 68 | 69 | bloomMaterial.SetTexture("_Bloom",rt1); 70 | // 把经过模糊的Bloom图和原图进行混合 71 | Graphics.Blit(source,destination,BloomMaterial,1); 72 | 73 | // 释放内存 74 | RenderTexture.ReleaseTemporary(rt1); 75 | RenderTexture.ReleaseTemporary(rt2); 76 | } else { 77 | Graphics.Blit(source,destination); 78 | } 79 | } 80 | } 81 | 82 | -------------------------------------------------------------------------------- /Volume 07 SimplePostEffect简单屏幕后处理/Script/BrightnessSaturationAndContrast.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | public class BrightnessSaturationAndContrast : PostEffectBase{ 4 | 5 | // 亮度 6 | [Range(1,2)] 7 | public float Brightness; 8 | 9 | // 对比度 10 | [Range(0,1)] 11 | public float Contrast; 12 | 13 | // 饱和度 14 | [Range(0, 1)] 15 | public float Saturation; 16 | 17 | // 用于调整亮度、饱和度、对比度的Shader 18 | public Shader BrightnessSaturationAndContrastShader; 19 | private Material material; 20 | 21 | public Material Material { 22 | get { 23 | if (material == null) 24 | material = CheckShaderAndCreateMaterial(BrightnessSaturationAndContrastShader); 25 | return material; 26 | } 27 | } 28 | 29 | private void OnRenderImage(RenderTexture source, RenderTexture destination) { 30 | if (Material != null) { 31 | 32 | material.SetFloat("_Brightness", Brightness); 33 | material.SetFloat("_Contrast", Contrast); 34 | material.SetFloat("_Saturation", Saturation); 35 | 36 | Graphics.Blit(source,destination,material); 37 | 38 | } else { 39 | Graphics.Blit(source,destination); 40 | } 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /Volume 07 SimplePostEffect简单屏幕后处理/Script/EdgeDetection.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | public class EdgeDetection : PostEffectBase{ 4 | public Shader edgeDetectionShader; 5 | private Material material; 6 | 7 | [Range(0,1)] 8 | public float edgeOnly; 9 | 10 | public Color edgeColor; 11 | 12 | public Color backgroundColor; 13 | 14 | public Material Material { 15 | get { 16 | if (material == null) 17 | material = CheckShaderAndCreateMaterial(edgeDetectionShader); 18 | return material; 19 | } 20 | } 21 | 22 | private void OnRenderImage(RenderTexture source, RenderTexture destination) { 23 | if (Material != null) { 24 | 25 | material.SetFloat("_EdgeOnly",edgeOnly); 26 | material.SetColor("_EdgeColor",edgeColor); 27 | material.SetColor("_BackgroundColor",backgroundColor); 28 | 29 | Graphics.Blit(source,destination,material); 30 | 31 | } else { 32 | Graphics.Blit(source,destination); 33 | } 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /Volume 07 SimplePostEffect简单屏幕后处理/Script/GaussianBlur.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace Assets.Shaders.Volume_07_SimplePostEffect简单屏幕后处理.Script { 4 | public class GaussianBlur : PostEffectBase{ 5 | 6 | // 高斯模糊迭代次数 7 | [Range(0, 10)] 8 | public int iterations; 9 | 10 | // 模糊半径 11 | [Range(0f,10)] 12 | public float BlurRadius; 13 | 14 | // 降RT的分辨率 15 | [Range(0,5)] 16 | public int downSample = 2; 17 | 18 | // 用于高斯模糊的Shader 19 | public Shader gaussianBlurShader; 20 | private Material material; 21 | private Material Material { 22 | get { 23 | if (material == null) 24 | material = CheckShaderAndCreateMaterial(gaussianBlurShader); 25 | return material; 26 | } 27 | } 28 | 29 | 30 | private void OnRenderImage(RenderTexture source, RenderTexture destination) { 31 | if (Material != null) { 32 | 33 | // 在迭代过程中逐步增加采样半径 34 | Material.SetFloat("_BlurSize", BlurRadius); 35 | 36 | // 申请两张RT,用于高斯模糊迭代,右移downSample位表示,原值/2的downSample次方 37 | RenderTexture rt1 = RenderTexture.GetTemporary(source.width>>downSample,source.height>>downSample,0,source.format); 38 | RenderTexture rt2 = RenderTexture.GetTemporary(source.width>>downSample,source.height>>downSample,0,source.format); 39 | 40 | // 将原图进行竖直方向卷积,并把模糊过后的图像给rt1 41 | Graphics.Blit(source, rt2, material,0); 42 | // 将原图进行水平方向卷积,并把模糊过后的图像给rt2 43 | Graphics.Blit(rt2, rt1, material,1); 44 | 45 | for (int i=0;i_FocusDepth) ? finalColor : lerp(main,blur,clamp((_FocusDepth-depth)*_NearBlurScale,0,1)); 62 | return finalColor; 63 | } 64 | 65 | ENDCG 66 | 67 | } 68 | } 69 | FallBack "Diffuse" 70 | 71 | } -------------------------------------------------------------------------------- /Volume 08 DepthNormalTexture深度和法线纹理的简单应用/FogWithDepthTexture.shader: -------------------------------------------------------------------------------- 1 | Shader "Volume 08/Depth Normal Texture/Fog With DepthTexture" { 2 | Properties { 3 | // 屏幕图像 4 | _MainTex("Main Texture",2D) = "white" {} 5 | // 雾的强度 6 | _FogDensity("Fog Density",Float) = 1.0 7 | // 雾的颜色 8 | _FogColor("Fog Color",Color) = (1, 1, 1, 1) 9 | // 受雾影响的最小高度 10 | _FogStart("Fog Start",Float) = 0.0 11 | // 受雾影响的最大高度 12 | _FogEnd("Fog End",Float) = 1.0 13 | } 14 | SubShader { 15 | Pass { 16 | 17 | CGPROGRAM 18 | 19 | #include "UnityCG.cginc" 20 | 21 | #pragma vertex vert 22 | #pragma fragment frag 23 | 24 | // 记录四个顶点到摄像机的向量(包含方向和距离信息) 25 | float4x4 _FrustumCornersRay; 26 | 27 | sampler2D _MainTex; 28 | float4 _MainTex_TexelSize; 29 | // 深度图 30 | sampler2D _CameraDepthTexture; 31 | float _FogDensity; 32 | fixed4 _FogColor; 33 | float _FogStart; 34 | float _FogEnd; 35 | 36 | 37 | struct v2f{ 38 | float4 pos : SV_POSITION; 39 | float2 uv : TEXCOORD0; 40 | float2 uv_depth : TEXCOORD1; 41 | float4 interpolatedRay : TEXCOORD2; 42 | }; 43 | 44 | v2f vert(appdata_img v){ 45 | v2f o; 46 | o.pos = UnityObjectToClipPos(v.vertex); 47 | 48 | o.uv = v.texcoord; 49 | o.uv_depth = v.texcoord; 50 | 51 | // 处理DX和GL带来的差异 52 | #if UNITY_UV_STARTS_AT_TOP 53 | if(_MainTex_TexelSize.y<0) 54 | o.uv_depth.y = 1-o.uv_depth.y; 55 | #endif 56 | 57 | int index = 0; 58 | if(v.texcoord.x < 0.5 && v.texcoord.y < 0.5){ 59 | index = 0; 60 | }else if(v.texcoord.x > 0.5 && v.texcoord.y < 0.5){ 61 | index = 1; 62 | }else if(v.texcoord.x > 0.5 && v.texcoord.y > 0.5){ 63 | index = 2; 64 | }else{ 65 | index = 3; 66 | } 67 | 68 | #if UNITY_UV_STARTS_AT_TOP 69 | if(_MainTex_TexelSize.y<0) 70 | index = 3 - index; 71 | #endif 72 | 73 | o.interpolatedRay = _FrustumCornersRay[index]; 74 | return o; 75 | } 76 | 77 | fixed4 frag(v2f i) : SV_TARGET{ 78 | float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv_depth)); 79 | 80 | // 根据视角空间下的深度值(z值)和该像素指向摄像机的向量,求得该像素的世界坐标 81 | float3 worldPos = _WorldSpaceCameraPos + linearDepth * i.interpolatedRay.xyz; 82 | 83 | // 计算原始颜色和雾的颜色的混合系数 84 | float fogDensity = (_FogEnd-worldPos.y) / (_FogEnd-_FogStart); 85 | // 将该混合系数根据 雾的强度 进行缩放 86 | fogDensity = saturate(fogDensity * _FogDensity); 87 | 88 | fixed4 finalColor = tex2D(_MainTex,i.uv); 89 | finalColor.rgb = lerp(finalColor.rgb,_FogColor.rgb,fogDensity); 90 | 91 | return finalColor; 92 | } 93 | 94 | ENDCG 95 | 96 | } 97 | } 98 | FallBack "Diffuse" 99 | 100 | } -------------------------------------------------------------------------------- /Volume 08 DepthNormalTexture深度和法线纹理的简单应用/MotionBlurWithDepthTexture.shader: -------------------------------------------------------------------------------- 1 | // 根据深度图生成速度映射图来实现运动模糊效果 2 | Shader "Volume 08/Depth Normal Texture/Motion Blur With Depth Texture" { 3 | Properties { 4 | _MainTex("_MainTex",2D) = "white" {} 5 | _BlurSize("Blur Size",Float) = 1.0 6 | } 7 | SubShader { 8 | Pass { 9 | CGPROGRAM 10 | 11 | #include "UnityCG.cginc" 12 | 13 | #pragma vertex vert 14 | #pragma fragment frag 15 | 16 | // 屏幕图像 17 | sampler2D _MainTex; 18 | // 屏幕纹素 19 | float4 _MainTex_TexelSize; 20 | // 深度图 21 | sampler2D _CameraDepthTexture; 22 | // 当前帧的裁剪-世界变换矩阵(用于将裁剪空间下的坐标变换到世界坐标下,其实就是世界-裁剪变换矩阵的逆矩阵) 23 | float4x4 _CurrentViewProjectionInverseMatrix; 24 | // 上一帧的世界-裁剪变换矩阵(用于将坐标从世界坐标变换到裁剪空间下) 25 | float4x4 _PreviousViewProjectionMatrix; 26 | // 模糊半径 27 | half _BlurSize; 28 | 29 | struct v2f{ 30 | float4 pos : SV_POSITION; 31 | float2 uv : TEXCOORD0; 32 | float2 uv_depth : TEXCOORD1; 33 | }; 34 | 35 | v2f vert(appdata_img v){ 36 | v2f o; 37 | o.pos = UnityObjectToClipPos(v.vertex); 38 | 39 | o.uv = v.texcoord; 40 | o.uv_depth = v.texcoord; 41 | 42 | // 处理平台差异导致的图像反转问题 43 | #if UNITY_UV_STARTS_AT_TOP 44 | if(_MainTex_TexelSize.y < 0) 45 | o.uv_depth.y = 1-o.uv_depth.y; 46 | #endif 47 | 48 | return o; 49 | } 50 | 51 | fixed3 frag(v2f i) : SV_TARGET{ 52 | // 根据深度图获得当前深度 53 | float d = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv_depth); 54 | // 根据当前的uv值和深度值构建当前的NDC坐标 55 | float4 NDCPos = float4(i.uv.x*2-1,i.uv.y*2-1,d*2-1,1); 56 | // 根据view-Projection矩阵将该NDC坐标变换到世界坐标下 57 | float4 worldPos = mul(_CurrentViewProjectionInverseMatrix,NDCPos); 58 | worldPos /= worldPos.w; 59 | 60 | // 当前NDC坐标 61 | float4 currentWorldPos = NDCPos; 62 | // 获得上一帧的裁剪空间坐标 63 | float4 previousPos = mul(_PreviousViewProjectionMatrix,worldPos); 64 | previousPos /= previousPos.w; 65 | 66 | // 根据这两帧的坐标,计算当前像素的速度 67 | float2 velocity = (currentWorldPos.xy - previousPos.xy)/2.0f; 68 | 69 | // 下面根据像素速度,对该像素速度方向的邻近像素进行均值模糊 70 | float2 uv = i.uv; 71 | float4 c = tex2D(_MainTex,uv); 72 | uv += velocity * _BlurSize; 73 | for(int it=1;it<3;it++,uv+=velocity*_BlurSize){ 74 | float4 currentColor = tex2D(_MainTex,uv); 75 | c += currentColor; 76 | } 77 | c /= 3; 78 | 79 | return fixed4(c.rgb, 1.0); 80 | } 81 | 82 | ENDCG 83 | } 84 | } 85 | FallBack "Diffuse" 86 | 87 | } -------------------------------------------------------------------------------- /Volume 08 DepthNormalTexture深度和法线纹理的简单应用/NDCtoWorld/NDCtoWorld0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 08 DepthNormalTexture深度和法线纹理的简单应用/NDCtoWorld/NDCtoWorld0.png -------------------------------------------------------------------------------- /Volume 08 DepthNormalTexture深度和法线纹理的简单应用/NDCtoWorld/NDCtoWorld1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 08 DepthNormalTexture深度和法线纹理的简单应用/NDCtoWorld/NDCtoWorld1.png -------------------------------------------------------------------------------- /Volume 08 DepthNormalTexture深度和法线纹理的简单应用/NDCtoWorld/NDCtoWorld2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 08 DepthNormalTexture深度和法线纹理的简单应用/NDCtoWorld/NDCtoWorld2.png -------------------------------------------------------------------------------- /Volume 08 DepthNormalTexture深度和法线纹理的简单应用/NDCtoWorld/NDCtoWorld3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 08 DepthNormalTexture深度和法线纹理的简单应用/NDCtoWorld/NDCtoWorld3.png -------------------------------------------------------------------------------- /Volume 08 DepthNormalTexture深度和法线纹理的简单应用/NDCtoWorld/NDCtoWorld4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 08 DepthNormalTexture深度和法线纹理的简单应用/NDCtoWorld/NDCtoWorld4.png -------------------------------------------------------------------------------- /Volume 08 DepthNormalTexture深度和法线纹理的简单应用/NDCtoWorld/NDCtoWorld5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 08 DepthNormalTexture深度和法线纹理的简单应用/NDCtoWorld/NDCtoWorld5.png -------------------------------------------------------------------------------- /Volume 08 DepthNormalTexture深度和法线纹理的简单应用/NDCtoWorld/NDCtoWorld6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 08 DepthNormalTexture深度和法线纹理的简单应用/NDCtoWorld/NDCtoWorld6.png -------------------------------------------------------------------------------- /Volume 08 DepthNormalTexture深度和法线纹理的简单应用/NDCtoWorld/NDCtoWorld7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 08 DepthNormalTexture深度和法线纹理的简单应用/NDCtoWorld/NDCtoWorld7.png -------------------------------------------------------------------------------- /Volume 08 DepthNormalTexture深度和法线纹理的简单应用/NDCtoWorld/NDCtoWorld8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 08 DepthNormalTexture深度和法线纹理的简单应用/NDCtoWorld/NDCtoWorld8.png -------------------------------------------------------------------------------- /Volume 08 DepthNormalTexture深度和法线纹理的简单应用/NDCtoWorld/NDCtoWorld9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 08 DepthNormalTexture深度和法线纹理的简单应用/NDCtoWorld/NDCtoWorld9.png -------------------------------------------------------------------------------- /Volume 08 DepthNormalTexture深度和法线纹理的简单应用/NDCtoWorld/从NDC坐标转换到世界坐标的数学原理.md: -------------------------------------------------------------------------------- 1 | # 从NDC(归一化的设备坐标)坐标转换到世界坐标要点 # 2 | ## 参考资料 ## 3 | > How to go from device coordinates back to worldspace http://feepingcreature.github.io/math.html 4 | > 《Unity Shader入门精要》 5 | ## 前情提要,从运动模糊说起 6 | 产生运动模糊效果,一般来说有两种做法,第一种是将当前帧和下一帧或上一帧等等图像混合起来作为当前的屏幕图像,这样做法可以导致物体运动时会出现多个残影(因为多个帧混合起来了),可以产生运动模糊效果,但效率不高。 7 | 8 | 第二种做法是,在Shader里面利用View-Projection矩阵及其逆矩阵获得当前帧和上一帧的世界坐标,通过这两个坐标得到当前像素的运动速度及其方向,再根据这个速度,向这个像素速度方向的N个纹素进行取样并混合,从而产生模糊效果。 9 | 10 | 而这第二种做法,就是我遇到问题的地方。 11 | 总所周知,在Shader里面,可以根据深度图和当前uv坐标得到当前像素的NDC坐标,那么只要采用View-Projection(视图-裁剪)的逆矩阵就可以将NDC坐标变换到世界坐标下(正是我们所需要的值),按理说,这样应该就可以了,但是我在《Unity Shader入门精要》中看到的计算方法却是下面这样。 12 | 13 | 1 fixed4 frag(v2f i) : SV_Target{ 14 | 2 // 从深度图中获得深度 15 | 3 float d = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv_depth); 16 | 4 // 根据深度和当前uv坐标反推当前NDC坐标(注意这个坐标已经经过了齐次除法了) 17 | 5 float4 NDCPos = float4(i.uv.x*2-1, i.uv.y*2-1, d*2-1,1); 18 | 6 // 根据NDC坐标及View-Projection的逆矩阵,将NDC坐标变换到世界坐标下 19 | 7 float4 worldPos = mul(_CurrentViewProjectionInverseMatrix,NDCPos); 20 | 8 21 | 9 worldPos /= worldPos.w; 22 | .... 23 | .... 24 | .... 25 | } 26 | 27 | 其中第9行是我最疑惑的内容,按说,你进行屏幕映射要除个w分量进行齐次除法我可以理解,但是你从NDC坐标变换到世界坐标还要除于w是个什么理喔? 28 | 29 | 百思不得其解之下,翻了下作者的GitHub,嗯,终于找到了答案(感谢作者及评论区的小伙伴!!!)。 30 | 31 | 在 http://feepingcreature.github.io/math.html 中有详细的数学证明,下面我给翻译一下~~ 32 | 33 | ## 数学推导过程 34 | 35 | ### 已知条件 36 | 首先考虑从世界坐标是如何变换到NDC坐标下的。 37 | 38 | 很显然,首先世界坐标是通过VP矩阵(不用MVP,因为这里已经是世界坐标了,不用在从模型空间变换到世界空剑侠了)变换到了裁剪空间下,数学公式如下: 39 | 40 | ![Avater](./NDCtoWorld0.png) 41 | 42 | 在裁剪空间下,通过进行齐次除法将坐标变换到NDC坐标中,公式如下: 43 | 44 | ![Avater](./NDCtoWorld1.png) 45 | 46 | 最后,第三个已知条件是,世界坐标下的w分量在Unity中定义通常都是1,那么就是下面这样: 47 | 48 | ![Avater](./NDCtoWorld2.png) 49 | 50 | ### 分析问题 51 | 那么,我们的问题就变成了下面这样: 52 | 53 | 已知: 54 | ①![Avater](./NDCtoWorld2.png) 55 | 56 | ②![Avater](./NDCtoWorld1.png) 57 | 58 | ③![Avater](./NDCtoWorld0.png) 59 | 60 | 三个公式,给定一个NDC坐标和一个View-Projection矩阵,求得该NDC坐标所对应的世界坐标. 61 | 62 | ### 推导 63 | 64 | 第一步,反转公式③,如下所示: 65 | 66 | ④![Avater](./NDCtoWorld3.png) 67 | 68 | 第二步,根据公式2可得: 69 | 70 | ⑤![Avater](./NDCtoWorld4.png) 71 | 72 | 同时,又因为clip=(clipxyz,clipw),所以可以根据公式⑤,可得下面的公式: 73 | 74 | ⑥![Avater](./NDCtoWorld5.png) 75 | 76 | 根据公式④和⑥,可得下面的公式: 77 | 78 | ⑦![Avater](./NDCtoWorld6.png) 79 | 80 | 下面有个解释不是很懂~,原文如下: 81 | > And note that since matrices are linear transforms, we can pull that clip_w in front of the matrix multiply: 82 | 83 | 大致意思是说,因为矩阵是线性变化的,所以可以把clipw放到矩阵的前面.(老实说clipw不是标量么,放哪里应该都可以把?) 84 | 85 | 总之,经过上面原文的解释,公式⑦变成下面这样. 86 | 87 | ⑦![Avater](./NDCtoWorld7.png) 88 | 89 | #### clip的w分量怎么得? 90 | 那么,一个比较严重的问题就出现了,clip坐标的w分量我们并不知道欸~~~我们已知的只有NDC坐标下的(X,Y,Z)分量(分别可以通过当前像素的uv值和深度算出来). 91 | 92 | 别急,已知条件不是还有一个没用么,就下面这个已知条件. 93 | 94 | ①![Avater](./NDCtoWorld2.png) 95 | 96 | 已知世界坐标的w分量恒为1,根据公式①、⑦,在只关注运算过程和结果的w分量的情况下,有下面这个公式: 97 | 98 | ⑧![Avater](./NDCtoWorld8.png) 99 | 100 | 那么,clip的w分量就经过公式⑧算出来了。 101 | 102 | #### 最终结果 103 | 104 | 那么,已知clipw,根据公式⑧和公式⑦,得到下面的公式⑨。 105 | 106 | ⑨![Avater](./NDCtoWorld9.png) 107 | 108 | ## 总结 109 | 110 | 根据公式⑨,就可以知道为什么最后我们还要除于w分量了~~~~~因为下面的分母就是w分量啊。。 -------------------------------------------------------------------------------- /Volume 08 DepthNormalTexture深度和法线纹理的简单应用/ScanLineWithDepth.shader: -------------------------------------------------------------------------------- 1 | /* 2 | 基于深度纹理实现的扫描线效果,效果就是,一道扫描线从深度大的地方一路扫到深度小的地方(即以靠近摄像机的方向) 3 | 4 | 基本思路是: 5 | 设置深度最大阈值和最小阈值,只有当前像素在这个范围内,才会出现扫描线的特征(如高亮) 6 | */ 7 | Shader "Volume 08/Depth Normal Texture/ScanLine With Depth Texture" { 8 | Properties { 9 | _MainTex("Main Texture",2D) = "white" {} 10 | 11 | // 扫描线宽度 12 | _Width("Scan Line Width",Range(0.0,1.0)) = 0.05 13 | 14 | // 扫描线阈值,只有当前深度大于阈值小于阈值+宽度,才出现扫描线的特征 15 | // 初始值为远裁剪平面 16 | _Throsle("Scan Line Throsle",Range(0.0,1.0)) = 1 17 | 18 | // 扫描线强度 19 | _ScanLineStrength("Scan Line Strength",Float) = 1 20 | 21 | // 扫描线颜色 22 | _Color("Scan Line Color",Color) = (1, 1, 1, 1) 23 | } 24 | SubShader { 25 | Pass { 26 | 27 | CGPROGRAM 28 | 29 | #include "UnityCG.cginc" 30 | 31 | #pragma vertex vert 32 | #pragma fragment frag 33 | 34 | sampler2D _MainTex; 35 | sampler2D _CameraDepthTexture; 36 | fixed _Width; 37 | fixed _Throsle; 38 | fixed4 _Color; 39 | float _ScanLineStrength; 40 | 41 | struct v2f{ 42 | float4 pos : SV_POSITION; 43 | float2 uv : TEXCOORD0; 44 | }; 45 | 46 | v2f vert(appdata_img v){ 47 | v2f o; 48 | o.pos = UnityObjectToClipPos(v.vertex); 49 | o.uv = v.texcoord; 50 | 51 | return o; 52 | } 53 | 54 | fixed4 frag(v2f i) : SV_TARGET{ 55 | // 获得范围在[0,1]之间的观察空间下的深度 56 | fixed depth = Linear01Depth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv)); 57 | 58 | // 屏幕本身的颜色 59 | fixed4 color = tex2D(_MainTex,i.uv); 60 | 61 | if(depth>=_Throsle && depth<=_Width+_Throsle) 62 | return color * _Color * _ScanLineStrength; 63 | else 64 | return color; 65 | 66 | } 67 | 68 | ENDCG 69 | 70 | } 71 | } 72 | FallBack "Diffuse" 73 | 74 | } -------------------------------------------------------------------------------- /Volume 08 DepthNormalTexture深度和法线纹理的简单应用/ScanLineWithRebuildWorldPos.shader: -------------------------------------------------------------------------------- 1 | /* 2 | 扩散扫描线,实现思路是: 3 | 1. 根据深度纹理重建屏幕空间上每个像素的世界坐标 4 | 2. 给定一个CenterPos(中心点),两个阈值,一个最大阈值,一个最小阈值 5 | 3. 当某个像素的世界坐标与CenterPos的距离大于最小阈值,小于最大阈值使,表现出扫描线的特征 6 | 7 | 问题的关键在于利用深度图重建世界坐标 8 | */ 9 | Shader "Volume 08/Depth Normal Texture/ScanLine With Rebuild WorldPos" { 10 | Properties { 11 | _MainTex("Main Texture",2D) = "white" {} 12 | 13 | // 扫描线最小阈值(即当前像素的世界坐标与中心点的距离只要大于这个阈值,就会表现出扫描线的特征) 14 | _Throsle("Throsle",Range(0.0,10)) = 5 15 | 16 | // 扫描线宽度(即当前像素的世界坐标与中心点的距离只要小于这个_Throsle+_Width且大于_Throsle,就会表现出扫描线的特征) 17 | _Width("Scan Line Width",Range(0.0,10)) = 0.1 18 | 19 | // 扫描线颜色 20 | _Color("Scan Line Color",Color) = (1, 1, 1, 1) 21 | 22 | // 扫描线强度 23 | _Strength("Scan Line Strength",Range(0,10)) = 1 24 | } 25 | SubShader { 26 | Pass { 27 | 28 | CGPROGRAM 29 | 30 | #include "UnityCG.cginc" 31 | 32 | #pragma vertex vert 33 | #pragma fragment frag 34 | 35 | // 屏幕图像 36 | sampler2D _MainTex; 37 | float _Throsle; 38 | float _Width; 39 | float _Strength; 40 | fixed4 _Color; 41 | 42 | // 深度图 43 | sampler2D _CameraDepthTexture; 44 | 45 | // 该扩散扫描线的中心点(世界坐标下) 46 | float4 _CenterPos; 47 | 48 | // 一个4*4的矩阵,每一行包含从当前摄像机触发指向近裁剪平面四个顶点的一条射线 49 | float4x4 _FrustumCornersRay; 50 | 51 | struct v2f{ 52 | float4 pos : SV_POSITION; 53 | float2 uv : TEXCOORD0; 54 | 55 | // 从当前摄像机出发,指向当前顶点的一条射线(包含方向和距离信息,不可归一化) 56 | float4 interpolatedRay : TEXCOORD1; 57 | }; 58 | 59 | v2f vert(appdata_img v){ 60 | v2f o; 61 | o.pos = UnityViewToClipPos(v.vertex); 62 | o.uv = v.texcoord; 63 | 64 | int index = 0; 65 | // 判断当前顶点偏向于近裁剪平面四个点中的哪个点 66 | if(v.texcoord.x<0.5 && v.texcoord.y < 0.5){ 67 | index = 0; 68 | }else if(v.texcoord.x>0.5 && v.texcoord.y>0.5){ 69 | index = 1; 70 | }else if(v.texcoord.x>0.5 && v.texcoord.y<0.5){ 71 | index = 2; 72 | }else{ 73 | index = 3; 74 | } 75 | 76 | o.interpolatedRay = _FrustumCornersRay[index]; 77 | return o; 78 | } 79 | 80 | fixed4 frag(v2f i) : SV_TARGET{ 81 | // 获得在视角空间下的深度值 82 | float depth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv)); 83 | // 根据深度值及射线,重建当前像素的世界坐标 84 | float3 worldPos = _WorldSpaceCameraPos + depth * i.interpolatedRay.xyz; 85 | 86 | // 得到当前像素(世界坐标)与中心点(世界坐标)的距离 87 | float distances = distance(worldPos,_CenterPos.xyz); 88 | 89 | // 当前像素颜色 90 | fixed4 color = tex2D(_MainTex,i.uv); 91 | 92 | // 判断当前像素是否在扫描线上,如果在,那么表现扫描线的特征 93 | if(distances>_Throsle && distances<_Throsle+_Width) 94 | return color * _Color * _Strength; 95 | return color; 96 | } 97 | 98 | ENDCG 99 | 100 | } 101 | } 102 | FallBack "Diffuse" 103 | 104 | } -------------------------------------------------------------------------------- /Volume 08 DepthNormalTexture深度和法线纹理的简单应用/Script/DepthOfField.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | public class DepthOfField : PostEffectBase { 4 | // 用于高斯模糊的Shader 5 | public Shader blurShader; 6 | private Material blurMaterial; 7 | public Material BlurMaterial { 8 | get { 9 | if (blurMaterial == null) blurMaterial = CheckShaderAndCreateMaterial(blurShader); 10 | return blurMaterial; 11 | } 12 | } 13 | 14 | public Material DepthOfFieldMaterial { 15 | get { 16 | if (depthOfFieldMaterial == null) depthOfFieldMaterial = CheckShaderAndCreateMaterial(depthOfFieldShader); 17 | return depthOfFieldMaterial; 18 | } 19 | } 20 | 21 | // 用于景深效果的Shader 22 | public Shader depthOfFieldShader; 23 | private Material depthOfFieldMaterial; 24 | 25 | // 高斯模糊迭代次数 26 | [Range(0, 10)] 27 | public int iterations; 28 | 29 | // 模糊半径 30 | [Range(0f, 10)] 31 | public float BlurRadius; 32 | 33 | // 降RT的分辨率 34 | [Range(0, 5)] 35 | public int downSample = 2; 36 | 37 | // 焦点的深度值 38 | [Range(-100,100)] 39 | public float focusDepth; 40 | 41 | [Range(1,5)] 42 | public float FarBlurScale; 43 | 44 | [Range(1,5)] 45 | public float NearBlurScale; 46 | 47 | private void OnEnable() { 48 | Camera.main.depthTextureMode |= DepthTextureMode.Depth; 49 | } 50 | private void OnDisable() { 51 | Camera.main.depthTextureMode &= ~DepthTextureMode.Depth; 52 | } 53 | 54 | private void OnRenderImage(RenderTexture source, RenderTexture destination) { 55 | if (BlurMaterial != null && DepthOfFieldMaterial != null) { 56 | 57 | // 在迭代过程中逐步增加采样半径 58 | blurMaterial.SetFloat("_BlurSize", BlurRadius); 59 | 60 | // 申请两张RT,用于高斯模糊迭代,右移downSample位表示,原值/2的downSample次方 61 | RenderTexture rt1 = RenderTexture.GetTemporary(source.width >> downSample, source.height >> downSample, 0, source.format); 62 | RenderTexture rt2 = RenderTexture.GetTemporary(source.width >> downSample, source.height >> downSample, 0, source.format); 63 | 64 | //============================================= 65 | // 第一步,先对原图进行一个高斯模糊,并放到RT1中 66 | 67 | // 将原图进行竖直方向卷积,并把模糊过后的图像给rt1 68 | Graphics.Blit(source, rt2, blurMaterial, 0); 69 | // 将原图进行水平方向卷积,并把模糊过后的图像给rt2 70 | Graphics.Blit(rt2, rt1, blurMaterial, 1); 71 | 72 | for (int i = 0; i < iterations; i++) { 73 | // 在迭代过程中逐步增加采样半径 74 | blurMaterial.SetFloat("_BlurSize", BlurRadius * i + 1.0f); 75 | Graphics.Blit(rt1, rt2, blurMaterial, 0); 76 | Graphics.Blit(rt2, rt1, blurMaterial, 1); 77 | } 78 | 79 | //=========================================================== 80 | // 第二步,将原图与高斯模糊后的图根据距离焦点的深度值进行混合 81 | 82 | depthOfFieldMaterial.SetTexture("_BlurTex",rt1); 83 | depthOfFieldMaterial.SetFloat("_FarBlurScale",FarBlurScale); 84 | depthOfFieldMaterial.SetFloat("_NearBlurScale",NearBlurScale); 85 | 86 | // 被归一化的、观察空间下的深度值 87 | float depthOfView01 = 88 | Camera.main.WorldToViewportPoint( 89 | (focusDepth - Camera.main.nearClipPlane) 90 | * Camera.main.transform.forward + 91 | Camera.main.transform.position 92 | ).z 93 | / 94 | ( 95 | Camera.main.farClipPlane 96 | - Camera.main.nearClipPlane 97 | ); 98 | 99 | // 将焦点的深度值归一化 100 | depthOfFieldMaterial.SetFloat("_FocusDepth",depthOfView01); 101 | 102 | Graphics.Blit(source,destination,depthOfFieldMaterial); 103 | 104 | 105 | // 释放RT申请的内存 106 | RenderTexture.ReleaseTemporary(rt1); 107 | RenderTexture.ReleaseTemporary(rt2); 108 | } else { 109 | Graphics.Blit(source,destination); 110 | } 111 | } 112 | } 113 | 114 | -------------------------------------------------------------------------------- /Volume 08 DepthNormalTexture深度和法线纹理的简单应用/Script/FogWithDepthTexture.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | public class FogWithDepthTexture : PostEffectBase { 6 | public Shader fogShader; 7 | private Material material; 8 | 9 | public Material Material { 10 | get { 11 | if (material == null) 12 | material = CheckShaderAndCreateMaterial(fogShader); 13 | return material; 14 | } 15 | } 16 | 17 | public Transform CameraTransform { 18 | get { 19 | return Camera.main.transform; 20 | } 21 | } 22 | 23 | // 雾的强度 24 | [Range(0.0f,3.0f)] 25 | public float fogDensity = 1.0f; 26 | 27 | public Color fogColor = Color.white; 28 | 29 | public float fogStart = 0f; 30 | public float fogEnd = 2.0f; 31 | 32 | private void OnEnable() { 33 | // 设置当前摄像机可以获取深度图 34 | Camera.main.depthTextureMode |= DepthTextureMode.Depth; 35 | } 36 | 37 | private void OnRenderImage(RenderTexture source, RenderTexture destination) { 38 | if (Material != null) { 39 | 40 | Matrix4x4 frustumConrners = Matrix4x4.identity; 41 | 42 | float fov = Camera.main.fieldOfView; 43 | float near = Camera.main.nearClipPlane; 44 | float far = Camera.main.farClipPlane; 45 | float aspect = Camera.main.aspect; 46 | 47 | float halfHeight = near * Mathf.Tan(fov*0.5f*Mathf.Deg2Rad); 48 | Vector3 toRight = CameraTransform.right * halfHeight * aspect; 49 | Vector3 toTop = CameraTransform.up * halfHeight; 50 | 51 | Vector3 topLeft = CameraTransform.forward * near + toTop - toRight; 52 | float scale = topLeft.magnitude / near; 53 | 54 | topLeft.Normalize(); 55 | topLeft *= scale; 56 | 57 | Vector3 topRight = CameraTransform.forward * near + toRight + toTop; 58 | topRight.Normalize(); 59 | topRight *= scale; 60 | 61 | Vector3 buttomLeft = CameraTransform.forward * near - toTop - toRight; 62 | buttomLeft.Normalize(); 63 | buttomLeft *= scale; 64 | 65 | Vector3 buttomRight = CameraTransform.forward * near + toRight - toTop; 66 | buttomRight.Normalize(); 67 | buttomRight *= scale; 68 | 69 | frustumConrners.SetRow(0,buttomLeft); 70 | frustumConrners.SetRow(1,buttomRight); 71 | frustumConrners.SetRow(2,topRight); 72 | frustumConrners.SetRow(3,topLeft); 73 | 74 | material.SetMatrix("_FrustumCornersRay",frustumConrners); 75 | 76 | material.SetFloat("_FogDensity",fogDensity); 77 | material.SetColor("_FogColor",fogColor); 78 | material.SetFloat("_FogStart",fogStart); 79 | material.SetFloat("_FogEnd",fogEnd); 80 | 81 | Graphics.Blit(source,destination,material); 82 | } else { 83 | Graphics.Blit(source,destination); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Volume 08 DepthNormalTexture深度和法线纹理的简单应用/Script/MotionBlurWithDepthTexture.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | public class MotionBlurWithDepthTexture : PostEffectBase{ 6 | public Shader motionBlurWithDepthTextureShader; 7 | private Material material; 8 | 9 | public Material Material { 10 | get { 11 | if (material == null) 12 | material = CheckShaderAndCreateMaterial(motionBlurWithDepthTextureShader); 13 | return material; 14 | } 15 | } 16 | 17 | [Range(0.0f,1.0f)] 18 | public float blurSize = 0.5f; 19 | 20 | private void OnEnable() { 21 | Camera.main.depthTextureMode |= DepthTextureMode.Depth; 22 | } 23 | 24 | // View-Projection变换矩阵 25 | private Matrix4x4 previousViewProjectionMatrix; 26 | 27 | private void OnRenderImage(RenderTexture source, RenderTexture destination) { 28 | if (Material != null) { 29 | 30 | material.SetFloat("_BlurSize",blurSize); 31 | 32 | material.SetMatrix("_PreviousViewProjectionMatrix", previousViewProjectionMatrix); 33 | // 当前帧的View-Projection变换矩阵 34 | Matrix4x4 currentViewProjectionMatrix = Camera.main.projectionMatrix * Camera.main.worldToCameraMatrix; 35 | // 当前帧的Projection-View变换矩阵 36 | Matrix4x4 currentViewProjectionInverseMatrix = currentViewProjectionMatrix.inverse; 37 | material.SetMatrix("_CurrentViewProjectionInverseMatrix",currentViewProjectionInverseMatrix); 38 | // 上一帧的View-Projection变换矩阵 39 | previousViewProjectionMatrix = currentViewProjectionMatrix; 40 | 41 | Graphics.Blit(source,destination,material); 42 | 43 | } else { 44 | Graphics.Blit(source,destination); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Volume 08 DepthNormalTexture深度和法线纹理的简单应用/Script/ScanLineWithDepthTexture.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | public class ScanLineWithDepthTexture : PostEffectBase{ 4 | public Shader scanLineShader; 5 | private Material material; 6 | 7 | // 扫描线阈值 8 | [Range(0.0f, 1.0f)] 9 | public float scanLineThrosle = 0; 10 | 11 | // 扫描线宽度 12 | [Range(0.0f, 1.0f)] 13 | public float scanLineWidth = 0.05f; 14 | 15 | // 扫描线强度 16 | [Range(0,10)] 17 | public float scanLineStrength = 1; 18 | 19 | // 扫描线颜色 20 | public Color scanLineColor = Color.white; 21 | 22 | public Material Material { 23 | get { 24 | if (material == null) material = CheckShaderAndCreateMaterial(scanLineShader); 25 | return material; 26 | } 27 | } 28 | 29 | private void OnRenderImage(RenderTexture source, RenderTexture destination) { 30 | if (Material != null) { 31 | 32 | material.SetFloat("_Width",scanLineWidth); 33 | material.SetFloat("_Throsle",scanLineThrosle); 34 | material.SetFloat("_ScanLineStrength",scanLineStrength); 35 | material.SetColor("_Color",scanLineColor); 36 | 37 | Graphics.Blit(source,destination,material); 38 | 39 | } else { 40 | Graphics.Blit(source,destination); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Volume 08 DepthNormalTexture深度和法线纹理的简单应用/Script/ScanLineWithRebuildWorldPos.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | public class ScanLineWithRebuildWorldPos : PostEffectBase{ 4 | 5 | public Shader scanLineShader; 6 | private Material material; 7 | 8 | public Material Material { 9 | get { 10 | if (material == null) material = CheckShaderAndCreateMaterial(scanLineShader); 11 | return material; 12 | } 13 | } 14 | 15 | // 扫描线阈值 16 | [Range(0.0f, 10.0f)] 17 | public float scanLineThrosle = 0; 18 | 19 | // 扫描线宽度 20 | [Range(0.0f, 10.0f)] 21 | public float scanLineWidth = 0.05f; 22 | 23 | // 扫描线强度 24 | [Range(0, 10)] 25 | public float scanLineStrength = 1; 26 | 27 | // 扫描线颜色 28 | public Color scanLineColor = Color.white; 29 | 30 | // 扫描线中心点 31 | public Vector3 centerPos = Vector3.zero; 32 | 33 | private void OnEnable() { 34 | Camera.main.depthTextureMode |= DepthTextureMode.Depth; 35 | } 36 | 37 | private void OnRenderImage(RenderTexture source, RenderTexture destination) { 38 | if (Material == null) { 39 | Graphics.Blit(source, destination); 40 | } else { 41 | 42 | // 得到当前摄像机的fov角度, 43 | // 近裁剪平面距离,远裁剪平面距离, 44 | // 以及用于获取近裁剪平面和远裁剪平面的aspect比值 45 | float fov = Camera.main.fieldOfView; 46 | float near = Camera.main.nearClipPlane; 47 | float far = Camera.main.farClipPlane; 48 | float aspect = Camera.main.aspect; 49 | 50 | float halfHeight = near * Mathf.Tan(fov*0.5f*Mathf.Deg2Rad); 51 | Vector3 toRight = Camera.main.transform.right * halfHeight * aspect; 52 | Vector3 toTop = Camera.main.transform.up * halfHeight; 53 | 54 | Vector3 topLeft = Camera.main.transform.forward * near + toTop - toRight; 55 | float scale = topLeft.magnitude / near; 56 | topLeft.Normalize(); 57 | topLeft *= scale; 58 | 59 | 60 | Vector3 topRight = Camera.main.transform.forward * near + toRight + toTop; 61 | topRight.Normalize(); 62 | topRight *= scale; 63 | 64 | 65 | Vector3 bottomLeft = Camera.main.transform.forward * near - toRight - toTop; 66 | bottomLeft.Normalize(); 67 | bottomLeft *= scale; 68 | 69 | 70 | Vector3 bottomRight = Camera.main.transform.forward * near + toRight - toTop; 71 | bottomRight.Normalize(); 72 | bottomRight *= scale; 73 | 74 | Matrix4x4 frustumConrners = Matrix4x4.identity; 75 | 76 | frustumConrners.SetRow(0,bottomLeft); 77 | frustumConrners.SetRow(1,bottomRight); 78 | frustumConrners.SetRow(2,topRight); 79 | frustumConrners.SetRow(3,topLeft); 80 | 81 | material.SetMatrix("_FrustumCornersRay",frustumConrners); 82 | material.SetFloat("_Width",scanLineWidth); 83 | material.SetFloat("_Throsle",scanLineThrosle); 84 | material.SetFloat("_Strength",scanLineStrength); 85 | material.SetColor("_Color",scanLineColor); 86 | material.SetVector("_CenterPos",centerPos); 87 | 88 | Graphics.Blit(source,destination,material); 89 | } 90 | } 91 | } 92 | 93 | -------------------------------------------------------------------------------- /Volume 08 DepthNormalTexture深度和法线纹理的简单应用/Script/ShowDepth.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/Volume 08 DepthNormalTexture深度和法线纹理的简单应用/Script/ShowDepth.cs -------------------------------------------------------------------------------- /Volume 08 DepthNormalTexture深度和法线纹理的简单应用/ShowDepth.shader: -------------------------------------------------------------------------------- 1 | /* 2 | 利用后处理效果,在屏幕上显示每个点的深度值 3 | */ 4 | Shader "Volume 08/Depth Normal Texture/Show Depth Texture" { 5 | Properties { 6 | _MainTex("Main Texture",2D) = "white" {} 7 | } 8 | SubShader { 9 | Pass { 10 | CGPROGRAM 11 | 12 | #include "UnityCG.cginc" 13 | 14 | #pragma vertex vert 15 | #pragma fragment frag 16 | 17 | sampler2D _MainTex; 18 | sampler2D _CameraDepthTexture; 19 | 20 | struct v2f{ 21 | float4 pos : SV_POSITION; 22 | float2 uv : TEXCOORD0; 23 | }; 24 | 25 | v2f vert(appdata_img v){ 26 | v2f o; 27 | o.pos = UnityObjectToClipPos(v.vertex); 28 | o.uv = v.texcoord; 29 | return o; 30 | } 31 | 32 | fixed4 frag(v2f i) : SV_TARGET{ 33 | float depthV = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv); 34 | // 将该深度变换到[0,1]范围内(在近裁剪平面上就是0,在远裁剪平面上就是1) 35 | fixed depth = Linear01Depth(depthV); 36 | 37 | // 在屏幕空间上显示深度图(近的小,远的大,即深度越大的地方越亮) 38 | return fixed4(1-depth,1-depth,1-depth,1); 39 | } 40 | 41 | ENDCG 42 | } 43 | } 44 | FallBack "Diffuse" 45 | 46 | } -------------------------------------------------------------------------------- /Volume 08 DepthNormalTexture深度和法线纹理的简单应用/ShowDepthNotLinear.shader: -------------------------------------------------------------------------------- 1 | /* 2 | 利用后处理效果,在屏幕上显示每个点的非线性深度值 3 | */ 4 | Shader "Volume 08/Depth Normal Texture/Show Depth Texture With Not Linear" { 5 | Properties { 6 | _MainTex("Main Texture",2D) = "white" {} 7 | } 8 | SubShader { 9 | Pass { 10 | CGPROGRAM 11 | 12 | #include "UnityCG.cginc" 13 | 14 | #pragma vertex vert 15 | #pragma fragment frag 16 | 17 | sampler2D _MainTex; 18 | sampler2D _CameraDepthTexture; 19 | 20 | struct v2f{ 21 | float4 pos : SV_POSITION; 22 | float2 uv : TEXCOORD0; 23 | }; 24 | 25 | v2f vert(appdata_img v){ 26 | v2f o; 27 | o.pos = UnityObjectToClipPos(v.vertex); 28 | o.uv = v.texcoord; 29 | return o; 30 | } 31 | 32 | fixed4 frag(v2f i) : SV_TARGET{ 33 | 34 | float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv); 35 | 36 | // 在屏幕空间上显示深度图 37 | return fixed4(1-depth,1-depth,1-depth,1); 38 | } 39 | 40 | ENDCG 41 | } 42 | } 43 | FallBack "Diffuse" 44 | 45 | } -------------------------------------------------------------------------------- /Volume 09 EdgeDetection详解边缘检测/BulletTimeStartWithEdgeDetection.shader: -------------------------------------------------------------------------------- 1 | Shader "Volume xx/PostEffect/BulletTimeStartWithEdgeDetection" { 2 | Properties { 3 | _MainTex("MainTex",2D) = "white" {} 4 | // 只显示边缘线的程度,为1时,只显示边缘线,为0时,不显示 5 | _EdgeOnly("Edge Only",Float) = 1.0 6 | // 边缘颜色 7 | _EdgeColor("Edge Color",Color) = (0, 0, 0, 1) 8 | // 背景颜色 9 | _BackgroundColor("Background Color",Color) = (1, 1, 1, 1) 10 | // 进行卷积运算时,采样的范围 11 | _SampleDistance("Sample Distance",Float) = 1.0 12 | // _Sensitivity的xy分量分别对应了法线和深度的检测灵敏度,ZW分量没用 13 | _Sensitivity("Sensitivity",Vector) = (1, 1, 1, 1) 14 | // 扫描线最小阈值(即当前像素的世界坐标与中心点的距离只要大于这个阈值,就会表现出扫描线的特征) 15 | _Throsle("Throsle",Float) = 5 16 | } 17 | SubShader { 18 | Pass { 19 | CGPROGRAM 20 | 21 | #include "UnityCG.cginc" 22 | 23 | #pragma vertex vert 24 | #pragma fragment frag 25 | 26 | sampler2D _MainTex; 27 | float4 _MainTex_TexelSize; 28 | float _EdgeOnly; 29 | fixed4 _EdgeColor; 30 | fixed4 _BackgroundColor; 31 | float _SampleDistance; 32 | float4 _Sensitivity; 33 | sampler2D _CameraDepthNormalsTexture; // Unity提供的深度/法线图 34 | float _Throsle; 35 | 36 | // 该扩散扫描线的中心点(世界坐标下) 37 | float4 _CenterPos; 38 | 39 | // 用于重建世界坐标 40 | // 一个4*4的矩阵,每一行包含从当前摄像机触发指向近裁剪平面四个顶点的一条射线 41 | float4x4 _FrustumCornersRay; 42 | 43 | // 噪声图 44 | sampler2D _NoiseTex; 45 | // Mask图 46 | sampler2D _MaskTex; 47 | 48 | // 摄像机距离远裁剪平面的距离值 49 | float _Far; 50 | 51 | struct v2f{ 52 | float4 pos : SV_POSITION; 53 | 54 | // 从当前摄像机出发,指向当前顶点的一条射线(包含方向和距离信息,不可归一化) 55 | float4 interpolatedRay : TEXCOORD0; 56 | 57 | // 用于在纹理周围进行法线和深度的采样 58 | float2 uv[5] : TEXCOORD1; 59 | }; 60 | 61 | v2f vert(appdata_img v){ 62 | v2f o; 63 | o.pos = UnityObjectToClipPos(v.vertex); 64 | 65 | half2 uv = v.texcoord; 66 | o.uv[0] = uv; 67 | 68 | o.uv[1] = uv + _MainTex_TexelSize.xy * half2(1,1) * _SampleDistance; 69 | o.uv[2] = uv + _MainTex_TexelSize.xy * half2(-1,-1) * _SampleDistance; 70 | o.uv[3] = uv + _MainTex_TexelSize.xy * half2(-1,1) * _SampleDistance; 71 | o.uv[4] = uv + _MainTex_TexelSize.xy * half2(1,-1) * _SampleDistance; 72 | 73 | int index = 0; 74 | // 判断当前顶点偏向于近裁剪平面四个点中的哪个点 75 | if(v.texcoord.x<0.5 && v.texcoord.y < 0.5){ 76 | index = 0; 77 | }else if(v.texcoord.x>0.5 && v.texcoord.y>0.5){ 78 | index = 1; 79 | }else if(v.texcoord.x>0.5 && v.texcoord.y<0.5){ 80 | index = 2; 81 | }else{ 82 | index = 3; 83 | } 84 | 85 | o.interpolatedRay = _FrustumCornersRay[index]; 86 | 87 | return o; 88 | } 89 | 90 | fixed CheckSame(fixed4 center,fixed4 sample){ 91 | fixed2 centerNormal = center.xy; 92 | float centerDepth = DecodeFloatRG(center.zw); 93 | fixed2 sampleNormal = sample.xy; 94 | float sampleDepth = DecodeFloatRG(sample.zw); 95 | 96 | fixed2 diffNormal = abs(centerNormal - sampleNormal) * _Sensitivity.x; 97 | int isSameNormal = (diffNormal.x + diffNormal.y) < 0.1; 98 | 99 | float diffDepth = abs(centerDepth - sampleDepth) * _Sensitivity.y; 100 | int isSameDepath = diffDepth < 0.1 * centerDepth; 101 | 102 | return isSameNormal*isSameDepath ? 1.0 : 0.0; 103 | } 104 | 105 | fixed4 frag(v2f i) : SV_TARGET{ 106 | fixed4 sample1 = tex2D(_CameraDepthNormalsTexture,i.uv[1]); 107 | fixed4 sample2 = tex2D(_CameraDepthNormalsTexture,i.uv[2]); 108 | fixed4 sample3 = tex2D(_CameraDepthNormalsTexture,i.uv[3]); 109 | fixed4 sample4 = tex2D(_CameraDepthNormalsTexture,i.uv[4]); 110 | 111 | // edge为0时表示两点之间存在一条边界 112 | fixed edge = 1.0; 113 | 114 | edge *= CheckSame(sample1,sample2); 115 | edge *= CheckSame(sample3,sample4); 116 | 117 | // 获得在视角空间下的深度值 118 | float depth = DecodeFloatRG(tex2D(_MainTex,i.uv[0]).zw); // 此时depth的深度在[0,1]之间,要乘于远裁剪平面将其变换到视角空间下 119 | depth *= _Far; 120 | 121 | // 根据深度值及射线,重建当前像素的世界坐标 122 | float3 worldPos = _WorldSpaceCameraPos + depth * i.interpolatedRay.xyz; 123 | 124 | // 得到当前像素(世界坐标)与中心点(世界坐标)的距离 125 | float distances = distance(worldPos,_CenterPos.xyz) + tex2D(_NoiseTex,i.uv[0]) * 5; 126 | 127 | // 当前像素颜色 128 | fixed4 color = tex2D(_MainTex,i.uv[0]); 129 | 130 | fixed4 withEdgeColor = lerp(_EdgeColor,color,edge); 131 | fixed4 onlyEdgeColor = lerp(_EdgeColor,_BackgroundColor,edge); 132 | 133 | // Mask图的r通道决定了当前像素是否取消边缘检测效果 134 | fixed isMaskOnlyEdgeColor = tex2D(_MaskTex,i.uv[0]).r; 135 | 136 | fixed4 finalColor = lerp(withEdgeColor,onlyEdgeColor,_EdgeOnly); 137 | 138 | finalColor = lerp(finalColor,withEdgeColor,isMaskOnlyEdgeColor); 139 | 140 | // 判断当前像素是否在扫描线上,如果在,那么表现扫描线的特征 141 | if(distances>_Throsle) 142 | return withEdgeColor; 143 | 144 | return finalColor; 145 | } 146 | 147 | ENDCG 148 | } 149 | } 150 | FallBack "Diffuse" 151 | 152 | } -------------------------------------------------------------------------------- /Volume 09 EdgeDetection详解边缘检测/Script/BulletTimeStartWithEdgeDetection.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.Rendering; 3 | 4 | /// 5 | /// 用于子弹时间【1】的屏幕后处理脚本 6 | /// 7 | /// 基于世界坐标上某一点,从该点开始, 8 | /// 将距离该点由近到远的物体全部刷上边缘检测(或者从黑白的边缘线中着色), 9 | /// 同时,支持在全黑白的边缘检测图像中,由一个Mask图,将某个特定单位上色 10 | /// 11 | public class BulletTimeStartWithEdgeDetection : PostEffectBase{ 12 | public Shader shader; 13 | private Material material; 14 | public Material Material { 15 | get { 16 | if (material == null) 17 | material = CheckShaderAndCreateMaterial(shader); 18 | return material; 19 | } 20 | } 21 | 22 | public Material OutlineSoliderMaterial { 23 | get { 24 | if (outlineSoliderMaterial == null) outlineSoliderMaterial = CheckShaderAndCreateMaterial(outlineSoliderColorShader); 25 | return outlineSoliderMaterial; 26 | } 27 | } 28 | 29 | // 用于渲染描边人物的纯色Shader 30 | public Shader outlineSoliderColorShader; 31 | private Material outlineSoliderMaterial; 32 | 33 | // 要进行描边的目标对象 34 | public GameObject targetObject; 35 | 36 | // CommandBuffer的渲染目标 37 | private RenderTexture maskTexture; 38 | private CommandBuffer commandBuffer; 39 | 40 | 41 | // 是否只显示边缘色 42 | [Range(0, 1f)] 43 | public float edgesOnly = 0.0f; 44 | // 边缘颜色 45 | public Color edgeColor = Color.black; 46 | // 背景色 47 | public Color backgroundColor = Color.white; 48 | // 描边的宽度 49 | public float sampleDistance = 1.0f; 50 | 51 | // 深度及法线灵敏度,将会影响当邻域的深度值或法线值相差多少时,会被认为存在一条边界 52 | public float sensitivityDepth = 1.0f; 53 | public float sensitivityNormals = 1.0f; 54 | 55 | // Mask图,用于取消某些单位的描边 56 | private Texture maskTex; 57 | 58 | // 噪声图,用于将扩散的扫描边扩散方式变得随机 59 | public Texture noiseTex; 60 | 61 | // 扫描线阈值 62 | public float scanLineThrosle = 0; 63 | 64 | // 扫描线中心点 65 | public Vector3 centerPos = Vector3.zero; 66 | 67 | private void OnEnable() { 68 | GetComponent().depthTextureMode |= DepthTextureMode.DepthNormals; 69 | 70 | // 用于存放纯色Mask图 71 | maskTexture = RenderTexture.GetTemporary(Screen.width, Screen.height, 0); 72 | 73 | // 创建用于渲染纯色目标RT的CommandBuffer 74 | commandBuffer = new CommandBuffer(); 75 | commandBuffer.SetRenderTarget(maskTexture); 76 | commandBuffer.ClearRenderTarget(true, true, Color.black); 77 | // 将目标物体的所有render都扔到CommandBuffer里面去 78 | Renderer[] renderers = targetObject.GetComponentsInChildren(); 79 | foreach (Renderer r in renderers) { 80 | commandBuffer.DrawRenderer(r, OutlineSoliderMaterial); 81 | } 82 | } 83 | 84 | private void OnDisable() { 85 | if (maskTexture != null) { 86 | RenderTexture.ReleaseTemporary(maskTexture); 87 | maskTexture = null; 88 | } 89 | if (commandBuffer != null) { 90 | commandBuffer.Release(); 91 | commandBuffer = null; 92 | } 93 | } 94 | 95 | // 在所有不透明物体渲染之后调用 96 | [ImageEffectOpaque] 97 | private void OnRenderImage(RenderTexture source, RenderTexture destination) { 98 | if (Material != null) { 99 | // 通过Graphic执行Command Buffer 100 | Graphics.ExecuteCommandBuffer(commandBuffer); 101 | 102 | material.SetFloat("_EdgeOnly", edgesOnly); 103 | material.SetColor("_EdgeColor", edgeColor); 104 | material.SetColor("_BackgroundColor", backgroundColor); 105 | material.SetFloat("_SampleDistance", sampleDistance); 106 | material.SetVector("_Sensitivity", new Vector4(sensitivityNormals, sensitivityDepth, 0, 0)); 107 | material.SetTexture("_MaskTex", maskTex); 108 | material.SetFloat("_Throsle", scanLineThrosle); 109 | material.SetTexture("_NoiseTex",noiseTex); 110 | material.SetVector("_CenterPos", centerPos); 111 | 112 | // 得到当前摄像机的fov角度, 113 | // 近裁剪平面距离,远裁剪平面距离, 114 | // 以及用于获取近裁剪平面和远裁剪平面的aspect比值 115 | float fov = Camera.main.fieldOfView; 116 | float near = Camera.main.nearClipPlane; 117 | float far = Camera.main.farClipPlane; 118 | float aspect = Camera.main.aspect; 119 | 120 | float halfHeight = near * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad); 121 | Vector3 toRight = Camera.main.transform.right * halfHeight * aspect; 122 | Vector3 toTop = Camera.main.transform.up * halfHeight; 123 | 124 | Vector3 topLeft = Camera.main.transform.forward * near + toTop - toRight; 125 | float scale = topLeft.magnitude / near; 126 | topLeft.Normalize(); 127 | topLeft *= scale; 128 | 129 | 130 | Vector3 topRight = Camera.main.transform.forward * near + toRight + toTop; 131 | topRight.Normalize(); 132 | topRight *= scale; 133 | 134 | 135 | Vector3 bottomLeft = Camera.main.transform.forward * near - toRight - toTop; 136 | bottomLeft.Normalize(); 137 | bottomLeft *= scale; 138 | 139 | 140 | Vector3 bottomRight = Camera.main.transform.forward * near + toRight - toTop; 141 | bottomRight.Normalize(); 142 | bottomRight *= scale; 143 | 144 | Matrix4x4 frustumConrners = Matrix4x4.identity; 145 | 146 | frustumConrners.SetRow(0, bottomLeft); 147 | frustumConrners.SetRow(1, bottomRight); 148 | frustumConrners.SetRow(2, topRight); 149 | frustumConrners.SetRow(3, topLeft); 150 | 151 | 152 | // 设置Mask图 153 | material.SetTexture("_MaskTex",maskTexture); 154 | material.SetMatrix("_FrustumCornersRay", frustumConrners); 155 | material.SetFloat("_Far",far); 156 | 157 | Graphics.Blit(source, destination, material); 158 | } else { 159 | Graphics.Blit(source, destination); 160 | } 161 | } 162 | } 163 | 164 | -------------------------------------------------------------------------------- /Volume 09 EdgeDetection详解边缘检测/edgeDetectionNormalAdnDepth.shader: -------------------------------------------------------------------------------- 1 | /* 2 | 基于深度及法线纹理的边缘检测(屏幕后处理) 3 | */ 4 | Shader "Volume xx/PostEffect/Edge Detection Normal And Depth" { 5 | Properties { 6 | _MainTex("MainTex",2D) = "white" {} 7 | // 只显示边缘线的程度,为1时,只显示边缘线,为0时,不显示 8 | _EdgeOnly("Edge Only",Float) = 1.0 9 | // 边缘颜色 10 | _EdgeColor("Edge Color",Color) = (0, 0, 0, 1) 11 | // 背景颜色 12 | _BackgroundColor("Background Color",Color) = (1, 1, 1, 1) 13 | // 进行卷积运算时,采样的范围 14 | _SampleDistance("Sample Distance",Float) = 1.0 15 | // _Sensitivity的xy分量分别对应了法线和深度的检测灵敏度,ZW分量没用 16 | _Sensitivity("Sensitivity",Vector) = (1, 1, 1, 1) 17 | } 18 | SubShader { 19 | Tags { "RenderType"="Opaque" "Queue"="Geometry" } 20 | Pass { 21 | CGPROGRAM 22 | 23 | #include "UnityCG.cginc" 24 | 25 | #pragma vertex vert 26 | #pragma fragment frag 27 | 28 | sampler2D _MainTex; 29 | float4 _MainTex_TexelSize; 30 | float _EdgeOnly; 31 | fixed4 _EdgeColor; 32 | fixed4 _BackgroundColor; 33 | float _SampleDistance; 34 | float4 _Sensitivity; 35 | sampler2D _CameraDepthNormalsTexture; // Unity提供的深度图 36 | 37 | struct v2f{ 38 | float4 pos : SV_POSITION; 39 | float2 uv[5] : TEXCOORD0; // 用于在纹理周围进行法线和深度的采样 40 | }; 41 | 42 | v2f vert(appdata_img v){ 43 | v2f o; 44 | o.pos = UnityObjectToClipPos(v.vertex); 45 | 46 | half2 uv = v.texcoord; 47 | o.uv[0] = uv; 48 | 49 | o.uv[1] = uv + _MainTex_TexelSize.xy * half2(1,1) * _SampleDistance; 50 | o.uv[2] = uv + _MainTex_TexelSize.xy * half2(-1,-1) * _SampleDistance; 51 | o.uv[3] = uv + _MainTex_TexelSize.xy * half2(-1,1) * _SampleDistance; 52 | o.uv[4] = uv + _MainTex_TexelSize.xy * half2(1,-1) * _SampleDistance; 53 | 54 | return o; 55 | } 56 | 57 | fixed CheckSame(fixed4 center,fixed4 sample){ 58 | fixed2 centerNormal = center.xy; 59 | float centerDepth = DecodeFloatRG(center.zw); 60 | fixed2 sampleNormal = sample.xy; 61 | float sampleDepth = DecodeFloatRG(sample.zw); 62 | 63 | fixed2 diffNormal = abs(centerNormal - sampleNormal) * _Sensitivity.x; 64 | int isSameNormal = (diffNormal.x + diffNormal.y) < 0.1; 65 | 66 | float diffDepth = abs(centerDepth - sampleDepth) * _Sensitivity.y; 67 | int isSameDepath = diffDepth < 0.1 * centerDepth; 68 | 69 | return isSameNormal*isSameDepath ? 1.0 : 0.0; 70 | } 71 | 72 | fixed4 frag(v2f i) : SV_TARGET{ 73 | fixed4 sample1 = tex2D(_CameraDepthNormalsTexture,i.uv[1]); 74 | fixed4 sample2 = tex2D(_CameraDepthNormalsTexture,i.uv[2]); 75 | fixed4 sample3 = tex2D(_CameraDepthNormalsTexture,i.uv[3]); 76 | fixed4 sample4 = tex2D(_CameraDepthNormalsTexture,i.uv[4]); 77 | 78 | // edge为0时表示两点之间存在一条边界 79 | fixed edge = 1.0; 80 | 81 | edge *= CheckSame(sample1,sample2); 82 | edge *= CheckSame(sample3,sample4); 83 | 84 | fixed4 withEdgeColor = lerp(_EdgeColor,tex2D(_MainTex,i.uv[0]),edge); 85 | fixed4 onlyEdgeColor = lerp(_EdgeColor,_BackgroundColor,edge); 86 | 87 | return lerp(withEdgeColor,onlyEdgeColor,_EdgeOnly); 88 | } 89 | ENDCG 90 | } 91 | } 92 | FallBack "Diffuse" 93 | 94 | } -------------------------------------------------------------------------------- /Volume 09 EdgeDetection详解边缘检测/edgeDetectionNormalAdnDepthWithFlashLight.shader: -------------------------------------------------------------------------------- 1 | /* 2 | 基于深度及法线纹理的边缘检测(屏幕后处理) 3 | 增加了简单的流光效果,在流光内的物体呈现原本的颜色或者只呈现线条颜色 4 | */ 5 | Shader "Volume xx/PostEffect/Edge Detection Normal And Depth With Flash Light" { 6 | Properties { 7 | _MainTex("MainTex",2D) = "white" {} 8 | // 只显示边缘线的程度,为1时,只显示边缘线,为0时,不显示 9 | _EdgeOnly("Edge Only",Float) = 1.0 10 | // 边缘颜色 11 | _EdgeColor("Edge Color",Color) = (0, 0, 0, 1) 12 | // 背景颜色 13 | _BackgroundColor("Background Color",Color) = (1, 1, 1, 1) 14 | // 进行卷积运算时,采样的范围 15 | _SampleDistance("Sample Distance",Float) = 1.0 16 | // _Sensitivity的xy分量分别对应了法线和深度的检测灵敏度,ZW分量没用 17 | _Sensitivity("Sensitivity",Vector) = (1, 1, 1, 1) 18 | // 流光图 19 | _FlashTex("Flash Tex",2D) = "black" {} 20 | // 流光采样x轴/y轴的速度,x分量是x轴,y分量是y轴,zw分量无意义 21 | _FlashSpeed("Flash Speed",Vector) = (0, 0, 0, 0) 22 | // 流光颜色 23 | _FlashColor("Flash Color",Color) = (1, 1, 1, 1) 24 | // 渐变阈值 25 | _EffectPercentage("EffectPercentage",Range(0,3)) = 0 26 | // 噪声图 27 | _NoiseTex("Noise Tex",2D) = "defaulttexture" {} 28 | } 29 | SubShader { 30 | Tags { "RenderType"="Opaque" "Queue"="Geometry" } 31 | Pass { 32 | CGPROGRAM 33 | 34 | #include "UnityCG.cginc" 35 | 36 | #pragma vertex vert 37 | #pragma fragment frag 38 | 39 | sampler2D _MainTex; 40 | float4 _MainTex_TexelSize; 41 | float _EdgeOnly; 42 | fixed4 _EdgeColor; 43 | fixed4 _BackgroundColor; 44 | float _SampleDistance; 45 | float4 _Sensitivity; 46 | sampler2D _CameraDepthNormalsTexture; // Unity提供的深度图 47 | sampler2D _FlashTex; 48 | float4 _FlashSpeed; 49 | fixed4 _FlashColor; 50 | fixed _EffectPercentage; 51 | sampler2D _NoiseTex; 52 | 53 | struct v2f{ 54 | float4 pos : SV_POSITION; 55 | float2 uv[5] : TEXCOORD0; // 用于在纹理周围进行法线和深度的采样 56 | 57 | float3 worldPos : TEXCOORD5; 58 | }; 59 | 60 | v2f vert(appdata_img v){ 61 | v2f o; 62 | o.pos = UnityObjectToClipPos(v.vertex); 63 | 64 | half2 uv = v.texcoord; 65 | o.uv[0] = uv; 66 | 67 | o.uv[1] = uv + _MainTex_TexelSize.xy * half2(1,1) * _SampleDistance; 68 | o.uv[2] = uv + _MainTex_TexelSize.xy * half2(-1,-1) * _SampleDistance; 69 | o.uv[3] = uv + _MainTex_TexelSize.xy * half2(-1,1) * _SampleDistance; 70 | o.uv[4] = uv + _MainTex_TexelSize.xy * half2(1,-1) * _SampleDistance; 71 | 72 | o.worldPos = mul(unity_ObjectToWorld,v.vertex); 73 | 74 | return o; 75 | } 76 | 77 | fixed CheckSame(fixed4 center,fixed4 sample){ 78 | fixed2 centerNormal = center.xy; 79 | float centerDepth = DecodeFloatRG(center.zw); 80 | fixed2 sampleNormal = sample.xy; 81 | float sampleDepth = DecodeFloatRG(sample.zw); 82 | 83 | fixed2 diffNormal = abs(centerNormal - sampleNormal) * _Sensitivity.x; 84 | int isSameNormal = (diffNormal.x + diffNormal.y) < 0.1; 85 | 86 | float diffDepth = abs(centerDepth - sampleDepth) * _Sensitivity.y; 87 | int isSameDepath = diffDepth < 0.1 * centerDepth; 88 | 89 | return isSameNormal*isSameDepath ? 1.0 : 0.0; 90 | } 91 | 92 | fixed4 frag(v2f i) : SV_TARGET{ 93 | fixed4 sample1 = tex2D(_CameraDepthNormalsTexture,i.uv[1]); 94 | fixed4 sample2 = tex2D(_CameraDepthNormalsTexture,i.uv[2]); 95 | fixed4 sample3 = tex2D(_CameraDepthNormalsTexture,i.uv[3]); 96 | fixed4 sample4 = tex2D(_CameraDepthNormalsTexture,i.uv[4]); 97 | 98 | // edge为0时表示两点之间存在一条边界 99 | fixed edge = 1.0; 100 | 101 | edge *= CheckSame(sample1,sample2); 102 | edge *= CheckSame(sample3,sample4); 103 | 104 | fixed4 withEdgeColor = lerp(_EdgeColor,tex2D(_MainTex,i.uv[0]),edge); 105 | fixed4 onlyEdgeColor = lerp(_EdgeColor,_BackgroundColor,edge); 106 | 107 | fixed4 finalColor = lerp(withEdgeColor,onlyEdgeColor,_EdgeOnly); 108 | 109 | // 根据世界坐标对流光图进行采样 110 | float2 flashUv = i.worldPos.zx + _FlashSpeed.xy * _Time.y; 111 | fixed4 flashColor = tex2D(_FlashTex,flashUv) * _FlashColor; 112 | 113 | // 根据uv坐标渐变边缘图至正常颜色 114 | finalColor.rgb = lerp(finalColor.rgb,withEdgeColor,saturate(_EffectPercentage-i.uv[0].x-tex2D(_NoiseTex,i.uv[0]).r)); 115 | 116 | 117 | // 在流光范围内的颜色回复正常 118 | // finalColor.rgb = lerp(withEdgeColor.rgb,finalColor.rgb,flashColor.r*2); 119 | 120 | return finalColor; 121 | } 122 | ENDCG 123 | } 124 | } 125 | FallBack "Diffuse" 126 | 127 | } -------------------------------------------------------------------------------- /Volume 09 EdgeDetection详解边缘检测/edgeDetectionWithMaskTex.shader: -------------------------------------------------------------------------------- 1 | /* 2 | 基于深度及法线纹理的边缘检测(屏幕后处理), 3 | 增加一张Mask图,使得一个在mask图中的像素保持原来的颜色而不受边缘处理的影响 4 | */ 5 | Shader "Volume xx/PostEffect/Edge Detection Normal And Depth" { 6 | Properties { 7 | _MainTex("MainTex",2D) = "white" {} 8 | // 只显示边缘线的程度,为1时,只显示边缘线,为0时,不显示 9 | _EdgeOnly("Edge Only",Float) = 1.0 10 | // 边缘颜色 11 | _EdgeColor("Edge Color",Color) = (0, 0, 0, 1) 12 | // 背景颜色 13 | _BackgroundColor("Background Color",Color) = (1, 1, 1, 1) 14 | // 进行卷积运算时,采样的范围 15 | _SampleDistance("Sample Distance",Float) = 1.0 16 | // _Sensitivity的xy分量分别对应了法线和深度的检测灵敏度,ZW分量没用 17 | _Sensitivity("Sensitivity",Vector) = (1, 1, 1, 1) 18 | // mask图 19 | _MaskTex("Mask Texture",2D) = "black" {} 20 | } 21 | SubShader { 22 | Tags { "RenderType"="Opaque" "Queue"="Geometry" } 23 | Pass { 24 | CGPROGRAM 25 | 26 | #include "UnityCG.cginc" 27 | 28 | #pragma vertex vert 29 | #pragma fragment frag 30 | 31 | sampler2D _MainTex; 32 | float4 _MainTex_TexelSize; 33 | float _EdgeOnly; 34 | fixed4 _EdgeColor; 35 | fixed4 _BackgroundColor; 36 | float _SampleDistance; 37 | float4 _Sensitivity; 38 | sampler2D _CameraDepthNormalsTexture; // Unity提供的深度图 39 | sampler2D _MaskTex; 40 | 41 | struct v2f{ 42 | float4 pos : SV_POSITION; 43 | float2 uv[5] : TEXCOORD0; // 用于在纹理周围进行法线和深度的采样 44 | }; 45 | 46 | v2f vert(appdata_img v){ 47 | v2f o; 48 | o.pos = UnityObjectToClipPos(v.vertex); 49 | 50 | half2 uv = v.texcoord; 51 | o.uv[0] = uv; 52 | 53 | o.uv[1] = uv + _MainTex_TexelSize.xy * half2(1,1) * _SampleDistance; 54 | o.uv[2] = uv + _MainTex_TexelSize.xy * half2(-1,-1) * _SampleDistance; 55 | o.uv[3] = uv + _MainTex_TexelSize.xy * half2(-1,1) * _SampleDistance; 56 | o.uv[4] = uv + _MainTex_TexelSize.xy * half2(1,-1) * _SampleDistance; 57 | 58 | return o; 59 | } 60 | 61 | fixed CheckSame(fixed4 center,fixed4 sample){ 62 | fixed2 centerNormal = center.xy; 63 | float centerDepth = DecodeFloatRG(center.zw); 64 | fixed2 sampleNormal = sample.xy; 65 | float sampleDepth = DecodeFloatRG(sample.zw); 66 | 67 | fixed2 diffNormal = abs(centerNormal - sampleNormal) * _Sensitivity.x; 68 | int isSameNormal = (diffNormal.x + diffNormal.y) < 0.1; 69 | 70 | float diffDepth = abs(centerDepth - sampleDepth) * _Sensitivity.y; 71 | int isSameDepath = diffDepth < 0.1 * centerDepth; 72 | 73 | return isSameNormal*isSameDepath ? 1.0 : 0.0; 74 | } 75 | 76 | fixed4 frag(v2f i) : SV_TARGET{ 77 | fixed4 sample1 = tex2D(_CameraDepthNormalsTexture,i.uv[1]); 78 | fixed4 sample2 = tex2D(_CameraDepthNormalsTexture,i.uv[2]); 79 | fixed4 sample3 = tex2D(_CameraDepthNormalsTexture,i.uv[3]); 80 | fixed4 sample4 = tex2D(_CameraDepthNormalsTexture,i.uv[4]); 81 | 82 | // edge为0时表示两点之间存在一条边界 83 | fixed edge = 1.0; 84 | 85 | edge *= CheckSame(sample1,sample2); 86 | edge *= CheckSame(sample3,sample4); 87 | 88 | fixed4 withEdgeColor = lerp(_EdgeColor,tex2D(_MainTex,i.uv[0]),edge); 89 | fixed4 onlyEdgeColor = lerp(_EdgeColor,_BackgroundColor,edge); 90 | 91 | fixed4 finalColor = lerp(withEdgeColor,onlyEdgeColor,_EdgeOnly); 92 | 93 | // 对mask图采样 94 | fixed mask = tex2D(_MaskTex,i.uv[0]).r; 95 | 96 | // 根据mask图决定最后的颜色 97 | finalColor = lerp(finalColor,withEdgeColor,min(mask*3,1)); 98 | 99 | return finalColor; 100 | } 101 | ENDCG 102 | } 103 | } 104 | FallBack "Diffuse" 105 | 106 | } -------------------------------------------------------------------------------- /Volume 09 EdgeDetection详解边缘检测/edgeDetectionWithPostEffect.shader: -------------------------------------------------------------------------------- 1 | /* 2 | 基于屏幕颜色的边缘检测 3 | */ 4 | Shader "Volume xx/PostEffect/Edge Detection Post Effect" { 5 | Properties { 6 | _MainTex("Main Tex",2D) = "white" {} 7 | } 8 | SubShader { 9 | Tags { "RenderType"="Opaque" "Queue"="Geometry" } 10 | Pass { 11 | Tags { "LightMode"="ForwardBase" } 12 | CGPROGRAM 13 | 14 | #include "UnityCG.cginc" 15 | 16 | #pragma vertex vert 17 | #pragma fragment frag 18 | 19 | sampler2D _MainTex; 20 | float4 _MainTex_TexelSize; // 纹素,纹理每个像素的大小 21 | fixed4 _EdgeColor; // 边缘颜色 22 | fixed4 _NonEdgeColor; 23 | float _EdgePower; 24 | float _SampleRange; 25 | float _EdgeOnly; 26 | 27 | struct a2v{ 28 | float4 vertex : POSITION; 29 | float2 uv : TEXCOORD0; 30 | }; 31 | struct v2f{ 32 | float4 pos : SV_POSITION; 33 | float2 uvSobel[9] : TEXCOORD0; 34 | }; 35 | 36 | // 使用Sobel算子计算水平/竖直方向梯度值 37 | float Sobel(v2f i){ 38 | const float Gx[9] = { 39 | -1,-2,-1, 40 | 0, 0, 0, 41 | 1, 2, 1 42 | }; 43 | const float Gy[9] = { 44 | 1, 0,-1, 45 | 2, 0,-2, 46 | 1, 0,-1 47 | }; 48 | 49 | float edgex,edgey; 50 | for(int j=0;j<9;j++){ 51 | // 对对应uv点进行采样 52 | fixed4 color = tex2D(_MainTex,i.uvSobel[j]); 53 | 54 | // 将颜色转为灰度值 55 | float lum = Luminance(color.rgb); 56 | 57 | // 进行卷积 58 | // 对两个方向上的梯度值 59 | edgex += lum * Gx[j]; 60 | edgey += lum * Gy[j]; 61 | } 62 | // abs(edgex) + abs(edgey)为梯度值 63 | // 梯度值越大,越有可能是边缘点 64 | // 1-(abs(edgex)+abs(edgey))表示梯度值的反面,即这个值越小,那么越有可能是梯度值 65 | return 1 - abs(edgex) - abs(edgey); 66 | } 67 | 68 | v2f vert(a2v v){ 69 | v2f o; 70 | o.pos = UnityObjectToClipPos(v.vertex); 71 | 72 | // 注意unity中的uv坐标,原点在左下角(directX在左上角) 73 | 74 | // 得到uv点周围8个像素的uv位置 75 | o.uvSobel[0] = v.uv + float2(-1,-1) * _MainTex_TexelSize * _SampleRange; 76 | o.uvSobel[1] = v.uv + float2( 0,-1) * _MainTex_TexelSize * _SampleRange; 77 | o.uvSobel[2] = v.uv + float2( 1,-1) * _MainTex_TexelSize * _SampleRange; 78 | o.uvSobel[3] = v.uv + float2(-1, 0) * _MainTex_TexelSize * _SampleRange; 79 | o.uvSobel[4] = v.uv + float2( 0, 0) * _MainTex_TexelSize * _SampleRange; 80 | o.uvSobel[5] = v.uv + float2( 1, 0) * _MainTex_TexelSize * _SampleRange; 81 | o.uvSobel[6] = v.uv + float2(-1, 1) * _MainTex_TexelSize * _SampleRange; 82 | o.uvSobel[7] = v.uv + float2( 0, 1) * _MainTex_TexelSize * _SampleRange; 83 | o.uvSobel[8] = v.uv + float2( 1, 1) * _MainTex_TexelSize * _SampleRange; 84 | 85 | return o; 86 | } 87 | 88 | fixed4 frag(v2f i) : SV_TARGET{ 89 | 90 | // i.uvSobel[4]对应当前uv点 91 | fixed4 color = tex2D(_MainTex,i.uvSobel[4]); 92 | 93 | // 计算该点的1-梯度值 94 | float g = Sobel(i); 95 | 96 | // 将"反梯度值"进一步缩小(这一步说明了上面为什么不直接计算梯度值,而是要用1减去) 97 | // 因为如果是真梯度值,这里应该放大梯度值,放大梯度值不能进行整数幂计算,而是要分数幂 98 | // 更消耗性能(个人猜测) 99 | g = pow(g,_EdgePower); 100 | 101 | // 使用"反梯度值"来过渡边缘颜色,梯度值越高,边缘颜色黑色更多,反之,呈白色 102 | fixed4 edgeColor = lerp(_EdgeColor,_NonEdgeColor,g); 103 | 104 | // 根据_EdgeOnly变量,判断如何从正常的颜色过渡到只显示边缘的屏幕效果 105 | color.rgb = lerp(color.rgb,edgeColor,_EdgeOnly); 106 | 107 | return color; 108 | } 109 | 110 | ENDCG 111 | } 112 | } 113 | FallBack "Diffuse" 114 | 115 | } -------------------------------------------------------------------------------- /Volume 10 Projector投影仪的原理及简单应用/CustomizeProjector.shader: -------------------------------------------------------------------------------- 1 | // 自制投影机 2 | Shader "Volume 10/Projector/Customize Projector" { 3 | Properties { 4 | _MainTex("Main Tex",2D) = "white" {} 5 | _Color("Color",Color) = (1, 1, 1, 1) 6 | } 7 | SubShader { 8 | 9 | Tags { "RenderType"="Opaque" "Queue"="Geometry" } 10 | 11 | // 漫反射Pass 12 | Pass { 13 | 14 | Blend SrcAlpha OneMinusSrcAlpha 15 | 16 | Tags { "LightMode"="ForwardBase" } 17 | 18 | CGPROGRAM 19 | 20 | #include "Lighting.cginc" 21 | 22 | #pragma vertex vert 23 | #pragma fragment frag 24 | 25 | fixed4 _Color; 26 | 27 | struct a2v{ 28 | float4 vertex : POSITION; 29 | float3 normal : NORMAL; 30 | }; 31 | struct v2f{ 32 | float4 pos : SV_POSITION; 33 | float3 worldNormal : TEXCOORD0; 34 | float3 worldPos : TEXCOORD1; 35 | }; 36 | 37 | v2f vert(a2v v){ 38 | v2f o; 39 | o.pos = UnityObjectToClipPos(v.vertex); 40 | 41 | o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz; 42 | o.worldNormal = UnityObjectToWorldNormal(v.normal); 43 | 44 | return o; 45 | } 46 | 47 | fixed4 frag(v2f i) : SV_TARGET{ 48 | fixed3 worldNormal = normalize(i.worldNormal); 49 | fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); 50 | 51 | fixed3 diffuse = _Color.rgb * _LightColor0.rgb * max(0,dot(worldNormal,worldLightDir)); 52 | 53 | return fixed4(diffuse,1.0); 54 | } 55 | 56 | ENDCG 57 | } 58 | 59 | // Projector的Pass 60 | Pass { 61 | 62 | Blend SrcAlpha OneMinusSrcAlpha 63 | 64 | CGPROGRAM 65 | 66 | #include "UnityCG.cginc" 67 | 68 | #pragma vertex vert 69 | #pragma fragment frag 70 | 71 | // 投影机的VP矩阵 72 | float4x4 _ProjectorVPMatrix; 73 | sampler2D _MainTex; 74 | 75 | struct v2f{ 76 | float4 pos : SV_POSITION; 77 | float4 uvDecal : TEXCOORD0; 78 | float4 uvFallOff : TEXCOORD1; 79 | }; 80 | 81 | v2f vert(appdata_base v){ 82 | v2f o; 83 | o.pos = UnityObjectToClipPos(v.vertex); 84 | 85 | // 将当前顶点转换到投影机的裁剪空间 86 | // 构造MVP矩阵 87 | float4x4 decalMVP = mul(_ProjectorVPMatrix,unity_ObjectToWorld); 88 | 89 | // 在裁剪空间下顶点的坐标 90 | float4 decalProjectSpacePos = mul(decalMVP,v.vertex); 91 | 92 | // 根据顶点坐标获得当前顶点所在屏幕的位置(没有进行齐次除法的) 93 | o.uvDecal = ComputeScreenPos(decalProjectSpacePos); 94 | return o; 95 | } 96 | 97 | fixed4 frag(v2f i) : SV_TARGET{ 98 | fixed4 decal = tex2D(_MainTex,i.uvDecal.xy/i.uvDecal.w); 99 | return decal; 100 | } 101 | 102 | ENDCG 103 | } 104 | } 105 | FallBack "Diffuse" 106 | 107 | } -------------------------------------------------------------------------------- /Volume 10 Projector投影仪的原理及简单应用/DissloveTextureWithProjector.shader: -------------------------------------------------------------------------------- 1 | /* 2 | // 模仿Effect的Unity内置资源Projector Shader的写法 3 | // 相比于自制的Projector,主要是多了个FallOff纹理 4 | // 该纹理用于控制Projector不会出现双面渲染,同时 5 | // 控制不在视椎体内的像素不着色 6 | */ 7 | Shader "Volume 10/Projector/Disslove Texture With Projector" { 8 | Properties { 9 | // 要投影的Texture 10 | _ProjTex("Projector Tex",2D) = "white" {} 11 | // 控制该Texture的颜色 12 | _Color("Color",Color) = (1, 1, 1, 1) 13 | // 衰减纹理 14 | _FalloffTex("Fall Off Texture",2D) = "white" {} 15 | // 噪声图,控制UV显示 16 | _NoiseTex("Noise Texture",2D) = "white" {} 17 | // 渐变阈值 18 | _EffectPercentage("EffectPercentage",Range(0,3)) = 0 19 | } 20 | SubShader { 21 | 22 | Tags { "Queue"="Transparent" } 23 | 24 | Pass { 25 | 26 | ZWrite Off 27 | ColorMask RGB 28 | Blend SrcAlpha OneMinusSrcAlpha 29 | Offset -1,-1 30 | 31 | CGPROGRAM 32 | 33 | #include "UnityCG.cginc" 34 | 35 | #pragma vertex vert 36 | #pragma fragment frag 37 | 38 | sampler2D _ProjTex; 39 | sampler2D _FalloffTex; 40 | fixed4 _Color; 41 | sampler2D _NoiseTex; 42 | float _EffectPercentage; 43 | 44 | struct v2f{ 45 | float4 pos : SV_POSITION; 46 | // 物体经过Projector的VP矩阵转移到投影机的裁剪空间后 47 | // 根据物体顶点的裁剪空间坐标算出物体的屏幕坐标 48 | // 这个没有经过齐次除法的屏幕坐标就是uvProjector 49 | // 下一步要在片元着色器中进行齐次除法 50 | float4 uvProjector : TEXCOORD0; 51 | 52 | // 用于对FallOff纹理进行采样,用来保证不在视椎体内的顶点 53 | // 不会受投影仪影响 54 | float4 uvFallOff : TEXCOORD1; 55 | 56 | float2 uv : TEXCOORD2; 57 | }; 58 | 59 | struct a2v{ 60 | float4 vertex : POSITION; 61 | float2 texcoord : TEXCOORD0; 62 | }; 63 | 64 | // 将顶点从世界坐标变换到投影机的裁剪空间内的VP矩阵 65 | float4x4 unity_Projector; 66 | 67 | // 用于变换顶点到一个不知名空间的矩阵(用于FallOFF纹理采样)..这个我不懂 68 | float4x4 unity_ProjectorClip; 69 | 70 | v2f vert(a2v v){ 71 | v2f o; 72 | o.pos = UnityObjectToClipPos(v.vertex); 73 | 74 | // 变换顶点到投影仪的裁剪空间 75 | o.uvProjector = mul(unity_Projector,v.vertex); 76 | o.uvFallOff = mul(unity_ProjectorClip,v.vertex); 77 | o.uv = v.texcoord; 78 | 79 | return o; 80 | } 81 | 82 | fixed4 frag(v2f i) : SV_TARGET{ 83 | // 相当于tex2D(_ProjTex,UNITY_PROJ_COORD(i.uvProjector).xy/UNITY_PROJ_COORD(i.uvProjector).w) 84 | fixed4 texS = tex2Dproj(_ProjTex,UNITY_PROJ_COORD(i.uvProjector)); 85 | 86 | texS.rgb *= _Color.rgb; 87 | 88 | fixed4 texF = tex2Dproj(_FalloffTex,UNITY_PROJ_COORD(i.uvFallOff)); 89 | 90 | texS.a = lerp(0,texS.a,texF.a); 91 | 92 | // 用噪声和阈值控制投影的图片的显示 93 | fixed degree = saturate(_EffectPercentage - (i.uv.y-i.uv.x) - tex2D(_NoiseTex,sin(i.uv.xx+cos(_Time.xy*0.2)))); 94 | texS.a = lerp(texS.a,0,degree); 95 | texS.rgb = lerp(texS.rgb,fixed3(0,0,1),degree); 96 | 97 | return texS; 98 | } 99 | 100 | ENDCG 101 | } 102 | } 103 | FallBack "Diffuse" 104 | 105 | } -------------------------------------------------------------------------------- /Volume 10 Projector投影仪的原理及简单应用/Scripts/CustomizeProjector.cs: -------------------------------------------------------------------------------- 1 |  2 | using UnityEngine; 3 | 4 | [ExecuteInEditMode] 5 | [RequireComponent(typeof(Camera))] 6 | public class CustomizeProjector : MonoBehaviour { 7 | 8 | private Camera projectorCamera; 9 | public Material projectorMaterial; 10 | 11 | private void Awake() { 12 | projectorCamera = GetComponent(); 13 | } 14 | 15 | // Update is called once per frame 16 | void Update () { 17 | // 获得摄像机的投影矩阵 18 | var projectMatrix = projectorCamera.projectionMatrix; 19 | projectMatrix = GL.GetGPUProjectionMatrix(projectMatrix,false); 20 | // 观察矩阵 21 | var viewMatrix = projectorCamera.worldToCameraMatrix; 22 | 23 | // 获得VP矩阵 24 | var vpMatrix = projectMatrix * viewMatrix; 25 | 26 | projectorMaterial.SetMatrix("_ProjectorVPMatrix",vpMatrix); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Volume 10 Projector投影仪的原理及简单应用/StandardProjector.shader: -------------------------------------------------------------------------------- 1 | /* 2 | // 模仿Effect的Unity内置资源Projector Shader的写法 3 | // 相比于自制的Projector,主要是多了个FallOff纹理 4 | // 该纹理用于控制Projector不会出现双面渲染,同时 5 | // 控制不在视椎体内的像素不着色 6 | */ 7 | Shader "Volume 10/Projector/Standard Projector" { 8 | Properties { 9 | // 要投影的Texture 10 | _ProjTex("Projector Tex",2D) = "white" {} 11 | // 控制该Texture的颜色 12 | _Color("Color",Color) = (1, 1, 1, 1) 13 | // 衰减纹理 14 | _FalloffTex("Fall Off Texture",2D) = "white" {} 15 | } 16 | SubShader { 17 | 18 | Tags { "Queue"="Transparent" } 19 | 20 | Pass { 21 | 22 | ZWrite Off 23 | ColorMask RGB 24 | Blend SrcAlpha OneMinusSrcAlpha 25 | Offset -1,-1 26 | 27 | CGPROGRAM 28 | 29 | #include "UnityCG.cginc" 30 | 31 | #pragma vertex vert 32 | #pragma fragment frag 33 | 34 | sampler2D _ProjTex; 35 | sampler2D _FalloffTex; 36 | fixed4 _Color; 37 | 38 | struct v2f{ 39 | float4 pos : SV_POSITION; 40 | // 物体经过Projector的VP矩阵转移到投影机的裁剪空间后 41 | // 根据物体顶点的裁剪空间坐标算出物体的屏幕坐标 42 | // 这个没有经过齐次除法的屏幕坐标就是uvProjector 43 | // 下一步要在片元着色器中进行齐次除法 44 | float4 uvProjector : TEXCOORD0; 45 | 46 | // 用于对FallOff纹理进行采样,用来保证不在视椎体内的顶点 47 | // 不会受投影仪影响 48 | float4 uvFallOff : TEXCOORD1; 49 | }; 50 | 51 | // 将顶点从世界坐标变换到投影机的裁剪空间内的VP矩阵 52 | float4x4 unity_Projector; 53 | 54 | // 用于变换顶点到一个不知名空间的矩阵(用于FallOFF纹理采样)..这个我不懂 55 | float4x4 unity_ProjectorClip; 56 | 57 | v2f vert(float4 vertex : POSITION){ 58 | v2f o; 59 | o.pos = UnityObjectToClipPos(vertex); 60 | 61 | // 变换顶点到投影仪的裁剪空间 62 | o.uvProjector = mul(unity_Projector,vertex); 63 | o.uvFallOff = mul(unity_ProjectorClip,vertex); 64 | 65 | return o; 66 | } 67 | 68 | fixed4 frag(v2f i) : SV_TARGET{ 69 | // 相当于tex2D(_ProjTex,UNITY_PROJ_COORD(i.uvProjector).xy/UNITY_PROJ_COORD(i.uvProjector).w) 70 | fixed4 texS = tex2Dproj(_ProjTex,UNITY_PROJ_COORD(i.uvProjector)); 71 | 72 | texS.rgb *= _Color.rgb; 73 | 74 | fixed4 texF = tex2Dproj(_FalloffTex,UNITY_PROJ_COORD(i.uvFallOff)); 75 | 76 | texS.a = lerp(0,texS.a,texF.a); 77 | 78 | return texS; 79 | } 80 | 81 | ENDCG 82 | } 83 | } 84 | FallBack "Diffuse" 85 | 86 | } -------------------------------------------------------------------------------- /Volume 11 Toon 非真实感渲染/Hatching.shader: -------------------------------------------------------------------------------- 1 | Shader "Volume 11/Toon/Hatching" { 2 | Properties { 3 | _Color("Color",Color) = (1, 1, 1, 1) 4 | _TileFactor("Tile Factor",Float) = 1 5 | _OutlineWidth("Outline",Range(0,1)) = 0.1 6 | _OutlineColor("OutLine Color",Color) = (1, 1, 1, 1) 7 | _Hatch0("Hatch 0",2D) = "white"{} 8 | _Hatch1("Hatch 1",2D) = "white" {} 9 | _Hatch2("Hatch 2",2D) = "white" {} 10 | _Hatch3("Hatch 3",2D) = "white" {} 11 | _Hatch4("Hatch 4",2D) = "white" {} 12 | _Hatch5("Hatch 5",2D) = "white" {} 13 | } 14 | SubShader { 15 | Tags { "RenderType"="Opaque" "Queue"="Geometry" } 16 | 17 | // 描边Pass 18 | Pass { 19 | Tags { "LightMode"="ForwardBase" } 20 | 21 | // 剔除正面 22 | Cull Front 23 | 24 | CGPROGRAM 25 | 26 | #pragma vertex vert 27 | #pragma fragment frag 28 | 29 | struct a2v{ 30 | float4 vertex : POSITION; 31 | float3 normal : NORMAL; 32 | }; 33 | 34 | struct v2f{ 35 | float4 pos : SV_POSITION; 36 | }; 37 | 38 | fixed4 _OutlineColor; 39 | float _OutlineWidth; 40 | 41 | v2f vert(a2v v){ 42 | v2f o; 43 | // 将单位的法线和顶点变换到视角空间 44 | float4 viewPos = mul(UNITY_MATRIX_MV,v.vertex); 45 | float3 viewNormal = mul((float3x3)UNITY_MATRIX_IT_MV,v.normal); 46 | 47 | viewNormal.z = -0.5; 48 | viewNormal = normalize(viewNormal); 49 | viewPos.xyz += viewNormal * _OutlineWidth; 50 | 51 | // 将顶点从观察空间变换到裁剪空间去 52 | o.pos = mul(UNITY_MATRIX_P,viewPos); 53 | return o; 54 | } 55 | 56 | fixed4 frag(v2f i) : SV_TARGET{ 57 | return fixed4(_OutlineColor.rgb,1.0); 58 | } 59 | 60 | ENDCG 61 | } 62 | 63 | Pass { 64 | Tags{ "LightMode"="ForwardBase" } 65 | CGPROGRAM 66 | 67 | #include "UnityCG.cginc" 68 | 69 | #pragma vertex vert 70 | #pragma fragment frag 71 | 72 | fixed4 _Color; 73 | float _TileFactor; 74 | sampler2D _Hatch0; 75 | sampler2D _Hatch1; 76 | sampler2D _Hatch2; 77 | sampler2D _Hatch3; 78 | sampler2D _Hatch4; 79 | sampler2D _Hatch5; 80 | 81 | struct v2f{ 82 | float4 pos : SV_POSITION; 83 | float2 uv : TEXCOORD0; 84 | fixed3 hatchWeights0 : TEXCOORD1; 85 | fixed3 hatchWeights1 : TEXCOORD2; 86 | }; 87 | 88 | struct a2v{ 89 | float4 vertex : POSITION; 90 | float3 normal : NORMAL; 91 | float2 texcoord : TEXCOORD0; 92 | }; 93 | 94 | v2f vert(a2v v){ 95 | v2f o; 96 | o.pos = UnityObjectToClipPos(v.vertex); 97 | 98 | o.uv = v.texcoord.xy * _TileFactor; 99 | 100 | float3 worldPos = mul(unity_ObjectToWorld,v.vertex).xyz; 101 | fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(worldPos)); 102 | fixed3 worldNormalDir = normalize(UnityObjectToWorldNormal(v.normal)); 103 | 104 | fixed diff = max(0,dot(worldLightDir,worldNormalDir)); 105 | 106 | o.hatchWeights0 = fixed3(0,0,0); 107 | o.hatchWeights1 = fixed3(0,0,0); 108 | 109 | float hatchFactor = diff * 7.0; 110 | 111 | if(hatchFactor>6.0){ 112 | 113 | }else if(hatchFactor > 5.0){ 114 | o.hatchWeights0.x = hatchFactor - 5.0; 115 | }else if(hatchFactor > 4.0){ 116 | o.hatchWeights0.x = hatchFactor - 4.0; 117 | o.hatchWeights0.y = 1.0 - o.hatchWeights0.x; 118 | }else if(hatchFactor > 3.0){ 119 | o.hatchWeights0.y = hatchFactor - 3.0; 120 | o.hatchWeights0.z = 1.0 - o.hatchWeights0.y; 121 | }else if(hatchFactor > 2.0){ 122 | o.hatchWeights0.z = hatchFactor - 2.0; 123 | o.hatchWeights1.x = 1.0 - o.hatchWeights0.z; 124 | }else if(hatchFactor > 1.0){ 125 | o.hatchWeights1.x = hatchFactor - 1.0; 126 | o.hatchWeights1.y = 1.0 - o.hatchWeights1.x; 127 | }else{ 128 | o.hatchWeights1.y = hatchFactor; 129 | o.hatchWeights1.z = 1.0 - o.hatchWeights1.y; 130 | } 131 | 132 | return o; 133 | } 134 | 135 | fixed4 frag(v2f i) : SV_TARGET{ 136 | fixed4 hatchTex0 = tex2D(_Hatch0,i.uv) * i.hatchWeights0.x; 137 | fixed4 hatchTex1 = tex2D(_Hatch1,i.uv) * i.hatchWeights0.y; 138 | fixed4 hatchTex2 = tex2D(_Hatch2,i.uv) * i.hatchWeights0.z; 139 | fixed4 hatchTex3 = tex2D(_Hatch3,i.uv) * i.hatchWeights1.x; 140 | fixed4 hatchTex4 = tex2D(_Hatch4,i.uv) * i.hatchWeights1.y; 141 | fixed4 hatchTex5 = tex2D(_Hatch5,i.uv) * i.hatchWeights1.z; 142 | 143 | fixed4 whiteColoor = fixed4(1,1,1,1) * (1-i.hatchWeights0.x-i.hatchWeights0.y-i.hatchWeights0.z-i.hatchWeights1.x-i.hatchWeights1.y-i.hatchWeights1.z); 144 | fixed4 hatchColor = hatchTex0 + hatchTex1 + hatchTex2 + hatchTex3 + hatchTex4 + hatchTex5 + whiteColoor; 145 | 146 | return fixed4(hatchColor.rgb*_Color.rgb,1.0); 147 | } 148 | 149 | ENDCG 150 | } 151 | } 152 | FallBack "Diffuse" 153 | 154 | } -------------------------------------------------------------------------------- /Volume 11 Toon 非真实感渲染/ToonShadering.shader: -------------------------------------------------------------------------------- 1 | /** 2 | 卡通化渲染,基本步骤是: 3 | 1. 给单位添加轮廓线 4 | 2. 给单位添加突变的高光,即不是渐变的,而是根据一个阈值, 5 | 当前像素辐照度大于该阈值时,表现出高光,否则不表现 6 | */ 7 | Shader "Volume 11/Toon/ToonShadering" { 8 | Properties { 9 | // 主纹理 10 | _MainTex("_MainTex",2D) = "white" {} 11 | // 高光阈值 12 | _Threshold("Threshold",Float) = 0.5 13 | // 渐变纹理,用于控制漫反射系数 14 | _RampTex("Ramp Texture",2D) = "white" {} 15 | // 漫反射颜色 16 | _Color("Color",Color) = (1, 1, 1, 1) 17 | // 高光反射颜色 18 | _Specular("Spcular Color",Color) = (1, 1, 1, 1) 19 | // 描边颜色 20 | _OutlineColor("OutLineColor",Color) = (0, 0, 0, 1) 21 | // 描边长度 22 | _OutlineWidth("outline Width",Float) = 1.0 23 | } 24 | SubShader { 25 | 26 | Tags { "RenderType"="Opaque" "Queue"="Geometry" } 27 | 28 | // 描边Pass 29 | Pass { 30 | Tags { "LightMode"="ForwardBase" } 31 | 32 | // 剔除正面 33 | Cull Front 34 | 35 | CGPROGRAM 36 | 37 | #pragma vertex vert 38 | #pragma fragment frag 39 | 40 | struct a2v{ 41 | float4 vertex : POSITION; 42 | float3 normal : NORMAL; 43 | }; 44 | 45 | struct v2f{ 46 | float4 pos : SV_POSITION; 47 | }; 48 | 49 | fixed4 _OutlineColor; 50 | float _OutlineWidth; 51 | 52 | v2f vert(a2v v){ 53 | v2f o; 54 | // 将单位的法线和顶点变换到视角空间 55 | float4 viewPos = mul(UNITY_MATRIX_MV,v.vertex); 56 | float3 viewNormal = mul((float3x3)UNITY_MATRIX_IT_MV,v.normal); 57 | 58 | viewNormal.z = -0.5; 59 | viewNormal = normalize(viewNormal); 60 | viewPos.xyz += viewNormal * _OutlineWidth; 61 | 62 | // 将顶点从观察空间变换到裁剪空间去 63 | o.pos = mul(UNITY_MATRIX_P,viewPos); 64 | return o; 65 | } 66 | 67 | fixed4 frag(v2f i) : SV_TARGET{ 68 | return fixed4(_OutlineColor.rgb,1.0); 69 | } 70 | 71 | ENDCG 72 | } 73 | 74 | // 正常输出颜色的Pass 75 | Pass { 76 | Tags { "LightMode"="ForwardBase" } 77 | 78 | // 剔除背面 79 | Cull Back 80 | 81 | CGPROGRAM 82 | 83 | #include "UnityCG.cginc" 84 | #include "Lighting.cginc" 85 | 86 | #pragma vertex vert 87 | #pragma fragment frag 88 | 89 | sampler2D _RampTex; 90 | sampler2D _MainTex; 91 | fixed4 _Color; 92 | fixed4 _Specular; 93 | float _Threshold; 94 | 95 | struct a2v{ 96 | float4 vertex : POSITION; 97 | float3 normal : NORMAL; 98 | float2 texcoord : TEXCOORD0; 99 | }; 100 | 101 | struct v2f{ 102 | float4 pos : SV_POSITION; 103 | float3 worldNormal : TEXCOORD0; 104 | float3 worldPos : TEXCOORD1; 105 | float2 uv : TEXCOORD2; 106 | }; 107 | 108 | v2f vert(a2v v){ 109 | v2f o; 110 | o.pos = UnityObjectToClipPos(v.vertex); 111 | 112 | o.worldNormal = UnityObjectToWorldNormal(v.normal); 113 | o.worldPos = mul(unity_ObjectToWorld,v.vertex); 114 | o.uv = v.texcoord; 115 | 116 | return o; 117 | } 118 | 119 | fixed4 frag(v2f i) : SV_TARGET{ 120 | fixed3 worldNormal = normalize(i.worldNormal); 121 | fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); 122 | fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos)); 123 | 124 | // 计算半兰伯特系数 125 | fixed halfLabert = 0.5 * dot(worldNormal,worldLightDir) + 0.5; 126 | 127 | // 根据半兰伯特系数对渐变纹理进行采样 128 | fixed3 ramp = tex2D(_RampTex,fixed2(halfLabert,halfLabert)).rgb; 129 | 130 | // 反射系数 131 | fixed3 albedo = tex2D(_MainTex,i.uv).rgb * _Color.rgb * ramp; 132 | fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo; 133 | 134 | // 漫反射光照 135 | fixed3 diffuse = albedo * _LightColor0.rgb; 136 | 137 | // 高光反射 138 | // fixed3 specularColor = _Specular.rgb * step(_Threshold,specular); 139 | 140 | // 计算高光反射 141 | fixed worldHalfDir = normalize(worldLightDir+worldViewDir); 142 | 143 | float specular = dot(worldHalfDir,worldNormal); 144 | 145 | fixed w = fwidth(specular) * 2.0; 146 | fixed3 specularColor = _Specular.rgb * lerp(0, 1, smoothstep(-w, w, specular + _Threshold - 1)) * step(0.0001, _Threshold); 147 | 148 | return fixed4(diffuse+ambient+specularColor,1.0); 149 | 150 | } 151 | 152 | ENDCG 153 | } 154 | } 155 | FallBack "Diffuse" 156 | 157 | } -------------------------------------------------------------------------------- /Volume 12 ScreenFade 屏幕转场效果/BreakingScreen.shader: -------------------------------------------------------------------------------- 1 | /* 2 | 屏幕碎裂的转场效果 3 | */ 4 | Shader "Volume 12/Screen Fade/Breaking Screen" { 5 | Properties { 6 | // 屏幕图像 7 | _MainTex("MainTex",2D) = "white" {} 8 | // 用于使屏幕呈碎裂效果的法线图(用于uv偏移) 9 | _DistortionTex("Distortion Texture",2D) = "bump" {} 10 | _Threshold("_Threshold",Float) = 0 11 | } 12 | SubShader { 13 | Pass { 14 | CGPROGRAM 15 | 16 | #include "UnityCG.cginc" 17 | 18 | #pragma vertex vert 19 | #pragma fragment frag 20 | 21 | sampler2D _MainTex; 22 | sampler2D _DistortionTex; 23 | float _Threshold; 24 | 25 | struct v2f{ 26 | float4 pos : SV_POSITION; 27 | float2 uv : TEXCOORD0; 28 | }; 29 | 30 | v2f vert(appdata_img v){ 31 | v2f o; 32 | o.pos = UnityObjectToClipPos(v.vertex); 33 | o.uv = v.texcoord; 34 | return o; 35 | } 36 | 37 | fixed4 frag(v2f i) : SV_TARGET{ 38 | // 从法线贴图中获得法线 39 | half2 bump = UnpackNormal(tex2D(_DistortionTex,i.uv)).xy; 40 | 41 | i.uv.y = (i.uv.y+bump.y*0.5*_Time.y)%1; 42 | bool result = (i.uv.y+bump.y*0.5 >= _Threshold) ; 43 | 44 | 45 | return tex2D(_MainTex,bump*0.5+i.uv) * !result; 46 | } 47 | 48 | ENDCG 49 | } 50 | } 51 | FallBack "Diffuse" 52 | 53 | } -------------------------------------------------------------------------------- /Volume 12 ScreenFade 屏幕转场效果/Scripts/BreakingScreenWithAoNoKiseki.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | /// 4 | /// 仿碧轨碎屏效果 5 | /// 6 | /// 思路: 7 | /// 1. 8 | /// 9 | public class BreakingScreenWithAoNoKiseki : MonoBehaviour{ 10 | 11 | // 观察这个碎屏特效的摄像机 12 | private Camera effectCamera; 13 | 14 | // 用于破碎的平面的宽度 15 | private float width; 16 | // 用于破碎的平面的高度 17 | private float height; 18 | 19 | // 将Width和Height分成N份 20 | public int rowTriangleSize = 2; 21 | public int colTriangleSize = 2; 22 | 23 | // 碎屏的模型 24 | private GameObject screenObject; 25 | private MeshFilter meshFilter; 26 | private MeshRenderer meshRenderer; 27 | 28 | // 碎屏所使用的Shader(其实就是一张简单的纹理贴图Shader) 29 | public Shader breakingScreenShader; 30 | private Material breakingScreenMaterial; 31 | 32 | private Material BreakingScreenMaterial { 33 | get { 34 | if (breakingScreenMaterial == null) { 35 | if (breakingScreenShader != null) 36 | breakingScreenMaterial = new Material(breakingScreenShader); 37 | else 38 | return null; 39 | } 40 | return breakingScreenMaterial; 41 | } 42 | } 43 | 44 | // mesh顶点序列 45 | private Vector3[] vertices; 46 | // uv序列 47 | private Vector2[] uv; 48 | // mesh顶点索引序列 49 | private int[] triangles; 50 | 51 | private void Start() { 52 | effectCamera = transform.parent.GetComponent(); 53 | height = effectCamera.orthographicSize * 2; 54 | width = effectCamera.aspect * height; 55 | 56 | screenObject = new GameObject("Screen Object"); 57 | screenObject.layer = 13; 58 | screenObject.transform.SetParent(effectCamera.transform,false); 59 | screenObject.transform.position = new Vector3(screenObject.transform.position.x,screenObject.transform.position.y,effectCamera.nearClipPlane+1); 60 | meshFilter = screenObject.AddComponent(); 61 | meshRenderer = screenObject.AddComponent(); 62 | screenObject.transform.localScale = new Vector3(width,height,1); 63 | meshRenderer.material = BreakingScreenMaterial; 64 | 65 | //GenerateMesh(); 66 | Mesh mesh = meshFilter.mesh; 67 | uv = new Vector2[6]; 68 | uv[0] = new Vector2(0, 0); 69 | uv[1] = new Vector2(0, 1); 70 | uv[2] = new Vector2(1, 0); 71 | uv[3] = new Vector2(0, 1); 72 | uv[4] = new Vector2(1, 1); 73 | uv[5] = new Vector2(1, 0); 74 | 75 | vertices = new Vector3[6]; 76 | vertices[0] = new Vector3(0, 0, 0); 77 | vertices[1] = new Vector3(0, 1, 0); 78 | vertices[2] = new Vector3(1, 0, 0); 79 | vertices[3] = new Vector3(0, 1, 0); 80 | vertices[4] = new Vector3(1, 1, 0); 81 | vertices[5] = new Vector3(1, 0, 0); 82 | 83 | 84 | triangles = new int[6]; 85 | triangles[0] = 0; 86 | triangles[1] = 1; 87 | triangles[2] = 2; 88 | triangles[3] = 3; 89 | triangles[4] = 4; 90 | triangles[5] = 5; 91 | 92 | 93 | mesh.vertices = vertices; 94 | mesh.triangles = triangles; 95 | mesh.uv = uv; 96 | 97 | mesh.RecalculateNormals(); // 重新计算法线 98 | } 99 | 100 | // 生成碎屏Mesh(由一块块碎片拼起来的四边形(此四边形覆盖屏幕)) 101 | // 此Mesh的X、Y坐标限定在[0,1]范围内 102 | private void GenerateMesh() { 103 | 104 | // 初始化 105 | vertices = new Vector3[rowTriangleSize*colTriangleSize*6]; 106 | uv = new Vector2[vertices.Length]; 107 | triangles = new int[vertices.Length]; 108 | 109 | 110 | int index = 0; 111 | float rowStep = (float)1 / rowTriangleSize; 112 | float colStep = (float)1 / colTriangleSize; 113 | for (int i=0;i< rowTriangleSize-1; i++) { 114 | for (int j=0;j 《Unity Shader 入门精要》 4 | > 大神博客 https://blog.csdn.net/puppet_master 5 | ## 简述 6 | 这里收录了我学习Shader时模仿或者自制的一些效果,并记录了实现这些效果的技术要点以及我对实现的一些反思与感悟。轻戳**技术要点**就可以看到SJM在写这些效果的时候遇到的各种奇奇怪怪的问题和他的解决方式喔~~~ 7 | 8 | 其中参考的资料都会在原文中提及,如果是将参考资料的一部分原文原封不动的搬过来的话,会像下面这样使用引用符号括起来~ 9 | 10 | > 这里类比一段摘抄的原文 11 | 12 | ## Volume 01 Diffuse 基于兰伯特光照模型的漫反射、高光反射光照 -> [技术要点](https://github.com/swordjoinmagic/Sjm-Shader-Collection/tree/master/Volume%2001%20Diffuse%20%E6%BC%AB%E5%8F%8D%E5%B0%84%E5%85%89%E7%85%A7) 13 | 14 | 1. 逐片元漫反射 15 | 16 | ![Avater](readmeImage/Volume01逐片元漫反射.png) 17 | 18 | 2. 逐顶点漫反射 19 | 20 | ![Avater](readmeImage/Volume01逐顶点漫反射.png) 21 | 22 | ## Volume 02 Texture 纹理的简单应用 -> [技术要点](https://github.com/swordjoinmagic/Sjm-Shader-Collection/tree/master/Volume%2002%20Texture%20纹理的应用) 23 | 24 | 1. 简单纹理映射 25 | 26 | ![Avater](readmeImage/Volume02-简单纹理映射.png) 27 | 28 | 2. 简单法线纹理映射(逐渐加大物体表面凹凸程度)-> [技术要点](https://github.com/swordjoinmagic/Sjm-Shader-Collection/tree/master/Volume%2002%20Texture%20%E7%BA%B9%E7%90%86%E7%9A%84%E5%BA%94%E7%94%A8#%E6%B3%95%E7%BA%BF%E6%98%A0%E5%B0%84) 29 | 30 | ![Avater](readmeImage/Volume02-法线纹理映射.gif) 31 | 32 | 3. 渐变纹理 -> [技术要点](https://github.com/swordjoinmagic/Sjm-Shader-Collection/tree/master/Volume%2002%20Texture%20%E7%BA%B9%E7%90%86%E7%9A%84%E5%BA%94%E7%94%A8#%E6%B8%90%E5%8F%98%E7%BA%B9%E7%90%86) 33 | 34 | ![Avater](readmeImage/Volume02-渐变纹理.png) 35 | 36 | ## Volume 03 Alpha 透明效果 -> [技术要点](https://github.com/swordjoinmagic/Sjm-Shader-Collection/tree/master/Volume%2003%20Alpha%20%E9%80%8F%E6%98%8E%E6%95%88%E6%9E%9C) 37 | 38 | 1. 透明度测试(在立方体上挖洞) -> [技术要点](https://github.com/swordjoinmagic/Sjm-Shader-Collection/tree/master/Volume%2003%20Alpha%20%E9%80%8F%E6%98%8E%E6%95%88%E6%9E%9C#%E9%80%8F%E6%98%8E%E5%BA%A6%E6%B5%8B%E8%AF%95) 39 | 40 | ![Avater](readmeImage/Volume03-透明度测试.png) 41 | 42 | 2. 透明度混合 -> [技术要点](https://github.com/swordjoinmagic/Sjm-Shader-Collection/tree/master/Volume%2003%20Alpha%20%E9%80%8F%E6%98%8E%E6%95%88%E6%9E%9C#%E9%80%8F%E6%98%8E%E5%BA%A6%E6%B7%B7%E5%90%88) 43 | 44 | ![Avater](readmeImage/Volume03-透明度混合.png) 45 | 46 | ## Volume 04 ForwardRender 渲染路径、阴影 -> [技术要点](https://github.com/swordjoinmagic/Sjm-Shader-Collection/tree/master/Volume%2004%20ForwardRender%E6%B8%B2%E6%9F%93%E8%B7%AF%E5%BE%84%E3%80%81%E9%98%B4%E5%BD%B1) 47 | 48 | 1. 基于ForwardAdd实现多光源照射 -> [技术要点](https://github.com/swordjoinmagic/Sjm-Shader-Collection/tree/master/Volume%2004%20ForwardRender%E6%B8%B2%E6%9F%93%E8%B7%AF%E5%BE%84%E3%80%81%E9%98%B4%E5%BD%B1#%E5%89%8D%E5%90%91%E6%B8%B2%E6%9F%93) 49 | 50 | ![Avater](readmeImage/Volume04-多光源照射.png) 51 | 52 | 2. 阴影的接受与投影 -> [技术要点](https://github.com/swordjoinmagic/Sjm-Shader-Collection/tree/master/Volume%2004%20ForwardRender%E6%B8%B2%E6%9F%93%E8%B7%AF%E5%BE%84%E3%80%81%E9%98%B4%E5%BD%B1#%E9%98%B4%E5%BD%B1) 53 | 54 | ![Avater](readmeImage/Volume04-阴影的投射.png) 55 | 56 | ## Volume 05 CubeMap 立方体纹理 -> [技术要点](https://github.com/swordjoinmagic/Sjm-Shader-Collection/tree/master/Volume%2005%20CubeMap%E7%AB%8B%E6%96%B9%E4%BD%93%E7%BA%B9%E7%90%86) 57 | 58 | 1. CubeMap实现天空盒的反射 -> [技术要点](https://github.com/swordjoinmagic/Sjm-Shader-Collection/tree/master/Volume%2005%20CubeMap%E7%AB%8B%E6%96%B9%E4%BD%93%E7%BA%B9%E7%90%86#121-%E5%8F%8D%E5%B0%84) 59 | 60 | ![Avater](readmeImage/Volume05-与天空盒的反射.png) 61 | 62 | 2. 玻璃效果的折射 -> [技术要点](https://github.com/swordjoinmagic/Sjm-Shader-Collection/tree/master/Volume%2005%20CubeMap%E7%AB%8B%E6%96%B9%E4%BD%93%E7%BA%B9%E7%90%86#122-%E6%8A%98%E5%B0%84) 63 | 64 | ![Avater](readmeImage/Volume05-玻璃折射.gif) 65 | 66 | 3. 火焰扭曲的折射效果 -> [技术要点](https://github.com/swordjoinmagic/Sjm-Shader-Collection/tree/master/Volume%2005%20CubeMap%E7%AB%8B%E6%96%B9%E4%BD%93%E7%BA%B9%E7%90%86#13-grabpass) 67 | 68 | ![Avater](readmeImage/Volume05-Fire.gif) 69 | 70 | ## Volume 06 VertexChange 顶点变换 -> [技术要点](https://github.com/swordjoinmagic/Sjm-Shader-Collection/tree/master/Volume%2006%20VertexChange%E9%A1%B6%E7%82%B9%E5%8F%98%E6%8D%A2) 71 | 72 | 1. 序列帧动画 -> [技术要点](https://github.com/swordjoinmagic/Sjm-Shader-Collection/tree/master/Volume%2006%20VertexChange%E9%A1%B6%E7%82%B9%E5%8F%98%E6%8D%A2#%E5%BA%8F%E5%88%97%E5%B8%A7%E5%8A%A8%E7%94%BB) 73 | 74 | ![Avater](readmeImage/序列帧动画.gif) 75 | 76 | 2. 广告板 -> [技术要点](https://github.com/swordjoinmagic/Sjm-Shader-Collection/tree/master/Volume%2006%20VertexChange%E9%A1%B6%E7%82%B9%E5%8F%98%E6%8D%A2#%E5%B9%BF%E5%91%8A%E6%9D%BFbillboarding%E6%8A%80%E6%9C%AF) 77 | 78 | ![Avater](readmeImage/广告版.gif) 79 | 80 | 在不使用广告版的情况下是这样的: 81 | 82 | ![Avater](readmeImage/不使用广告版.gif) 83 | 84 | 3. 广告板+序列帧动画(可以将2D动画移植到3D游戏中) 85 | 86 | ![Avater](readmeImage/广告版-序列帧.gif) 87 | 88 | 在不使用广告版的情况下是这样的: 89 | 90 | ![Avater](readmeImage/不使用广告版-序列帧.gif) 91 | 92 | ## Volume 07 SimplePostEffect 简单屏幕后处理 93 | 94 | 原图: 95 | 96 | ![Avater](readmeImage/Volume07-原图.png) 97 | 98 | 1. 高斯模糊 99 | 100 | ![Avater](readmeImage/Volume07-高斯模糊.png) 101 | 102 | 2. Bloom 103 | 104 | ![Avater](readmeImage/Volume07-Bloom.png) 105 | 106 | 3. 运动模糊(基于当前帧与上一帧混合的方案制作) 107 | 108 | ![Avater](readmeImage/Volume07-motionBlur.gif) 109 | 110 | 4. 基于边缘检测进行描边(使用屏幕像素值和sobel算子判断边缘) 111 | 112 | ![Avater](readmeImage/Volume07-边缘检测进行描边.png) 113 | 114 | ## Volume 08 DepthNormalTexture 深度和法线纹理的简单应用 115 | 116 | 1. 运动模糊(使用矩阵变换和深度重建世界坐标) 117 | 118 | ![Avater](readmeImage/Volume08-运动模糊.gif) 119 | 120 | 2. 基于高度的雾效(使用射线+摄像机位置+深度重建世界坐标) 121 | 122 | ![Avater](readmeImage/Volume08-高度雾.gif) 123 | 124 | ## Volume 09 EdgeDetection 详解边缘检测 125 | 126 | 1. 基于边缘检测的转场效果(基于噪声、uv变换、边缘检测) 127 | 128 | ![Avater](readmeImage/Volume09-边缘检测转场.gif) 129 | 130 | 2. 基于边缘检测的子弹时间效果(基于重建世界坐标、噪声、uv变换、边缘检测、mask图) 131 | 132 | ![Avater](readmeImage/Volume09-边缘检测子弹时间.gif) 133 | 134 | ## Volume 10 Projector 投影仪的原理及简单应用 135 | 136 | 1. 仿动画Revisions ED中人物以线条形式投影到大楼上的效果(虽然跟动画里效果差了十万八千里就是了) 137 | 138 | ![Avater](readmeImage/projectorDisslove.gif) 139 | 140 | 第二版,加上了_Time变量和噪声使线条呈不规则运动(依旧不像): 141 | 142 | ![Avater](readmeImage/projectorDisslove2.gif) 143 | -------------------------------------------------------------------------------- /readmeImage/Volume01逐片元漫反射.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/readmeImage/Volume01逐片元漫反射.png -------------------------------------------------------------------------------- /readmeImage/Volume01逐顶点漫反射.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/readmeImage/Volume01逐顶点漫反射.png -------------------------------------------------------------------------------- /readmeImage/Volume02-法线纹理映射.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/readmeImage/Volume02-法线纹理映射.gif -------------------------------------------------------------------------------- /readmeImage/Volume02-渐变纹理.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/readmeImage/Volume02-渐变纹理.png -------------------------------------------------------------------------------- /readmeImage/Volume02-简单纹理映射.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/readmeImage/Volume02-简单纹理映射.png -------------------------------------------------------------------------------- /readmeImage/Volume03-透明度测试.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/readmeImage/Volume03-透明度测试.png -------------------------------------------------------------------------------- /readmeImage/Volume03-透明度混合.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/readmeImage/Volume03-透明度混合.png -------------------------------------------------------------------------------- /readmeImage/Volume04-多光源照射.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/readmeImage/Volume04-多光源照射.png -------------------------------------------------------------------------------- /readmeImage/Volume04-阴影的投射.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/readmeImage/Volume04-阴影的投射.png -------------------------------------------------------------------------------- /readmeImage/Volume05-Fire.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/readmeImage/Volume05-Fire.gif -------------------------------------------------------------------------------- /readmeImage/Volume05-与天空盒的反射.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/readmeImage/Volume05-与天空盒的反射.png -------------------------------------------------------------------------------- /readmeImage/Volume05-玻璃折射.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/readmeImage/Volume05-玻璃折射.gif -------------------------------------------------------------------------------- /readmeImage/Volume07-Bloom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/readmeImage/Volume07-Bloom.png -------------------------------------------------------------------------------- /readmeImage/Volume07-motionBlur.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/readmeImage/Volume07-motionBlur.gif -------------------------------------------------------------------------------- /readmeImage/Volume07-原图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/readmeImage/Volume07-原图.png -------------------------------------------------------------------------------- /readmeImage/Volume07-边缘检测进行描边.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/readmeImage/Volume07-边缘检测进行描边.png -------------------------------------------------------------------------------- /readmeImage/Volume07-高斯模糊.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/readmeImage/Volume07-高斯模糊.png -------------------------------------------------------------------------------- /readmeImage/Volume08-运动模糊.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/readmeImage/Volume08-运动模糊.gif -------------------------------------------------------------------------------- /readmeImage/Volume08-高度雾.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/readmeImage/Volume08-高度雾.gif -------------------------------------------------------------------------------- /readmeImage/Volume09-边缘检测子弹时间.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/readmeImage/Volume09-边缘检测子弹时间.gif -------------------------------------------------------------------------------- /readmeImage/Volume09-边缘检测转场.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/readmeImage/Volume09-边缘检测转场.gif -------------------------------------------------------------------------------- /readmeImage/projectorDisslove.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/readmeImage/projectorDisslove.gif -------------------------------------------------------------------------------- /readmeImage/projectorDisslove2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/readmeImage/projectorDisslove2.gif -------------------------------------------------------------------------------- /readmeImage/不使用广告版-序列帧.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/readmeImage/不使用广告版-序列帧.gif -------------------------------------------------------------------------------- /readmeImage/不使用广告版.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/readmeImage/不使用广告版.gif -------------------------------------------------------------------------------- /readmeImage/广告版-序列帧.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/readmeImage/广告版-序列帧.gif -------------------------------------------------------------------------------- /readmeImage/广告版.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/readmeImage/广告版.gif -------------------------------------------------------------------------------- /readmeImage/序列帧动画.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swordjoinmagic/Sjm-Shader-Collection/c4d1993526e63867b4481e95f2dda82fedac5006/readmeImage/序列帧动画.gif --------------------------------------------------------------------------------