Unity——Shader入门
新建Shader并使用VS+ShaderLabVS进行编译,默认的Shader代码是这样的:
Shader "Unlit/MyShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}
Shader "Unlit/MyShader"
可以设置新建Shader的路径
属性
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
用来向监视器里面展示可控数据的代码段
属性名("在材质球(*Material*)上展示的名称",属性类型) = 默认值
子着色器(SubShader)
SubShader用于将 Shader 对象分成多个部分,分别兼容不同的硬件、渲染管线和运行时(Runtime)设置
有点像条件编译,就是Unity会自动遍历所有的SubShader直到找到第一个能用到的着色器,如果找不到,
一个子着色器包含以下内容:
- 与其他设备设置兼容信息
- Tags——提供有关SubShader相关信息的键值对
- Pass——通道
通道
Shader的基本元素,一个Shader至少包含一个通道
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
**********************************************************
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
**********************************************************
**********************************************************
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
**********************************************************
sampler2D _MainTex;
float4 _MainTex_ST;
**********************************************************
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
**********************************************************
**********************************************************
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
**********************************************************
ENDCG
}
- appdata指CPU向Shader提供的模型数据
- v2f是自定义的数据结构体,是顶点着色器输出的数据也是片元着色器输入的数据
- vert是顶点着色器处理函数
- frag是片元着色器处理函数
Shader的处理流程和渲染管线是相像的
语义解释:
CPU->顶点着色器
顶点着色器->片元着色器
片元着色器->外界
实验:手动建立标准光照Shader
建立默认Shader与默认材质,挂载到物体上就是一个普通的白球(默认无光照)
尝试利用Blinn-Phong光照模型为其增加检测光照的效果(图中已经添加环境光与直线光)
也就是将这个unity默认的无光照着色器手动编译成标准着色器
从之前学到的知识,Blinn-Phong模型的最终结果就是环境光+漫反射+高光
环境光在unity中设置
漫反射与高光是由片元着色器处理,于是我们聚焦到frag上
可以看到frag的实现非常简单,从_MainTex中获取颜色,利用i.uv提供的坐标,再将颜色坐标信息返回给col。(那个什么FOG是利用untiy的api启用雾效)
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
要加上环境光+漫反射+高光,也就是最终返回的结果最好是“final=col×(_Diffuse×diffuse+pow(specular,__Intensity)+ambient)"
其中diffuse为漫反射,specular为高光,ambient为环境光——为了区分把公式特征也写进去了,实际编写过程取决于编写者。
漫反射:
fixed3 worldNormal = normalize(i.normal);
fixed3 worldPos = i.worldPos;
fixed3 lightDir = normalize(_WorldSpaceLightPos0 - worldPos);
float diffuse = max(0, dot(worldNormal, lightDir));
高光:
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
fixed3 lightDir = normalize(_WorldSpaceLightPos0 - worldPos);
fixed3 halfDir = normalize(lightDir + viewDir);
float specular = max(0, dot(worldNormal, halfDir));
specular = pow(specular, _Intensity);
将这两个值的最终结果结合一下返回给finalColor:
有了能接受光照的雏形,漫反射也能通过光照方向改变而改变光亮和高光方向,但是模拟光照的技术瓶颈还是取决于硬件支持,也就是说漫反射只能反反射单一光线,而不能反射其他物体反射出的光线,所以看起来并不真实。
一般这种”二手光“直接用统一的环境光来代替
fixed4 ambient = (UNITY_LIGHTMODEL_AMBIENT.xyz,1);
但是加上环境光却出现了问题:
物体光线又回到了最初的起点,就好像环境光将光线都扰乱了一样。
最后尝试了很多种方式,发现竟然只是代码的语法错误
float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed4 finalColor = col * (_Diffuse * diffuse + specular ) + fixed4(ambient, 1);
如上,直接通过在fixed3后面加上标量1的方式是错误的,必须新建以fixed4(ambient,1)
的格式新建fixed类型的向量——最终还是因为我没有正式安装CG代码的编译环境,所以并没有报错。。。
但是新的问题又出现了,那就是这个环境光只相应纯色类型的环境光,而不能相应天空盒类型环境光。
在Unity社区中寻找到有人在讨论这个问题,最终把代码改成
half3 ambient = half3(unity_SHAr.w, unity_SHAg.w, unity_SHAb.w);
fixed4 finalColor = col * (_Diffuse * diffuse + specular ) + fixed4(ambient, 1);
最终形成天空盒也能充当环境光的效果。
优化:
最终很遗憾地发现最终效果仍然很奇怪,因为最后才发现所使用的unity的光照是太阳光而不是点光源(太阳光默认直线平行,而点光源有距离缩减且角度都不同)
所以更改代码逻辑如下:
Shader "Unlit/NewUnlitShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Diffuse("Diffuse",Range(0,1)) = 1
_Intensity("Intensity",float) = 1
_IntensityHigh("IntensityHigh",float) = 1
_Specluar("Specular",Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
Tags {"LightMode" = "ForwardBase"}
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "Lighting.cginc"
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
float3 worldPos : TEXCOORD1;
float3 normal : TEXCOORD2;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o, o.vertex);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.normal = UnityObjectToWorldNormal(v.normal);
return o;
}
float _Intensity;
float _IntensityHigh;
float _Diffuse;
fixed4 frag(v2f i) : SV_Target
{
// Sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// Calculate the lighting
fixed3 worldNormal = normalize(i.normal);
fixed3 worldPos = i.worldPos;
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 halfDir = normalize(lightDir + viewDir);
fixed4 lightColor = _LightColor0;
float diffuse = _Intensity*max(0, dot(worldNormal, lightDir));
float specular = max(0, dot(worldNormal, halfDir));
specular = pow(specular, _IntensityHigh);
half3 ambient = half3(unity_SHAr.w, unity_SHAg.w, unity_SHAb.w);
fixed4 finalColor = col * _Diffuse *( diffuse +specular ) + fixed4(ambient, 1);
// Apply fog
UNITY_APPLY_FOG(i.fogCoord, finalColor);
return finalColor;
}
ENDCG
}
}
}
高光反射的p值比我想象中的大很多,突然想起闫令祺老师说过一般要达到128才能接近现实的效果。。
与官方的标准光照着色器对比——你可以猜一下哪个是我写的
后记:
从别人的博客上看见使用半兰伯特(Half Lambert)模型来优化漫反射。
就是单纯变更一下diffuse:
float diffuse = 0.5*_Intensity*max(0, dot(worldNormal, lightDir))+0.5;
这样可以去掉环境光也能形成很好的效果:
但是我其实觉得这是一种投机取巧的做法——虽然看起来确实像摸像样了一点,但不能跟踪天空盒,而且和其他反射严重割裂。