Unity Shader | 半兰伯特、高光反射、BlinnPhone

写 Shader,到底是在做什么?Shader 就像一个拥有魔法的黑箱,我们最终从它那里得到的,是像素的颜色。而得到的是什么样的像素颜色,则取决于我们丢进黑箱里的是什么东西,以及使用的魔法咒语是什么。丢进同样的东西,使用不同的魔法咒语,得到的像素颜色也是不一样的。输入顶点的坐标,光线方向,以及自定义的颜色,使用不同的计算方式,得到的像素颜色是不一样的。使用同样的计算方式,丢进一个纯色颜色值,和丢进一张纹理,得到的像素,也是不一样的。 如果把整个 Shader 比作一个函数,那么顶点位置,自定义的颜色值,纹理,灯光等这些就相当于函数的输入参数,而 Shader 中写的各种计算,用的各种算法,都是对输入的那些参数进行操作,而最终生成的,就是颜色值,也就是相当于函数的返回值。把 Shader 想的简单一点,就是输入需要的东西,进行计算,得到像素值。 这篇博客,还是关于光照模型的,接下来我们首先总结一下常用的光照模型。 Lambert (兰伯特) 光照模型 在之前的博客中说了漫反射的实现,实现漫反射用到的模型,叫做 Lambert (兰伯特) 光照模型。Lambert 实现出来的效果,一旦入射光向量与材质表面的角度大于90度,那么得到的漫反射颜色就会全部变为黑色,没有任何明暗变化效果。 Lambert 光照模型公式: 最终颜色 = 直射光颜色 * 漫反射颜色 * max(0, dot(光源方向, 法线方向)) 其中,直射光颜色,漫反射颜色,都是我们自定义的变量。 Half Lambert (半兰伯特) 光照模型 Half Lambert 是在 Lambert 模型的基础上,做了微调,也就是将光源方向与法线方向的点乘结果,从原来[-1, 1],映射为 [0, 1],这样原来背光面,也会有明暗效果。 Half Lambert 光照模型公式: 最终颜色 = 直射光颜色 * 漫反射颜色 * (dot(光源方向, 法线方向) * 0.5 + 0.5) Specular (高光反射) 这里的高光反射使用了 Phone 模型,的原理很简单,想象一束光射向某个点,然后反射出去,我们的眼睛同样看向那个点,当我们的眼睛看向那个点的方向,与光线反射的方向,越接近时,进入我们眼睛的反射光则越多,也就是更亮。看下面的图 很明显,当视野方向与光的反射方向夹角越小时,也就是说进入眼睛的光越多,所以那个点也就会越亮,这就是高光反射的原理。所以高光反射,实现起来也就很简单了,只要拿到视野方向,拿到直射光的反射方向,就可以求出最终的颜色值。 Specular 光照模型公式: 最终颜色 = 直射光颜色 * 反射光颜色 * pow(max(0, dot(反射光方向, 视野方向)), 光泽度(gloss)) + 漫反射颜色 + 环境光颜色...

March 19, 2020 · 4 min · fred

Unity Shader | 光照模型和漫反射

在之前的文章中写的Shader,呈现出来的物体样子是一个平面2D的状态,即使物体是3D的,那是因为,我们还没有将灯光加入到Shader的运算中。现在,我们将介绍灯光相关的东西,最后呈现出和 Unity Diffuse Shader 一样的效果。 什么是光照模型 光照模型,简单理解就是一种运算,或者说一个公式,计算的结果,决定了一个点受到光照时,所表现出来的效果。例如,光照在木板上,和照在一面镜子上,我们所看到的效果是不一样的,照在镜子上,很大一部分光会被镜子反射,而木板,却不会反射那么多光。 进入摄相机的光线分类 在游戏中,我们可以将进入摄相机的光分为 高光反射、漫反射、自发光等。像上面说的镜子反射了大部分光,就是高光反射,现实中比较光滑的表面,受到光照时,都会产生这种效果,很亮。而光线照在木头上,就是漫反射,其实是木头先吸收了光,然后向周围散射出去,这个就不会很亮。而自发光,就是字面意思,自身是一个发光体。这里大概知道这些词是什么就可以,不必深究里面的原理。 这一篇博客,接下来我们将在Shader中实现一下漫反射。实现漫反射,可以在顶点函数中,这叫做逐顶点光照。也可以在片元函数中实现,这叫做逐片元光照。在顶点函数中实现,也就是对每一个顶点都进行一次光照的计算,而在片元函数中也就是对每一像素执行光照计算,所以,在片元函数中实现相对来说要更耗费一点性能。 在顶点函数中实现漫反射 漫反射的计算公式是 最终颜色=直射光颜色 * max(0, dot(光线,法线)),也就是使用 Directional Light 的颜色 乘 光线发射方向 与顶点法线方向的夹角,dot函数就是点乘,结果就是夹角。有一点要注意的是,dot中的 光线 和 法线 都是单位向量,也就是我们要对其进行标准化。max函数是取最大值,也就是说,如果dot计算出来的结果小于0,那就取0。 看下面的代码,注意看注释,从上往下每一个注释都要看 Shader "iMoeGirl/04-DiffuseVertex" { SubShader { Pass { // 要使用光照,首先要定义一下LightMode,这里我们使用ForwardBase, // 这里先不用管意思,只要照着写上就行 Tags { "LightMode" = "ForwardBase" } CGPROGRAM // 这里我们将 Unity 一些预定义的Shader代码包含进来, // 里面有我们需要的东西,场景中第一个Directional Light的信息(后面用来做计算) #include "Lighting.cginc" #pragma vertex vert #pragma fragment frag // 根据共识,要计算最终顶点的颜色,需要法线数据,所以这里将法线从Application传到顶点处理函数中 struct a2v { float4 vertex: POSITION; float3 normal: NORMAL; // NORMAL就是法线语义,之前的文章说过 }; struct v2f { float4 position: SV_POSITION; fixed3 color : COLOR; // 这个颜色就是在顶点函数中计算完的顶点的漫反射颜色,传到片元函数中 }; // 把光照的计算放在顶点函数中,所以叫做顶点光照 v2f vert(a2v v) { // 定义一个数据传送结构体(传送到片元函数中) v2f f; f....

March 15, 2020 · 2 min · fred

Unity UGUI RGB通道分离抖动

今天我们要在 UGUI 上实现图片RGB通道分离抖动效果,先看最终效果图 ...

March 8, 2020 · 2 min · fred

Unity Shader | 使用Struct传递数据

上一篇博客 说了在 CGPROGRAM 中写代码、顶点处理函数、片元处理函数、以及在两个函数之间传递简单的数据、从 ShaderLab 属性到CG数据类型之间的联系等。这一篇博将稍详细一点说一下 Shader 的基本知识,以及在顶点和片元函数之间传递更多的数据。 Shader的基本理解 简单来说,Shader 决定了一个模型最终呈现在屏幕上的样子。一个模型由很多顶点构成,而每一个顶点,都会经过 Shader 中的顶点处理函数,这个过程,就是从应用将数据传递到顶点处理函数,顶点函数需要将顶点从模型空间转换到屏幕空间,或者说是裁剪空间,也可以简单理解为从 3 维空间转换到屏幕上的 2 维空间。在这个过程中,还可以做一些其他对顶点的操作。 顶点数据经过顶点处理函数处理后,接下来就返回,然后传给片元处理函数,到了这一步,面对的,就是像素,也就是每一个像素的颜色值。在这里,可以根据自己的需求,对每一个像素做处理,例如做高斯模糊,RGB通道分离,等等,各种各样的效果。 向 Shader 传递更多的数据 在之前的博客文章中,我们只是将顶点的坐标传给了Shader,但是我们还需要其他的数据,例如法线,例如切线,纹理坐标等等。接下来,我们将使用结构体来存储要传递的数据,看下面的代码 Shader "iMoeGirl/03-Shader" { Properties { _MainColor("颜色类型", Color) = (1,1,1,1) } SubShader { Pass { CGPROGRAM float4 _MainColor; #pragma vertex vert #pragma fragment frag // 这里定义一个结构体,封装需要从应用传到顶点函数的数据 struct a2v { float4 vertex : POSITION; // 顶点坐标 float3 normal : NORMAL; // 顶点法线 float4 textcoord : TEXCOORD0; // 第一套纹理坐标(可以有多套) }; // 这里定义另一个结构体,封装从顶点函数传到片元函数的数据 struct v2f { float4 position : SV_POSITION; float3 temp : COLOR0; }; // 顶点处理函数,传入的是a2v结构体,返回的是要传到片元函数的v2f结构体 v2f vert(a2v v){ v2f result; result....

March 7, 2020 · 1 min · fred

Unity Shader | 属性、顶点与片元函数

上一篇博客 介绍了Shader的基本结构,这里我们继续来说Shader的编写,也就是要在 CGPROGRAM 中写代码。首先我们把之前的Shader结构代码复制过来。 Shader "iMoeGirl/MyShader" { // Shader 名字 // 这里定义一些属性,可以显示在UI面板上用于调节 Properties { // 属性名("Inspector面板上显示出来的属性名", 属性类型) = 默认值 _Color("颜色类型", Color) = (1,1,1,1) _Vector("向量类型", Vector) = (1, 2, 3, 4) _Int("整型", Int) = 11111 _Float("浮点型", Float) = 12.11 _Range("范围类型", Range(100, 1000)) = 128 _Tex2D("贴图类型", 2D) = "white"{} _Cube("立方体贴图类型", Cube) = "white"{} _Tex3D("3D纹理", 3D) = "white"{} } // 子 Shader,可以写多个,显卡运行时, // 从第一个SubShader开始,如果第一个里面的效果都支持,则使用第一个, // 如果发现这个SubShader里面某些效果不支持,则自动运行下一个SubShader SubShader { // 至少有一个Pass,相当于一个方法 Pass { // 在Pass块里写Shader代码 CGPROGRAM // 使用 CG语言编写Shader ENDCG } } // 如果发现所有的SubShader都不支持,则使用Fallback,相当于后备方案 Fallback "VertexLit" } 怎样使用 Properties 中定义的属性 Unity3D定义Shader属性所使用的语法,和CG所使用的说法是不一样的,所以我们要在一个Pass中使用Properties中定义的属性,需要在Pass中再以CG的语法再写一遍,其实就是变量名相同,而数据类型不同,在Shader在编译的时候,就会自动将两个变量关联起来。看下面的代码...

March 2, 2020 · 2 min · fred

Unity Shader | 基础

MeshFilter 存储一个Mesh(网格,模型的网格,模型的三角面顶点信息) MeshRenderer 用于渲染一个物体的外观,数据来源于MeshFilter Material 材质包含两部分,贴图和Shader OpenGL DirectX 直接与显卡交互图形渲染库,可以理解为应用程序与显卡之间的桥梁,为应用程序提供一些渲染接口,用于渲染。 Shader Shader可以理解为是一种渲染命令,由opengl或DX进行解析,用于控制图形的渲染。 GLSL/HLSL/CG shader编程语言,GLSL面向OpenGL,HLSL面向DirectX,CG是Nvidia公司出的,跨平台的shader编程语言。 ShaderLab 我们在Unitiy中写Shader用的语言是ShaderLab,可以理解为Unity为了方便使用者写Shader而创造的一种新的Shader语言,最后其实都会在底层被翻译成GLSL或HLSL或CG。 Unity中的Shader分类 Shader的中文名叫做着色器 表面着色器,Surface Shader 顶点/片元着色器,Vertex/Fragment Shader 固定功能着色器,Fixed function Shader (在现代硬件上基本已被弃用) 表面着色器可以理解为是对顶点/片元着色器的一种封装,它帮我们处理了很多渲染上比较麻烦的事情。而顶点/片元着色器就相对更灵活一些,也就是说很多东西要自己处理,相对来说要写的代码更多一些。进一步讲,使用顶点/片元着色器能实现的效果,使用表面着色器并不一定能实现,或者说,并不一定那么方便地实现。 Unity Shader 结构 Shader "iMoeGirl/MyShader" { // Shader 名字 Properties { // 这里定义一些属性,可以显示在UI面板上用于调节 } SubShader { // 子 Shader,可以写多个,显卡运行时, // 从第一个SubShader开始,如果第一个里面的效果都支持,则使用第一个, // 如果发现这个SubShader里面某些效果不支持,则自动运行下一个SubShader } // 如果发现所有的SubShader都不支持,则使用Fallback,相当于后备方案 Fallback "VertexLit" } Unity Shader 属性类型 Shader "iMoeGirl/MyShader" { // Shader 名字 // 这里定义一些属性,可以显示在UI面板上用于调节 Properties { // 属性名("Inspector面板上显示出来的属性名", 属性类型) = 默认值 _Color("颜色类型", Color) = (1,1,1,1) _Vector("向量类型", Vector) = (1, 2, 3, 4) _Int("整型", Int) = 11111 _Float("浮点型", Float) = 12....

February 24, 2020 · 1 min · fred