unity custom shader 102

Trick


Name of Property

在 unity 提供的默认 shader 中,properties 的名称都是固定的,当材质切换 shader 的时候,unity 会根据相同的 properties 词条名称将原有状态赋值到下一个状态中。

same_name_of_property

特别是美术贴图的设置,设置起来比较麻烦,所以维持一些 unity 默认的用法是有好处的,或许不太好看,觉得不符合自己的风格。即使要使用自己的风格,也应该尽量让自己一套 shader 在一致用法上使用相同名称。不过我建议还是和 unity 一致比较好。不然调试效果的时候简直崩溃。下面收集了一些比较常用的名称列表:

名称 说明
_Color 颜色值
_MainTex 基贴图什么的
_BumpMap 法线图什么的
_LightMap 光照图,手机上应该比较常用了
_Detail 细节贴图
_TintColor 做particle都在用这个颜色。wiki
_Cutoff 一般是半透明柔化边界用的裁减系数

UV Animation

可以从上图中看到每一张贴图都会有这么一个 Tiling-Offset的设置做 uv 动画用的。参数在 shader 中是一个 vector4。unity 对它有包装,在 unityCG.cginc 头文件中定义了 transform_tex 函数,此函数会通过输入的贴图变量的名称 _texture_name,加上_ST后缀查找一个名字为 uniform float4 _texture_name_ST 变量,对uv做scale和offset操作。所以在获取纹理UV坐标的时候,用 transform_tex 对 UV 变换。

properties
{
  _MainTex ("基贴图", 2D) = "white" { } 
}
....
CGINCLUDE
  #include "unityCG.cginc"
  uniform sampler2D _MainTex;
  uniform float4 _MainTex_ST;
  ....
  vs_out vertex_shader( ....
    half4 texcoord : TEXCOORD0,
    ....)
  {
    out.texcoord = TRANSFORM_TEX(texcoord, _MainTex);
    ....

每个采样器都能生成相应的 _ST,参数也会被正确设置。transform 的定义如下:

#define TRANSFORM_TEX(tex,name) (tex.xy * name##_ST.xy + name##_ST.zw)

其余的可以参考手册Accessing shader properties in Cg

Lighting


在 unity 中使用 cg 书写跟光照相关的 shader 是比较具有挑战的,因为这方面手册欠缺说明,只能通过阅读 unityCG/Lighting/AutoLight.cginc 几个头文件窥斑见豹。其中比较难解决的问题有2,分别是:如何获取光照参数如何获得点光源、探照灯的衰减

在unity场景中,每个光源可以通过设置 render mode 来指定光源的渲染计算方式,选用important的光源会强制使用pixel lighting进行渲染。对于点光源和探照灯,无论设为important还是auto,unity都会使用 add pass 计算渲染。当一个物体受到多个光源影响的时候,需要根据情况排序,大概情况是光照按照强度 intensity、与材质的距离进行排序,受光强的排前面使用 base pass,之后最多有 n-1 个点光源(render setting 中设置)进行 add pass 计算,最后是SH光照。具体的规则可以参照手册 Forward Rendering Path Details

光照用到的参数在 unity 中并没有详细说明,主要参数分布在 unity 提供的一些 build-in 头文件中,在渲染过程中,forward 和 vetrex 能够访问到的参数是不一致的。经过不断的测试,得出一些经验。头文件如下:

  • UnityShaderVariables.cginc
  • UnityCG.cginc
  • Lighting.cginc
  • AutoLight.cginc

想要访问 unity built-in 的参数,可以直接 include 以上的文件,也可以记住一些常用的参数名称直接把uniform参数写出来,前提是名称要对的。

环境光在 unity 中默认是整个完整项目使用同一个参数,一般在 edit->render setting 中设置。不过这个参数可以通过代码修改,一个场景可以略微改变 ambient 适应环境氛围。在 shader 中,可以根据 built-in 参数 float4 UNITY_LIGHTMODEL_AMBIENT 进行访问。需要注意的是,因为早起的 unity 会把 ambient 除以 2 再传给 shader,为了保证一致性,现在 UNITY_LIGHTMODEL_AMBIENT 的值,会是实际设置的一般,需要在shader中由书写者乘2操作。

forward

forward 渲染需要书写 2 个 pass :

  • forward base:tags { "lightmode" = "ForwardBase" }
    base pass 计算主光源的颜色值,这个阶段输出主要颜色值。
  • forward add:tags { "lightmode" = "ForwardAdd" }
    add pass,根据物体受到光的数量,每盏灯光都以 add 的方式叠加在 base 的颜色值上。

其实 base / add 的主要任务是计算1盏光照的颜色,因为add pass 是以叠加的方式渲染,设置blending,然后做一个预编译选项如下:

pass
{
  ....
  tags { "LightMode" = "ForwardAdd" }
  blending one one
  CGPROGRAM
    #pragma multi_compile_fwdadd
    ....
}

使用 forward 时可以访问并使用以下被正确设置过的参数:

uniform参数 说明
float4 _LightColor0 光的颜色值
float4 _WorldSpaceLightPos0 世界坐标当前光的位置
float4 _WorldSpaceCameraPos 世界坐标中摄像机的位置

注意 vertex 有自己的参数,在使用 vertex 渲染的时候,这三个值是默认值,没有被正确设置过的。由于提供的参数都是世界坐标的,因此需要计算法线时也应该尽量转到世界空间中会比较好。可以通过访问_World2Object_Object2World 两个变换矩阵来达到目的。

float3 normal_w = normalize(mul( float4( normal, 0.0 ), _Word2Object).xyz); 

因为 forward 中,point light 只能是 add pass,在 base pass 计算 view direction 的时候,可以不用访问 light_position.w。但是 add pass 有可能会有 directional light 参与,所以仍然需要判断光是否具有位置性。

vertex

当使用vertex光照的时候,可以使用unity_前缀的参数,这些参数包括

uniform参数 说明
float4 unity_LightPosition[n] 光源在摄像机中的位置
float4 unity_LightColor[n] 光源颜色
float4 unity_LightAtten[n] 光源的衰减,一般我们使用它的z分量

光源是view空间的坐标。平行光会将.w值设定为0,其余为1,因此光源方向我们可以根据这个公式计算出来。

float3 light_direction = normalize( unity_LightPosition[ index ].xyz - eye_position * unity_LightPosition[ index ].w );

最早尝试以 unity_4LightPosX0unity_4LightPosY0unity_4LightPosZ0来指定物体坐标,位置能按照下头的公式来计算:

float4 light_position = float4(
  unity_4LightPosX0[ index ],
  unity_4LightPosY0[ index ],
  unity_4LightPosZ0[ index ],
  1.0);

但是在 vertex 中就没办法取到 attenuation,attenuation主要是计算点光源、探照灯的衰减以模拟出明暗效果。为了配合 unity_4LightAtten0,一致采用 unity_LightPosition 访问坐标。

光源的衰减

attenuation = 1.0 / (1.0 + distance(eye_2_object) * unity_LightAtten[i])

UnityCG里有是通过这段代码来计算衰减的。在 unity 提供的默认参数里,虽然有 range 参数,但是在 vertex path 中是无法正确获取的。所以因为物体远离光源产生的衰减,就只能靠 unity_LightAtten 来计算了。虽然用了以上函数,但是仍然无法得到和 built-in 效果一致的表现,经过测试用了两个魔法数字可以得到比较不错的效果:

float light_attenuation = unity_LightAtten[ index ].z;
float diffuse_attenuation = 3.0 / (1.0 + length(light_direction) * light_attenuation);

其他的效果可以参照:各种效果的教程

Post Effect


做屏幕特效的做法是给camera加上组件。component脚本都需要继承自MonoBehaviour,除此之外要实现post effect,还需要要求摄像机。摄像机会自动调用脚本的 void OnRenderImage(RenderTexture source, RenderTexture destination)。代码就会像下面这个样子。

public class PostEffect : MonoBehaviour {
  private void OnRenderImage(RenderTexture _src, RenderTexture _dst) {
    Graphics.Blit(_src, _dst);
    //Graphics.Blit(_src, _dst, m_material, pass_index);
  }
} 

在函数内部可以用 Graphic 的操作进行图像渲染,一般会需要获取一个RenderTexture做一些source的处理,最后不要忘记释放它。也可以在程序启动的时候就创建这些临时rt,这样可以在运行期间节省分配的时间。

....
RenderTexture temp = RenderTexture.GetTemporary(width, height, depth, RGBA);
material.SetFloat("name", float_value);
Graphics.Blit(_src, temp, m_material, pass_index1);
material.SetTexture("texture", temp);
Graphics.Blit(_src, _dst, m_material, pass_index2);
temp.DiscardContents(need_discard_color, need_discard_depth);
....
Graphics.Blit(_dst, temp, m_material, pass_index2);
RenderTexture.ReleaseTemporary(temp);

DiscardContent

其中有一个坑爹的极易让人忽略的地方就是:多次对某一个rt进行多次绘制的时候,绘制之前千万不要忘记擦除RenderTexture!不然会发现效率损耗很大!满脸都是泪。

_CameraDepthTexture – mini G-buffer

当需要深度的时候可以直接在shader中sample这张贴图_CameraDepthTexture,方式如下:

float depth = UNITY_SAMPLE_DEPTH(tex2D(_CameraDepthTexture, screen_position));
float depth01 = Linear01Depth( depth );
float depth_eyelinear = LinearEyeDepth( depth );

要正确获得深度图,需要设置 Camera.DepthTextureMode 属性,把深度渲染打开,可在shader中正确获取深度信息,其余的就参考手册可以得到答案[手册]。当然光有深度图还不行,屏幕坐标也得计算好,有屏幕坐标,就需要像素点大小了,可以通过half4 _MainTex_TexelSize来取得。说到像素点大小,不得不说1diot遇到的坑,一下是内置的pixel perfect的实现,各位自己体会吧。

pixel perfect

inline float4 UnityPixelSnap (float4 pos)
{
  float2 hpc = _ScreenParams.xy * 0.5f;
  #ifdef UNITY_HALF_TEXEL_OFFSET
    float2 hpcO = float2(-0.5f, 0.5f);
  #else
    float2 hpcO = float2(0,0);
  #endif
  float2 pixelPos = round ((pos.xy / pos.w) * hpc);
  pos.xy = (pixelPos + hpcO) / hpc * pos.w;
  return pos;
}

坑爹的replacement shader

在逐步深入post effect的过程中,发现自己定制的对象比如半透明的物体什么的,无法正确渲染出深度。unity 通过一个叫做 replacement shader 的东西来渲染深度图。 unity 选用 camera 设定好的 replacement shader(没有就是默认预设的深度图),对场景上的物体做一些特殊的操作,比如渲染深度什么的。其余的请参考:Rendering with Replaced Shaders

可以在 built-in shader 中找到 Camera-DepthTexture0.shader 所有的代码,它定义了每一种 RenderType 渲染深度的具体 shader 。由于没尝试过定制 replacement shader,就不献丑了。至于为啥没有正确渲染深度,看看默认 transparency 的代码:

fixed4 frag(v2f i) : SV_Target {
  fixed4 texcol = tex2D( _MainTex, i.uv );
  clip( texcol.a*_Color.a - _Cutoff );
  UNITY_OUTPUT_DEPTH(i.depth);
}

观察许久,感触良多。你懂的, _Cutoff 不能变啊!除了 _Color 之外,还有 _DetailTex_MainTex 几个固定参数,最好能在写shader的时候都保持名字不被修改。除了因为在材质切换 shader 的时候参数不会丢失,还因为成 depthmap、particle 使用的 alpha blend支持等特殊特性需要这些默认名字的支持。

参考文献

Accessing shader properties in Cg
Forward Rendering Path Details
Rendering with Replaced Shaders
各种效果的教程
built-in shader package download

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容