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 · 猫猫

Unity UGUI RGB通道分离抖动

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

March 8, 2020 · 2 min · 猫猫

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 · 猫猫

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 · 猫猫

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 · 猫猫

使用 Unity 实现漂亮的数学曲面(下)

上一篇博客我们实现了一些简单的数学曲面,这一节我们将继续更复杂的数学曲面展示,所有资源完全承接上一篇内容。 2.5 创建一个涟漪效果 先来看一下最终要实现的效果图 我们一步一步来实现这个效果。首先,要创建一个基于到原点距离正弦波。而这个距离,我们使用毕达哥拉斯定理也就是勾股定理 $a^2 + b^2 = c^2$ 。对于这个效果来说,我们是基于XZ坐标来求Y坐标的,所以也就是 $\sqrt{x^2 + z^2}$ 。 ...

January 16, 2020 · 6 min · 猫猫

使用 Unity 实现漂亮的数学曲面(上)

这篇博客是上一篇 使用Unity3D展示Sin函数动画 的续篇,在上一篇的基础上,来实现更复杂的效果。在文章最后会有完整的 C# 代码和 Shader 代码,先来看一下最终的效果图 由于篇幅太长,所以将分为上下两部分,现在开始第一部分的内容。 先将上一节 使用Unity3D展示Sin函数动画 的资源准备好,可以按下面的步骤手动建立,也可以直接导入上一节内容的完整 Unity 资源包,点这里下载 使用Unity的Cube做成一个Prefab 新建一个 Shader,命名为 ColoredPoint,Shader 的代码为上一篇博客的 Shader 新建一个材质,命名为 ColoredPoint,并使用第2步中创建的Shader C# 逻辑代码,也使用上一篇博客中的完整代码,在本文中会有很多修改 接下来,我们开始新的内容。 1 在不同的效果函数之间切换 在上一篇博客中我们实现了Sine函数的展示,现在要加入更多函数的展示,为了方便在运行状态可以随时切换到其他函数,我们需要把每一种类型的展示放在独立的函数中。 1.1 将 Sine 函数的表示放在独立函数中 首先在 Graph 脚本中添加一个新的函数 float SineFunction(float x, float t) {},这个函数将用于展示 $f(x,t) = sin(π(x + t))$。然后我们需要将函数体的内容填写进去,代码如下 float SineFunction(float x, float t) { Mathf.Sin(Mathf.PI * (x + t)); } 然后把 Update 函数里的代码改为调用我们新添加的函数 void Update () { for (int i = 0; i < points....

January 10, 2020 · 8 min · 猫猫

使用Unity3D展示Sin函数动画

今天我们要实现的东西,就是下面这个动图的效果。使用代码控制方块的坐标,来展示 Sin 函数。方块的颜色变化,是随着坐标变化而动态改变的,我们会写一个超简单的 Shader 来实现。 ...

January 6, 2020 · 2 min · 猫猫

制作一个会动的时钟

这是 Unity 基础系列教程的第一篇博客。我们将由浅入深,一步一步学习Unity及游戏开发相关的东西。整个系列教程所使用的Unity版本为 2018.4 及以上就可以,如果要使用特殊版本,则会在文中指出。 今天要做的是一个能够实时显示当前时间的时钟,就是下面这个东东。 1 首先建立一个新工程 首先打开Unity,创建一个新工程,名字自己定,例如 Clock 就可以。默认打开后,Unity 默认布局是下面图的样子。 你也可以更新布局,例如换成同时显示View和Game视图的布局,只需要点击编辑器最右上角的那个按钮,然后选择 2 by 3 即可成为下面图的样子。 下面是分辨率的设置,一般游戏开发中,会使用一个基准分辨率,很多游戏采用的是 16:9 的模式。这个在 Game 视图中设置,看下面的图,选择 16:9 即可 1.1 创建一个 GameObject 默认的场景中,会包含两个 GameObject,一个是主相机 MainCamera, 一个是灯光 Directional Light。这两个东西保持默认就好,现在我们创建一个新的物体,在 Hierarchy 面板右键,然后 Create Empty,或者通过菜单栏 GameObject/Create Empty都可以,这样就会在Hierarchy 面板上看到我们新建的物体,然后对这个物体重命名为 Clock,并且把它的位置置为 (0,0,0)。看下面的图 1.2 创建时钟的表盘 创建表盘,我们使用 Unity 默认的物体 Cylinder,然后改变它的大小,使其成为我们的表盘。首先,通过右键 Hierarchy 空白处,或者通过菜单栏 GameObject 中的 3D Object/Cylinder 选项,来创建一个 Cylinder,就是一个圆柱体。 Cylinder 默认已经有了很多组件,Mesh Filter、Capsule Collider、MeshRenderer。默认我们不需要物理模拟方面的东西,所以我们先把 Capsule Collider 这个碰撞器给删掉,通过右键这个组件,Remove Component 即可。 然后我们改变园柱体的大小,因为表盘是一个圆盘形状的东西,所以我们把圆柱体压平,也就是改变y轴的大小始可。把圆柱体 Scale 设置为 (10, 0.1, 10),如下图。...

January 6, 2020 · 2 min · 猫猫

梦开始的地方

经历过很多项目,遇到和解决过很多问题,但是没有没有以文字的形式总结过。所以这个系列,我打算从客户端的视角,去总结一个游戏项目从开始到上线,整个过程中的一些事情,人员之间的配合,客户端一些重要模块的实现,开发过程中遇到的问题等等。 一个项目的开始,可能始于某一个人的某一个微小的想法,经过一段时间的构思,然后到达公司层面,在经过很多人的会议讨论,市场调研,等等,觉得方案可行,然后就是立项,准备进入开发阶段。这个过程不是一两天完成的,可能经历了很长时间。这个阶段,也会大概预计项目周期,项目成本,什么时候上线,盈利情况预估等等。 一旦确认了项目开发,策划方面会先行,进行项目的具体设计,开会讨论,确定方案等等。 然后美术也会根据项目的方向,做一些可以看的东西,可能这些东西最终不会进到版本中,但是现阶段是需要的。 至于程序呢?这个阶段可能就开始搭框架了,做一些与具体游戏逻辑没有太大关联的,框架层面的模块,例如资源管理啦,网络通信啦,等等。 这个阶段可能会持续一段时间,然后就会逐渐进入更加规范和流程化的游戏逻辑开发。不同的程序写不同的游戏模块。 在前期开发过程中,可能会有很多东西做出来了,然后效果不太好,就被砍掉了,或者整个设计变化很大,基本上等于这个模块推到重来,这都是很正常的情况。不管是美术,策划,和程序,都会有这种情况发生,不过随着游戏的开发进度,这种情况会越来越少。 从开发到上线,游戏会经历很多测试版本,可能有公司内部测试版本,外部小规模测试版本,外部大规模测试版本,付费测试版本等等。每一次版本测试,都会得到很多信息,用于调整和改善游戏内容。经过几轮测试以及测试后的版本打磨,整个项目会越来越接近上线版本。 当然,在一切顺利的情况下,游戏最终会提交上线。然后就是后期的运营,以及上线后的版本迭代,内容更新,Bug 修复等等。 这大概就是一个游戏项目的完整生命周期。

December 8, 2019 · 1 min · 猫猫