Done is better than perfect

0%

后处理是指,在正常渲染管线结束后,对渲染出来的结果进行加工,以此来模拟各种效果。

颜色

颜色(color) 对应电磁波的可见光波段,是被后期处理的波长信息。颜色既是物体的客观属性——确定的波长,又带有大脑的主观属性——不同的个体对特定波长的电磁波敏感程度不同,感受的颜色也有差异。 为了表示色彩,人们建立了一维、二维、三维甚至四维空间坐标模型,这些色彩模型称为颜色空间。颜色空间多达百种,常见的有如下5种。

颜色数据表示(Linear,LogC)

在影像制作和后期处理中,Linear、LogC和Gamma是三种关键的概念,它们描述了不同的图像数据处理和表示方法。理解它们之间的区别对于正确处理图像和视频数据非常重要。

Linear(线性): Linear指的是一种线性响应的色彩空间,其中记录的图像亮度值直接对应于场景中的实际光照强度。在线性色彩空间中,如果场景中一个区域的光照强度是另一个区域的两倍,那么记录的数值也会是两倍。这种表示方式使得图像的色彩混合和处理在数学上更加直接和简单,但由于人眼对亮度的感知是非线性的,线性空间通常不适用于最终图像的显示。

LogC(对数) LogC是ARRI摄影机特有的一种对数色彩空间,它旨在通过对数曲线来模拟人眼对亮度的非线性感知,使得在有限的比特深度下能够捕获更宽的动态范围。LogC色彩空间特别适合于记录高动态范围的场景,因为它能够有效地保留高光和阴影中的细节。然而,LogC图像在没有经过适当的色彩校正或应用LUT(查找表)之前,看起来会显得非常低饱和和低对比度。

Gamma Gamma校正是一种用于调整图像亮度的非线性操作,旨在使图像在特定显示设备上的显示更符合人眼的感知特性。Gamma校正可以被视为在图像数据和最终显示之间的一个桥梁,用于调整图像的整体亮度和对比度。不同的显示设备和媒体标准可能会使用不同的Gamma值,如sRGB标准使用大约2.2的Gamma值。

总结

Linear色彩空间: 最适合图像的处理和合成,需要在最终输出前转换到适合观看的色彩空间。

LogC色彩空间: 用于捕获和记录高动态范围的图像,需要在后期处理中进行色彩校正。

Gamma校正: 用于调整图像的显示,以符合人眼对亮度的非线性感知和特定显示设备的要求。

阅读全文 »

本文的目的是通过Shader代码实现Photoshop的图层混合模式,图层混合模式是将上图层中的颜色与下层图层中的颜色,通过一个公式计算出最总的颜色。

颜色混合公式

具体的公式如下:

混合模式 公式 说明
透明度 alpha * foregroundCol + bgCol * (1.0 - alpha) 实现Alpha混合
变暗 min(bgCol, foregroundCol) 上下图层中,取最小的颜色值
正片叠底 bgCol * foregroundCol 混合后颜色整体压暗,最常用的混合模式
颜色加深 1 - ((1-bgCol) / foregroundCol) -
线性加深 bgCol + foregroundCol - 1 -
变亮 max(bgCol, foregroundCol) 上下图层中,取最大的颜色值
滤色 1 - (1-bgCol) * (1-foregroundCol) 和正片叠底真好向反,两图层取反后再乘,再取反,也是比较常用的混合模式
颜色减淡 bgCol / (1-foregroundCol) -
线性减淡 bgCol + foregroundCol -
叠加 当bgCol<0.5时, 2(bgColforegroundCol); 当bgCol >= 0.5时, 1-2(1-bgCol)(1-foregroundCol) 当bgCol小于0.5时,使用2倍的正片叠底;当bgColr大于等于0.5时,使用2倍的滤色,使用2倍是为了当bgCol=0.5时不断层.
柔光 当foregroundCol<0.5时, 2bgColforegroundCol+pow(bgCol,2)(1-2foregroundCol); 当foregroundCol >= 0.5时, (2bgCol(1-foregroundCol)+sqrt(bgCol)(2foregroundCol - 1)) -
强光 当foregroundCol<0.5时, bgCol * (2foregroundCol); 当foregroundCol >= 0.5时, 1-((1-bgCol) (1-2*(foregroundCol - 0.5))) -
亮光 当foregroundCol<0.5时, 1-(1-bgCol)/(2foregroundCol); 当foregroundCol >= 0.5时, bgCol/(1-2(foregroundCol-0.5)) -
线性光 当foregroundCol<0.5时, bgCol+2foregroundCol-1; 当foregroundCol >= 0.5时, bgCol+2(foregroundCol-0.5) -
点光 当foregroundCol<0.5时, min(bgCol, 2foregroundCol); 当foregroundCol >= 0.5时, max(bgCol, 2(foregroundCol-0.5)) -
差值 abs(bgCol-foregroundCol) -
排除 0.5-2(bgCol-0.5)(foregroundCol-0.5) -
减去 bgCol-foregroundCol -
阅读全文 »

要在屏幕上绘制几何体时,Unity会调用底层的图形API的Draw命令进行绘制。一个Draw命令高数图形API绘制什么以及如何绘制。每个Draw命令都包含图形API所需的所有信息,其中包括texture,shader和buffers数据。绘图调用可能是资源密集型的,但绘图调用的准备工作通常比绘图调用本身更耗费资源。

准备绘制调用时,CPU去构建资源并通过图形API改变GPU的内部设置。这些设置统称为渲染状态。改变这些渲染状态通常时资源密集型的操作,比如切换不同的材质时。 因为渲染状态的改变是资源密集型的,所有减少渲染状态改变的次数是主要的优化方法,这有两种方法能到达此目的:

  • 减少总的绘制调用
  • 有效组织绘制调用,以减少渲染状态的切换。

优化绘制调用和渲染状态改变数量,主要减少每帧的时间,它也能:

  • 减少应用程序的电池的消耗。
  • 提高应用程序未来开发的可维护性。当你更早的优化绘制调用和渲染状态改变,那么它将长期维持在一个相对优化的级别。

Unity提供了如下几种优化绘制调用和渲染状态的方法,一些方法只适用于特定的场景。

  • Static Batching : 静态合并Mesh来减少绘制调用和渲染状态的改变。需要将对象标记为static。Unity将组合数据发送到GPU,但单独渲染组合中的每个网格。Unity仍然可以单独剔除网格,但每次绘制调用占用的资源较少,因为数据状态永远不会改变。(减少DrawCall)
  • Dynamic Batching : 在CPU动态上转换网格顶点,将相同配置的顶点分组,并在一次绘制调用中渲染它们。 比如顶点存储相同数量和类型的属性,则它们共享相同的配置。例如,位置和法线。 (减少DrawCall)
  • Manually combining meshes : 通过调用Mesh.CombineMeshes函数将多个Mesh合并为一个。 (减少DrawCall)
  • GPU Instancing : 渲染相同的mesh多次。GPU 实例化对于绘制在场景中多次出现的几何图形非常有用,例如树木或灌木丛。(减少DrawCall)
  • SRP Batcher : 在SRP项目中,可以使用SRP Batcher减少相同着色器变体的材质准备和绘制调用所需的CPU时间。 (不减少DrawCall,减少状态改变次数)

您可以在同一场景中使用多个绘制调用优化方法,但请注意,Unity会按特定顺序对绘制调用优化方法进行优先排序。如果您将一个游戏对象标记为使用多种绘制调用优化方法,Unity将使用优先级最高的方法。唯一的例外是SRP Batcher。当您使用SRP Batcher时,Unity还支持对与SRP Batcher兼容的游戏对象进行静态批处理。 Unity 按以下顺序对绘制调用优化进行优先排序:

  1. SRP Batcher and Static batching
  2. GPU Instancing
  3. Dynamic Batching

如果您将GameObject标记为静态批处理并且Unity成功对其进行了批处理,Unity会禁用该GameObject的GPU实例化,即使渲染器使用实例化着色器也是如此。发生这种情况时,Inspector 窗口显示一条警告消息,建议您禁用静态批处理。 同样,如果Unity可以对网格使用GPU实例化,Unity会禁用该网格的动态批处理。

阅读全文 »

通用渲染管线(URP)是Unity官方创建的可编程渲染管线(SRP)。URP提供了艺术家友好的工作流程,能够快速且简单创建跨平台的图形,从移动端到高端控制台和PC。SRP的内容可以参见:Unity渲染管线介绍

Unity将渲染管线的代码分为了几个包,分别如下:

  • com.unity.render-pipelines.core 此包包含了一些公共的可重用代码,URP和HDRP都会使用此包。
  • com.unity.render-pipelines.high-definition-config HDRP配置包。
  • com.unity.render-pipelines.high-definition HDRP的核心包。
  • com.unity.render-pipelines.universal URP的核心包。

应用阶段

从名字我们可以看出,这个阶段是由我们的应用主导的,因此通常由CPU负责实现。换句话说,我们这些开发者具有这个阶段的绝对控制权。 在这阶段中,开发者有3个主要任务:

  1. 我们要准备好场景数据,例如摄像机的位置、视椎体、场景中包含了哪些模型、使用了哪些光源等等;
  2. 为了渲染性能,我们往往需要做一个粗粒度剔除(culling)工作,以把哪些不可见的物体踢出去,这样就不需要再移交给几何阶段进行处理;
  3. 最后我们要设置好每个模型的渲染状态,这些渲染状态包括但不限于它们使用的材质(漫反射颜色、高光反色颜色)、使用的纹理、使用的Shader等。

这一阶段最重要的输出是渲染所需的几何信息,即渲染图元(rendering primitives)。通俗来讲,渲染图元可以是点、线、三角面等。这些渲染图元将会被传递给下一个阶段——几何阶段。

阅读全文 »

在开始学习ShaderLab之前,我们先简单的了解一下着色器语言。着色器语言主要分为离线渲染时使用的着色器语言和实时渲染中使用的着色器语言。

离线作色器语言

  • RenderMan 着色语言(RSL)
  • Houdini VEX 着色语言
  • Gelato 着色语言
  • 开放着色器编程语言(OSL)

实时着色器语言

  • ARB汇编语言(英语:ARB assembly language)
  • OpenGL 着色语言(GLSL)
  • Cg语言
  • DirectX 着色器汇编语言
  • DirectX 高级着色器语言(HLSL)
  • Metal 着色语言

ShaderLab与以上的作色语言有什么区别呢?ShaderLab是Unity定义的一个Shader描述性语言,不能直接在对应图形平台运行,并且是夸平台。为了更好的理解它们的区别,先来看一下OpenGL是如何通过GLSL着色语言对一个三角形进行着色的。

阅读全文 »

文件结构

Lua文件结构

提供外部使用的头文件

  • lua.h 定义了Lua的核心API。
  • lauxlib.h 定义了Lua的辅助API,此文件中定义的函数,是对lua.h中函数的简单封装,方便使用。
  • lualib.h 定义了Lua的标准库(coroutine, table, io, os, string, utf8, math, debug, package)。
  • luaconf.h 定义编译相关的宏,主要用于区分别不同平台的特性以及一些其他编译特性。

核心代码

Lua的核心代码主要分为以下几个模块:

核心API

  • lapi.h 定义了几个栈的操作宏(api_incr_top:栈顶加一, adjustresults:调节栈顶以容纳更多的返回值, api_checknelems:检查栈中是否有对应的栈空间)。
  • lapi.c lua.h头文件中定义核心API的实现。
  • lctype.h C语言的标准字符检查函数头文件,主要检查单个字符的类型,增加了EOZ,允许传入-1(EOZ)进行检测。
  • lctype.c 定义了符号表。
  • ldo.c 处理Lua的栈和调用结构。
  • lfunc.c 定义了用于操纵函数原型和闭包的辅助函数。
  • llimits.c 定义了一些基础的类型和安装依赖信息。
  • lmen.c 内存管理接口。
  • lobject.h Lua中的对象类型定义。
  • lobject.c 对象上的一些操作。
  • lstate.h 定义了lua_State相关的结构,这是Lua的核心结构。
  • lstate.c lstate.h中定义结构的相关操作。
  • lstring.c Lua字符串表,处理和缓存Lua中使用的字符串。
  • ltable.c 定义了Lua中Tale相关的操作。
  • ltm.c 元表相关的定义和操作。
  • ldump.c 将Lua函数转储为预编译块。
  • lundump.c 加载预编译的Lua块。
  • lzio.c 定义了一个buffer流,用于读取数据。

GC管理

  • lgc.c 垃圾回收器

词法,语法和语义分析器

  • llex.c 词法分析器。
  • lparser.h 语法和语义分析器。

Lua虚拟机(解释器)

  • lopcodes.h 定义了Lua虚拟机的操作码(OP_MOVE, OP_LOADI等)跟汇编的MOV指令类型,虚拟机就是用来执行这些指令的。
  • lopnames.h 定义了操作码对应的名字。
  • ljumptab.h 定义了Lua解释器跳转表相关的宏(vmdispatch, vmcase, vmbreak)。
  • lvm.c Lua的虚拟机用于执行opcode。

调试

  • ldebug.c 获取调试信息(函数调用, 行号信息等)。
阅读全文 »

这篇文章覆盖了制作六边形地图所涉及到相关的方法,通用公式和算法。为了文章更加优雅简洁,文中的实例都将使用伪代码进行描述。

几何性质

六边形是由六个边组成的。正六边形的每条边长度相同。我们这里假设所有的六边形都是正六边形。六边形的方向有两个: 1. 垂直列方向(六边形的任意两条平行边在垂直方向上) 垂直方向

  1. 水平行反向(六边形的任意两条平行边在水平方向上) 水平方向

六边形有6个边和6个角。每个边被两个六边形共享。每个角被三个六边形共享。更多关于中心点(centers), 边(sides), 和 角(corners)可以参见这篇文章(四边形,六边性和三角形)

阅读全文 »

什么渲染管线

渲染管线跟工厂的流水线一样,工厂的流水线为了生成最终的一个产品,每个流水线的工人只干一件特定的事情,最后将一个产品完成。计算机的图形渲染管线和工厂的流水线一样,只是图形渲染管线最终生成的是一张二维图片。 我们将一个渲染流程分成3个阶段:应用阶段几何阶段光栅化阶段。注意这仅仅是概念性阶段,每个阶段本身通常也是一个流水线系统,即包含了子流水线阶段。如下图: 渲染流水线三个概念图

  • 应用阶段

从名字我们可以看出,这个阶段是由我们的应用主导的,因此通常由CPU负责实现。换句话说,我们这些开发者具有这个阶段的绝对控制权。 在这阶段中,开发者有3个主要任务: 1. 我们要准备好场景数据,例如摄像机的位置、视椎体、场景中包含了哪些模型、使用了哪些光源等等; 2. 为了渲染性能,我们往往需要做一个粗粒度剔除(culling)工作,以把哪些不可见的物体踢出去,这样就不需要再移交给几何阶段进行处理; 3. 最后我们要设置好每个模型的渲染状态,这些渲染状态包括但不限于它们使用的材质(漫反射颜色、高光反色颜色)、使用的纹理、使用的Shader等。 这一阶段最重要的输出是渲染所需的几何信息,即渲染图元(rendering primitives)。通俗来讲,渲染图元可以是点、线、三角面等。这些渲染图元将会被传递给下一个阶段——几何阶段。

  • 几何阶段

几何阶段用于处理所有和我们要绘制的几何相关的事情。例如,决定需要绘制的图元是什么,怎么绘制它们,在哪里绘制它们。这一阶段通常在GPU上进行。 几何阶段负责和每个渲染图元打交道,进行逐顶点、逐多边形的操作。这个阶段可以进一步分成更小的流水阶段,这在下一章中会讲到。几何阶段的一个重要任务就是把顶点坐标变换到屏幕空间中,再交给光栅器进行处理。通过对输入的渲染图元进行多步处理后,这一阶段将会输出屏幕空间的二维顶点坐标、每个顶点对应的深度值,着色相等相关信息**,并传递给下一个阶段。

  • 光栅化阶段

这一阶段将会使用上个阶段传递的数据来生成屏幕上的像素,并渲染出最终的图像。这个阶段也是在GPU上运行。光栅化的任务主要是决定每个渲染图元中的哪些像素应该被绘制在屏幕上。它需要对上一个阶段得到的逐顶点数据(例如纹理坐标,顶点颜色等)进行插值,然后在进行逐像素处理。和上一个阶段相似,光栅化阶段也可以分成更小的流水线阶段。

阅读全文 »

Canvas的渲染

渲染基本层次

基本渲染层次是根据可视化对象在继承窗口中的显示顺序来渲染的,在此有两种方式可以调节对象的顺序。第一种:直接在继承窗口拖拽。第二种:通过Transform的SetAsFirstSibling, SetAsLastSibling, and SetSiblingIndex函数来进行调节。

渲染模式

  • 屏幕覆盖模式

直接屏幕映射方式显示,永远在屏幕的最上面,跟相机无关即使没有相机也可以显示

  • 屏幕相机模式

此模式和屏幕模式相似但是受到相机的限制,但是始终都显示在相机的前面,如果有3D物体在UI的前面也是会挡住UI。

  • 世界模式

把Canvas当成普通的对象在世界坐标系中定位渲染。

阅读全文 »

基础回顾

在正式分析UGUI源码前,让我们回顾一下,模型的组成,以及渲染。

模型组成

模型是由一系列的三角形组成,三角形又是由三个顶点构成的,每个顶点上保存了相关的顶点属性(顶点位置,顶点颜色,顶点UV,顶点法线,顶点切线)在Unity中使用Mesh类来表示。

模型渲染

模型筛选:

  1. 首先根据相机的Culling Mask剔除那些不需要此相机渲染的模型。
  2. 然后根据摄像机的视锥体大小,剔除完全在视锥体外的模型。

模型分类:

Unity将根据需要渲染的模型Shader中RenderQueue对模型进行分类,分为不透明模型(小于2500)和透明模型(大于等于2500)。

排序渲染模型:

  1. 首先通过sortLayer和sortingOrder对不透明模型进行进行排序,越低的越先渲染;然后再通过Shader中的RenderQueue进行排序;再然后就通过包围盒中心点的深度(距离摄像机的位置)进行排序;最后如果深度相同则还可以通过ZBias进行深度调节。
  2. 透明物体同样的使用上述规则进行排序。
阅读全文 »