C#进阶(一)-闭包
什么是闭包?
C#中的闭包(Closure)指函数可以访问其定义作用域之外的变量(外部变量)。换句话说,一个闭包就是一个“捕获”或“携带”了其生成的环境中、所引用的自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。不止C#中存在闭包的特性,JS中也存在闭包。
C#闭包最常见的例子是在匿名函数(或Lambda表达式)中捕获自由变量,例如:
12345678910public static Action a; public static void Main(string[] args) { for (int i = 0; i < 5; i++) { a += () => { // 闭包捕获i Console.WriteLine(i); }; } a();}
为什么输出全是 5 而不是 1, 2, 3, 4, 5 呢?那是因为for ...
基于Gerstner Wave算法的海洋实现
介绍
Sin Wave的局限
在引入Gerstner Wave之前,人们更多使用正弦波来模拟海浪,因为正弦波天生具有类似海浪的形状。但它的局限也很明显,那就是波峰过于圆润,纵然有办法通过代数方法约束波峰的形状,但效果还是无法达到预期的尖锐。
什么是Gerstner Wave
Gerstner Wave是Sin Wave的后继者,到现在也是一种常用的用来模拟海洋波浪的算法。他的历史其实已经很古老了,可以追溯到1986年。相比于快速傅里叶变换(FFT),Gerstner Wave方法的开销更小,效果也很真实,因此被更多的应用与游戏领域(FFT更适合影视行业,因为它的效果更好但开销更大)。
Gerstner Wave的公式如下:
P(x,y,t)=(x+∑(QiAi∗Di.x∗cos(ωiDi(x,y)+φt))y+∑(QiAi∗Di.y∗cos(ωiDi(x,y)+φt))∑(Ai∗sin(ωiDi(x,y)+φt)))P(x,y,t) = \begin{pmatrix}x + \sum({Q_{i}A_{i} * D_{i}.x * \cos(\omega_{i} D_{i}(x ...
水面、波浪、白沫和焦散
介绍与准备
我最近打算开始做毕设项目的场景啦XD!这个项目的名字叫元素宇宙(Elemental Universe),是一个化学元素拟人世界观下的小宇宙的故事,目前只有我一个人在做,第一步是搭场景。主场景打算用polyBrush磨出来,其中有一些湖泊和海洋。海洋部分我打算用GerstnerWave的方法来做,湖泊部分采用本文所介绍的方法(当然,还有很多可以改进的地方。有改进的部分会在“更新说明”里提及)。
废话交代完了,现在正式开始吧:
首先,准备一个细分好的平面(因为我们需要一定数量的顶点)作为水面。在这个项目中我使用的是:
演示模型来自Sketchfab,使用遵守CC版权协议。如下预览所示,模型原本的水面只是一块普通的、smooth值几乎设置为1的水平面:
Little Pond & fish by Kenny Kwok. on Sketchfab
波浪
波浪的基本原理是顶点动画,通过修改水面顶点y值(高度)实现,这也是为什么前文提到我们需要一块顶点数比较多的面,越多的顶点意味着越高的波浪精细程度。
我们现在希望的事情是:水面能够上下波动形成波浪。对于湖泊这样较为波 ...
Lua实现面向对象
最近在实习,接到的第一个任务是用公司的自研引擎制作一款抖音特效《宅家舞蹈秀》并于五一期间上线抖音。目前特效已经上线了,不过数据不是很好看,希望之后能被推起来吧orz。
说到自研引擎,给我的感觉和 Unity 一类的游戏引擎很像,只不过有些功能开发的不是很成熟(或者说健全),会有一些偶发的诡异的bug(例如粒子播放失败)。虽然存在些许问题,但这款引擎的功能足够满足抖音小游戏/抖音特效这样轻量级的开发了。
自研引擎所使用的编程语言是Lua。Lua是一个灵活且轻量的脚本语言,在实际的游戏开发工作流中,可以用它结合xLua框架来实现游戏的热更新,也可以直接用Lua开发游戏(特别是一些早期的小游戏)。Lua本身并没有提供面向对象的特性,当游戏系统变得复杂时我们该怎么办呢?
在现代游戏编程中,我们会采用具有面向对象特性的语言(例如Java、C#、C++)来开发游戏。面向对象有三个特性,分别是:
封装:就是把对象的属性和行为(数据)结合为一个独立的整体,并尽可能隐藏对象的内部实现细节
继承:从已有的类中派生出新的类称为子类,子类继承父类的数据属性和行为,并能根据自己的需求扩展出新的行为,可以提高 ...
贴地雾效实现
接上篇文章画的饼,这回我打算试着实现一下雾效,我们最终的目标就是实现类似下图的效果。当然,纪念碑谷中的不怎么被雾影响的方块和浸泡在雾气中的方块使用的应该是两种shader,他们不会混在一起。但如果我们要实现的是一个可以移动的场景呢?使用我想在纪念碑谷的静态贴地雾效上加点东西,即:当摄像机移动时,雾的范围会随着摄像机的移动而移动,始终做到远处模糊、近处清晰的效果。
思路
其实我们实现纪念碑谷中的这种雾效的思路和全局渐变非常类似。总结一下,主要有以下两点需求:
离摄像机越远,雾气越浓,具体表现在物体整体与雾气的颜色相融
离摄像机越近,雾气越轻,具体表现在雾气很浅,几乎消失
即,离相机较远的物体如下左所示;离相机较近的物体如下右所示:
我们可以调整地平线(渐变中心位置)的值来实现这个效果。当一个物体离摄像机较近时,它的渐变中心也较低;反之则较高。
实现
变量
我们需要这么几个变量:
渐变纹理
主色调
雾的颜色
雾的浓度
12345678Properties{ _RampTex("渐变纹理", 2D) = "white&q ...
如何正确地搞颜色?
如何正确地搞颜色?
如何正确地“搞颜色”?看到这里,你的脑海中是不是闪过了一些奇奇怪怪的东西?不,这是篇非常正经的科普!所以现在,让我来给大家看一张色图。很正经吧~这张图上的小可爱叫做色轮,它将是我们接下来的科普中的主角,现在请先记住它的样子吧!
本文中我将围绕色彩的基本知识、几种常见的配色方案逐一展开介绍。
颜色理论与配色模式
人类是如何看见颜色的?
在自然界中自然光从光源发出,到达物体的表面。大部分被照射到的物体会吸收掉其中的一部分光,将剩下的光线反射到我们的眼睛里,成为了我们所看到的这个物体的颜色。如果一个物体不吸收任何落在其表面的光线,也就是说呢它把所有照射在它身上的光线都原封不动的反射回去了,那么它在人眼看来就是白色的。而另一方面,如果一个物体吸收了所有照射到它表面的光,那么这个物体看起来就会是黑色的。
这种成色原理被称为减色法,在多媒体领域中对应于CMYK颜色模型。在日常生活中,这种颜色模型广泛应用于打印机等需要在不会自然发光的物体上显示颜色的场合。这是因为物体不会自发光,所 ...
全局渐变效果实现
我想试着实现一下下图中纪念碑谷中的全局渐变的效果。关于如何实现这种渐变效果之前和别人讨论过,一开始我认为是通过从下往上打光来实现的,后来同学说是利用了shader和雾。利用后者实现的效果更为缓和,而利用光照可能会造成底部过亮的问题,不好控制。
为什么我管这种效果叫“全局渐变”呢?因为当我移动模型的时候,渐变的范围并不是固定在模型身上的,而是在世界空间的“某个位置”(如下图所示)。实际上,我们可以自行规定这个渐变地平线的位置,只需要在shader里加个控制变量就可以了。
实现
变量
首先,我们需要这么几个变量:
顶部的颜色
底部的颜色
渐变混合的程度
光照的强度
渐变地平线
渐变纹理
1234567891011121314Properties{ // 光照的强度 _Contrast("Contrast",Range(0,1))=0.1 // 渐变纹理 _RampTex("渐变纹理", 2D) = "white"{} // 上下渐变色 _UpColo ...
程序棋盘格效果实现
为什么会写这篇文章呢…因为我已经在大大小小的场合中遇到三次相关的题目了,前几次答得挺不尽人意的。第一次是在剑心互娱的校招笔试上,要求画一个黑灰白三条杠(下左),第二次是字节的二面要求画一个棋盘格的效果(下右),还有一次忘了(
对于这两种效果,我们的思路其实是一样的(棋盘格只需要多一步加法)。
开始绘制
怎么用程序画东西呢?我们在做材质的时候似乎一直是基于贴图进行处理的。实际上,我们是可以直接对纹理映射(texcoord节点)下手的,如果将其连接到根节点的base color,我们会得到如下画面:
为什么是这个颜色?首先需要知道的是:texcoord只有uv两个量,而颜色具有三个分量。当我们默认第三个分量为0时,就会得到上述的颜色信息。而如果我们把第三个分量设置为1,就会得到:
数学处理
黑、灰、白
为了实现三条边、四条边甚至多边的情况,我们需要对texcoord进行缩放,这一步很简单,只需要让texcoord乘上一个用来控制的常量:
然后,对texcoord的值进行取整(floor节点)。这一步的目的是将贴图“阶梯化”,方便后续实现黑白分条的效果。下图好 ...
菲涅尔反射实现边缘光效果
在之前的项目中,我从网上抓了一个用菲涅尔反射写的边缘光shader。经过自己的魔改之后,加上了法线等用在后续的项目中。现在我们回过头来探寻一下菲涅尔效应是如何用来实现边缘光的。
参数
我们有三个至关重要的参数:边缘光的颜色、边缘光强度和强度系数。
边缘光颜色
这个没什么好说的,就是控制边缘光的颜色。但因为是光,所以当颜色调整为黑色时不论强度多少都是看不到效果的。
边缘光强度
这个值可以控制菲涅尔影响范围的大小,这个值越大,效果上越边缘化(上:强度为1.5;下:强度为4)。
边缘光强度系数
这个值控制菲涅尔反射的反射强度,值越大,反射越亮,反之越暗(上:强度为1.5;下:强度为12)。代码里的注释有错,因为原先的代码的强度系数在内部被原作者定死了。
片元着色器
边缘检测
这一步是实现边缘光效果的核心。
首先我们来理清一下思路,如何进行边缘检测?其实很简单的一种方式就是通过视线方向和片元的法线方向点乘进行判断。当我们差不多正视一个片元的时候,夹角通常都是小于45°的。而片元越靠近边缘,与视线的夹角越大,最终达到视线相切的90°,再大就看不到了。
所以我们对视线和片元法线归一 ...
解决Z-Fighting
解决Z-Fighting
Z-Fighting是在实际项目中困扰到场景布局的一大因素。从理论上来说,在相同深度值的地方有多个片元,这导致颜色缓冲区不知道该呈现哪个颜色比较合适,而当视角运动时就会出现糟糕的闪烁:
通过调整渲染队列并不能避免这种问题,就比如上图的半透明物体的渲染队列是在不透明物体之后的,但他们重叠时依然会发生z-fighting现象。
解决方法
Shaderlab语法中的Offset命令
12345678910Properties{ _OffsetFactor("深度斜率",Float) = 0 _OffsetUnit("深度偏移",Float) = 0 }SubShader{ Pass{ Tags{ "LightMode" = "ForwardBase" } Offset [_OffsetFactor],[_OffsetUnit] } ...
游戏中的命令模式
什么是命令模式?
命令模式是《游戏编程模式》中介绍的第一种设计模式,也是作者“最喜欢的设计模式”。它到底有什么魅力呢?
命令模式是一种行为型设计模式(Behavioral Pattern),和之前介绍的几种创建型设计模式有所不同。行为型模式是对在不同的对象之间划分责任和算法的抽象化,不仅仅关注类和对象的结构,而且重点关注它们之间的相互作用。
GoF对命令模式的定义如下:
命令模式将一个请求( request) 封装成一个对象,从而允许开发者使用不同的请求、队列或日志将客户端参数化,同时支持请求操作的撤销与恢复。”命令就是面向对象化的回调。
《游戏编程模式》的作者认为这个定义不够明确,因此在书中给出了一个更简单明朗的定义:
命令就是一个对象化(实例化)的方法调用(这通常涉及到委托/函数指针/闭包…)。
在游戏开发中,命令模式由三个部分组成:调用器(invoker),命令(command)和接收者(receiver)。
调用器
也成为“发送者”,负责对请求进行初始化,其中必须包含一个成员变量来存储对于命令对象的引用。
发送者触发命令,而不向接收者直接发送请求。发送者并不负责创 ...
游戏中的原型模式
什么是原型模式?
原型模式也是一种创建型设计模式,是一种通过克隆(clone)/复制的方式降低实例化开销的设计模式。通常来说,我们会克隆一个原型并稍作修改,得到一个新的实例,而不是新建实例。被复制的实例就是我们所称的“原型(prototype)”,这个原型是可定制的。原型模式多用于创建复杂的或者耗时的实例,因为这种情况下,复制一个已经存在的实例使程序运行更高效;或者创建值相等,只是命名不一样的同类数据。
让我们回忆一下工厂模式。工厂模式需要为产品创建产品基类、具体产品类、工厂基类和具体工厂类。当产品种类繁多(例如100种不同类型的敌人)时,太多的类将使系统变得异常复杂。
而在原型模式中,如果你有一个远程敌人,则你可以通过这个远程敌人制作出更多的远程敌人。如果你有一个近战敌人,那你就能制作出其他近战敌人。任何类型的敌人都能被看作是一个原型,用这个原型就可以复制出更多不同版本的敌人。我们不再需要复杂的抽象工厂和具体工厂类,这让类的总数减少了一半。
不过,假如需要动态添加新类型,必须为每种类型准备一个原型对象。工厂模式更通用,而原型模式在特定场景(创建大量初始状态相同的对象)中提升性能。 ...
游戏中的工厂模式
什么是工厂模式?
工厂模式(Factory pattern)是一种实现了“工厂”概念的面向对象设计模式,它处理在不指定对象具体类别的情况下创建对象的问题。工厂方法模式的实质是“定义一个创建对象的接口,但让实现这个接口的类别来决定实例化哪个类别。工厂方法让类别的实例化推迟到子类中进行。”
由此可知,工厂模式定义一个创建对象的接口,但让子类决定实例化哪个类。工厂方法允许类将实例化延迟到子类,这节省了实例化的开销。在游戏开发中,工厂模式可以被用于创建某一类对象的诸多不同类型的“变体”,例如敌人这个大类分为远程敌人、近战敌人、辅助敌人等小类。
实例化一个具体的敌人是一个复杂的过程(需要指定类别、设置不同的不同初始数值),但又具有相似性,那么为什么不把这一过程抽象出来、交给一个专门的管理器呢?这就是工厂类的思想,创建过程都由工厂统一管理,当发生创建业务逻辑变化时,只需要修改工厂即可。
根据产品是 具体产品 还是 具体工厂,工厂模式可分为简单工厂模式和工厂方法模式;根据工厂的 抽象程度 可分为工厂方法模式和抽象工厂模式。但不论是哪种工厂模式,其中都有工厂和产品两个主要角色。
简单工厂模式(Si ...
游戏中的单例模式
什么是单例模式?
这学期开始教设计模式这门课程,其中第一节课所介绍的就是所有设计模式中最简单也最好理解的单例模式。单例模式是几大设计模式中最常被使用的模式之一,在游戏编程的实战中也有很多用途,比如说维护一个全局唯一的UI管理器、全局唯一的特效/音效控制器等等。该模式适合针对“有唯一需求,不希望存在多个”的系统。也就是说,单例模式的实例同时只能存在 0 个或者 1 个,实例化的过程分为懒实例(懒汉式)、饥渴实例(饥饿式)等。
《游戏编程模式》对于单例模式的定义为:“确保一个类只有一个实例, 并为其提供一个全局访问入口。”
类图
代码实现
final + 懒实例 + 线程锁实现法
final 关键字(等同于 C# 中的 sealed)的主要目的是防止单例类被继承。如果去掉 final,内部类可能会继承单例类,调用内部类的时候会造成多实例化的问题。然而在一些特定的情况下,你可以继承单例,这是一个强大但是经常被忽视的特性。
假设我们需要让文件封装类跨平台,为了实现这一点,我们可以将单例实现为一个抽象接口,并由它的子类提供各个平台上的实现。
什么是懒实例/懒汉式?简单地说,懒实例指的 ...
Oculus Quest电脑端有线连接教程
由于无线连接需要代理路由或代理wifi,所以走有线
本篇教程使用github作为图床,使用代理来加载图片
本教程的前提是你的设备已经激活并且连上手机。激活需要代理热点,比如刷了老毛子固件的路由器或者坐船去台湾,也可以通过oculus helper、SSTap等来实现
基础步骤
安装Oculus电脑客户端
从官网上下载Oculus Hub,点击oculus setup就可以直接安装,如下(如果你是开发者,可以安装Oculus Developer Hub):
安装建议使用科学上网,一个好的代理可以在半小时内下完5G多的资源,否则要五个小时。安装好之后桌面上会出现客户端图标:
这时候使用之前注册的开发者账户登录会出错,原因是国内墙的dns污染导致无法连接oculus的服务器。解决方式是打开电脑文件目录:
1C:\WINDOWS\System32\drivers\etc
里面有一个hosts文件,以记事本打开它,在末尾加上:
123157.240.217.51 graph.oculus.com157.240.195.51 graph.oculus.com157.240.199.54 gra ...