如何正确地搞颜色?
如何正确地搞颜色?
如何正确地“搞颜色”?看到这里,你的脑海中是不是闪过了一些奇奇怪怪的东西?不,这是篇非常正经的科普!所以现在,让我来给大家看一张色图。很正经吧~这张图上的小可爱叫做色轮,它将是我们接下来的科普中的主角,现在请先记住它的样子吧!
本文中我将围绕色彩的基本知识、几种常见的配色方案逐一展开介绍。
颜色理论与配色模式
人类是如何看见颜色的?
在自然界中自然光从光源发出,到达物体的表面。大部分被照射到的物体会吸收掉其中的一部分光,将剩下的光线反射到我们的眼睛里,成为了我们所看到的这个物体的颜色。如果一个物体不吸收任何落在其表面的光线,也就是说呢它把所有照射在它身上的光线都原封不动的反射回去了,那么它在人眼看来就是白色的。而另一方面,如果一个物体吸收了所有照射到它表面的光,那么这个物体看起来就会是黑色的。
这种成色原理被称为减色法,在多媒体领域中对应于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°,再大就看不到了。
所以我们对视线和片元法线归一 ...
Unity动画系统:性能优化
通过之前两篇文章的介绍,相信大家对 Unity 的动画系统已经有了更深刻的了解了吧!Unity 的动画系统功能十分强大,但在实际项目中随着动画数量的增加,我们有可能会遇到性能瓶颈,导致游戏帧率直线下降,因此性能优化在动画系统中同样举足轻重。如何让动画系统更高效地运作呢?今天我们就来聊聊 Unity 动画性能优化的一些技巧吧!
Animation
简单动画
播放没有混合的单个简单动画(也就是状态机里头只有一个动画片段)会使 Unity 的速度比旧版动画系统更慢。如果动画较为简单,建议直接用 Legacy 下的 Animation 播放或尝试改用动画区间库例如 Dotween 来实现。
Animation 与 Animator 播放动画
当动画在检查器中必须标记为“Legacy”时我们就可以通过 Animation.Play 来播放动画了。Animation 与 Animator 播放动画的效率取决于动画的曲线条数,当曲线条数小于某个临界点时 Animation 更快,当动画曲线条数大于这个临界点时 Animator 更快。一次你对于简单动画来说可以采用 Animation 来播放。
...
解决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,内部类可能会继承单例类,调用内部类的时候会造成多实例化的问题。然而在一些特定的情况下,你可以继承单例,这是一个强大但是经常被忽视的特性。
假设我们需要让文件封装类跨平台,为了实现这一点,我们可以将单例实现为一个抽象接口,并由它的子类提供各个平台上的实现。
什么是懒实例/懒汉式?简单地说,懒实例指的 ...
Unity动画系统:动画状态机
在 Unity 的动画状态机(Animator)中,主要有以下几个核心组件,它们共同组成了动画系统的运行框架:
Animator Controller:动画控制器
State:状态,通常对应的是动画剪辑
State Machine:状态机,用来组织和管理一组动画状态。
Transition:定义两个状态之间的切换逻辑
Parameters:动画参数,用于传递条件或数据
Layers:层,支持在一个动画状态机中处理多个动画序列
Blend Tree:混合树,根据一个或多个参数动态混合多个动画
状态机
状态机就是Animator右侧大块区域所展示的“状态与转移”所组成的图(如上图所示)。
状态
点击状态,我们可以在检查器中看到相关信息,包括:
tag:动画状态也可以打标签,通过标签可以对状态进行管理。
Motion:该状态播放的是哪段动画剪辑(或者混合树所管理的树)。
Speed:动画播放速度。如果想通过脚本更改播放速度,可以勾选 Parameter 选框,为速度关联一个浮点类型的变量进行控制。
值为 0:卡在第一帧不动
值大于 0:正向播放
值小于 0:倒放
Motion ...
Unity动画系统:动画系统基础
Hi~ o(* ̄▽ ̄*)ブ哈喽大家好,我是时光!经过将近一年的 Unity 学习,我已经对 Unity 的各个系统有了不少认知啦~欢迎大家看看我之前发布的一些文章哦:
合成大西瓜
Unity2D制作的合成大西瓜小游戏
明日方舟
Unity实现的明日方舟战斗系统
我打算开一个系列介绍一下 Unity 动画系统的基础知识,也算是对我这段时间使用 Unity 动画系统的一个总结,欢迎大家评论转发φ(゜▽゜*)♪!
本文将从 Unity 动画系统的基本概念出发,介绍动画机的工作原理,并为动画添加动画事件。 ...
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 ...
LD49复盘:游戏中的观察者模式
Ludum Dare 49圆满结束了,我们的《绝地天通(Babel Blocks)》最终取得了1413名,放在三千多个参赛作品中也还是不错的!欢迎大家试玩我们的游戏:https://guinytime.itch.io/babel-blocks
在此我想对我在本次开发中负责的模块做一些复盘。UI部分已经是老生常谈了,我想聊聊我在开发中用到的观察者模式。
什么是观察者模式?观察者模式是一种行为型设计模式,定义了一种一对多的依赖关系,即当一个对象(称为发布者或被观察者)的状态发生变化时,所有依赖于它的对象(称为订阅者或观察者)都会自动收到通知并更新。
在游戏开发中,这种模式通常用于处理事件广播、状态更新等场景。例如,当玩家生命值减少时,游戏中的血条、UI、音效等观察者都会随之更新。
观察者模式的关键结构由以下三部分组成:
发布者(Subject):
管理观察者列表,提供添加、移除订阅者的方法。
当状态发生变化时,通知所有订阅者。
订阅者(Observer):
定义一个接口,所有观察者必须实现该接口中的更新方法。
当主题通知时,观察者根据需要更新自己的状态。
通知机制:
发布 ...
§2-2:基础纹理
基础概念
纹理映射(Texture Mapping)
纹理映射指的是将一张贴图映射在模型表面,逐纹素(texel)的控制模型的颜色。
纹理映射坐标(Texture-mapping Coordinates)
通过模型的纹理展开(展uv),我们可以获得一组纹理映射坐标,储存在每个顶点上。通常纹理映射坐标由(u, v)来表示,所以我们会简称纹理映射坐标为UV坐标。
不管贴图实际上有多大,UV坐标的范围通常被归于[0, 1]之间,并以纹理贴图的左下角为原点
纹理空间(Texture Image Sace)
纹理空间在纹理映射坐标的基础上乘以了纹理分辨率(如分辨率为256*256,新的uv坐标为(256u,256v))而得到的新坐标,用于寻找纹理贴图上对应的像素点的信息
纹理采样(Texture Sampleing)
我们获取纹理映射后对应纹理贴图像素点的过程
纹理管线的完整流程,可参考:https://zhuanlan.zhihu.com/p/393323667
Unity中的纹理属性
在向Unity导入纹理之后,检查器中可以对纹理进行一系列设置:
具体的设置见下:
纹理类型(Textu ...
§2-1:基础光照模型
光照模型分为两种:
一种是基于物理的光照模型(PBR),非常真实的反映了现实世界的光照,但是需要大量复杂的计算
另一种是经验光照模型(如兰伯特),用较为简单的方式模拟了物理光照,也能得到不错的效果
本章节主要介绍经验光照模型
基本概念
光源(Light Source)
光从光源中发射出来,照到物体表面。对于不同类型的光,需要设置不同种类的光源(例如影响全局的场景光、点光源以及投射灯等等)。
在Unity中,当我们选择创建光源时,有以下四种选项(反射球暂时先不考虑):
辐照度(Irradiance)
辐照度是衡量一个光源发出的光的多少使用的单位,用来量化光。
对于平行光,辐照度可以通过计算垂直于光源方向l的单位面积上单位时间内穿过的能量得到。
而存在一定夹角的时候,可以通过求光源方向l和法线之间的夹角的余弦值求得(就是投影到垂直平面的方向的分量)。
当光线间隔相同时,光与平面的夹角越大,单位面积内光的数量越少。
出射度(Exitance)
衡量出射光线的数量和方向的量,利用入射光线的数量和角度计算。和辐照度存在线性关系。
吸收和散射 ...
§1-4:光栅化/Rasterize
光栅(Raster)这个词最初来源于德语的“屏幕”一词,相当于英语中的Screen。那么光栅化的含义就比较明确了,指的是把图形从标准立方体(NDC)绘制到屏幕上的过程。
屏幕坐标系
我的笔记本电脑的主显示屏分辨率是1920*1080,代表长1920个像素,宽1080个像素,一共有1920*1080个像素。假设以屏幕左下角为原点建立坐标系,可以给每个像素一个坐标。但由于像素具有实际大小,每个像素的中心点坐标为(x+0.5,y+0.5)(x + 0.5, y + 0.5)(x+0.5,y+0.5)
从NDC到屏幕坐标系
屏幕映射(Screen Mapping)
NDC是一个长宽高范围均为[-1, 1]的标准立方体。在经过MVP矩阵变换与齐次除法之后,现在要做的就是把具有三维属性的NDC映射到只有二维属性的屏幕空间,怎么实现呢?
如果忽略z(深度信息),其实可以直接把NDC的x和y属性“拉伸”成屏幕空间的形状,使用视口变换矩阵:
M=(width200width20height20height200100001)M =
\begin{pmatrix}
\frac{width}{ ...
§1-3:坐标空间与MVP矩阵变换
从观察者的观察空间投影到屏幕空间
什么是MVP矩阵?MVP矩阵的三个字母指的是模型矩阵(Model)、观察矩阵(View)和投影矩阵(Projection)。回想一个模型从导出到最终呈现在屏幕上的过程,我们发现模型的顶点的坐标经历了这么些事:
在建模软件中,模型顶点的坐标是以模型中心为原点计算的
在把模型拖入场景之后,模型顶点的坐标是以世界中心为原点计算的
从摄像机出发观察模型
将模型投影到摄像机上,进行渲染,最终呈现在玩家眼前
实际上,这个变换就是使用了MVP矩阵。从模型空间的顶点,我们最终得到了投影在相机上的结果。再想想写shader的时候,顶点着色器往往包含以下语句:
12v2f o; // 传输到片元着色器o.pos = UnityObjectToClipPos(v.vertex); // 将顶点坐标从模型空间(Object)转换到裁剪空间(Clip)
我们知道,顶点着色器最基本的功能就是将顶点坐标从模型空间转换到裁剪空间。这里的UnityObjectToClipPos函数的作用即将顶点坐标乘上 ...
§1-2:数学基础
写shader对线性代数的要求还是挺高的w…毕竟基础原理和线代密切相关,主要是矩阵和矩阵变换那块的内容,现在来复习一下!
坐标系
为了给每个点一个确切的表示,我们规定一个坐标系。坐标系基于一个人为规定的原点,以及相互垂直的坐标轴。二维的笛卡尔坐标系属于小学生都会的范畴就不多提了,三维笛卡尔坐标系由于镜面对称(像手性碳一样)分为两种:左手系和右手系。
如何区分左手系和右手系?习惯上,我们用:
拇指表示x轴正方向
食指表示y轴正方向
中指表示z轴正方向
很明显,用左手和右手都可以获得一个三维笛卡尔坐标系,它们镜面对称从而不完全一样,于是我们把左手表示的坐标系称为左手系,右手的称为右手系。Unity和Blender使用的都是左手系。
那么这两种手系可以互相转换吗?答案当然是可以,只需要把左手系的一根坐标轴的方向反过来它就变成了右手系,对于右手系也是同理。这也是为什么从摄像机角度出发的观察空间(View Space)是右手系,因为摄像机出发的前向的z轴是负方向。
不同手系的正向旋转
不同手系的正向旋转完全不同,不过我们可以用手性法则来表示不同手系的正向旋转!
由 ...
§1-1:渲染流水线
我们总在说渲染管线,几乎每一个与TA有关的课程第一课都和渲染管线相关,这是一切图像的基础。就拿最近在用的Unity内置渲染管线为例吧,其中包含了CPU和GPU两个部分,总的流程图如下所示:
应用阶段/CPU处理阶段
应用阶段的处理场所在CPU。这一阶段主要处理以下几件事:
准备好场景数据
剔除不需要渲染的部分
设置渲染顺序
加载数据到显存
调用DrawCall
输出渲染图元(Rendering Primitives)作为几何阶段的输入。通俗来讲,渲染图元可以是点、线、三角面等,这些信息会传递给GPU渲染管线处理。
剔除(Culling)
CPU阶段的剔除是粗粒度的,和GPU阶段的裁剪不同。粗粒度剔除发生在物体层面,剔除是以物体1为单位进行的。这一步包含了三种剔除:
视锥体剔除
视锥体剔除指的是剔除掉相机视锥体之外的元素,视锥体即相机的可视范围,全部在外的物体(比如下图的鸟)不需要渲染,部分在外的物体会在GPU渲染部分被裁剪。
层级剔除
层级剔除指物体所处的层(Layer)是否被相机所渲染。如果相机的渲染层不包含该物体的layer,则该物体不被渲染。譬如在上文的例子中, ...