Unity Shader 基础入门

阅前提示

记录Unity Shader 学习笔记,拿起这块砖,砸开Shader的门。
适合人群:Shader 初学者
阅读方式:目录顺序阅读
知道的越多,不知道的越多。希望我的文章对你有所帮助
获取更多:我的博客


Shader

渲染管线:

​ 渲染流程主要分为三个阶段,由CPU与GPU完成

1.应用阶段(cpu部分)

此阶段主要是处理数据(硬盘——>内存),设置渲染状态,DrawCall

加载数据到显存
设置渲染状态
DrawCall

​ 将准备的数据(点,线,面,材质,等信息打包传递给GPU【图元数据】,通知GPU开始渲染)

2.几何阶段(gpu)
在这里插入图片描述

该阶段主要做的事情是坐标转换,将空间坐标转换为屏幕坐标。

细分下来有如下几个过程:

顶点着色器(Vertex)

​ 实现顶点的空间变化,顶点着色。

曲面着色器(Tessellation)

​ 细分图元

几何着色器(Geometry)

​ 执行逐图元着色操作,或用于产生更多图元

剪裁(Clipping)

​ 剪裁不在摄像机内的顶点,剔除某些三角图元的面片。

屏幕映射(ScreenMapping)

​ 将图元坐标转到屏幕坐标新中

3.光栅化阶段(gpu)

设置三角面,计算每个像素的颜色,最终呈现。

三角形设置

​ 上阶段输出的三角网格顶点数据,计算三角网格。

三角形遍历

​ 找寻被三角网格覆盖的像素(片元)

片元着色器

​ 可编程,完成重要的渲染技术。

逐片元操作

​ 称为输出合并阶段,此阶段片元将进行很多测试工作。

模板测试:用于限制渲染区域,或一些高级用法 渲染阴影,轮廓渲染

深度测试:用于判断遮挡

混合:用于处理透明,半透明

数学

向量/矢量v(x,y,z)

|\vec v| = \sqrt{x^2 + y^2 + z^2}

点积

\vec {v}_1\cdot \vec {v}_2 = {x}_1\times {x}_2 + {y}_1\times {y}_2 + {z}_1\times {z}_2 \vec {v}_1\cdot \vec {v}_2 = |\vec {v}_1||\vec {v}_2|\cosθ

​ v1 在v2上的投影长度 (>0 方向相同 反之)
θ = \arccos(\frac{\vec {v}_1}{|\vec {v}_1|}\cdot\frac{\vec {v}_2}{|\vec {v}_2|})

叉积

\vec {v}_1 \times \vec {v}_2 = ({y}_1{z}_2 - {z}_1{y}_2,{z}_1{x}_2 - {x}_1{z}_2,{y}_1{x}_2 - {x}_2{y}_1)

向量叉积来计算垂直于该平面的向量
|\vec {v}_1 \times \vec {v}_2| = |\vec {v}_1||\vec {v}_2|\sinθ

矩阵
单位矩阵

{I}_n = \begin{matrix} 1 & 0 & 0 &. \\ 0 & 1 & 0 &. \\ 0 & 0 & 1 &.\\ .&.&.&.n \end{matrix}\\ I = I^{-1}

置换矩阵

{M^T}_{ij} = {M}_{ji}\\ {AB}^T = B^TA^T

逆矩阵

{M}{M^{-1}} = {I}\\ (AB)^{-1} = B^{-1}A^{-1}

高斯-若尔当消元法

​ 逆矩阵的求法
[A\space I] = [I\space A^{-1}]\\ \begin{matrix} ...&1 & 0 & 0 \\ ...&0 & 1 & 0 \\ ...&0 & 0 & 1 \\ \end{matrix} => \begin{matrix} 1 & 0 & 0 &... \\ 0 & 1 & 0 &... \\ 0 & 0 & 1 &...\\ \end{matrix}

正交矩阵

MM^T = I\\ M^T = M^{-1}

正交矩阵 M = \begin{matrix} & \vec a &\\ & \vec b &\\ & \vec c &\\ \end{matrix} \vec a、 \vec b、 \vec c 单位矢量,互相垂直

矩阵变换
基础变化矩阵

\begin{matrix} {M}_{3*3} & {t}_{3*1} &\\ {0}_{1*3} & {1} &\\ \end{matrix}

法线变换

若一个点T 的变换矩阵为M,则法线N的变换矩阵为M逆转置矩阵
{T}_B{N}_B =(M{T}_A)(G{N}_A) = 0 \\ G = (M^{-1})^T = (M^{T})^{-1}\\ {T}_B = M{T}_A\\ {N}_B = (M^{-1})^T{N}_A

Unity Shader

四种Shader
  1. Standard Surface Shader 表面着色器模板(Unity后台转换为顶点/片元着色器)

    Shader "Custom/NewSurfaceShader" {
        Properties {}
        SubShader {
            Tags { "RenderType"="Opaque" }
            //[RenderSetup]
            CGPROGRAM
            ENDCG
        }
        FallBack "Diffuse"
    }
    
  1. Unlit Shader 顶点/片元着色器

    Shader "Unlit/NewUnlitShader"
    {
        Properties{}
        SubShader
        {
            Tags { "RenderType"="Opaque" }
            LOD 100
            Pass
            {
                CGPROGRAM
                ENDCG
            }
        }
    }
    
    1. Image Effect Shader 屏幕后处理效果模板

    2. Compute Shader 常规渲染流水线无关的计算

第一个Shader
Shader "Unlit/SimpleShader"
{
    SubShader
    {
        Pass
        {
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag

                float4 vert(float4 v:POSITION):SV_POSITION
                {
                    return UnityObjectToClipPos(v);//对顶点坐标进行了裁剪空间转换
                }

                fixed4 frag():SV_TARGET
                {
                    return fixed4(1,1,0,1);
                }
                ENDCG   
        }
    }
}
vertex

​ 声明了哪个函数包含顶点着色器代码

​ 顶点着色器:逐顶点执行

fragment

​ 声明了哪个函数包含了片元着色器代码

​ 片元着色器:逐片元执行

顶点与片元的关系

​ 在渲染管线中,GPU执行的开始便是顶点作色器,它的输入来自CPU,主要工作是 坐标转换逐顶点光照,且可以为后续阶段输出数据

​ 片元着色器作用于光栅化阶段,三角形遍历阶段会检查每个像素是否被三角网络覆盖,如果被覆盖的话就会生成一个片元

光照
漫反射

{C}_{diffuse} = ({C}_{light}*{M}_{diffuse})\max(0,\vec N\cdot\vec L)

_LightColor0.rgb * _Color.rgb * saturate(dot(worldNormal,worldLight))
半兰伯特模型(Half Lambert)

{C}_{diffuse} = ({C}_{light}*{M}_{diffuse})\max(0,\alpha(\vec N\cdot\vec L) + \beta)\\ \alpha,\beta \space 一般取0.5

_LightColor0.rgb * _Color.rgb * (dot(worldNormal,worldLight)*0.5 + 0.5)

目的在于把[-1,1]映射到[0,1],模型的背光面也会有明暗变化。因为普通漫反射会与0做比较。

高光反射光照模型(Phong)

{C}_{specular} = ({C}_{light}*{M}_{specular})\max(0,\vec v\cdot\vec r)^{{m}_{gloss}} \\ \vec r = \vec l-2(\vec n\cdot \vec l)\vec n

Clight是入射光的颜色和强度,材质的高光反射系数Mspecular,视角方向V和反射方向R点乘的结果的光系数Mgloss次方

fixed3 reflectDir = normalize(reflect(worldLight,worldNormal));
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld,v.vertex));
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(viewDir,reflectDir)),_Gloss);
Blinn

{C}_{specular} = ({C}_{light}*{M}_{specular})\max(0,\vec n\cdot\vec h)^{{m}_{gloss}} \\ \vec h = \frac{\vec v + \vec l}{|\vec v + \vec l |}

纹理
属性:_MainTex ("Texture", 2D) = "white" {}
声明:
sampler2D _MainTex;
float4 _MainTex_ST;
// float2 uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
float2 uv = TRANSFORM_TEX(v.texcoord,_MainTex);//缩放与平移确定最终纹理坐标
float3 TexelsValue = tex2D(_MainTex,uv);//通过纹理坐标采样纹素值

纹理名_ST 代表纹理属性 ST(scale,translation)

X_ST.xy 代表缩放,X_ST.zw 代表平移

法线纹理

凹凸映射有两种方式:

​ 高度纹理:颜色深浅表示凹凸

​ 法线纹理:通过法线扰动方向确定凹凸

因为法线分量范围[-1,1] 像素的分量范围[0,1]
normal = pixel*2-1

模型空间的法线纹理

​ 实现简单,直白,计算少。但得到的是绝对法线信息,仅可用于创建时的模型。

切线空间的法线纹理

​ 自由度高,记录的是相对法线信息,适用于不同的网格。

​ 可进行UV动画

​ 可压缩。切线空间下法线纹理的Z方向总是正方向,因此可靠XY方向推导Z方向。

切线空间下的代码:

//把模型空间下切线方向,副切线方向,法线方向 排列得到模型空间到切线空间的旋转矩阵
//副切线由其他两个做叉乘得到,乘w 是为了区分方向(因为有两个)
// float3 binormal = cross(normalize(v.normal),normalize(v.tangent.xyz))*v.tangent.w;
// float3x3 rotation = float3x3(v.tangent.xyz,binormal,v.normal);
// TANGENT_SPACE_ROTATION;//内置宏,效果同上
TANGENT_SPACE_ROTATION;
//计算切线空间下的光线与视角
o.lightDir = mul(rotation,ObjSpaceLightDir(v.vertex)); //模型——>切线
o.viewDir = mul(rotation,ObjSpaceViewDir(v.vertex));

//计算切线空间下的法线坐标
fixed3 tangentLightDir = normalize(i.lightDir);
fixed3 tangentViewDir = normalize(i.viewDir);

fixed4 packedNormal = tex2D(_BumpMap,i.uv.zw);
fixed3 tangentNromalDir;
tangentNromalDir = UnpackNormal(packedNormal);//法线纹理设置成 normal map 后使用此方法
// tangentNromalDir.xy = (packedNormal*2 - 1)*_BumpScale;
tangentNromalDir.xy *= _BumpScale;
tangentNromalDir.z = 
sqrt(1-saturate(dot(tangentNromalDir.xy,tangentNromalDir.xy)));//根据xy推导z
渐变纹理

通过渐变纹理使漫反射轮廓鲜明。

原理是由半兰伯特而来,利用HalfLambert 使法线和光线的点积的范围变为了[0,1],从而在UV上以halfLambert为坐标采样。

fixed halfLambert = 0.5*(dot(worldNormalDir,worldLightDir)) + 0.5;
fixed3 diffuseColor = tex2D(_RampTex,float2(halfLambert,halfLambert)).rgb * _Color.rgb;
fixed3 diffuse = _LightColor0.rgb * diffuseColor;
遮罩纹理

遮罩纹理为我们提供了更为精确(像素级)地控制模型表面的各种性质。

例如:可以通过一张遮罩纹理的R通道的值来控制高光反射的强弱。

fixed specularMask = tex2D(_SpecularMark,uv).r * _SpecularScale;
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(i.worldNormal,blinnDir)),_Gloss) * specularMask;

所以,通过增加遮罩纹理,我们可以控制更多的表面属性。

透明
透明度测试

判断一个片元透明度满不满足条件从而进行舍去,保留的片元将于普通的不透明物体一起处理。无其他特殊开关,即物体要不就是纯透明,要不就是不透明。

clip(texColor.a - _Cutoff);
//当 a - 定义的系数 < 0 是将执行 discard 指令剔除该片元
透明度混合

真正意义上的半透明。它会将当前片元颜色于存储在颜色缓冲中点颜色进行混合。但此混合会关闭深度写入,所以必须要注意渲染顺序。

相关链接

CG标准函数库

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