UnityShader[1]光照模型

计算机图形学介绍

计算机图形学(ComputerGraphics,CG)是使用数学算法,将二维/三维图形转化为计算机显示设备的栅格形式的学科。

二维/三维图形:贴图,模型等 显示设备的栅格:显示器上的一个个像素

计算机图形学研究的内容是将结构特征变为图像信号

在这里插入图片描述

平直的横线经过旋转后发生走样:

![](https://img-blog.csdnimg.cn/675d12518fe046eb9557f077f25010e7.png)
在这里插入图片描述
从图像信号变为结构特征,还有另外一个学科名为计算机视觉,与计算机图形学相反。计算机视觉的主要功用:人脸识别,车牌识别等。

图像信号的改变:数字图像处理,数字图像处理的主要功用:Ps、滤镜等。


计算机图形学的主要研究内容

建模(Modeling)、渲染(Rendering)、动画(Animation)、人机交互

建模:用点线面来表达一个物体的几何形态。(传统3D建模/雕刻软件(次世代)/扫描建模/程序化建模(类似SubstanceDesigner程序化贴图))

渲染:将图形数据利用数学算法转换成3D空间图像的操作,分为离线渲染和实时渲染

离线渲染:为达到画面真实,不计渲染成本,不考虑流畅性的复杂渲染,一般渲染时间较长(一般用于CG电影)

实时渲染:要保证图形数据的实时计算和输出,画面帧率要流畅,因此必须在画质上妥协。

动画:

序列帧动画:每一帧都需要绘制的帧动画

蒙皮动画:在建模软件中,使用骨骼蒙皮,创造骨骼动画

视效模拟:烟雾,爆炸,落雨,流体等视觉特效

计算机图形学的应用:

电子游戏、CG电影、动画、计算机辅助设计(CAD)等。


图形显示系统

计算机图形学将二维/三维图形经过一系列渲染管线转换为显示器上的栅格显示。渲染好的栅格画面作为一组数据被显示器读取,或被帧缓存读取。帧缓存存储了每一帧的画面信息,经由显示控制器,投放到显示器上。

早期的帧缓存是存放在内存中的,这使得显示控制器想要获取帧缓存中的帧信息就需要经过总线,导致画面加载变慢。而现在,在系统总线中加入图形显示处理器(显卡),将帧缓存存放到图形显示处理器中,并且进行显示处理,提高了渲染效率。

现在的图形显示,一般首先从硬盘中读取渲染所需信息加载到内存(RAM),然后一些数据又被加载到显卡的存储单元,即显存(VRAM)。大多数显卡对内存没有直接访问权力。

GPU:显卡的心脏

GPU(GraphicalProgressingUnit),又称显卡核心、显示芯片,是一种在移动设备上进行图像运算的微处理器。GPU的核处理器多,适合进行浮点运算和并行运算。但是管理控制能力较弱,功耗大。是大体上与CPU相反的处理器。一般的显卡商:因特尔集成显卡芯片,NVIDIA,AMD。

对显卡的了解可以优化Shader和图形处理的工作流程。


可编程渲染流水线

早期的图形编程是开发人员直接对底层的GPU进行操作,然后显示到图形设备上的。这样的图形编程虽然对GPU的可控制性更好,但是费时费力,而且因为面向硬件编程,它的跨平台性不好。

固定功能渲染流水线:
在这里插入图片描述
标准图形函数库中封装了很多固定的算法。这种固定功能渲染流水线简单方便,可以跨平台(与硬件无关),但是控制权限低(因为从硬件函数库中抽象,封装出来,不能编写)。
在这里插入图片描述
可编程渲染流水线:

使用编程功能干涉标准图形函数库。这种可编程渲染流水线简单方便,可以跨平台(与硬件无关),控制权限高(编程)

在这里插入图片描述

图形学流水线

流水线:一种工业生产方式,每个生产单位只专注处理某一部分工作,快速稳固的完成总体工作,以提高工作效率与产量。

左图:一般生产方式 右图:流水线生产方式。同样的生产周期内,流水线生产比一般生产多生产了一倍的产品。

在这里插入图片描述

渲染流水线/渲染管线:通过引擎内部的算法,将三维场景中的物件展现到显示设备上成为二维画面的过程

主要功能:在给定相机、三维物体、光源、材质等诸多条件下,生成一幅二维图像


渲染管线的三个阶段

应用程序阶段Application 几何阶段Geometry 光栅化阶段Rasterizer

在这里插入图片描述

应用程序阶段(不可操控)

应用程序阶段的最主要目的有三个:

1,将渲染所需要的场景数据准备好,如顶点,模型,光照,材质;然后交给显存。

2,为了节省性能,可能还需要进行粗粒度剔除(culling)操作,将不可见的物体剔除出去不做渲染。

3,设置每个模型的渲染状态。如shader、纹理、贴图。

应用程序阶段最主要的输出是渲染所需的全部几何信息,即渲染图元,渲染图元可以是点、线、三角面等,会被输出给下一阶段。


几何阶段

几何阶段往往在GPU上进行,对每个顶点、多边形进行操作,几何阶段的重要目标就是把顶点坐标变换到屏幕空间中,交给光栅化处理。

几何阶段输出每个顶点在屏幕空间的二维坐标,以及它们的深度值、着色等相关信息给下一阶段。

顶点着色(可操控):对模型顶点进行颜色赋予,空间变换(模型变换视图变换)后,输出到下一阶段

​ 模型变换:通过对模型进行平移,缩放,旋转,镜像,错切等操作来调整模型的过程,以合理指定场景中模型的位置等信息

​ 视图变换:在场景中放置摄像机的过程,调整拍摄的位置,拍摄角度,焦距,拍摄

几何曲面细分(可选)(可操控):为得到更加光滑的模型,而进行顶点的增加(细分图元),如果API不支持,这一步可以移到应用程序阶段,即建模时建成高模

裁剪(可配置):通过已经通过视图变换处理好的顶点信息,放入裁剪空间,进行投影变换,将所有顶点转入一个规范化的空间内,方便下一步操作

​ 投影变换:将图片投影到一个新的视平面的过程

屏幕映射(不可操控):将裁剪得到的图形映射到屏幕,将几何阶段的结果输出到下阶段


光栅化阶段

光栅化阶段使用上一阶段传递的数据产生屏幕上的像素,并渲染最终图像,往往在GPU上进行。它需要对上一阶段得到的逐顶点数据进行插值运算,再进行逐像素处理。

三角形设置(不可操控):通过顶点着色得到的信息,将所有顶点拼接成一个个面片,供光栅化使用

三角形遍历(不可操控):遍历每个像素点,判断是否为三角形面片的所占位置,如果是,则存储,不是则剔除

片断着色/片元着色器(可选)(可操控):对遍历出来的每一个像素进行着色,可以自己定义不同的算法对此进行操控

合并/逐片元操作(可配置):某物体的某像素可能被另一物体遮挡或穿透(玻璃、滤镜),所以需要进行Alpha测试、模板测试、深度测试等一系列复杂操作,决定片元的可见性,通过所有测试的片元会被提取颜色信息,与已经存放到颜色缓冲区的颜色进行混合


*渲染流程

一般的渲染过程为: 模型->输入数据结构->顶点Shader->输出数据结构->像素Shader->渲染输出

输入数据结构 的过程:

主要功能为数据的选取与采集.下图为一般obj格式的模型模型文件内的数据结构

在这里插入图片描述
模型数据文件中包含的基本信息:

顶点的坐标信息(v开头,三维变量,意为XYZ三轴向坐标,顶点编号为代码顺序从上到下)

三角面信息(f开头,三维变量,意为构成三角面的三个顶点的编号,)

0 Polygons - 12 triangles 代表0个四边面,12个三角面.不使用四边面的原因:

在这里插入图片描述

描述一个四边面需要四个三维变量,而描述一个三角面只需要一个三维变量

额外可能包含的信息:UV信息,顶点色信息,法线的向量信息…

这些信息经过输入数据结构的转换后,变为各种结构体存储到程序中,供后续渲染使用

相当于渲染管线的应用程序阶段


顶点Shader 的过程:

1,将模型的顶点信息转变为该顶点对应显示器屏幕中的位置信息(透视理论).

2,计算并赋予顶点的其他信息,如UV,顶点色,法线.

在代码中具体的表现形式为InputShader结构体经过顶点Shader后变为OutputShader的过程.

相当于渲染管线的几何阶段


输出数据结构 的过程:

将透视变换后的几何体构成信息推广至块面,这个过程主要是三角形顶点的方向信息推广到整个三角形块面上。三个相邻顶点的方向一致,说明这三个顶点构成一个三角形面,且这个三角形的朝向与这三个顶点的朝向相同


像素Shader 的过程:

将得到的输出数据结构信息,给定的材质信息,光照信息,摄像机信息整合后,通过在显示器上逐像素扫描的方式,将图像以它该有的颜色表达到它该出现的像素上.(深入刻画每一个块面)

相当于渲染管线的光栅化阶段


坐标空间与变换

渲染游戏的过程相当于把一个个顶点经过层层处理最终转换到屏幕上的过程。换句话说,顶点经过许多坐标空间最终被渲染到屏幕上。何为坐标空间?在Unity中,被挂载到Hierarchy下的对象都是主要以世界空间(WorldSpace)为坐标空间的,而被挂载到这些对象上的子对象,都是主要以其父对象为坐标空间的。坐标空间会形成一个层次结构,每个坐标空间都是另一个坐标空间的子空间,每个坐标空间都有个父坐标空间,对坐标空间的变换实际上是在父空间和子空间之间对点和矢量进行变换,这其中涉及到矩阵运算。我们需要在不同情况下使用不同坐标空间,因为一些概念只在特定坐标空间下才有意义。坐标空间的变换贯穿整个图形学流水线的几何阶段。

模型空间

模型空间(ModelSpace)也被叫做对象空间(ObjectSpace)、局部空间(LocalSpace)。每个游戏对象都有自己的模型空间,当它移动或旋转的时候,模型空间也会随着它移动旋转。在Unity中,模型普遍使用左手坐标系,即+x为右、+y为上、+z为前。而前后左右上下这些方向代名词一般被称为自然方向。在模型制作时,如果成果需要导入Unity中,一般会约定将z轴正方向作为模型的前方向导出模型

此外,制作模型的过程也包括规定模型上每个顶点在模型空间中的坐标位置、规定模型原点位置和三轴向

世界空间

世界空间(WorldSpace)是Unity中的最大空间,是最外层的坐标系。世界空间可以用来描述绝对位置。一个物体的Transform组件中的PRS属性都基于它的父节点,如果这个物体没有父节点,那么Transform组件中的PRS属性代表该物体在世界空间下的PRS属性

顶点着色(顶点变换)的第一步就是将顶点坐标从模型空间变换到世界空间中,这个变换通常被称为模型变换(ModelTransform)。即模型空间中每个顶点的坐标信息和世界空间中该模型空间的坐标信息,通过矩阵运算得到每个顶点在世界空间中的坐标信息

观察空间

观察空间(ViewSpace)指的是摄像机的模型空间,属于模型空间中比较特殊的一种。观察空间特殊在它的+z方向为后方,即唯独观察空间使用了右手坐标系,这是为了符合OpenGL传统。这样的差异一般不会对编程有影响。

观察空间与屏幕空间是不同的,观察空间是一个三维空间,而屏幕空间是一个二维空间,从观察空间到屏幕空间的转换需要经过投影操作。

顶点着色(顶点变换)的第二部就是将顶点坐标从世界空间变换到观察空间,这个变换被称为观察变换(ViewTransform也叫视图变换),即世界空间中每个顶点的坐标信息,通过矩阵运算得到每个顶点在观察空间中的坐标信息

裁剪空间

在观察变换后,顶点需要从观察空间变换到裁剪空间(ClipSpace齐次裁剪空间)中,用于变换的矩阵被称为裁剪矩阵(ClipMatrix)或投影矩阵(ProjectionMatrix)。裁剪空间的目的是为了方便对渲染图元(面片)进裁剪,这块空间是由视锥体确定的,完全位于这块空间外部的图元会被剔除,完全位于其内部的图元会被保留,而与这块空间相交的图元会被裁剪

视锥体的6块平面决定了裁剪空间的大小,而有两块裁剪平面比较特殊:近裁剪平面和远裁剪平面。它们决定了摄像机可以看到的深度范围,下图的立方体超出远裁剪平面外的部分被裁剪掉了。

对于视锥体来说,判断一个点是否在视锥体内部很麻烦,更加简洁的办法是:使用投影矩阵把顶点转换到裁剪空间中。经过投影矩阵变换后,顶点坐标xyzw的w分量有确定范围的作用。在投影矩阵前,顶点的w为1,矢量的w为0。投影矩阵后,w有决定该顶点是否在裁剪空间外的作用,如果顶点的x、y、z都在这个范围内,说明该顶点位于裁剪空间内。

在Unity中,六个裁剪平面由Camera组件中的参数和视图的纵横比确定,我们可以通过Camera组件中的Projection确定摄像机使用透视视图还是正交视图,FOV(FieldOfView)属性改变视锥体的纵向角度,ClippingPanes决定视锥体的近裁剪平面和远裁剪平面的距离。而视锥体的横向信息由视图的纵横比与ViewPortRect共同决定。

如果一个顶点在视锥体内,它变换后的坐标必须满足:-w <= x <= w;-w <= y <= w;-w <= z <= w

屏幕空间

在完成投影矩阵变换、且完成裁剪操作后,就需要进行真正的投影。需要将视锥体投影到屏幕空间(ScreenSpace)中,经过这一变换后,我们就能得到真正的像素位置。

首先,我们需要进行标准齐次除法/透视除法,就是用齐次坐标系的w分量除以xyz分量,在OpenGL中,这个过程得到的坐标位置被称为归一化的设备坐标(NDC)。经过透视投影的裁剪空间坐标,继续经过齐次除法后会变换到一个立方体内,在OpenGL中这个立方体的范围是[-1,1],在DirectX中这个立方体的范围是[0,1]。可以根据变换后的xy坐标来映射到输出窗口的对应像素坐标上。


何为BRDF

假设一束激光照射在桌面上,当人移动观察位置时,桌上激光的成像亮度会发生变换。观察位置不动,转而移动激光方向,成像亮度同样会发生变化。光方向和视线方向都不动,只移动桌子倾斜角度,成像亮度也会发生变化。也就是说桌面对于不同的入射角和反射角的组合,拥有不同的反射率,这个反射率决定了成像的亮度。

为了在计算机中还原这个现象,出现了BRDF(bidirectional reflectance distribution function 双向反射分布函数)。该函数需要外部给定的参数有三项:vDir(视线方向),lDir(光线方向),nDir(物体表面法线方向)。拥有这三项数据后,可以计算得出当前入射角和反射角组合下的反射率。每当我们移动上述三个参数时,反射率都会随之改变。BRDF是所有光照模型的理论基础。诸如金属度、粗糙度这种对光照模型进行微调的参数不会修改物体的nDir,只是在其上对反射率进行二次调整,如反射率的密集程度、扩散广度、整体亮度等。


UnityShader编程

概念一览

可编程渲染流水线中的标准图形函数库:OpenGL、DirectX

OpenGL是在驱动层之上提供跨平台的图形标准API,高版本的OpenGL,如果想要向下兼容,需要考虑语法,API上的支持与否。

DirectX(DirectEXtension简称DX)是由微软创建的多媒体编程接口。

UnityShader的编程要依赖以上两种API进行。

GLSL:OpenGL着色器编程语言(OpenGLShaderingLanguage),用来在OpenGL中编写着色器的语言,是一种具有C/C++风格的高级语言

HLSL:高级着色器语言(HighLevelShaderLanguage),由微软拥有及开发的一种着色器语言,主要用于Direct3D,与OpenGL标准不兼容

Cg:CforGraphic,Cg是由NVIDIA与微软相互协作开发的一种高级着色器语言,与HLSL非常相似

ShaderLab:Unity中编写着色器的一种语言,是Unity在图形标准API的上一层再封装一层的标准语言。在 CGPROGRAM 与 ENDCG 之间可以使用 HLSL/Cg 编写。

计算机图形学:一门研究通过计算机显示二维/三维图形的学科

GPU:用于渲染二维画面的硬件

OpenGL/DirectX:与显卡驱动交互的图形标准API函数库

Unity:用于编写游戏客户端的引擎软件

Shaderlab:Unity中的Shader编程语言,是OpenGL/DirectX图形标准API的进一步封装

Unity中编写Shaderlab来通过OpenGL/DirectX告诉显卡驱动要做什么、怎么做,然后显卡驱动指挥GPU进行计算并输出到显示器


Shader前准备

Shader(着色器)是用来渲染图形的一种技术,通过shader,可以自定义显卡渲染画面的算法,使画面达到想要的效果。Shader是一段代码,用于告诉GPU如何绘制每个顶点的颜色和屏幕上每个像素显示的颜色

shader编程的两种方法:

代码编写:自由灵活,功能强大,性能可控,但上手困难。

可视化节点编辑:容易上手,无需代码,能快速出效果,但功能有限,性能很难最优。

可视化节点编辑的软件:

ShaderForge 已停止更新 最后支持2019版本

AmplifyShaderEditor ASE倾向于表面着色器

ShaderGraph Unity2018后内置的Shader编辑器,只支持自定义渲染管线(SRP)

Shaderlab的几种形式:

固定管线着色器(FixedFunctionShader) 对应固定功能渲染流水线 淘汰

表面着色器(SurfaceShader) 代码简洁 倾向于人类思维(颜色-法线-透明度) 来自于对图形标准API的封装,底层是顶点片断着色器

顶点片断着色器(Vertex/FragmentShader) Unity中最根本的着色器形式 功能最强大 分为顶点着色器和片断着色器

来自Shaderlab的模板

StandardSurfaceShader 表面着色器模板

UnlitShader 无光照效果着色器模板 用于在此之上添加光照效果 底层是顶点片断着色器

ImageEffectShader 后处理着色器模板 用于屏幕后处理(整体调色,bloom等)

ComputeShader GPU着色器模板 独立于普通渲染管线外 通常用于大量的并行计算

RayTracingShader 光线追踪着色器模板 暂不了解

模板只是初始代码,本质相同

编程环境IDE:VSCode

编写Shader推荐的Unity视图方式:

漫反射与镜面反射

初中物理学过,光线与物体表面发生碰撞会发生反射,反射方向为:物体表面方向为法线,入射方向的对向。而由于光源大小的原因,向物体表面发射的平行光线肯定不止一条,此时反射光就会根据物体表面的粗糙程度改变效果。

通常情况下如果物体极度粗糙(如石膏像、投影屏),则反射光为漫反射(Emission)。漫无目的、四面八方、均匀散射

如果物体极度光滑(如镜子,车漆),则反射光为镜面反射(Specular)。有目的,镜面方向,不均匀散射

]

Unity图形渲染中常用的向量及其通用称呼:

lDir:光方向。表示物体表面指向光源的方向。简称l。

nDir:法线方向。表示物体表面的垂直方向。简称n。

vDir:视线方向。表示物体表面指向摄像机的方向。简称v。

rDir:反射方向。表示光线碰撞到物体表面发生反射的方向。简称r。

hDir:半角方向。lDir和vDir的中间角方向。简称h。h代表Halfway。

Unity中的空间坐标系及其通用称呼:

OS:ObjectSpace 物体空间,本地空间。

WS:WorldSpace 世界空间。

VS:ViewSpace 观察空间。

CS:HomogenousClipSpace 齐次裁剪空间。

TS:TangentSpace 切线空间。

TXS:TextureSpace 纹理空间。

一般情况下,nDirWS代表世界空间下的法线方向

在UnityShader中,漫反射的实现方式为Lambert光照模型。不谈论光的强度问题,lDir与nDir夹角越小,物体表面的漫反射强度越大。因为物体表面的漫反射强度不随vDir的变化而变化,所以无论怎么观察,在光方向不变的前提下物体表面的指定位置漫反射强度都是不变的。

镜面反射的实现方式为Phone光照模型和Blinn-Phone光照模型。在镜面反射中,观察者的视角决定了反射光线的有无以及明暗,Phone光照模型:rDir与vDir夹角越小,物体表面的指定位置镜面反射强度越大(实际上是镜头承接到的镜面反射光越多)。且vDir必须在rDir的圆锥形范围内才能观测到镜面反射。Blinn-Phone光照模型:nDir与hDir夹角越小,物体表面的镜面反射强度越大(镜面反射光线最强的时候,rDir会与vDir重合,此时lDir与rDir的中线hDir刚好会与nDir相重合)。


了解光在模型上的成色原理

两种数据类型:

标量(Scalar),只有大小没有方向的量,如重量体积等;

向量(Vector),既有大小也有方向的量,如速度和力等;

向量的运算方法:

点乘/点积/Dot:两向量相运算,结果为标量,意为一个向量在另一个向量上的投影长度。图形学中,定义两向量点乘结果为+1时,两个向量方向相同。点乘结果为0时,两个向量方向相互垂直。点乘结果为-1时,两向量方向相反。

所以默认Shader下在计算光时,Shader先获取模型网格的法线方向(nDir,NormanDirection),光照方向的反方向(lDir,LightDirection);将法线方向与光源方向的反方向(保证结果为+1的位置是最亮的)进行点乘运算(nDir * lDir),结果为:法线方向与光源方向相反的地方最亮,相互垂直的地方最暗。

如下图,模型最亮部分点乘结果为+1,模型最暗部分点乘结果为0~-1。

负数是无意义的亮度,所以将所有负数的值都改为0。

计算方法为:对得到的点乘结果进行Max(0,nDir * lDir)运算,让所有负值结果变成0,非负值结果还是原结果

兰伯特光照模型(Lambert):


但是显然这样的光照模型有多一半的面积是暗色的,一半的面积是纯黑色的。虽然具有真实感,但是却过于单调。所以有了半兰伯特光照模型

计算方法为:对得到的点乘结果进行 [(nDir * lDir) * 0.5 + 0.5] 运算,让-1变成0,0变成+0.5

这样的模型比原来的模型过渡更平缓,色彩更丰富,增加了透气感。


ShaderForge插件基础

前往GitHub获取ShaderForge工程文件,最高支持版本为Unity2019,下载后打开:

本质上相当于一个Unity项目文件,但是在已经有现成项目的情况下不需要全部使用.打开Assets文件夹将ShaderForge包拖进Unity项目文件中,此时可以在顶层栏中的Tools中直接找到ShaderForge工具.

New Shader新建无光照Shader(Unlit),这些选项也只是模板

右键创建好的Shader,新建一个Material,该Material使用的Shader就是新建好的Shader.将它挂载到场景中新建的球上,现在场景中的材质球与Shader Forge中显示的一样了.

通过这种方式,可以将Shader Forge制作的Shader实时显示在场景中的一个物件的材质上,也反映了Shader的最终目的就是调整出一个符合需求的材质.

在ShaderForge的官网有节点文档:https://acegikmo.com/shaderforge/nodes/


ShaderForge实现光照模型

默认情况下ShaderForge中,空Shader的情况:

视图中有两个节点,一个连线,分别代表一个颜色节点,主材质节点。它们之间的连线用于规定主材质当前显示该颜色。

在ShaderSetting中,可以在Path中设置该Shader的路径,用于分组。这个路径与Unity显示界面中的路径是不相同的,仅用来分组,而Unity中的路径为实际资源保存路径。在新建一个Shader时,应首先对Shader进行分组。

[\image-20211227101528100.png)]

对Shader的分组会体现在制作材质时,方便进行分组筛选:

右键视图中的任意位置可以弹出创建新节点的对话框,可以按输出类型筛选需要的节点。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-akHgXK4w-1651394089569)(G:\TyporaImage\UnityShader学习笔记\image-20211226110622800.png)]

进入VectorOperation(向量运算) / DotProduct(点乘运算),在视图中加入该节点。节点的左半边为输入接口,用来从其他节点接收参数;右半边为输出接口,用于将节点处理过的数据进行输出(return)。节点在刚被创建时,必须被赋值的输入接口会被打上感叹号,意为该接口为必填项。Dot节点用于输入两个向量信息,输出它们的点乘积信息。

]

计算光照模型需要的两个向量,是法线向量和光照向量,右键新建节点GeometryData(几何体参数)/NormalDir.(法线向量);Lighting(光照)/LightDir.(光照向量)。进行连接后:

[]

Emission接口代表模型的自发光属性,代表了模型最终会发出什么颜色的像素;如果在默认情况下,蓝色Color接口接入,模型的每个像素发出的颜色都是蓝色。

此时被渲染出来的模型:

[]


之后进行兰伯特光照模型化操作:

新建节点Arithmetic/Clamp 01,用于将输入的值规定在01范围内,小于0的部分赋值为0,大于1的部分赋值为1。连接在Dot与Emission之间。输出结果与原先一样,这是因为默认情况下点积结果小于0处显示亮度本来就为0

[]


进行半兰伯特光照模型化操作:

根据之前推导的公式,需要对点积结果进行 [(nDir * lDir) * 0.5 + 0.5] ,此时需要Multiply节点和Add节点,并且需要一个静态值 0.5 ,用来参与乘法加法运算。在ConstantVectors(静态参数)组中可以找到各种数据结构的静态值,包括三维变量,四维变量,使用Value规定一个静态值,得到一个半兰伯特光照模型:

[]


镜面反射光照模型

Phone光照模型的实现:首先Phone光照模型的成像原理:光与物体表面发生碰撞,从碰撞点向反射光方向散射出一系列光线,如果反射光方向rDir与视线方向vDir越接近,反射光强度越大,反之越弱。

Phone光照模型:首先通过lDir(光方向的反方向)与-1相乘得到光方向,使用Reflect结点,计算光方向在法线方向下的反射方向rDir。rDir与vDir点乘得到基本光照模型,然后使用Max限制最小值为0,最后使用Power(开方,相当于正片叠底次数)结点+滑块灵活控制反射光强度。

Blinn-Phone光照模型则更加简单,不需要计算rDir,使用现成的hDir结点即可。

Phone与Blinn-Phone的区别:因为计算方式不同,效果也有一定区别,当光方向与视线方向几乎平行的这种极端情况下,Phone要比Blinn-Phone更加真实:左为Blinn-Phone,右为Phone。但是Phone的计算量要比Blinn-Phone大一些。


映射

映射是指两个元素集之间相互对应的关系,如投影仪射影口的小型长方体框对应投影的大型长方体框,它们属于映射关系。通过映射,我们可以将兰伯特光照模型的色调进行改变,使它不再被局限于灰度值,而是上升到RGB。如将下图的纯灰度图形,作为UV中的U坐标,任取一常量为V坐标,从给定渐变色贴图(RampTex)进行采样,对每个像素进行重新着色。下面进行模拟:

[]

自制的渐变色(RampTex),为方便观察,V坐标方向上没有设置颜色变化:

预测进行映射后得到的结果:


进行实操:

首先,需要构建一个UV坐标系,以原先计算得出的半兰伯特光照模型的点积结果作为U坐标(代表不同点积结果会映射不同的颜色),给定一个**固定值0.4(任意)**作为V坐标:

然后将在PhotoShop中制作好的RampTex(渐变色贴图)导入进来。

构建UV坐标系的方法:

使用VectorOperation/Append(附加)操作,相当于为一个自变量附加一个应变量构成坐标系:

从Unity资源栏中拖入任意贴图就能完成2D贴图节点的导入(外部可控节点的方框是浅绿色的):

将得到的UV给RampTex后接入Emission:

最终结果:


对于VectorOperations/Append操作,也可以给一个二维变量附加应变量成为三维变量,提升数据的维度;最高能够提升到四维,也就是RGBA变量。也可以使用VectorOperations/ComponentMask操作从一个变量中提取它的所有子变量

点击节点左下方的按钮修改输出的子变量是哪个。


渲染模型出现斑点的解决方法:

在模型的末端或首端出现小斑点,这是模型对贴图进行采样时浮点数精度造成的误差,在渲染半兰伯特光照模型时,规定的灰度范围应当在0~1之间,但由于浮点数精度的影响,会在端点处出现误差,如在1位置出现1.0001;在0位置出现-0.0001,此时会去掉整数部分,变成0.0001,造成颜色突然变化。解决方法为将贴图的WrapMode(衔接方式)改为Clamp(紧凑)


卡通渲染和RampTex

使用半兰伯特光照模型+描边+卡硬过渡贴图模拟卡渲:

使用的RampTex(卡渲3cut):

其中包含的美术技法:

1,过渡处卡硬模拟卡通阴影和正面的反光效果;

2,暗部色相变化大,亮度变化小,模拟卡通的高饱和度效果;

3,亮部加入少许白斑,模拟高光(实际情况下不这么加);


炫彩猴头:

可以使用下图这样的RampTex,发现不仅是U轴向,V轴向也有了颜色变化,这方便我们对RampTex的采样进行动态处理

如:以点积结果为U轴,以游戏时间的小数部分为V轴(Time节点输出游戏时间;Frac节点让得到的值仅包含小数),模拟0~1的无限循环。配合上下相循环的RampTex,可以模拟虹色效果。


法线偏移和高光、菲涅尔

只需要在设计RampTex时将亮部与暗部颠倒,就能模拟玉石的透光性。

但是这样模拟的玉石效果不是很真实,需要点缀一些高光。使用法线偏移操作,可以让物体表面的高光位置发生偏移。首先定义一个简单的兰伯特光照模型:

然后添加Properties/Vector4(四维变量参数),在Properties中的参数都可以在使用该Shader建立的材质中直接进行修改(外部可控),这个Vector4形的参数用于控制法线在三个轴向上的偏移量。将这个偏移量与从NormalDir中获取的法线方向相加(达到法线整体方向发生固定角度的偏移),进行归一化(将所有法线向量变为单位向量),然后再与光线方向进行点乘。

这样就能得到一个光线方向可控(实际上是法线方向可控,当然也可以直接给光线方向加入偏移量)的高光模板了,使用法线偏移就能使高光在物体表面进行相对位移。使用点乘结果附加一个RampTex,并将得到的高光与原本的玉石效果相Add,就能得到:

也可以使用一些新方法:

为点乘结果加入If判断,如果点乘结果大于0.9,则显示纯白,如果点乘结果小于0.9,则显示纯黑,这样的二值性也可以用来加高光。

使用同样的方法制作第二个高光后,可以使用Arithmetic/Max(两输入中每个像素取最大值输出)节点将两个高光合在一起,不使用Add的原因:使用Add可能出现0以下,1以上的值。最后使用Arithmetic/Lerp(蒙版)节点,以高光为蒙版,将玉石节点和新定义的高光色节点相运算(Lerp结点的A:底部;B:顶部;T:蒙版),得到最终结果:

也可以给在Shader中增加一菲涅尔反射效果,ShaderForge自带菲涅尔节点:GeometryData/Fresnel节点。该节点的输入Exp可以设置菲涅尔节点的衰减强度,给菲涅尔节点Multiply乘以一个颜色值,这个颜色就是菲涅尔效应的颜色,然后将其与原结果进行混合Blend,混合模式选择Screen(按透明度覆盖)。

菲涅尔效应:视线与较光滑平面的夹角越大,平面折射外部光线的效果越强。放在图形学中的解释为:视线方向与法线方向的夹角越趋近于-1(反向),物体表面的环境光反射越弱,反之如果视线方向与法线方向的夹角越趋近于垂直,物体表面的环境光反射越强。反映到物体表面的现象是:物体边缘变亮。菲涅尔效应常出现在水面、玻璃、宝石等光滑表面。

如上图所示:比较光滑的平面(水面)视线与水平面越垂直(1),来自水面的反射效果越弱,反之(3)越强。将Fresnel节点拆开是如下结构。不进行除负数操作的原因:因为点积结果的负值都在模型背面,不会被摄像机捕捉,此外如果选择了剔除模型背面的话则更好。


假的环境反射光照模型

在漫反射与镜面反射中,光来自光源,但是在实际环境中,光不会只来自于光源,更多的来自于环境中的其他物品,它们吸收来自光源的光,通过漫反射或镜面反射发出加工后的光,这些光普遍是多且复杂的,并且实现起来开销很大,而如果少了这些光,会让场景中缺少真实感。当物体的粗糙度够高时,环境反射光不会很显眼。如果物体比较光滑,环境反射光就很重要,尤其在一些金属表面,如果没有合适的环境反射光会显得很奇怪。现在可以利用所学到的Phone+RampTex的方式,简单制作一些假的环境反射光照模型,开销不大,并且如果RampTex合适的话,看起来不会很违和。

使用Phone光照模型进行两次使用。使用的RampTex:

这样的环境反射光照模型虽然简单,但是实现效果不是很好,这个光照模型对模型起伏要求比较高,模型不规则时能达到更好的效果,而模型表面平滑时,效果就不那么好了:


UV、深度

UV实际上相当于一个坐标系,横坐标为U,纵坐标为V,UV代表了计算机显示器的屏幕坐标系,以屏幕中心为坐标系原点,U与V最大值都为1,最小值都为-1。因为UV会输出一个二位变量,所以显示出来的图像为RG图像(U为红值,V为绿值)。

使用UV可以做到一些视觉效果:最简单如GeometryData/ScreenPosition就可以获取物体表面某点在UV坐标系中的位置。获取到该位置后,可以显示一张图片(最好是首尾相接的图片):


深度是摄像机相对于物体的一个属性,摄像机离物体越接近,这个值越小,反之越大。深度类似于距离;可以使用深度来对同一物体表面距离摄像机远近不同的地方进行视觉操控

在GeometryData/Depth获取深度结点,输出当前物体表面距离摄像机距离。

但是可以看到物体是全白的,这是因为该物体表面就算距离摄像机最近的地方深度也大于1。为此要减去一个常量:

通过深度测量可以看到摄像机距离物体大约4个单位长度。(数值小于1的地方灰度下降)

将UV坐标与深度进行乘法运算,得到的结果为:图片在深度方向上发生偏移(越靠近摄像机的部位图片越大,越远离摄像机的部位图片缩小),这样能让图片随着摄像机远近发生缩放,产生近大远小效果,在视觉上形成图片的立体感。


一些美化处理:

将深度+UV图片算法得到的结果与正常的兰伯特光照模型使用Athemetic/Step(A<=B)结点(B小于A的部分为0,大于A的部分为1),将结果二值化:


由于兰伯特光照模型的暗部有负值,所以暗部一定小于深度+UV图片算法结果。如果亮部不够亮,可能是因为图片的线条间隔比较大,在兰伯特光照模型上增加一个值来增大亮部。

将结果作为遮罩附加颜色,BaseColor(A)在下,LineColor(B)在上

此外,还要添加一次颜色,这个颜色来自兰伯特光照模型,使用光照模型来控制颜色的明度。为点乘结果乘以一个颜色,这个操作与映射不同,映射根据点乘结果作为U坐标,根据灰度变化产生不同的过渡效果。而光照模型直接乘以一个颜色则会直接改变光照模型的颜色(改变色阶)。

然后将其与结果相加,得到一个颜色错落有致的模型,在其上添加描边也能增加动感。最终结果:



Halftone

Halftone(半色调/灰度级)是一种反映图像亮度层次,黑白对比变化的指标,它通过网点的大小、密集程度区分明暗。生活中的图像分为两类,连续调图像(Continuous-Tone Image)和半色调图像(Halftone Image),连续调图像就是常见的由淡到浓/由深到浅的物质颗粒密度决定的图像。而半色调图像则是通过网点面积、覆盖率表现(网点可以是任何形状,一般是圆点和正方形)。半色调图像在美术方面也有比较好的视觉效果。

为了制作Halftone图像,首先要对UV进行一些拓展。我们知道UV最大值为1,最小值为-1。

如果分别输出U值(横坐标)和V值(纵坐标)就能分别得到在横坐标和纵坐标上的-1~1的灰度渐变(也可以对UV图像使用VectorOperations/ComponentMask结点对RG两轴上的数值进行分别提取):

那么如果将UV坐标乘以任意整数,就能将UV的大小扩大任意倍,因为只有0~1的部分回有灰度渐变,所以这个渐变就变得微乎其微了:

此时使用Athematic/Frac进行取余操作,就能得到余数渐变构成的图像了:

直接对UV图像进行取余可以得到栅格化图像(可以将每个栅格看成一个小型坐标系,横纵坐标最小值为0,最大值为1):

拥有这样的栅格化图像后,我们需要将每一个栅格映射为一个圆点,方法:使用Athematic/Remap(Simple)结点将栅格的数据范围映射为(-0.5~0.5),然后使用VectorOperations/Length结点将数据距离0的长度计算出来(内部将RG两值所在位置距离0的距离使用勾股定理求出),输出得到一维数。


得到了点阵图像,将它输出到物体表面:

显然还需要进行光照映射,对Halftone的光照要求:暗部全暗,亮部全亮,过度部分要使用Halftone点阵特有的效果,越亮的部分点的面积越小,密度越小;越暗的部分点的面积越大,密度越大。问题是:怎么同时将亮部的点阵变小,将暗部的点阵变大。现在的图像的最大值为1,最小值为0,我们知道小数进行平方将变小,进行开放将变大,而平方与开放的区别为指数的正负。结论:在亮部对图像进行开方,在暗部对图像进行平方,

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值