# Unity-Shader-学习 **Repository Path**: unitysir_admin/unity-shader-learning ## Basic Information - **Project Name**: Unity-Shader-学习 - **Description**: 网易云课程 : https://study.163.com/course/courseMain.htm?courseId=1209506831&share=1&shareId=1034776399 - **Primary Language**: C# - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2022-03-24 - **Last Updated**: 2023-02-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README [toc] # 多敲代码! 多敲代码! 多敲代码! ## Shader基本认识 1. **Mesh Filter** :存储一个Mesh(网格,模型的网格,就是模型的由哪些三角面组成,组成一个什么样子的模型,三角面的一些顶点信息) 2. **Mesh Renderer** : 用来渲染一个模型的外观,就是样子, 按照 mesh给它皮肤,给它颜色通过Material(材质)控制模型渲染的样子 3. **Material** :可以使用贴图(可以没有,可以是一个单纯的颜色) , 也可以使用Shader --- ### 什么是OpenGL、DirectX shader可以认为是一种渲染命令 ,由opengl 或者dx进行解析,来控制渲染丰富多彩的图形 OpenGL 使用GLSL 编写shader DirectX 使用HSSL 编写shader 英伟达 CG 编写shader(跨平台) --- ### Unity Shader的分类 使用的是ShaderLab编写Unity中的Shader 1,表面着色器 Surface Shader 2,顶点/片元着色器 Vertex/Fragment Shader 3,固定函数着色器 Fixed Function Shader --- ### Unity Shader的基本结构 ```C //这里指定Shader的名称,不要求与文件名称相同 Shader "Custom/M1_1" { Properties{ //属性 _Color("Color",Color)=(1,1,1,1) } //可以有多个SubShader //当显卡运行效果时,会从第一个SubShader开始查找,找到第一个能够完整运行的SubShader SubShader{ //至少有一个Pass Pass{ CGPROGRAM //使用CG语言在这里编写Shader代码 ENDCG } } //当之前的SubShader在显卡上都不能运行时,就运行Fallback之后的这个低级的Shader Fallback "VertexLit" } ``` --- ### Shader常用属性类型 ```C _Color("Color",Color)=(1,1,1,1) //flaot4 _Vector("Vector",Vector)=(1,2,3,4) //float4 _Int("Int",Int)=12345 //float _Float("Float",Float)=66 //float _Range("Range",Range(1,100))=2 //float _2D("Texture",2D)="red"{} //sampler2D _Cube("Cube",Cube)="white"{} //samplerCube _3D("3D",3D)="black"{} //sampler3D ``` --- ### Shader中使用属性 ```c SubShader{ Pass{ CGPROGRAM //想要在Shader中使用上面的属性,必须要再次声明,如下: float4 _Color; float4 _Vector; float _Int; float _Float; float _Range; sampler2D _2D; samplerCube _Cube; sampler3D _3D; ENDCG } } ``` 其中 `float` 为 32位 ; `half` 为16位 , 值在 `-6万 ~ 6万之间` ,如果超过该值 , 必须使用 `float` `fixed` 为 11位 , 值在 `-2 ~ +2 之间` , 可用于定义颜色(颜色在[0 ~ 1]之间) --- ### Shader中的vert和frag函数 #### Shader中声明vert和frag函数 ```c Shader "Custom/M2" { SubShader{ Pass{ CGPROGRAM //顶点函数 , 这里只是声明了顶点函数的函数名(vert) //该函数作用: 完成顶点坐标从模型坐标到剪裁空间的转换 (从游戏环境转换到视野相机屏幕上) #pragma vertex vert //片元函数 , 这里只是声明了片元函数的函数名(frag) //该函数作用: 返回模型对应的屏幕上的每一个像素的颜色值 #pragma fragment frag ENDCG } } Fallback "VertexLit" } ``` **vertex** 函数作用 : `完成顶点坐标从模型坐标到剪裁空间的转换 (从游戏环境转换到视野相机屏幕上)` **fragment** 函数作用 : `返回模型对应的屏幕上的每一个像素的颜色值` 例子 : `#pragma vertex vert` 中 `vert`为函数名 , `#pragma fragment frag`中`frag`为函数名 #### Shader中使用vert和frag函数 ```c Shader "Custom/M2" { SubShader{ Pass{ CGPROGRAM //顶点函数 , 这里只是声明了顶点函数的函数名 //该函数作用: 完成顶点坐标从模型坐标到剪裁空间的转换 (从游戏环境转换到视野相机屏幕上) #pragma vertex vert //片元函数 , 这里只是声明了片元函数的函数名 //该函数作用: 返回模型对应的屏幕上的每一个像素的颜色值 #pragma fragment frag //使用 vert 和 frag 函数 // 语义: [ float v : POSITION ] 将当前的 顶点坐标值传给 变量 v // 语义: [ SV_POSITON ] 通过 float4 返回剪裁空间下的顶点坐标 float4 vert(float4 v : POSITION) : SV_POSITION { return UnityObjectToClipPos(v); } // 语义: [ SV_Target ] 获取当前的颜色 fixed4 frag() : SV_Target{ //这里返回时,需要加上返回值的类型 return fixed4(1,1,1,1); } ENDCG } } Fallback "VertexLit" } ``` --- ### Shader中函数的传值 ```c Shader "Custom/M3" { SubShader{ Pass{ CGPROGRAM #pragma vertex vert #pragma fragment frag struct a2v{ // 告诉Unity把模型空间下的顶点坐标填充给 _vertex float4 vertex : POSITION; // 告诉Unity把模型空间下的法线填充给 _normal float3 normal : NORMAL; // 告诉Unity把第一套纹理坐标填充给 _texcoord float4 texcoord : TEXCOORD0; }; struct v2f{ float4 position : SV_POSITION; float3 normal : COLOR0; }; //通过结构体传值 v2f vert(a2v v) { v2f f; f.position = UnityObjectToClipPos(v.vertex); f.normal = v.normal; return f; } //通过结构体传值 fixed4 frag(v2f f) : SV_Target{ return fixed4(f.normal,1); } ENDCG } } Fallback "VertexLit" } ``` 在这段代码中 , 顶点函数(`vert`) 和 片元函数(`frag`) 之间的传值 , 主要靠声明的结构体 `a2v` 和 `v2f` --- ### 光照模型 #### 1.什么是光照模型 光照模型就是一个公式 , 我们可以使用这个公式来计算在某个点的光照效果 #### 2.标准光照模型 > 1. **只有定义了正确的 LightModel 才能得到一些Unity中内置的光照变量** > > 在`Pass`中,在`CGPROGRAM`前使用,如下: > ```c > Pass{ > Tags{ > //使用的光照模型 > "LightModel" = "ForwardBase" > } > } > CGPROGRAM > ... > ENDCG > ``` > > 2. **包含Unity中内置的文件,才能使用Unity内置的一些变量** > > 在`CGPROGRAM`中引入Unity内置文件,如下: > > ```C > CGPROGRAM > //引入Unity的光照文件 => 相对于C#中使用 using > #include "Lighting.cginc" > ENDCG > ``` > > 在标准的光照模型里面,我们把进入相机的光分为四个部分 ##### 1.自发光 ##### 2.漫反射(常用) > **漫反射计算公式** > > `Diffuse = 直射光的颜色 * Max(0,Cos夹角(光与法线的夹角))` > > `Cosθ = 光的方向 · 法线方向` > > ### 漫反射 > > ```c > Shader "Custom/M4" > { > SubShader{ > Pass{ > Tags{ > //使用的光照模型 > "LightModel" = "ForwardBase" > } > CGPROGRAM > //引入Unity的光照文件 => 相对于C#中使用 using > #include "Lighting.cginc" > //取得第一个直射光的颜色 => _LightColor0 , 第一个直射光的位置 => _WorldSpaceLightPos0 > > //声明顶点函数和片元函数 > #pragma vertex vert > #pragma fragment frag > > struct a2v{ > // 告诉Unity把模型空间下的顶点坐标填充给 vertex > float4 vertex : POSITION; > //获取到模型空间下的法线 > float3 normal : NORMAL; > }; > > struct v2f{ > float4 position : SV_POSITION; > fixed3 color : COLOR; > }; > > v2f vert(a2v v) { > v2f f; > > //问:为什么说光的方向就是光的位置? > //答:对于每个顶点来说,光的位置就是光的方向,因为光是平行光 > //_WorldSpaceLightPos0.xyz 是一个向量,而 normalize就是将这个向量给归一化 > fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz); > //获取法线的方向 > fixed3 normalDir = normalize(mul(v.normal,(float3x3)unity_WorldToObject)); > //获取漫反射的颜色 > fixed3 diffuse = _LightColor0.rgb * max(dot(normalDir,lightDir),0); > f.color = diffuse; > f.position = UnityObjectToClipPos(v.vertex); > return f; > } > > fixed4 frag(v2f f) : SV_Target{ > return fixed4(f.color,1); > } > ENDCG > } > } > Fallback "VertexLit" > } > ``` > > `normalize()` 将一个向量 单位化(归一化) => 原来的方向保持不变,长度最长变为1 > > `max()` 获取最大的一个值 > > `dot()` 获取两个向量的点积 > > `_LightColor0` 获取平行光的颜色 > > `_WorldSpaceLightPos0` 获取平行光的位置 > > `UnityObjectToClipPos()` 矩阵=>把一个坐标从模型空间转到裁剪空间 > > `unity_WorldToObject` 矩阵=>把一个方向从世界坐标转到模型空间 > > > > ### 给*漫反射*和*环境光*添加颜色控制 > > ```C > Shader "Custom/M4" > { > Properties{ > _Diffuse("Diffuse Color",Color)=(1,1,1,1) > } > > SubShader{ > Pass{ > > Tags{ > //使用的光照模型 > "LightModel" = "ForwardBase" > } > > CGPROGRAM > > //引入Unity的光照文件 => 相对于C#中使用 using > #include "Lighting.cginc" > //取得第一个直射光的颜色 => _LightColor0 > //取得第一个直射光的位置 => _WorldSpaceLightPos0 > > //声明顶点函数和片元函数 > #pragma vertex vert > #pragma fragment frag > > fixed4 _Diffuse; > > struct a2v{ > // 告诉Unity把模型空间下的顶点坐标填充给 vertex > float4 vertex : POSITION; > //获取到模型空间下的法线 > float3 normal : NORMAL; > }; > > struct v2f{ > float4 position : SV_POSITION; > fixed3 color : COLOR; > }; > > v2f vert(a2v v) { > v2f f; > > //获取环境光 > fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb; > > //问:为什么说光的方向就是光的位置? > //答:对于每个顶点来说,光的位置就是光的方向,因为光是平行光 > //_WorldSpaceLightPos0.xyz 是一个向量,而 normalize就是将这个向量给归一化 > fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz); > //获取法线的方向 > fixed3 normalDir = normalize(mul(v.normal,(float3x3)unity_WorldToObject)); > //获取漫反射的颜色,如果需要外部来控制颜色的改变只需要乘以颜色值即可,如:_Diffuse.rgb > fixed3 diffuse = _LightColor0.rgb * max(dot(normalDir,lightDir),0) * _Diffuse.rgb; > f.color = diffuse + ambient;//漫反射 + 环境光 , 加了环境光之后,模型的亮度增加了 > f.position = UnityObjectToClipPos(v.vertex); > return f; > } > > fixed4 frag(v2f f) : SV_Target{ > return fixed4(f.color,1); > } > ENDCG > } > } > Fallback "VertexLit" > } > ``` > > 获取环境光 `UNITY_LIGHTMODEL_AMBIENT.rgb` > > 第一个直射光颜色 `_LightColor0` > > 第一个直射光位置 `_WorldSpaceLightPos0` > > 颜色融合 使用 `*` > > 颜色叠加 使用 `+` > > ### 逐像素光照(片元函数) > > ```c > Shader "Custom/M5-Diffuse_Fragment" > { > Properties{ > _Diffuse("Diffuse Color",Color)=(1,1,1,1) > } > > SubShader{ > Pass{ > > Tags{ > //使用的光照模型 > "LightModel" = "ForwardBase" > } > > CGPROGRAM > > //引入Unity的光照文件 => 相对于C#中使用 using > #include "Lighting.cginc" > //取得第一个直射光的颜色 => _LightColor0 , 第一个直射光的位置 => _WorldSpaceLightPos0 > > //声明顶点函数和片元函数 > #pragma vertex vert > #pragma fragment frag > > fixed4 _Diffuse; > > struct a2v{ > // 告诉Unity把模型空间下的顶点坐标填充给 vertex > float4 vertex : POSITION; > //获取到模型空间下的法线 > float3 normal : NORMAL; > }; > > struct v2f{ > float4 position : SV_POSITION; > fixed3 worldNormalDir : COLOR0; > }; > > v2f vert(a2v v) { > v2f f; > > f.position = UnityObjectToClipPos(v.vertex); > f.worldNormalDir = mul(v.normal,(float3x3)unity_WorldToObject); > return f; > } > > fixed4 frag(v2f f) : SV_Target{ > > //获取环境光 > fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb; > > //问:为什么说光的方向就是光的位置? > //答:对于每个顶点来说,光的位置就是光的方向,因为光是平行光 > //_WorldSpaceLightPos0.xyz 是一个向量,而 normalize就是将这个向量给归一化 > fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz); > //获取法线的方向 > fixed3 normalDir = normalize(f.worldNormalDir); > //获取漫反射的颜色,如果需要外部来控制颜色的改变只需要乘以颜色值即可,如:_Diffuse.rgb > fixed3 diffuse = _LightColor0.rgb * max(dot(normalDir,lightDir),0) * _Diffuse.rgb; > fixed3 tempColor = diffuse + ambient;//漫反射 + 环境光 , 加了环境光之后,模型的亮度增加了 > > return fixed4(tempColor,1); > } > ENDCG > } > } > Fallback "VertexLit" > } > ``` > > `M4`和`M5`的代码分别在`顶点函数`和`片元函数`中计算,如下 : > ![image-20220323201757155](Unity Shader 笔记.assets/image-20220323201757155.png) > > 片元函数中计算的效果明显比顶点函数计算的效果好,因为顶点函数中的渲染次数较少 > > **关于光的方向** > > 在代码中有`lightDir`的变量 , 该光的方向如下图所示 : > ![image-20220324142007857](Unity Shader 笔记.assets/image-20220324142007857.png) ##### 3.高光反射 > **公式:** > > 1. Blinn光照模型公式 > > `Specular = 直射光颜色 * pow(max(cosθ,0),高光参数x)` pow()函数为求次方 > > `高光参数 默认 = 10` > > `θ ` 是**反射光方向**和**视野方向**的**夹角**,如下 : > ![image-20220323210303307](Unity Shader 笔记.assets/image-20220323210303307.png) > > 2. Blinn-Phong光照模型公式 > > `Specular = 直射光颜色 * pow(max(cosθ,0),高光参数x)` pow()函数为求次方 > > `高光参数 默认 = 10` > > `θ ` 是[法线]和**[平行光和视野方向的平分线]**的**夹角**,如下 : > ![image-20220324214211210](Unity Shader 笔记.assets/image-20220324214211210.png) > > ### 基于顶点函数的高光反射(逐顶点) > > ```c > Shader "Custom/M7-Specular_Vertex" > { > Properties{ > _Diffuse("Diffuse Color",Color)=(1,1,1,1) > _Specular("Specular Color",Color)=(1,1,1,1) > _Gloss("Gloss",Range(8,100))=10 > } > > SubShader{ > Pass{ > > Tags{ > //使用的光照模型 > "LightModel" = "ForwardBase" > } > > CGPROGRAM > > //引入Unity的光照文件 => 相对于C#中使用 using > #include "Lighting.cginc" > //取得第一个直射光的颜色 => _LightColor0 , 第一个直射光的位置 => _WorldSpaceLightPos0 > > //声明顶点函数和片元函数 > #pragma vertex vert > #pragma fragment frag > > fixed4 _Diffuse;//控制漫反射的颜色 > half _Gloss;//控制高光反射的亮度 > fixed4 _Specular;//控制高光反射的颜色 > > struct a2v{ > // 告诉Unity把模型空间下的顶点坐标填充给 vertex > float4 vertex : POSITION; > //获取到模型空间下的法线 > float3 normal : NORMAL; > }; > > struct v2f{ > float4 position : SV_POSITION; > fixed3 color : COLOR; > }; > > v2f vert(a2v v) { > v2f f; > > f.position = UnityObjectToClipPos(v.vertex); > > //获取环境光 > fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb; > > //问:为什么说光的方向就是光的位置? > //答:对于每个顶点来说,光的位置就是光的方向,因为光是平行光 > //_WorldSpaceLightPos0.xyz 是一个向量,而 normalize就是将这个向量给归一化 > fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz); > //获取法线的方向 > fixed3 normalDir = normalize(mul(v.normal,(float3x3)unity_WorldToObject)); > //获取漫反射的颜色,如果需要外部来控制颜色的改变只需要乘以颜色值即可,如:_Diffuse.rgb > fixed3 diffuse = _LightColor0.rgb * max(dot(normalDir,lightDir),0) * _Diffuse.rgb; > > fixed3 reflectDir = normalize(reflect(-lightDir,normalDir)); > fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(v.vertex,unity_WorldToObject).xyz); > fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(dot(reflectDir,viewDir),0),_Gloss); > > f.color = diffuse + ambient + specular; > > return f; > } > > fixed4 frag(v2f f) : SV_Target{ > return fixed4(f.color,1); > } > ENDCG > } > } > Fallback "VertexLit" > } > ``` > > **效果如下 : ** > ![image-20220324203050079](Unity Shader 笔记.assets/image-20220324203050079.png) > > ### 基于片元函数的高光反射(逐像素) > > ```c > Shader "Custom/M8-Specular_Fragment" > { > Properties{ > _Diffuse("Diffuse Color",Color)=(1,1,1,1) > _Specular("Specular Color",Color)=(1,1,1,1) > _Gloss("Gloss",Range(8,100))=10 > } > > SubShader{ > Pass{ > > Tags{ > //使用的光照模型 > "LightModel" = "ForwardBase" > } > > CGPROGRAM > > //引入Unity的光照文件 => 相对于C#中使用 using > #include "Lighting.cginc" > //取得第一个直射光的颜色 => _LightColor0 , 第一个直射光的位置 => _WorldSpaceLightPos0 > > //声明顶点函数和片元函数 > #pragma vertex vert > #pragma fragment frag > > fixed4 _Diffuse;//控制漫反射的颜色 > half _Gloss;//控制高光反射的亮度 > fixed4 _Specular;//控制高光反射的颜色 > > struct a2v{ > // 告诉Unity把模型空间下的顶点坐标填充给 vertex > float4 vertex : POSITION; > //获取到模型空间下的法线 > float3 normal : NORMAL; > }; > > struct v2f{ > float4 position : SV_POSITION; > float3 worldNormal : TEXCOORD0; > float3 worldVertex : TEXCOORD1; > }; > > v2f vert(a2v v) { > v2f f; > > f.position = UnityObjectToClipPos(v.vertex); > > f.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject); > f.worldVertex = mul(v.vertex,unity_WorldToObject).xyz; > > return f; > } > > fixed4 frag(v2f f) : SV_Target{ > > //获取环境光 > fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb; > > //问:为什么说光的方向就是光的位置? > //答:对于每个顶点来说,光的位置就是光的方向,因为光是平行光 > //_WorldSpaceLightPos0.xyz 是一个向量,而 normalize就是将这个向量给归一化 > fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz); > //获取法线的方向 > fixed3 normalDir = normalize(f.worldNormal); > //获取漫反射的颜色,如果需要外部来控制颜色的改变只需要乘以颜色值即可,如:_Diffuse.rgb > fixed3 diffuse = _LightColor0.rgb * max(dot(normalDir,lightDir),0) * _Diffuse.rgb; > > fixed3 reflectDir = normalize(reflect(-lightDir,normalDir)); > fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - f.worldVertex); > fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(dot(reflectDir,viewDir),0),_Gloss); > > fixed3 tempColor = diffuse + ambient + specular; > > return fixed4(tempColor,1); > } > ENDCG > } > } > Fallback "VertexLit" > } > ``` > > **效果如下 : ** > ![image-20220324210957468](Unity Shader 笔记.assets/image-20220324210957468.png) > > ### 基于片元函数的[Blinn-Phong]高光反射(逐像素) > > ```c > Shader "Custom/M9-Specular_Fragment_BlinnPhong" > { > Properties{ > _Diffuse("Diffuse Color",Color)=(1,1,1,1) > _Specular("Specular Color",Color)=(1,1,1,1) > _Gloss("Gloss",Range(8,100))=10 > } > > SubShader{ > Pass{ > > Tags{ > //使用的光照模型 > "LightModel" = "ForwardBase" > } > > CGPROGRAM > > //引入Unity的光照文件 => 相对于C#中使用 using > #include "Lighting.cginc" > //取得第一个直射光的颜色 => _LightColor0 , 第一个直射光的位置 => _WorldSpaceLightPos0 > > //声明顶点函数和片元函数 > #pragma vertex vert > #pragma fragment frag > > fixed4 _Diffuse;//控制漫反射的颜色 > half _Gloss;//控制高光反射的亮度 > fixed4 _Specular;//控制高光反射的颜色 > > struct a2v{ > // 告诉Unity把模型空间下的顶点坐标填充给 vertex > float4 vertex : POSITION; > //获取到模型空间下的法线 > float3 normal : NORMAL; > }; > > struct v2f{ > float4 position : SV_POSITION; > float3 worldNormal : TEXCOORD0; > float3 worldVertex : TEXCOORD1; > }; > > v2f vert(a2v v) { > v2f f; > > f.position = UnityObjectToClipPos(v.vertex); > > f.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject); > f.worldVertex = mul(v.vertex,unity_WorldToObject).xyz; > > return f; > } > > fixed4 frag(v2f f) : SV_Target{ > > //获取环境光 > fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb; > > //问:为什么说光的方向就是光的位置? > //答:对于每个顶点来说,光的位置就是光的方向,因为光是平行光 > //_WorldSpaceLightPos0.xyz 是一个向量,而 normalize就是将这个向量给归一化 > fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz); > //获取法线的方向 > fixed3 normalDir = normalize(f.worldNormal); > //获取漫反射的颜色,如果需要外部来控制颜色的改变只需要乘以颜色值即可,如:_Diffuse.rgb > fixed3 diffuse = _LightColor0.rgb * max(dot(normalDir,lightDir),0) * _Diffuse.rgb; > //相机视野方向 > fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - f.worldVertex); > //平行光和视野方向平分线方向 > fixed3 halfDir = normalize(lightDir + viewDir); > //高光反射 > fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(dot(normalDir,halfDir),0),_Gloss); > > fixed3 tempColor = diffuse + ambient + specular; > > return fixed4(tempColor,1); > } > ENDCG > } > } > Fallback "VertexLit" > } > ``` > > **使用Unity内置函数实现同样效果,如下 : ** > > ```c > Shader "Custom/M9-Specular_Fragment_BlinnPhong" > { > Properties{ > _Diffuse("Diffuse Color",Color)=(1,1,1,1) > _Specular("Specular Color",Color)=(1,1,1,1) > _Gloss("Gloss",Range(8,100))=10 > } > > SubShader{ > Pass{ > > Tags{ > //使用的光照模型 > "LightModel" = "ForwardBase" > } > > CGPROGRAM > > //引入Unity的光照文件 => 相对于C#中使用 using > #include "Lighting.cginc" > //取得第一个直射光的颜色 => _LightColor0 , 第一个直射光的位置 => _WorldSpaceLightPos0 > > //声明顶点函数和片元函数 > #pragma vertex vert > #pragma fragment frag > > fixed4 _Diffuse;//控制漫反射的颜色 > half _Gloss;//控制高光反射的亮度 > fixed4 _Specular;//控制高光反射的颜色 > > struct a2v{ > // 告诉Unity把模型空间下的顶点坐标填充给 vertex > float4 vertex : POSITION; > //获取到模型空间下的法线 > float3 normal : NORMAL; > }; > > struct v2f{ > float4 position : SV_POSITION; > float3 worldNormal : TEXCOORD0; > float4 worldVertex : TEXCOORD1; > }; > > v2f vert(a2v v) { > v2f f; > > f.position = UnityObjectToClipPos(v.vertex); > //f.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject); > f.worldNormal = UnityObjectToWorldNormal(v.normal); > f.worldVertex = mul(v.vertex,unity_WorldToObject); > > return f; > } > > fixed4 frag(v2f f) : SV_Target{ > > //获取环境光 > fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb; > > //问:为什么说光的方向就是光的位置? > //答:对于每个顶点来说,光的位置就是光的方向,因为光是平行光 > //_WorldSpaceLightPos0.xyz 是一个向量,而 normalize就是将这个向量给归一化 > //fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz); > fixed3 lightDir = normalize(WorldSpaceLightDir(f.worldVertex)); > //获取法线的方向 > fixed3 normalDir = normalize(f.worldNormal); > //获取漫反射的颜色,如果需要外部来控制颜色的改变只需要乘以颜色值即可,如:_Diffuse.rgb > fixed3 diffuse = _LightColor0.rgb * max(dot(normalDir,lightDir),0) * _Diffuse.rgb; > //相机视野方向 > //fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - f.worldVertex); > fixed3 viewDir = normalize(UnityWorldSpaceViewDir(f.worldVertex)); > //平行光和视野方向平分线方向 > fixed3 halfDir = normalize(lightDir + viewDir); > //高光反射 > fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(dot(normalDir,halfDir),0),_Gloss); > > fixed3 tempColor = diffuse + ambient + specular; > > return fixed4(tempColor,1); > } > ENDCG > } > } > Fallback "VertexLit" > } > ``` > > > > **效果如下 : ** > ![image-20220324215708789](Unity Shader 笔记.assets/image-20220324215708789.png) ##### 4.环境光 > **个人总结** > > shader代码的流程 : > `通过顶点函数传入并获取到模型的数据,然后再顶点函数中由程序员自定义计算和操作,顶点函数在将最后的结果返回到片元函数;片元函数接收到数据之后,再由程序员自定义计算和操作,将最终的结果返回到屏幕并呈现出来.` --- #### 兰伯特光照模型 编写好Shader后,将环境光设置为黑色 , 如果模型的背面出现这种纯黑的颜色 , 我们就称之为 => "半兰伯特光照" , 如下: ![image-20220323202641037](Unity Shader 笔记.assets/image-20220323202641037.png) > **半兰伯特光照公式** > > `Diffuse = 直射光的颜色 * (Cosθ * 0.5 + 0.5)` > > `Cosθ = 光的方向 · 法线方向` > > **代码** > > ```c > Shader "Custom/M6-Diffuse_Fragment_HalfLambert" > { > Properties{ > _Diffuse("Diffuse Color",Color)=(1,1,1,1) > } > > SubShader{ > Pass{ > > Tags{ > //使用的光照模型 > "LightModel" = "ForwardBase" > } > > CGPROGRAM > > //引入Unity的光照文件 => 相对于C#中使用 using > #include "Lighting.cginc" > //取得第一个直射光的颜色 => _LightColor0 , 第一个直射光的位置 => _WorldSpaceLightPos0 > > //声明顶点函数和片元函数 > #pragma vertex vert > #pragma fragment frag > > fixed4 _Diffuse; > > struct a2v{ > // 告诉Unity把模型空间下的顶点坐标填充给 vertex > float4 vertex : POSITION; > //获取到模型空间下的法线 > float3 normal : NORMAL; > }; > > struct v2f{ > float4 position : SV_POSITION; > fixed3 worldNormalDir : COLOR0; > }; > > v2f vert(a2v v) { > v2f f; > > f.position = UnityObjectToClipPos(v.vertex); > f.worldNormalDir = mul(v.normal,(float3x3)unity_WorldToObject); > return f; > } > > fixed4 frag(v2f f) : SV_Target{ > > //获取环境光 > fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb; > > //问:为什么说光的方向就是光的位置? > //答:对于每个顶点来说,光的位置就是光的方向,因为光是平行光 > //_WorldSpaceLightPos0.xyz 是一个向量,而 normalize就是将这个向量给归一化 > fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz); > //获取法线的方向 > fixed3 normalDir = normalize(f.worldNormalDir); > float halfLambert = dot(normalDir,lightDir) * 0.5 + 0.5; > //获取漫反射的颜色,如果需要外部来控制颜色的改变只需要乘以颜色值即可,如:_Diffuse.rgb > fixed3 diffuse = _LightColor0.rgb * halfLambert * _Diffuse.rgb; > fixed3 tempColor = diffuse + ambient;//漫反射 + 环境光 , 加了环境光之后,模型的亮度增加了 > > return fixed4(tempColor,1); > } > ENDCG > } > } > Fallback "VertexLit" > } > ``` > > **效果** > > 使用了 Lambert 光照之后,模型的背面会更加立体 , 如下: > > ![image-20220323204816649](Unity Shader 笔记.assets/image-20220323204816649.png) #### 漫反射和高光反射弧的统一实现 > ```c > Shader "Custom/M10-Diffuse_Specular" > { > Properties > { > _Diffuse("Diffuse Color",Color)=(1,1,1,1) > _Specular("Specular Color",Color)=(1,1,1,1) > _Gloss("Gloss",Range(10,100))=15 > } > > SubShader > { > Pass > { > Tags > { > "LightModel"="ForwardBase" > } > > CGPROGRAM > > #include "Lighting.cginc" > #pragma vertex vert > #pragma fragment frag > > fixed4 _Diffuse; > fixed4 _Specular; > half _Gloss; > > struct a2v > { > float4 vertex : POSITION; > float3 normal : NORMAL; > }; > > struct v2f > { > float4 svPos : SV_POSITION; > float3 worldNormal : TEXCOORD0; > float4 worldVertex : TEXCOORD1; > }; > > v2f vert(a2v v) > { > v2f f; > f.svPos = UnityObjectToClipPos(v.vertex); > f.worldNormal = UnityObjectToWorldNormal(v.normal); > f.worldVertex = mul(v.vertex,unity_WorldToObject); > return f; > } > > fixed4 frag(v2f f) : SV_Target > { > fixed3 normalDir = normalize(f.worldNormal); > fixed3 lightDir = normalize(WorldSpaceLightDir(f.worldVertex)); > fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(dot(normalDir,lightDir),0); > > fixed3 viewDir = normalize(UnityWorldSpaceViewDir(f.worldVertex)); > fixed3 halfDir = normalize(lightDir + viewDir); > > fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(dot(normalDir,halfDir),0),_Gloss); > > fixed3 tempColor = diffuse + specular + UNITY_LIGHTMODEL_AMBIENT.rgb; > > return fixed4(tempColor,1); > } > ENDCG > } > } > Fallback "Specular" > } > ``` > > **效果** > ![image-20220325211859542](Unity Shader 笔记.assets/image-20220325211859542.png) #### Shader中添加纹理 > ```c > Shader "Custom/M11-Single_Textures" > { > Properties > { > //_Diffuse("Diffuse Color",Color)=(1,1,1,1) > _Color("Color",Color)=(1,1,1,1) > _MainTex("Main Texture",2D)="white"{} > _Specular("Specular Color",Color)=(1,1,1,1) > _Gloss("Gloss",Range(10,100))=15 > } > > SubShader > { > Pass > { > Tags > { > "LightModel"="ForwardBase" > } > > CGPROGRAM > > #include "Lighting.cginc" > #pragma vertex vert > #pragma fragment frag > > //fixed4 _Diffuse; > > fixed4 _Color; > sampler2D _MainTex; > fixed4 _Specular; > half _Gloss; > > struct a2v > { > float4 vertex : POSITION; > float3 normal : NORMAL; > float4 texcoord : TEXCOORD0; > }; > > struct v2f > { > float4 svPos : SV_POSITION; > float3 worldNormal : TEXCOORD0; > float4 worldVertex : TEXCOORD1; > float4 uv : TEXCOORD2; > }; > > > v2f vert(a2v v) > { > v2f f; > f.svPos = UnityObjectToClipPos(v.vertex); > f.worldNormal = UnityObjectToWorldNormal(v.normal); > f.worldVertex = mul(v.vertex,unity_WorldToObject); > f.uv = v.texcoord; > return f; > } > > fixed4 frag(v2f f) : SV_Target > { > fixed3 normalDir = normalize(f.worldNormal); > fixed3 lightDir = normalize(WorldSpaceLightDir(f.worldVertex)); > > fixed3 texColor = tex2D(_MainTex,f.uv.xy) * _Color; > fixed3 diffuse = _LightColor0.rgb * texColor * max(dot(normalDir,lightDir),0); > > //fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(dot(normalDir,lightDir),0); > > fixed3 viewDir = normalize(UnityWorldSpaceViewDir(f.worldVertex)); > fixed3 halfDir = normalize(lightDir + viewDir); > > fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(dot(normalDir,halfDir),0),_Gloss); > > //纹理颜色和环境颜色融合,纹理显得更加清晰 => [UNITY_LIGHTMODEL_AMBIENT.rgb * texColor] > fixed3 tempColor = diffuse + specular + UNITY_LIGHTMODEL_AMBIENT.rgb * texColor; > > return fixed4(tempColor,1); > } > > ENDCG > } > } > > Fallback "Specular" > } > ``` > > **效果图** > ![image-20220325215316420](Unity Shader 笔记.assets/image-20220325215316420.png) #### Shader中纹理的缩放和偏移 > ```C > Shader "Custom/M11-Single_Textures" > { > Properties > { > //_Diffuse("Diffuse Color",Color)=(1,1,1,1) > _Color("Color",Color)=(1,1,1,1) > _MainTex("Main Texture",2D)="white"{} > _Specular("Specular Color",Color)=(1,1,1,1) > _Gloss("Gloss",Range(10,100))=15 > } > > SubShader > { > Pass > { > Tags > { > "LightModel"="ForwardBase" > } > > CGPROGRAM > > #include "Lighting.cginc" > #pragma vertex vert > #pragma fragment frag > > //fixed4 _Diffuse; > > fixed4 _Color; > sampler2D _MainTex; > //纹理的缩放和偏移 : 在纹理变量后加上 _ST 即可 > float4 _MainTex_ST; > fixed4 _Specular; > half _Gloss; > > struct a2v > { > float4 vertex : POSITION; > float3 normal : NORMAL; > float4 texcoord : TEXCOORD0; > }; > > struct v2f > { > float4 svPos : SV_POSITION; > float3 worldNormal : TEXCOORD0; > float4 worldVertex : TEXCOORD1; > float2 uv : TEXCOORD2; > }; > > > v2f vert(a2v v) > { > v2f f; > f.svPos = UnityObjectToClipPos(v.vertex); > f.worldNormal = UnityObjectToWorldNormal(v.normal); > f.worldVertex = mul(v.vertex,unity_WorldToObject); > //增加缩放 : _MainTex_ST.xy ; 增加偏移值 : _MainTex_ST.zw > f.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw; > return f; > } > > fixed4 frag(v2f f) : SV_Target > { > fixed3 normalDir = normalize(f.worldNormal); > fixed3 lightDir = normalize(WorldSpaceLightDir(f.worldVertex)); > > fixed3 texColor = tex2D(_MainTex,f.uv.xy) * _Color; > fixed3 diffuse = _LightColor0.rgb * texColor * max(dot(normalDir,lightDir),0); > > //fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(dot(normalDir,lightDir),0); > > fixed3 viewDir = normalize(UnityWorldSpaceViewDir(f.worldVertex)); > fixed3 halfDir = normalize(lightDir + viewDir); > > fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(dot(normalDir,halfDir),0),_Gloss); > > //纹理颜色和环境颜色融合,纹理显得更加清晰 => [UNITY_LIGHTMODEL_AMBIENT.rgb * texColor] > fixed3 tempColor = diffuse + specular + UNITY_LIGHTMODEL_AMBIENT.rgb * texColor; > > return fixed4(tempColor,1); > } > > ENDCG > } > } > > Fallback "Specular" > } > ``` > > **效果** > ![纹理的偏移和缩放](Unity Shader 笔记.assets/纹理的偏移和缩放.gif) ### 凹凸映射和法线映射 > 像素 : `pixel = (normal + 1) / 2` > 法线 : `normal = pixel * 2 - 1` > > ```C > Shader "Custom/M13-Rock_Normal" { > Properties { > //_Diffuse ("Diffuse Color", Color) =(1,1,1,1) > _Color ("Color", Color) = (1, 1, 1, 1) > _MainTex ("Main Texture", 2D) = "white" { } > _NormalMap ("Normal Map", 2D) = "dump" { } > } > > SubShader { > Pass { > Tags { "LightModel" = "ForwardBase" } > > CGPROGRAM > > #include "Lighting.cginc" > #pragma vertex vert > #pragma fragment frag > > //fixed4 _Diffuse; > > fixed4 _Color; > sampler2D _MainTex; > //纹理的缩放和偏移 : 在纹理变量后加上 _ST 即可 > float4 _MainTex_ST; > sampler2D _NormalMap; > float4 _NormalMap_ST; > > struct a2v { > float4 vertex : POSITION; > //切线空间的确定是通过 法线(存储到模型里面的) 和 切线(存储到模型里面的) 确定的 > float3 normal : NORMAL; > //tangent.w 是用来确定切线空间中坐标轴的方向 > float4 tangent : TANGENT; > float4 texcoord : TEXCOORD0; > }; > > struct v2f { > float4 svPos : SV_POSITION; > //float3 worldNormal : TEXCOORD0; > //切线空间下的平行光方向 > float3 lightDir : TEXCOORD0; > float4 worldVertex : TEXCOORD1; > float4 uv : TEXCOORD2;//xy用来存储_MainTex的纹理坐标,zw用来存储_NormalMap的纹理坐标 > }; > > > v2f vert(a2v v) { > v2f f; > f.svPos = UnityObjectToClipPos(v.vertex); > //f.worldNormal = UnityObjectToWorldNormal(v.normal); > f.worldVertex = mul(v.vertex, unity_WorldToObject); > //增加缩放 : _MainTex_ST.xy ; 增加偏移值 : _MainTex_ST.zw > f.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw; > f.uv.zw = v.texcoord.xy * _NormalMap_ST.xy + _NormalMap_ST.zw; > > //调用这个宏之后,可以得到一个矩阵[rotation],这个矩阵用来把切线空间下的方向转成模型空间下的方向 > TANGENT_SPACE_ROTATION; > > f.lightDir = mul(rotation , ObjSpaceLightDir(v.vertex)); > > return f; > } > > //把所有跟法线相关的运算都放在 切线空间下 , 从法线贴图里面取得的法线方向是在切线空间下的 > fixed4 frag(v2f f) : SV_Target { > //fixed3 normalDir = normalize(f.worldNormal); > > fixed4 normalColor = tex2D(_NormalMap, f.uv.zw); > //切线空间下的法线 > //fixed3 tangentNormal = normalColor.xyz * 2 - 1; > fixed3 tangentNormal = normalize(UnpackNormal(normalColor)); > fixed3 lightDir = normalize(f.lightDir); > > fixed3 texColor = tex2D(_MainTex, f.uv.xy) * _Color; > fixed3 diffuse = _LightColor0.rgb * texColor * max(dot(tangentNormal, lightDir), 0); > //纹理颜色和环境颜色融合,纹理显得更加清晰 => [UNITY_LIGHTMODEL_AMBIENT.rgb * texColor] > fixed3 tempColor = diffuse + UNITY_LIGHTMODEL_AMBIENT.rgb * texColor; > > return fixed4(tempColor, 1); > } > ENDCG > } > } > Fallback "Specular" > } > ``` > > **效果** > ![image-20220326155912157](Unity Shader 笔记.assets/image-20220326155912157.png) > > #### 给法线添加凹凸参数 > > ```c > Shader "Custom/M13-Rock_Normal" { > Properties { > //_Diffuse ("Diffuse Color", Color) =(1,1,1,1) > _Color ("Color", Color) = (1, 1, 1, 1) > _MainTex ("Main Texture", 2D) = "white" { } > _NormalMap ("Normal Map", 2D) = "dump" { } > _BumpScale("Dump Scale",Float) = 1 > } > > SubShader { > Pass { > Tags { "LightModel" = "ForwardBase" } > > CGPROGRAM > > #include "Lighting.cginc" > #pragma vertex vert > #pragma fragment frag > > //fixed4 _Diffuse; > > fixed4 _Color; > sampler2D _MainTex; > //纹理的缩放和偏移 : 在纹理变量后加上 _ST 即可 > float4 _MainTex_ST; > sampler2D _NormalMap; > float4 _NormalMap_ST; > float _BumpScale; > > struct a2v { > float4 vertex : POSITION; > //切线空间的确定是通过 法线(存储到模型里面的) 和 切线(存储到模型里面的) 确定的 > float3 normal : NORMAL; > //tangent.w 是用来确定切线空间中坐标轴的方向 > float4 tangent : TANGENT; > float4 texcoord : TEXCOORD0; > }; > > struct v2f { > float4 svPos : SV_POSITION; > //float3 worldNormal : TEXCOORD0; > //切线空间下的平行光方向 > float3 lightDir : TEXCOORD0; > float4 worldVertex : TEXCOORD1; > float4 uv : TEXCOORD2;//xy用来存储_MainTex的纹理坐标,zw用来存储_NormalMap的纹理坐标 > }; > > > v2f vert(a2v v) { > v2f f; > f.svPos = UnityObjectToClipPos(v.vertex); > //f.worldNormal = UnityObjectToWorldNormal(v.normal); > f.worldVertex = mul(v.vertex, unity_WorldToObject); > //增加缩放 : _MainTex_ST.xy ; 增加偏移值 : _MainTex_ST.zw > f.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw; > f.uv.zw = v.texcoord.xy * _NormalMap_ST.xy + _NormalMap_ST.zw; > > //调用这个宏之后,可以得到一个矩阵[rotation],这个矩阵用来把切线空间下的方向转成模型空间下的方向 > TANGENT_SPACE_ROTATION; > > f.lightDir = mul(rotation , ObjSpaceLightDir(v.vertex)); > > return f; > } > > //把所有跟法线相关的运算都放在 切线空间下 , 从法线贴图里面取得的法线方向是在切线空间下的 > fixed4 frag(v2f f) : SV_Target { > //fixed3 normalDir = normalize(f.worldNormal); > > fixed4 normalColor = tex2D(_NormalMap, f.uv.zw); > //切线空间下的法线 > //fixed3 tangentNormal = normalColor.xyz * 2 - 1; > fixed3 tangentNormal = UnpackNormal(normalColor); > tangentNormal.xy = tangentNormal.xy * _BumpScale; > tangentNormal = normalize(tangentNormal); > fixed3 lightDir = normalize(f.lightDir); > > fixed3 texColor = tex2D(_MainTex, f.uv.xy) * _Color; > fixed3 diffuse = _LightColor0.rgb * texColor * max(dot(tangentNormal, lightDir), 0); > //纹理颜色和环境颜色融合,纹理显得更加清晰 => [UNITY_LIGHTMODEL_AMBIENT.rgb * texColor] > fixed3 tempColor = diffuse + UNITY_LIGHTMODEL_AMBIENT.rgb * texColor; > > return fixed4(tempColor, 1); > } > ENDCG > } > } > Fallback "Specular" > } > ``` > > **效果** > ![纹理的凹凸值](Unity Shader 笔记.assets/纹理的凹凸值.gif) ### 编写透明的Shader > --- ## Unity Shader 中的语义 ### 从应用程序传递到顶点函数的语义 | 语义 | 说明 | | :---------: | :--------------------: | | POSITION | 模型空间下的顶点坐标值 | | NORMAL | 模型空间下的法线 | | TANGENT | 模型空间下的切线 | | TEXCOORD0~N | 纹理坐标 | | COLOR | 顶点颜色 | | | | ### 顶点函数传递给片元函数的语义 | 语义 | 说明 | | :---------: | :------------------------------------: | | SV_POSITION | 裁剪空间下的顶点坐标(一般系统直接使用) | | COLOR0 | 可以传递一组值 最大4个 | | COLOR1 | 可以传递一组值 最大4个 | | TEXCOORD0~7 | 传递纹理坐标 | | | | ### 片元函数传递给系统 | 语义 | 说明 | | :-------: | :-----------------------: | | SV_Target | 颜色值,显示到屏幕上的颜色 | | | | ### UnityCG.cginc中一些常用的函数 #### 摄像机方向(视角方向) 1. 根据模型空间中的顶点坐标 得到 (世界空间)从这个点到摄像机的观察方向 `float3 WorldSpaceViewDir(float4 v)` 2. 世界空间中的顶点坐标 => 世界空间从这个点到摄像机的观察方向 `float3 UnityWorldSpaceViewDir(float4 v)` 3. 模型空间中的顶点坐标 => 模型空间从这个点到摄像机的观察方向 `float3 ObjSpaceViewDir(float4 v)` #### 光源方向 1. 模型空间中的顶点坐标 => 世界空间中从这个点到光源的方向 `float3 WorldSpaceLightDir(float4 v)` 2. 世界空间中的顶点坐标 => 世界空间中从这个点到光源的方向 `float3 UnityWorldSpaceLightDir(float4 v)` 3. 模型空间中的顶点坐标 => 模型空间中从这个点到光源的方向 `float3 ObjSpaceLightDir(float4 v)` #### 方向转换 1. 把法线方向 模型空间 => 世界空间 `float3 UnityObjectToWorldNormal(float3 norm)` 2. 把方向 模型空间 => 世界空间 `float3 UnityObjectToWorldDir(float3 dir)` 3. 把方向 世界空间 => 模型空间 `float3 UnityWorldToObjectDir(float3 dir)` #### 其他 1. 把顶点 从 模型空间 => 裁剪空间 `float4 UnityObjectToClipPos(float4 v)` 2. 通过乘法 将 法线 从 模型空间 => 世界空间 `mul(v.normal,(float3x3)unity_WorldToObject);` --- ## Unity学习资源 1. Unity 官方网站 [Unity - Manual: Shader semantics](https://docs.unity.cn/2022.2/Documentation/Manual/SL-ShaderSemantics.html)