现代计算机图形学 —— 着色(Shading)
现代计算机图形学着色(Shading)Blinn-Phong Reflection Model漫反射项(Diffuse Reflect)数学公式
L_d = k_d(I/r^2)max(0,n \cdot l) 其中 是笼统的漫反射系数, 为能量, 光源至反射点的距离, 和 分别为 漫反射点所在平面法向量 以及 漫反射点指向光源的向量。
高光项(Specular Term)产生原因 当物体绝对光滑时,光线打在在其表面呈现镜面反射。
因此相对光滑的物体,光线打到其表面,反射光分布在镜面反射方向周围,此时观察方向与镜面反射方向接近,就可以看见高光,因此 高光项(Specular Term) 取决于我们的 观察方向(view direction),这是根据 经验 所得的结果。
Blinn-Phong模型 Blinn-Phong模型很巧妙的修改了上述判断方式:
V 向量是否与镜面反射方向接近 <==> V 与 l 的 半程向量 是否与原法向相近
数学公式
L_s = k_s (I/r^2)max(0, n \cdot h)^p ...
现代计算机图形学 —— 深度缓存(Z-Buffer)
现代计算机图形学深度缓存(Z-Buffer)前言 当我们完成了场景中所有三角形的光栅化,下一步就是将他们正确的绘制在屏幕空间上,此时就需要正确处理他们的遮挡关系。
我们可以先试想一种最简单的绘制方法:像画油画一样,先绘制最远处的三角形,然后由远到进依次绘制所有的三角形,后面绘制的三角形会覆盖前面绘制的三角形,从而得到一张遮挡关系正确的图像。
由远及近依次绘制,能得到正确的遮挡关系
但是此时存在一种特殊情况:当3个三角形两两相互遮挡,其整体形成一个环,如果此时依旧使用简单的绘制方法,就无法正确解析出各三角形的深度关系。
存在无法解析深度的情况
为了解决场景中三角环的深度解析问题,使其能够正确绘制在屏幕上,就有了今天所讲的 深度解析(Z-Buffer).
具体思路方法 深度缓存(Z-Buffer)算法对每个像素的远近进行排序,每个像素都记录当其能够表示的 最浅的深度(min z-value)。
要执行深度缓存算法需要生成两个图像:
生成成品图像,其保存着图像的色彩信息(frame buffer)
生成一个只存储每个像 ...
现代计算机图形学 —— 反走样(Antialiasing)
现代计算机图形学反走样(Antialiasing) 当我们完成光栅化(Rasterizer)后,我们将会得到一个能在屏幕空间中显示的图案。这个图案的形状类似于三角形,但现在它的三条边都有着很严重的锯齿现象,不符合我们的预期。这就需要使用到我们今天要讲的 反走样(Antialiasing) 技术,也就是俗称的 抗锯齿。
走样原因概述 要将物体显示到屏幕上,就一定需要进行采样(Sampling)。而如果我们的 采样速率 < 信号改变频率,就会出现 走样(Aliasing)。
不同走样形式的产生原因:
锯齿(Jaggies)—— sampling in sapce
摩尔纹(Moire)—— undersampling images
车轮现象(Wagon wheel effect)—— sampling in time
如何做反走样对原始图像进行 模糊/滤波(Pre-Filtering)滤波操作概述 通俗的讲,就是在我们在采样之前,先对原始图片进行模糊处理,再进行采样。
使用滤波法(Pre-Filtering)进行反走样
注意反走样(Antialiasin ...
委托类型(delegate)
C#-委托类型(delegate)为什么要使用委托 由前面所学可知,我们可以将操作相同、参数不同的几个方法写成同名方法的重载,以减少代码量。可是,当参数相同时,我们就不能使用重载实现类似的操作。委托就帮我们解决了这类问题。
委托概念 在某些特定情况下,需要把方法(函数)作为参数传给另一个方法。当一个方法作为参数传递给另一个方法时,这个参数就是委托类型。
注意,只有当函数的形参类型与委托的形参类型相同,才能将函数传递给委托。
下面是实例代码:
1234567891011121314151617181920212223242526272829303132public delegate void DelSayHellow(string name);//创建委托public SayHellow{ static void Main(string[] args) { Test("张三",ChineseSayHellow);//将函数赋值给委托,传给另一个函数 Test("l ...
事件(event)
C#-事件(event)为什么要使用事件 根据之前在委托中的所学知识,我们可以了解到委托中可以存入一个或多个相同返回值与参数的函数,这样在调用委托的使用就会将委托中存入的所有函数都调用一遍,用不着在一个一个的调用各个函数,很好的实现了程序的封装。
但是这又出现了另一些问题——委托是可以被赋值的,且委托被赋值之后,从前保存的函数将不再存在。针对这一问题,先辈们提出了事件的解决方法。
事件是什么? 为了防止各种参数被修改,于是拥有了参数的属性。同样的,为了防止委托被修改,于是出现了事件。事件不允许被直接赋值,只可以用 “+=” 和 “-=” 对其中保存的函数进行修改,提高了程序的安全型。
事件在类中声明且生成,且通过使用同一个类或其他类中的委托与事件处理程序关联。包含事件的类用于发布事件。这被称为 发布器(publisher) 类。其他接受该事件的类被称为 订阅器(subscriber) 类。事件使用 发布-订阅(publisher-subscriber) 模型。
发布器(publisher) 是一个包含事件和委托定义的对象。事件和委托之间的联 ...
多态
C#-多态实现方法
虚方法
抽象类
接口
虚方法
实现代码
123456789101112131415public class Person{ public virtual void SayHello() //可被重写的父类源代码 { ........; }}public class Son:Person{ public override void SayHello() { ........; }}
实现原理
在子类中将父类的代码进行重写,如果一个父类的方法被调用,且这个父类中存着子类对象,子类中还将同名方法重写过,那么这次调用就会直接调用子类 重写后的方法。
若父类中存的是父类对象,那调用该方法的时候还是会调用改写前的方法,同样的,子类若是没有重写该方法,那么调用的时候还是会调用父类的方法。
抽象类
实现代码
123456789101112public abstract class Person{ p ...
Unity-背包系统
Unity-背包系统简介 背包是每个成功游戏中不可缺少的,玩家获取的装备与道具将会放入背包,需要时再拿出来使用。如果没有背包来储存玩家在游戏中获得的武器和道具,或许游戏将会变得十分单一枯燥,出招方式一成不变。
有了背包系统,玩家才可以使用不同的武器,搭配不同的道具,使出不同的攻击搭配,从而提高游戏的多样性。
简单构思 首先我们思考一下背包系统的简单逻辑,玩家拾取物品后,背包中出现该物品,点击该物品之后又可以使用。
上述步骤的实现,需要我们完成两个层面的工作,一个是 背包的数据库 ,一个是 背包的UI 。
背包的数据库数据库逻辑 现在需要构思如何拾取物品后记录数据。这里可以使用 ScriptableObject 来记录各个物品以及背包的数据,当玩家拾取物品后,将物品的信息传入背包的数据库进行记录。
创建数据库的步骤及细节 根据上述简单逻辑,可以得到下列创建数据库的步骤:
需要给 每个物品 和 每个背包 都创建自己的 ScriptableObject 数据
物品数据中包含自身的各项参数;背包数据需要有将物品数据存进背包的函数
同时需要 ...
Unity-工厂模式
Unity-工厂模式引言 在一个游戏中,时常需要动态创建一些游戏对象,例如:怪物的生成、道具的掉落、等,这些操作都需要我们动态的生成对象。
如果我们在创建对象的时候,让一个专门创建对象的实例,持有需要创建的对象的引用,也同样能够实现上述的功能,但是如果这么做,代码的复用性会非常低:我们在需要创建不同的对象的时候,都需要重新再写一套代码。因此,我们需要一个能够动态创建对象的方法,来解决上述的问题。
工厂模式的应用 通过对工厂模式的使用,可以 动态的创建游戏中需要的对象。例如:当敌人死亡时,就构造对应的掉落物品;当玩家进入特殊地形时,就构造对应的敌人。
代码实现代码逻辑
首先最重要的就是要创建我们的工厂类,其中包含创建对象的函数。
创建对象的需求可能在游戏的任何时候都需要用到,因此我们可以将其定为单例模式(Singleton)
需要创建哪个类的对象,事先是无从得知的,因此需要通过Type.getType()的方法获取对应的类
代码实现 根据上述逻辑创建我们的工厂类:
123456789101112131415161718192021222324252 ...
Unity-对象池&多对象池
Unity-对象池 & 多对象池简介 在制作游戏的过程中,人物和boss的设计往往会有释放多个子弹的攻击方式。我们可以用直接创造子弹然后销毁的办法来实现这些技能的效果,但当子弹开始变多,游戏就会不断的消耗我们的内存。为了解决这个问题,开发者们就引入了状态机。
普通对象池创建思路 在不创建新的实例的前提下,我们应该怎么实现多个实例同时出现的效果呢?其实我们只要将之前创建过的实例充分利用起来就可以了。
首先还是要创建足够的实例,并将这些实例保存同一个父类对象下,我们将其称为 对象池。对象池中的子类,我们只用在需要时候开启,不需要的时候关闭,这样就实现了与之前一样的效果。
实现(三个函数) 首先我们需要注意的是,在同一个场景中,同一个对象,我们只希望拥有一个保存它实例的对象池。因此我们要将对象池写成单例模式,保证在一个场景中只能有一个对应的对象池。
其次,我们需要在 游戏开始 的时候给对象池 创建足够的对象,保证在角色在使用对象池的时候,对象池中的对象足够多,防止程序报错。因此我们需要一个 初始化对象池的函数 以在游戏开始的时候使用对应对象 ...
Unity-场景的异步加载
Unity-场景的异步加载为什么需要异步加载 在诸多大型游戏里,场景渲染精度都是动态的,随着场景与角色距离的增加,渲染精度也在递减,这样极大的减少了硬件性能的消耗。
但如果角色使用了某些传送技能,将自己传送到为渲染的地点,游戏可能就会因为需要瞬间渲染大量的场景而卡顿。此时就需要用到 场景的异步加载 了。
异步加载,指的就是在加载角色之前,事先将角色周围的场景渲染好,防止角色传送后出现严重的卡顿,以提高玩家的游戏体验。
场景加载构想 在提供直接的代码之前,我们需要对基本的传送进行一个简单的构思。首先我们需要一个传送门以确定我们 能够触发传送 的地点;传送门前还需要设置一点以确定 传送地点,并将其作为传送门的子物体。
注意这两者的区别:传送门 只是确定 触发传送的地点,而角色需要前往的地点由 传送门前的点 决定。
编写代码,分别给传送门与传送点设置对应的属性(传送门:设置传送模式、传送场景、传送终点名字,以及编写 判断何时传送的函数)(传送点:设置传送点名字),这样简易的传送门就设置好了。
关键在于场景传送代码的逻辑:
...