前言

最近通关了《Alan Wake》(心灵杀手),整体感觉很不错,游戏虽然是2010年发行的,但是画面至今看来也还是不错的,尤其是游戏内的体积光效果,贯穿了整个游戏,因为光本身就是这个游戏中最强力的武器。

主人公是一位小说家,但是当你发现你所写的小说都变成了现实的时候,这肯定是一件细思极恐的事情。故事情节一波三折,颇有些美剧的感觉。

每次看到电锯,不由得想起被《生化危机》和《恶灵附身》支配的恐惧,好在这款游戏的电锯不太抗揍。其实《恶灵附身》跟这款游戏有不少相似的地方,都是在“意念”的世界中,所以游戏中经常出现走着走着天上掉下来辆火车啦,走着走着桥飞啦之类的情况。

游戏还是很给力的,我就不多剧透了,下面是本文的正题。之前的blog里面,大多是一些特殊效果以及屏幕后处理相关的内容,但是关于模型本身光照效果的比较少。今天刚好来玩一个简单,但是却很有意思的东东,叫做Matcap。

简介

Matcap,全称为Material Capture,翻译过来就是材质捕获。简单来说就是预先生成的一种存储了光照和反射等信息的贴图,运行时使用法线方向进行采样。Matcap的好处就是可以用很低的消耗来实现很多特殊风格的效果,但是Matcap也有一些缺陷,在于Matcap仅对于固定相机视角的情况较好,这也是Matcap的原理决定的。

Matcap主要是在Zbrush,Mudbox这些软件里面使用的,ZBrush里面雕模的时候有时候虽然没有贴图,但是效果看起来也挺好的,实际上就是Matcap的作用,而且这些美术大佬们不断在扩展这些matcap,积累了很多好玩的matcap效果,而使用这些效果却非常容易,只需要换一下贴图。

Matcap的原理

先来看一下Matcap的原理。先可以考虑一下CubeMap的原理,CubeMap是一个六面体,正常的反射采样时使用的反射对应方向上投影最近的面上进行采样,更简单一点的(应该算是一个Trick了),甚至可以直接使用法线方向进行采样,对于天空盒类型的反射也可以得到不错的效果,即不同法线所指向的方向有不同的效果。而Matcap将这一点发挥得更加淋漓尽致,因为Matcap只用一张图就可以。所以我们需要考虑怎样将这个法线转换到一个合适的区间来采样Matcap。

由于物体的法线是一个三维的朝向,但是我们最终采样到二维贴图也只有两个维度。我们需要去掉一个维度,在相机空间下的物体法线向量,我们可以不考虑Z轴的指向,即不考虑Z轴本身对屏幕空间的XY平面的贡献,因为法线是一个单位向量,如果法线在XY方向上的投影权重很大,那么说明在Z方向的权重就很小,对应到二维的Matcap上采样的位置越靠近边缘;而如果法线在XY方向的投影权重很小,那么Z方向的权重就很大,对应到二维的Matcap上的采样位置就越靠近中心。

接下来我们需要考虑的是在哪一个空间计算Matcap,如果是物体空间或者是世界空间,由于相机位置朝向不确定,不好确定法线的Z方向与屏幕空间的关系,我们不好确定舍弃哪一个维度。而相机空间下的物体在屏幕上的方位都已经大致确定,相机空间下相对运算要比屏幕空间更少一些。所以最合适的实际上就是相机空间下进行计算。

我们可以通过Unity的内置矩阵将法线从物体空间转化到视空间,然后对于是空间的法线方向,由于方向是(-1,1)区间,我们需要再将其*0.5 + 0.5变化到(0,1)区间,就可以实现使用这个xy方向采样Matcap贴图了。

Matcap的实现

上面了解了Matcap的原理,下面直接上代码:

/********************************************************************FileName: Matcap.shaderDescription: Matcap效果history: 4:11:2018 by puppet_masterhttps://blog.csdn.net/puppet_master
*********************************************************************/
Shader "Unlit/Matcap"
{Properties{_MatCap ("Matcap", 2D) = "white" {}}SubShader{Tags { "RenderType"="Opaque" }LOD 100Pass{CGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"struct v2f{float2 matcapuv : TEXCOORD0;float4 vertex : SV_POSITION;};sampler2D _MatCap;v2f vert (appdata_base v){v2f o;o.vertex = UnityObjectToClipPos(v.vertex);//乘以逆转置矩阵将normal变换到视空间float3 viewnormal = mul(UNITY_MATRIX_IT_MV, v.normal);//需要normalize一下,否则保证normal处在(-1,1)区间,否则有scale的object效果不对viewnormal = normalize(viewnormal);o.matcapuv = viewnormal.xy * 0.5 + 0.5;           return o;}fixed4 frag (v2f i) : SV_Target{fixed4 mat = tex2D(_MatCap, i.matcapuv);return mat;}ENDCG}}
}

我们使用了UNITY_MATRIX_IT_MV,即正常矩阵的逆转置进行计算,原因在于直接用ModelView矩阵变换法线时,对于非uniform变换可能导致法线与平面不垂直的问题。关于法线的变换,可以参考本人之前的blog《Unity Shader-描边效果》。

我们使用一张Matcap,在Matcap中,我们最终采样的是一个法线方向变换到(0,1)区间的结果,实际上有效的区域就只是贴图中心周围的圆形范围内有效。而Matcap比较直白的效果就是,在当前视角方向看,Matcap的上下左右方向的颜色就对应模型在当前视角方向对应的法线所指向的上下左右方向。

比如我们用下面一张Matcap贴图:

在不同的模型上的效果如下:

从上图中,我们很容易看出,对于一个球体,球体上显示的,实际上就是Matcap上对应的内容,不论我们从哪个视角看,球体上都是这样的一个表现。Matcap左上角的高光两点,对应到模型上左上角的部分也会有高光亮点。

另外,还有一点需要注意,在我们把Normal变换到视空间后,我们进行了一次Normalize操作。其实我在网上看到了绝大部分的版本的Matcap的uv计算实现是这样的:

o.matcapuv.x = mul(UNITY_MATRIX_IT_MV[0], v.normal);
o.matcapuv.y = mul(UNITY_MATRIX_IT_MV[1], v.normal);

可能是出于性能的考虑,直接使用UNITY_MATRIX_IT_MV的x和y轴作为基,直接求Normal在xy轴的投影,虽然可以减少矩阵的计算,但是这样有一个比较严重的问题。如果对象的Scale是1,那么效果没有问题,但是如果对象的Scale非1,那么得到的法线并非在(-1,1)区间,转化之后的uv值肯定也跑偏了,结果自然就不对了,如下图。

上图中,中间的球体Scale为1,效果正常;左侧Scale为0.5,边缘的采样效果不对;右侧Scale为2,只采样了一部分Matcap。所以,在计算后一定要保证ViewNormal处在(-1,1)区间,否则最终(0,1)区间采样Matcap的结果肯定不对。所以,建议还是使用更加保证效果正确的方式,直接Normalize之后,就可以保证Scale之后效果也正常啦。

Matcap的效果

因为Matcap本身的原理很简单,我们使用的Shader就相当于是一个模板了,任意的效果都是通过替换这张Matcap图来实现的。下面就是来看一下Matcap效果的时候啦,我从ZBrush内置的Matcap以及提供的Matcap库中选取了几个好玩的Matcap图,ZBrush中的材质是ZMT格式的,我们直接可以预览的时候把Matcap求截个图使用(感觉方法low了点,不过这也是最简单的方法啦)。注:对应Matcap图实际上就与场景中的球体表现一致。

先来个小金人效果,最简单的模拟金属的效果:

玉石次表面效果:

另一种玉石的效果,有点大理石的赶脚:

Matcap也可以实现边缘光效果,甚至画一个半球也可以实现一部分的边缘光效果:

简单模拟反射的效果:

一种挺好玩的风格,ZBrush里面叫fish skin(鱼皮效果是什么鬼):

Matcap优化

虽然直接使用Matcap可以用很省的消耗做出各种特殊效果,但是实际上Matcap也有一些限制,这也是Matcap的原理导致的。我们看下面的图片:

在比较圆润的对象上,我们可以看到较好的效果,但是在小狮子的底座上平面,还有右侧的正方体上,在同样的一个平面内,我们看不到任何变化,整个平面都是平的;而且在传统的Matcap上有一个问题,整体的渲染效果与视角关系不算很大,有区别的情况仅在于平面的法线分布不同导致表面效果不同,这导致了Matcap在视角方面有一些限制,仅对于固定的相机视角较好。

我们考虑一下上面我们采样matcap的操作应该就可以了解这种情况的原因啦,当遇到一个平面时,这个平面上的所有的法线方向都是相同的,转化到视空间后,方向也是相同的,再*0.5 + 0.5之后的uv值也是相同的,最终就导致了一个平面上所有的像素点采样matcap得到是值都是相同的。为了缓解这种情况,我们就需要不仅仅考虑视方向的问题,还需要考虑一下位置的问题,换句话说,我们可以把相机的位置也加入计算,物体相对于相机的方向也作为matcap采样的影响因子之一。

最简单的,我们可以在计算方向的时候,不直接使用Normal计算,而是根据当前像素点的相机空间位置,相机空间法线,计算一个反射的方向,再用这个反射的方向进行matcap采样即可:

float3 viewnormal = mul(UNITY_MATRIX_IT_MV, v.normal);
float3 viewPos = UnityObjectToViewPos(v.vertex);
float3 r = reflect(viewPos, viewnormal);
r = normalize(r);
o.matcapuv = r.xy * 0.5 + 0.5;

还有一种方式,是我从一个老外的blog里面发现的,也是使用了反射方向进行计算,但是不是直接用反射方向的xy,而是通过一个公式将xy按照一个权重进行调制,得到的效果更好。公式如下:

优化后的Shader如下:

/********************************************************************FileName: Matcap.shaderDescription: Matcap效果history: 5:11:2018 by puppet_masterhttps://blog.csdn.net/puppet_master
*********************************************************************/
Shader "Unlit/MatcapReflect"
{Properties{_MatCap ("Matcap", 2D) = "white" {}}SubShader{Tags { "RenderType"="Opaque" }LOD 100Pass{CGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"struct v2f{float2 matcapuv : TEXCOORD0;float4 vertex : SV_POSITION;};sampler2D _MatCap;sampler2D _GlobalMatcap;v2f vert (appdata_base v){v2f o;o.vertex = UnityObjectToClipPos(v.vertex);//乘以逆转置矩阵将normal变换到视空间float3 viewnormal = mul(UNITY_MATRIX_IT_MV, v.normal);viewnormal = normalize(viewnormal);float3 viewPos = UnityObjectToViewPos(v.vertex);float3 r = reflect(viewPos, viewnormal);float m = 2.0 * sqrt(r.x * r.x + r.y * r.y + (r.z + 1) * (r.z + 1));o.matcapuv = r.xy / m + 0.5;return o;}fixed4 frag (v2f i) : SV_Target{fixed4 mat = tex2D(_MatCap, i.matcapuv);return mat;}ENDCG}}
}

最终效果如下,可见,在正方体的平面上,也出现了类似次表面玉石的效果变化:

在换一张反射效果的贴图,在Cube上也能显示出比较好的效果,来张动图:

动态生成Matcap

Matcap目前主要还是用于实现一些好玩的特殊效果,但是对于动态光照等效果,Matcap着实是做不到的。毕竟Matcap本身就是一种预计算好的特殊光照效果贴图,要想随着场景的光进行动态变化,目前本人想到的就两种方式。第一,仅把Matcap作为一个输入的参数,额外按照光照计算一个权重来调制Matcap效果;另一种就是直接渲染一个球体,作为动态的Matcap,这个球体可以使用很复杂的计算Shader,然后场景里面其他的对象采样Matcap。这个idea也是源自另一篇老外的blog《World Space MatCap Shading》(不过该blog作者使用的是世界空间的Normal进行的计算,结果遇到了一堆问题,个人并不是很看好这种方式)。这种方式可能会节省一些光照的计算,但是本人没有具体测试过,所以就当玩一下啦。

下面,我们实现一下动态生成一张Matcap进行一个最简单的diffuse计算效果,首先我们使用一个简单的Shader如下:


Shader "Unlit/SimpleLight"
{SubShader{Tags { "RenderType"="Opaque" }LOD 100Pass{CGPROGRAM#pragma vertex vert#pragma fragment frag#define UNITY_PASS_FORWARDBASE#pragma multi_compile_fwdbase#include "UnityCG.cginc"struct v2f{float4 vertex : SV_POSITION;float3 worldNormal : NORMAL;};v2f vert (appdata_base v){v2f o;o.vertex = UnityObjectToClipPos(v.vertex);o.worldNormal = UnityObjectToWorldNormal(v.normal);return o;}fixed4 frag (v2f i) : SV_Target{float3 lightDir = _WorldSpaceLightPos0.xyz;float3 normal = normalize(i.worldNormal);float ndotl = saturate(dot(lightDir, normal));return fixed4(ndotl,ndotl,ndotl,1);}ENDCG}}
}

然后我们用CommandBuffer在相机前面绘制一个Sphere到RT上作为动态的Matcap:

var cam = GetComponent<Camera>();
matcap = RenderTexture.GetTemporary(512, 512, 24, RenderTextureFormat.Default, RenderTextureReadWrite.Default, 4);
var commandbuffer = new CommandBuffer();
commandbuffer.ClearRenderTarget(true, true, Color.black);
commandbuffer.SetRenderTarget(matcap);
commandbuffer.DrawRenderer(sphereRenderer, sphereRenderer.sharedMaterial);
cam.AddCommandBuffer(CameraEvent.BeforeForwardOpaque, commandbuffer);
Shader.SetGlobalTexture("_GlobalMatcap", matcap);

我们在场景中放置两个模型,左侧为Matcap效果,右侧为SimpleLight效果,上面的Sphere显示动态的Matcap,可见Matcap效果与SimpleLight效果类似:

通过这样的一个方式,如果计算方式很复杂的光照计算,我们就可以通过Matcap进行预计算一次,然后其他所有对象采样Matcap来达到动态的效果。

总结

本文主要实现了基本的Matcap效果,基于反射的Matcap效果,以及动态生成Matcap效果。在正式使用时Matcap可能并不会直接作为结果输出使用,有可能是用于某些特殊光照效果,或者特殊材质效果,配合正常的贴图,Mask贴图等使用。

周末又通关了一个小游戏《12 is better than 6》,流程很短,但是很硬核,而且很有特点,下一篇的开头又有东西写啦!

Unity Shader-Matcap(材质捕获)相关推荐

  1. 学习Shader Unity Shader 基础

    1.如何充分利用 Unity Shader 来为我们的游戏增光添彩? 材质和 Unity Shader: 在Unity中,我们需要配合使用材质(Material)和 Unity Shader 才能达到 ...

  2. 【备份】《Unity Shader入门精要》配图

    说明:本页面是书籍<Unity Shader入门精要>的随书彩图集锦,包含了书中所有的插图,使用时可通过图片编号进行搜索.  作者:冯乐乐  邮箱:lelefeng1992@gmail.c ...

  3. 【备忘】《Unity Shader入门精要》随书彩色插图

    转载来源:http://www.manew.com/blog-194008-42590.html <Unity Shader入门精要>随书彩色插图 <Unity Shader入门精要 ...

  4. 《Unity Shader入门精要》随书彩色插图

    说明:本页面是书籍<Unity Shader入门精要>的随书彩图集锦,包含了书中所有的插图,使用时可通过图片编号进行搜索.  作者:冯乐乐  邮箱:lelefeng1992@gmail.c ...

  5. Unity Shader入门精要——第3章 Unity Shader基础

    Unity Shader入门精要读书笔记系列 第1章 欢迎来到Shader的世界 第2章 渲染流水线 第3章 Unity Shader基础 文章目录 Unity Shader入门精要读书笔记系列 前言 ...

  6. 《 Unity Shader 入门精要》 第3章 Unity Shader 基础

    第3章 Unity Shader 基础 3.1 Unity Shader 概述 材质与 Unity Shader 在 Unity 中我们通常需要将材质(Material) 和 Unity Shader ...

  7. unity Shader

    unity Shader 前言 1 unity shader代码结构 1.1 shader命名 1.2 Properties语义块 1.3 SubShader 1.3.1 Pass 1.3.2 Sub ...

  8. Unity Shader入门精要第3 章 Unity Shader 基础

    Unity系列文章目录 文章目录 Unity系列文章目录 前言 一.Unity Shader 概述 二.使用步骤 1.3.1.2 Unity 中的材质 2.Unity 中的Shader 3.Unity ...

  9. Unity Shader Graph 使用安装步骤缺失材质球,以及场景原本物体材质球丢失问题解决

    Unity Shader Graph 使用安装步骤,以及原本物体材质球丢失问题 我是用的版本是2019.3.9版本的,仅供参考. 第1步:Window-PackageManager-点击All Pac ...

最新文章

  1. ddr5内存上市时间_辣评烩:SK海力士首发DDR5内存:频率冲上5600MHz
  2. axios请求报Uncaught (in promise) Error: Request failed with status code 404
  3. 为什么 Netflix 这么强?网飞 CEO 哈斯廷斯跟陆奇摊牌了
  4. module 'inn' not found:No LuaRocks module found for inn
  5. 带你搭建一个H5游戏平台,附源码下载
  6. 尚硅谷大数据课程flink1.13代码实现与笔记记录
  7. oracle系统的物料编码,关于标准form 物料编码查询 不通过lov
  8. 【Qt】解决“ QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to ‘/tmp/runtime-root‘ ”
  9. 附录E-分贝(dB和dBm)的理解
  10. 计算机c盘主要放那些,c盘哪些文件可以删除(电脑C盘文件夹哪些可以删除?)...
  11. 迅为4418/6818开发板 Yocto 系统烧写
  12. android 圆形渐变背景,android实现圆形渐变进度条
  13. 苹果手机快速发送文件到win10电脑
  14. 气象环境监测系统有哪些组成设备
  15. Servlet的工作原理简介
  16. 关于LTE终端的所谓的五模、七模、十频、十一频
  17. 集成电路,微小电子元件焊接技巧
  18. 【微服务】6、一篇文章学会使用 SpringCloud 的网关
  19. leaflet加载离线瓦片地图
  20. 定义一个三角形类Ctriangle,求三角形面积和周长。

热门文章

  1. 快来薅羊毛,轻量、快捷、低使用门槛的PaaS平台在这里
  2. iPad忘记屏幕密码如何解锁
  3. 人生的境界,就可以用四个词来概括:苦而不言,笑而不语,迷而不失,惊而不乱。
  4. 计算机毕业设计Node.js+Vue酒店客户管理系统(程序+源码+LW+部署)
  5. 机械臂长度的设计计算_Ubuntu(14.04-16.04-18.04)Openrave 配置安装用于计算机械臂可达空间...
  6. java计算机毕业设计springboot+vue考研资料分享系统
  7. python中遍历字典判断是否存在_Python基础之(判断,循环,列表,字典)
  8. 2021年度训练联盟热身训练赛第二场 C.Tip to be Palindrome
  9. 微信的发展堪称疯狂,这 7 点值得所有人学习|投资人说-20170504早读课
  10. js本地缓存的三种方式