Shader学习笔记(一)
1.属性
Shader "Unlit/001"
{
//属性块
Properties
{
_Int("Int",Int) = 2
_Float("Float",float) = 1.5
_Range("Range",range(0.0,2.0))= 1.0
_Color("Color",Color) = (1,1,1,1)
_Vector("Vector",Vector) = (1,4,3,8)
_MainTex("Texture", 2D) = "white" {}
_Cube("Cube",Cube) = "white"{}
_3D("3D",3D) = "black"{}
}
SubShader
{
//标签 可选 key = value
Tags
{
"Queue"="Transparent"//渲染顺序
"RenderType"="Opaque"//着色器替换功能
"DisableBatching" = "True"//是否进行合批
"ForceNoShadowCasting" = "True"//是否投射阴影
"IgnoreProjector"="True"//受不受Projector的影响,通常用于透明物体
"CanUseSpriteAltas" = "False"//是否用于图片的Shader,通常用于UI
"PreviewType"="Plane"//用作shader面板预览的类型
}
//Render设置 可选
//Cull off/back/front //选择渲染那个面
//ZTest Always/Less Greater/LEqual/GEqual/Equal/NotEqual //深度测试
//Zwrite off/on //深度写入
//Blend SrcFactor DstFactor//混合
//LOD 100 //不同情况下使用不同的LOD,达到性能提升
//必须
Pass
{
//Name "Default" //Pass通道名称
Tags
{
"LightMode"="ForwardBase"//定义该Pass通道在Unity渲染流水中的角色
//"RequireOptions"="SoftVegetation"//满足某些条件时才渲染该Pass通道
} //可以在每个Pass通道里面进行定义
//Render设置 可以在每个Pass通道里面进行定义
//CG语言所写的代码,主要是顶点片元着色器
CGPROGRAM
//...
ENDCG
}
}
//Fallback "Legacy Shaders/Transparent/VertexLit" Fallback Off //当上面shader运行不了的时候会使用下面shader渲染
}
float,half,fixed 的区别: 内存大小区别
float: 32位浮点数据来存储,一般存位置
half : 16位浮点数据来存储 -6万~6万
fixed 11位浮点数据来存储 -2~ +2,一般来存颜色
2.CG
pragma vertex vert
#pragma fragment frag
定义后的函数由系统调用,类似Start() , Updata()
Shader "Unlit/002"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
//顶点函数这里只是声明了,顶点函数的函歌名
//基本作用是完成顶点坐标从模型空间到剪裁空间的转换(从游戏环境转换到视野相机屏幕上)
#pragma vertex vert
//片元函数这里只是声明了,片元函数的函数名
//基本作用返回模型对应的屏幕上的每一个像素的颜色值
#pragma fragment frag
//POSITION SV_POSITION 语义:用来告诉系统,我修饰的参数是干嘛的
//下面第一个POSITION告诉系统,我需要顶点坐标,SV_POSITION用来说明返回值是裁剪空间下的顶点坐标
float4 vert(float4 v:POSITION):SV_POSITION
{
return UnityObjectToClipPos(v);//顶点坐标从模型空间到剪裁空间
}
fixed4 frag():SV_TARGET//SV_Target 说明返回值是颜色
{
return fixed4(1,1,1,1);
}
ENDCG
}
}
}
3.语义
顶点着色器输入结构体中常用语义(从应用程序到顶点函数时候使用的语义)
语义 | 描述 |
---|---|
POSITION | 模型空间下的顶点位置,通常是float4类型 |
NORMAL | 顶点法线(模型空间下的),通常是float3类型 |
TANGENT | 顶点切线(模型空间下的),通常是float4类型 |
TEXCOORD0~n,TEXCOORD0,TEXCOORD1 | 该顶点的纹理坐标,TEXCOORD0表示第一组坐标纹理,依次类推,通常是float2,float4类型 |
COLOR | 顶点颜色,通常是fixed4或float4类型 |
顶点着色器输出结构体中常用语义(从顶点函数传递给片元函数时候使用的语义)
语义 | 描述 |
---|---|
SV_POSITION | 裁剪空间中的顶点坐标,结构体中必须包含一个用该语义修饰的变量。等同于DX9中的POSITION。 |
COLOR0 | 通常用于输出第一组顶点颜色,不是必须 |
COLOR1 | 通常用于输出第二组顶点颜色,不是必须 |
TEXCOORD0-TEXCOORD7 | 通常用于输出纹理坐标,不是必须 |
片元着色器输出时常用语义(片元函数传递给系统)
语义 | 描述 |
---|---|
SV_Target | 输出值将会储存到渲染目标(render target)中。等同于DX9中COLOR语义。 显示到屏幕上的颜色。 |
代码:
将上面代码传入参数用结构体封装,结构体内参数也用语义修饰.
Shader "Unlit/003"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Color("Color" , Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//#include "UnityCG.cginc"
//只有在CGPROGRAM内再次定义一个与属性块内名字与类型相同的变量,属性块对应的变量才能起作用
fixed4 _Color;
struct a2v//application to vert
{
//把模型顶点填充vertex变量
float4 vertex:POSITION;
//把模型的法线填充给normal变量,方向用float3
float3 normal:NORMAL;
//把模型的第一套uv(纹理坐标)填充texcoord
float4 texcoord: TEXCOORD0;
};
struct v2f// v vert to frag
{
//SV_POSITION语义告诉unity : pos为裁剪空间中的位置信息
float4 pos:SV_POSITION;
//COLOR0 语义可以储存颜色信息,这个语义可以由用户自己定义
fixed3 color:COLOR0;
};
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
//将 【-1,1】转变为【0,1】 x/2 + 0.5;
o.color = v.normal * 0.5 +fixed3(0.5,0.5,0.5);
return o;
}
fixed4 frag(v2f i):SV_TARGET//SV_Target 说明返回值是颜色
{
fixed3 c = i.color;
//.xyzw .rgba .x .y .xw
c*=_Color.rgb;
//return fixed4(0,0,0,1);
return fixed4(c, 1);
}
ENDCG
}
}
}
坐标转化
//Unitycc.cginc中一些常用的函数
//摄像机方向(视角方向)
float3 WorldSpaceViewDir (float4 v)//根据模型空间中的顶点坐标得到(世界空间)从这个点到摄像机的观察方向
float3 UnityWorldSpaceViewDir (float4 v)//世界空间中的顶点坐标==》世界空间从这个点到摄像机的观察方向
float3 ObjSpaceViewDir (float4 v)//模型空间中的顶点坐标==》模型空间从这个点到摄像机的观察方向
//光源方向
float3 WorldSpaceLightDir(float4 v)//模型空间中的顶点坐标==》世界空间中从这个点到光源的方向
float3 UnityWorldSpaceLightDir(float4 v)//世界空间中的顶点坐标==》世界空间中从这个点到光源的方向
float3 ObjSpaceLightDir(float4 v)//模型空间中的顶点坐标==》模型空间中从这个点到光源的方向
//方向转换
float3 UnityObjectToWorldNormal(float3 norm)//把法线方向模型空间==》世界空间
float3 UnityObjectToWorldDir(float3 dir)//把方向模型空间==》世界空间
float3 UnityWorldToObjectDir(float3 dir)//把方向世界空间==》模型空间
获取光的方向两种方式:
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 lightDir = normalize(WorldSpaceLightDir(f.worldVertex).xyz);
appdata类型
appdata类型,是shader内置的结构类型,分为appdata_base、appdata_tan、appdata_full三个类型。
struct appdata_base {
float4 vertex : POSITION;//顶点位置
float3 normal : NORMAL;//法线
float4 texcoord : TEXCOORD0;//纹理坐标
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct appdata_tan {
float4 vertex : POSITION;//顶点坐标位置
float4 tangent : TANGENT;//切线
float3 normal : NORMAL;//法线
float4 texcoord : TEXCOORD0;//第一纹理坐标
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct appdata_full {
float4 vertex : POSITION;
float4 tangent : TANGENT;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
float4 texcoord1 : TEXCOORD1;//第二纹理坐标
float4 texcoord2 : TEXCOORD2;//第三纹理坐标
float4 texcoord3 : TEXCOORD3;//第四纹理坐标
fixed4 color : COLOR;//顶点颜色
UNITY_VERTEX_INPUT_INSTANCE_ID
};
当然也可以自定义appdata类型,注意:自定义的属性,只能到appdata_full 里面选择,才能被识别出来。如
struct appdata
{
float4 vertex : POSITION;//获得顶点数据
float2 uv : TEXCOORD0;//获得纹理坐标数据
float4 normal: NORMAL;//获得法线数据
fixed4 color : COLOR;//顶点颜色
};
4.光照模型
光照模型是一个公式,用来计算某个点的光照效果
进入摄像机的光由四个:
自发光:
高光反射 Specular:= 直射光颜色 * pow( max(cosθ,0) , 高光的参数) θ : 是反射光方向和视野方向的夹角
漫反射 Diffuse:= 直射光颜色 * Max(0,Cos夹角(光和法线的夹角)) cosθ = 光方向 · 法线方向 点乘
环境光:
在计算机图形中:点乘可以用来计算夹角的余弦值,叉乘用来计算平面法向量
要想使用光颜色,法线方向等Unity内置的参数,需要定义标签和头文件:
Tag{"LightMode" = "ForwardBase"}
#include "Lighting.cginc"
5.漫反射
兰伯特 = 直射光颜色 * 漫反射颜色 * Max(0,光照方向 · 法线方向)) , 背光面会全黑
半兰伯特 = 直射光颜色 * 漫反射颜色 * (0.5 * 光照方向 · 法线方向 + 0.5),将值在[0,1], 背光面也会渲染颜色。
5.1顶点漫反射
Shader "Unlit/005"
{
Properties
{
_Diffuse("Diffuse", Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc" //取得第一个直射光的颜色_LightColor0
fixed4 _Diffuse;//cg里面使用属性的值耀再次定义
struct v2f
{
float4 vertex : SV_POSITION;
fixed3 color: Color;
};
v2f vert (appdata_base v)//appdata_base为unity内置的结构体
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);//模型空间转裁剪空间
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;//获取环境光
fixed3 worldNormal = UnityObjectToWorldNormal( v.normal);//把法线方向模型空间==》世界空间
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);//光的位置就是光的方向,因为是平行光
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal,worldLight));//兰伯特
//max,cg内置函数
//fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * (0.5*dot(worldNormal,worldLight)+0.5);半兰伯特
o.color = diffuse + ambient;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return fixed4(i.color,1);//在顶点计算的颜色,顶点个数有限,而像素无限的
//所以在frag中的颜色是由插值得出来的。
}
ENDCG
}
}
FallBack "Diffuse"
}
5.2片元漫反射
struct v2f
{
float4 vertex : SV_POSITION;
fixed3 worldNormal: TEXCOORD0;
};
v2f vert (appdata_base v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
fixed3 worldNormal = UnityObjectToWorldNormal( v.normal);
o.worldNormal = worldNormal;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0,dot(worldLightDir,i.worldNormal));
fixed3 color = ambient + diffuse;
return fixed4(color,1);
}
片元漫反射过度比顶点更加光滑,因为每个像素都会计算,因此消耗性能也更多。
6.高光反射
漫反射:在视野改变的时候,物体颜色不会改变,而高光反射在视野角度改变的时候,会有不同的高光。
高光反射 Specular:= 直射光颜色 * pow( max(反射光方向 · 视野方向,0) , 高光的参数) (Blinn光照模型)
= 直射光颜色 * pow( max(法线和 · x,0) , 高光的参数) x方向是平行光和视野方向的平分线(Blinn-Phong光照模型)
高光反射函数如下:
6.1顶点高光Blinn
Shader "Unlit/008"
{
Properties
{
_Diffuse("Diffuse", Color) = (1,1,1,1)
_Specular("Specular", Color) = (1,1,1,1)//高光的颜色
_Gloss("Gloss", Range(1,256)) = 5 //光泽大小
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct v2f
{
float4 vertex : SV_POSITION;
fixed3 color: Color;
};
v2f vert (appdata_base v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
fixed3 worldPos = mul(unity_ObjectToWorld, v.vertex);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
//fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
fixed3 worldLight = UnityWorldSpaceLightDir(worldPos);
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal,worldLight));
//在漫反射基础上加上高光
fixed3 reflectDir = normalize(reflect(-worldLight,worldNormal));//反射光方向
//fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - UnityObjectToWorldDir(v.vertex));
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));//视野方向
//fixed3 viewDir = normalize(WorldSpaceViewDir(v.vertex));
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(reflectDir,viewDir)),_Gloss);
o.color = diffuse + ambient + specular;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return fixed4(i.color,1);
}
ENDCG
}
}
FallBack "Diffuse"
}
6.2片元高光Blinn
v2f vert (appdata_base v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
fixed3 worldNormal = UnityObjectToWorldNormal( v.normal);
o.worldNormal = worldNormal;
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//漫反射
fixed3 worldLightDir = UnityWorldSpaceLightDir(i.worldPos);
//fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0,dot(worldLightDir,i.worldNormal));
//高光反射
fixed3 reflectDir = normalize(reflect(-worldLightDir,i.worldNormal));
//fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir,viewDir)),_Gloss);
fixed3 color = ambient + diffuse + specular;
return fixed4(color,1);
}
6.3 Blinn-Phong高光
Blinn-Phong(常用) = 直射光颜色 * pow( max(cosθ,0) , 高光的参数) θ : 是法线和x方向的夹角 x方向是平行光和视野方向的平分线
fixed4 frag (v2f i) : SV_Target
{
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//漫反射
fixed3 worldLightDir = UnityWorldSpaceLightDir(i.worldPos);
//fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0,dot(worldLightDir,i.worldNormal));
//高光反射
//fixed3 reflectDir = normalize(reflect(-worldLightDir,i.worldNormal));
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
//fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 halfDir = normalize(worldLightDir + viewDir);//平分线
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(i.worldNormal,halfDir)),_Gloss);
fixed3 color = ambient + diffuse + specular;
return fixed4(color,1);
}
7.纹理映射
计算机图形学(八)-纹理映射、计算重心坐标、UV插值、双线性插值、MipMap_如何获取uv坐标_点燃火柴的博客-CSDN博客
纹理映射
这张二维的图片就是纹理,
把这个图贴到球表面伴随着这纹理的放大缩小以及将纹理中的某个点和球上的某个点对应起来,这个过程就是纹理映射
纹理的UV坐标
一张纹理通常会有自己的纹理坐标,如图,纹理坐标系以左下角为原点,向右为u正方向,向上为v正方向,不论贴图时正方向还是长方形,u和v的取值范围都是[0,1]
在代码里,使用纹理颜色代替漫反射颜色,就可以实现贴图。
7.1代码
Shader "Unlit/011"
{
Properties
{
_MainTex("MainTex", 2D) = "white" {}//贴图
_Diffuse("Diffuse", Color) = (1,1,1,1)
_Specular("Specular", Color) = (1,1,1,1)
_Gloss("Gloss", Range(1,256)) = 5
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
sampler2D _MainTex;//贴图
float4 _MainTex_ST;
//CG获取偏移和缩放: float4 _MainTex_ST ;
//格式固定: _MainTex为Properties定义的名字,后面 _ST为shrink和translate缩写,
//使用float4来表示,前两个是图片的Tiling 后面两个是Offset。
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct v2f
{
float4 vertex : SV_POSITION;
fixed3 worldNormal: TEXCOORD0;
float3 worldPos: TEXCOORD1;
float2 uv : TEXCOORD2;//世界坐标的UV
};
v2f vert (appdata_base v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
fixed3 worldNormal = UnityObjectToWorldNormal( v.normal);
o.worldNormal = worldNormal;
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);//等价于v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 albedo = tex2D(_MainTex, i.uv).rgb;//获取UV贴图对应点的颜色
//漫反射
fixed3 worldLightDir = UnityWorldSpaceLightDir(i.worldPos);
fixed3 diffuse = _LightColor0.rgb * albedo * _Diffuse.rgb * (dot(worldLightDir,i.worldNormal)*0.5+0.5);
//高光反射
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(i.worldNormal,halfDir)),_Gloss);
fixed3 color = ambient + diffuse + specular;
return fixed4(color,1);
}
ENDCG
}
}
}
7.2纹理属性
Texture Type: 纹理类型。
Texture(纹理)、Normal map(法线贴图)、GUI(图形用户界面)、Cursor(图标文件)、Reflection(反射)、Cookie(作用于光源的Cookie)、Lightmap(光照贴图)、Advanced(高级)
Wrap Mode:循环模式。
控制纹理平铺时的样式。会改变Tiling和Offset方式。
- Repeat:重复。
- Clamp:截断。
Filter Mode: 过滤模式。
控制纹理通过三维变换拉伸时的计算方式,性能优化
-Point:点模式。一种较简单材质图像插值的处理方式,使用包含像素最多部分的图素来贴图。图像插值概念见下注,此种方式容易产生马赛克。
注:图像插值就是利用已知邻近像素点的灰度值(或rgb图像中的三色值)来产生未知像素点的灰度值,以便由原始图像再生出具有更高分辨率的图像。
-Bilinear:双线性。一种较好的材质图像插值处理方式,先找出最接近像素的四个图素,然后在他们之间做插值计算,最后产生结果会贴到像素位置上,不会产生马赛克。适用于有一定景深的静态影像。
-Trilinear:三线性。一种更复杂材质图像插值处理方式。会用到相当多的材质贴图。这里适用于动态且有较大景深的物体。2D 游戏中一般是用不到的。
·Aniso Level: 各向异性级别。以一个过小的角度观察纹理时,此数值越高观察到的纹理质量就越高。一般用于3D游戏或有视角缩放功能的游戏中,2D游戏一般用不到。
8.凹凸映射和法线映射
原物体的凹凸表面的每个点的法线,将法线方向用颜色来表示,这样可以降低表现物体渲染时需要的面数和计算内容,从而达到优化动画和游戏的渲染效果。像素=(法线+1)/ 2 法线 = 像素 * 2 - 1;
法线贴图有三种算法:世界坐标算法 、矢量切线算法 、自身轴向算法 其中以矢量切线算法运用最普及 每个软件的矢量切线算法因坐标系的确定有所不同就目前看来 DirectX默认是左手坐标系,OpenGL默认是右手坐标系。 UE、Unity采用的是左手坐标系 3DMax、Cocos采用的是右手坐标系
右手坐标系(Z轴向上) 左手坐标系(Y轴向上 Z轴纵深)
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库