Shader实现各种材质和光照效果,镜头景深,动态模糊,卡通渲染等。
U3D是简单的游戏引擎,提供了尽量多的内建功能,丰富全面的Shader就是其中之一。要善于使用U3D内建的Shader和自己编写的Shader实现效果。
一.内建Shader有简单光效,高光,法线,反射等,分类有:
1)普通,用于不透明的物体。
2)透明,用于透明物体。
3)透明镂空效果,用于包含完全透明部分的半透明对象。
4)自发光。
5)反射,用于能反射环境立方体贴图的不透明对象。
每个类型都包含了
Vertex lit, simple, bumped(凹凸法线), parallax bumped(视差凹凸高光) 从简单到复杂的光照效果,每种类型越复杂性能开销越大。
二.U3D ShaderLab基础语法结构:
Shader "Custom/NewShader" { // 名称
Properties { // 定义属性
_MainTex ("Base (RGB)", 2D) = "white" {}
}
// 子着色器1,U3D会从上往下找到一个子着色器则运行之,后面的不再理会。
SubShader { // 实现代码
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Lambert
sampler2D _MainTex;
struct Input {
float2 uv_MainTex;
};
void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex);
o.Albedo = c.rgb;
o.Alpha = c.a;
}
ENDCG
}
// 子着色器2
SubShader {}
// 如果所有着色器都不能正确运行,使用备用着色器
FallBack "Diffuse"
}
属性:
Properties
{
_WaveScale ("Wave scale", Range (0.02,0.15)) = 0.07 // sliders
_ReflDistort ("Reflection distort", Range (0,1.5)) = 0.5
_RefrDistort ("Refraction distort", Range (0,1.5)) = 0.4
_RefrColor ("Refraction color", Color) = (.34, .85, .92, 1) // color
_ReflectionTex ("Environment Reflection", 2D) = "" {} // textures
_RefractionTex ("Environment Refraction", 2D) = "" {}
_Fresnel ("Fresnel (A) ", 2D) = "" {}
_BumpMap ("Bumpmap (RGB) ", 2D) = "" {}
}
SubShader:
子着色器,会从上到下选择一个进行,为了兼容不同硬件要注意支持下。
Pass:
有多个少个pass则会渲染多少遍(draw call),因此要减少pass的使用。Pass里面可以包含名称和标签用于重用,和渲染设置图形硬件的各种状态和功能。
SubShader { Pass { Lighting Off SetTexture [_MainTex] {} }}
UsePass "Specular/BASE" 可以重用Pass需要指明着色器名,和pass名称,方便Pass重用,可以跨着色器共享代码片断。
GrabPass 可以将本次渲染的后台缓存抓取到纹理中,后期可以用_GrabTexture来访问。
FallBack:
FallBack "Diffuse" 使用默认的Diffuse着色器。
或FallBack Off 关闭,没有子着色器选中也不会报错。
Category分类器:
分类可以让多个子着色器继承分类的设置,例如:
Shader "example"{
Category{
Fog {Mode off}
Blend one one
// 后面的子着色器可以继承Category中的设置,避免每个都写一份,改类型不能跨着色器使用
SubShader{
}
SubShader{
}
}
}
Shader Lab语法详解
Shader "name" {
//只有一个Shader名字
[Properties]
// 键值对,暴露给Unity编辑器,让 美术进行调整
Category
{
// 逻辑上封装的渲染状态,避免写很多的重复状态,后面多个subShader不用声明就能公用。只有在shader括号内起效,且不会加快运行速度。
例如:fragment后的雾颜色,测试后的alpha融合设置,可以被后面多个subshader公用。
Fog { Mode Off }Blend One One
Subshader {
// unity会根据当前硬件,从上往下选择一个Shader进行渲染
[Tags]
标签键值对,用于渲染层级等
the following tags recognized by Unity
must
be inside SubShader section and not inside Pass!
Rendering Order - Queue tag
例如:
Tags { "Queue" = "Transparent" }
RenderType tag
DisableBatching tag
ForceNoShadowCasting tag
IgnoreProjector tag
CanUseSpriteAtlas tag
PreviewType tag
[CommonState]
//
This will make all passes use this “shared” state.
Pass {
// 多个pass 将会进行多次渲染
,在GPU中多次draw call(非CPU的),会增加开销,尽量少pass.
// Pass的重用语法:UsePass "Shader/Name"
例如在Pass内部:
UsePass "VertexLit/SHADOWCASTER"
Name "MyPassName"
// 获取当前绘制产生在后台缓冲中的纹理,用GrabPass
例如:
GrabPass { "_BackgroundTexture" //后面pass可以使用该纹理 }
[Name and Tags]
// name是Pass的名字,用于标识和重用;Tags键值对可以指定渲染类型,延后或向前渲染
// 例如:向前渲染的
ForwardAdd对物体为每盏灯都执行Pass(应该只是灯光颜色部分)。
Note that the following tags recognized by Unity
must
be inside Pass section and not inside SubShader!
LightMode tag
ForwardBase等
PassFlags tag
OnlyDirectional
RequireOptions tag
SoftVegetation
[RenderSetup]
/*渲染状态的设置,类似:
Cull Back | Front | Off
ZTest (Less | Greater | LEqual | GEqual | Equal | NotEqual | Always)
ZWrite On | Off
Offset OffsetFactor, OffsetUnits
Blend sourceBlendMode destBlendModeBlendOp colorOp
ColorMask RGB | A | 0 | any combination of R, G, B, A
仅在遗留的固定管线Shader中起作用的:
Lighting On | OffMaterial { Material Block }SeparateSpecular On | OffColor Color-valueColorMaterial AmbientAndDiffuse | Emission
Fog { Fog Block }
AlphaTest (Less | Greater | LEqual | GEqual | Equal | NotEqual | Always) CutoffValue
SetTexture textureProperty { combine options }
*/
}
}
}
[Fallback]
//没有Subshader满足,则用Fallback的Shader
[CustomEditor]
A CustomEditor can be defined for your shader,When you do this Unity will look for a class that extends ShaderGUI with this name. If one is found any material that uses this shader will use this ShaderGUI.
}
三.U3D Shader分为三种类型:
1.Surface shader
是经过U3D封装的,可以方便的实现灯光,阴影和投影器效果,可以同时正常的工作在向前和延后的光照渲染路径中。用ShaderLab包装CG/HLSL语言编写,且封装了很多内置的函数。
需要放置在
CGPROGREAM ... ENDCG代码块之间,会自己编译到各个pass中。
使用
#pragma surface 表面函数 光照模型 [可选参数] 命令来指明它是一个表面着色器。
表面函数的输入是自定义的Input结构体
结构体可以包含的数据是:
float2 uv_Tex1Name(纹理1的uv坐标名称);
float2 uv2_Tex2Name(纹理2的uv坐标名称);
float4 color;
float3 viewDir;
float4 screenPos
float3 worldPos
float3 worldRefl
float3 worldNormal
INTERNAL_DATA
输出是SurfaceOutput结构体:
struct SurfaceOutput{
half3 Albedo;// 反射光
half3 Normal; // 法线
half3 Enission; // 自发光
half Specular; // 高光
half Gloss; // 光泽度
half Alpha; // 透明度
}
Surface shader 会在封装的内部使用光照模型对这里输出的SurfaceOutput进行光照计算得到最终颜色。
示例:
Shader "Example/Diffuse Bump" {
Properties {
_MainTex ("Texture", 2D) = "white" {}
_BumpMap ("Bumpmap", 2D) = "bump" {}
}
SubShader {
Tags { "RenderType" = "Opaque" }
CGPROGRAM
#pragma surface surf Lambert
struct Input {
float2 uv_MainTex;
float2 uv_BumpMap;
};
sampler2D _MainTex;
sampler2D _BumpMap;
void surf (Input IN, inout SurfaceOutput o) {
o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
}
ENDCG
}
Fallback "Diffuse"
}
自定义光照模型:
...ShaderLab code...
CGPROGRAM
#pragma surface surf WrapLambert
half4 LightingWrapLambert (SurfaceOutput s, half3 lightDir, half atten) {
half NdotL = dot (s.Normal, lightDir);
half diff = NdotL * 0.5 + 0.5;
half4 c;
c.rgb = s.Albedo * _LightColor0.rgb * (diff * atten);
c.a = s.Alpha;
return c;
}
struct Input {
float2 uv_MainTex;
};
sampler2D _MainTex;
void surf (Input IN, inout SurfaceOutput o) {
o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
}
ENDCG
...ShaderLab code...
拓展更多详细信息:
(1)编译配置
会自动生成一些代码,将不同光照类型,不同阴影选择,不同渲染路径(向前或延后渲染)封装起来,根据一些开关,就可以编译生成完全的Vertex/Fragment Shader。
标志是:
CGPROGRAM..ENDCG在SubShader内部,不在Pass内。
#pragma surface surfaceFunction lightModel [optionalparams]
surfaceFunction一般是surf函数,lightModel 是内建的光照模型,
optionalparams可以是:
Transparency and alpha testing类型:
alpha:blend
decal:add //贴花,在其它shader前面
decal:blend // 贴花使用alpha blend
等
Custom modifier functions类型:
vertex:VertexFunction
// 顶点shader自定义函数,会插入默认的vertexshader前面,处理每个顶点的信息。
finalcolor:ColorFunction
//片段着色器中,最终计算颜色的函数
finalgbuffer:ColorFunction
- Custom deferred path for altering gbuffer content.
finalprepass:ColorFunction
- Custom prepass base path.
Shadows and Tessellation类型:
addshadow
fullforwardshadows
tessellate:TessFunction
Code generation options类型:
exclude_path:deferred
,
exclude_path:forward
,
exclude_path:prepass
- noshadow - Disables all shadow receiving support in this shader.
- noambient - Do not apply any ambient lighting or light probes.
- novertexlights - Do not apply any light probes or per-vertex lights in Forward rendering.
- nofog - Disables all built-in Fog support.
等。
(2).Input surface着色器输入类型
输入是位置,uv,法向量,颜色等作为输入,在编辑器中设置输入 就行。
The input structure
Input
generally has any texture coordinates needed by the shader. Texture coordinates must be named “
uv
” followed by texture name (or start it with “
uv2
” to use second texture coordinate set).
也 可以是其它类型:
Additional values that can be put into Input structure:
- float3 viewDir - contains view direction, for computing Parallax effects, rim lighting etc.
- float4 with COLOR semantic - contains interpolated per-vertex color.
- float4 screenPos - contains screen space position for reflection or screenspace effects. Note that this is not suitable for GrabPass; you need to compute custom UV yourself using ComputeGrabScreenPos function.
- float3 worldPos - contains world space position.
- float3 worldRefl - contains world reflection vector if surface shader does not write to o.Normal. See Reflect-Diffuse shader for example.
- float3 worldNormal - contains world normal vector if surface shader does not write to o.Normal.
- float3 worldRefl; INTERNAL_DATA - contains world reflection vector if surface shader writes to o.Normal. To get the reflection vector based on per-pixel normal map, use WorldReflectionVector (IN, o.Normal). See Reflect-Bumped shader for example.
- float3 worldNormal; INTERNAL_DATA - contains world normal vector if surface shader writes to o.Normal. To get the normal vector based on per-pixel normal map, use WorldNormalVector (IN, o.Normal).
自定义Surface shader的Input结构体,只能使用前面预定义的类型。
实例代码:
Shader "Example/Rim" {
Properties {
_MainTex ("Texture", 2D) = "white" {}
_BumpMap ("Bumpmap", 2D) = "bump" {}
_RimColor ("Rim Color", Color) = (0.26,0.19,0.16,0.0)
_RimPower ("Rim Power", Range(0.5,8.0)) = 3.0
}
SubShader {
Tags { "RenderType" = "Opaque" }
CGPROGRAM
#pragma surface surf Lambert
struct Input {
float2 uv_MainTex;
float2 uv_BumpMap;
float3 viewDir;
};
sampler2D _MainTex;
sampler2D _BumpMap;
float4 _RimColor;
float _RimPower;
void surf (Input IN, inout SurfaceOutput o) {
o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
half rim = 1.0 - saturate(dot (normalize(IN.viewDir), o.Normal));
o.Emission = _RimColor.rgb * pow (rim, _RimPower);
}
ENDCG
}
Fallback "Diffuse"
}
(3)surface着色器输出类型
输出是:
struct SurfaceOutput
{
fixed3 Albedo; // diffuse color
fixed3 Normal; // tangent space normal, if written
fixed3 Emission;
half Specular; // specular power in 0..1 range
fixed Gloss; // specular intensity
fixed Alpha; // alpha for transparencies
};
unity5是:
struct SurfaceOutputStandard
{
fixed3 Albedo; // base (diffuse or specular) color
fixed3 Normal; // tangent space normal, if written
half3 Emission;
half Metallic; // 0=non-metal, 1=metal
half Smoothness; // 0=rough, 1=smooth
half Occlusion; // occlusion (default 1)
fixed Alpha; // alpha for transparencies
};
struct SurfaceOutputStandardSpecular
{
fixed3 Albedo; // diffuse color
fixed3 Specular; // specular color
fixed3 Normal; // tangent space normal, if written
half3 Emission;
half Smoothness; // 0=rough, 1=smooth
half Occlusion; // occlusion (default 1)
fixed Alpha; // alpha for transparencies
};
实例:
Shader "Example/Diffuse Bump" {
Properties {
_MainTex ("Texture", 2D) = "white" {}
_BumpMap ("Bumpmap", 2D) = "bump" {}
}
SubShader {
Tags { "RenderType" = "Opaque" }
// 在 SubShader后,Tags后面指定CG语言
CGPROGRAM
#pragma surface surf Lambert
struct Input {
float2 uv_MainTex;
float2 uv_BumpMap;
};
sampler2D _MainTex;
sampler2D _BumpMap;
void surf (Input IN, inout SurfaceOutput o) {
o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
}
// 结束CG,结束SubShader
ENDCG
}
Fallback "Diffuse"
}
(4)Surface生成代码查看
Surface类型可以在编辑器inspector shader组件下->Select Shader下的:
Show Generate code可以看到surface shader生成的代码,Complie and show code可以查看当前平台编译后生成的代码。
5)surface shader的限制
Currently some parts of surface shader compilation pipeline do not understand
DirectX 11
-specific HLSL syntax, so if you’re using HLSL features like StructuredBuffers, RWTextures and other non-DX9 syntax, you have to wrap it into a DX11-only preprocessor macro.
2.Vertex and fragment shader
可以非常灵活的实现效果但是要编写更多代码。用ShaderLab包装CG/HLSL语言编写。
需要放置在
ShaderLab的Pass命令中
需要用
CGPROGREAM .. ENDCG包围起来。
需要用#pragma vertex vertfunctionname
#pragma fragment fragfunctionname 指定顶点和片断着色器函数,还有更多的编译命令,例如使用geometry shader,domain shader, glsl_no_auto_normalization 编译到移动平台时 关闭在顶点着色器中对法线向量和切线向量自动进行规范化,#pragma target2.0 指定编译目标对应不同版本的着色器模型。
可以使用#include "UnityCG.cginc" 引入U3D预定义内建的头文件,使用内建的函数和变量
。
例如:
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
实例代码:
Shader "Unlit/SimpleUnlitTexturedShader"
{
Properties
{
// we have removed support for texture tiling/offset,
// so make them not be displayed in material inspector
[NoScaleOffset] _MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Pass
{
CGPROGRAM
// use "vert" function as the vertex shader
#pragma vertex vert
// use "frag" function as the pixel (fragment) shader
#pragma fragment frag
// vertex shader inputs
struct appdata
{
float4 vertex : POSITION; // vertex position
float2 uv : TEXCOORD0; // texture coordinate
};
// vertex shader outputs ("vertex to fragment")
struct v2f
{
float2 uv : TEXCOORD0; // texture coordinate
float4 vertex : SV_POSITION; // clip space position
};
// vertex shader
v2f vert (appdata v)
{
v2f o;
// transform position to clip space
// (multiply with model*view*projection matrix)
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
// just pass the texture coordinate
o.uv = v.uv;
return o;
}
// texture we will sample
sampler2D _MainTex;
// pixel shader; returns low precision ("fixed4" type)
// color ("SV_Target" semantic)
fixed4 frag (v2f i) : SV_Target
{
// sample texture and return it
fixed4 col = tex2D(_MainTex, i.uv);
return col;
}
ENDCG
}
}
}
拓展更多详细信息:
顶点和片段着色器中的顶点着色器输入结构体定义在:UnityCG.cginc,可以借助CG的预定义,也可以不使用,不过一般顶点输入都使用。
例如顶点着色器的输入为:
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
};
实例:
Shader "Tutorial/Textured Colored" {
Properties {
_Color ("Main Color", Color) = (1,1,1,0.5)
_MainTex ("Texture", 2D) = "white" { }
}
SubShader {
Pass {
// 需要再pass后面
CGPROGRAM
// 编译使用的顶点着色器和像素着色器
#pragma vertex vert
#pragma fragment frag
// 引入预定义的CG结构体,和函数方便使用
#include "UnityCG.cginc"
fixed4 _Color;
sampler2D _MainTex;
struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
float4 _MainTex_ST;
//appdata_base是 UnityCG.cginc中定义的输入结构体,也 可以自己指定
v2f vert (appdata_base v)
{
v2f o;
// 利用UnityCG.cginc中定义的UnityObjectToClipPos(计算*MVP
o.pos = UnityObjectToClipPos(v.vertex);
// 利用UnityCG.cginc确保uv是正确的
o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// 像素着色器计算
fixed4 texcol = tex2D (_MainTex, i.uv);
return texcol * _Color;
}
// 结束CG语言,结束Pass
ENDCG
}
}
}
3.fixed function shaders
一般在老旧的硬件上使用,可以作为上述着色器的备用选择,用ShaderLab语言实现。主要用于开启图形管线状态和功能,设置材质和设置纹理。
固定管线的实例:
Shader "Example/Test" {
Properties {
}
SubShader {
Pass {
Lighting Off
SetTexture [_MainTex] {}
}
}
Fallback "Diffuse"
}
拓展更多详细信息:
fixed function shader只能用shaderlab来描述。
shaderlab语法,包含了固定管线中预定义的标准光照模型和设置渲染状态,一个很简短的语句包含了内部丰富的含义,所以要多查询,多应用才能熟练。
Shader "VertexLit" {
Properties {
_Color ("Main Color", Color) = (1,1,1,0.5)
_SpecColor ("Spec Color", Color) = (1,1,1,1)
_Emission ("Emmisive Color", Color) = (0,0,0,0)
_Shininess ("Shininess", Range (0.01, 1)) = 0.7
_MainTex ("Base (RGB)", 2D) = "white" { }
}
// unity会从下往下选用硬件能够支持的SubShader
SubShader {
// 一个pass就是一次GPU渲染,尽量少用
Pass {
// 定义材质,从inpector设置属性到材质之间的映射关系,材质属性是固定的,因为光照模型是固定的,这里的材质是指狭隘的物体的材质,其实都是狭隘的,只是unity材质球却是不一样的。
Material {
Diffuse [_Color]
Ambient [_Color]
Shininess [_Shininess]
Specular [_SpecColor]
Emission [_Emission]
}
// 启用固定管线标准的顶点光照
Lighting On
// 对于镜面高光,采用分开的颜色,也就是辅助颜色到片段着色后再应用
SeparateSpecular On
// 定义应用的纹理,以及如何和光照混合应用
SetTexture [_MainTex] {
// 后面的constant就是constantColor
constantColor [_Color]
// Combine命令格式是:Combine ColorPart, AlphaPart
// 这里的意思是颜色部分:tex(texture,uv)*2倍的顶点光照主颜色,为了增强光照效果
// alpha部分用纹理的alpha乘以_Color颜色。
// 因为开启了镜面高光辅助颜色,最后颜色部分还会加上镜面反射的辅助颜色,为最终结果
Combine texture * primary DOUBLE, texture * constant
}
}
}
}