unity 基于MatCap实现适于移动平台的“次时代”车漆Shader

转自:http://blog.csdn.net/poem_qianmo/article/details/55803629

这篇文章将基于MatCap的思想,在Unity中实现了具有高度真实感的MatCap车漆Shader。采用MatCap思想的Shader,用低廉的计算成本,就可以达到类似PBS非常真实的渲染效果,可谓是在移动平台实现次时代渲染效果的一种优秀解决方案。
本文以车漆Shader为例,但MatCap思想能实现的,并不局限于车漆Shader。本来准备给本文取名《一种基于MatCap的低计算成本、高真实感移动平台Shader解决方案》的,但这个名字太大了,遂改之。

先看一下最终的效果图。

一、MatCap概述

Material Capture(材质捕获),通常被简称为MatCap,在Zbrush、Sculptris、Mudbox等3D软件中有比较多的使用。

MatCap Shader的基本思路是,使用某特定材质球的贴图,作为当前材质的视图空间环境贴图(view-space environment map),来实现具有均匀表面着色的反射材质物体的显示。考虑到物体的所有法线的投影的范围在x(-1,1),y(-1,1),构成了一个圆形,所以MatCap 贴图中存储光照信息的区域是一个圆形。

基于MatCap思想的Shader,可以无需提供任何光照,只需提供一张或多张合适的MatCap贴图作为光照结果的“指导”即可。


上图来自(http://digitalrune.github.io/DigitalRune-Documentation/html/9a8c8b37-b996-477a-aeab-5d92714be3ca.htm

不像一般的Shader,需要提供光照,需要在Shader代码中进行漫长的演算,基于MatCap思想的Shader相当于MatCap贴图就把光照结果应该是怎样的标准答案告知Shader,我们只用在试卷下写出答案,进行一些加工即可。

需要注意,MatCap Shader有一定的局限性。因为从某种意义上来说,基于MatCap的Shader,就是某种固定光照条件下,从某个特定方向,特定角度的光照表现结果。

正是因为是选择的固定的MatCap贴图,得到相对固定的整体光照表现,若单单仅使用MatCap,就仅适用于摄像机不调整角度的情形,并不适合摄像机会频繁旋转,调节角度的情形。但我们可以在某些Shader中,用MatCap配合与光照交互的其他属性,如将MatCap结合一个作为光照反射的颜色指导的Reflection Cube Map,就有了与光照之间的交互表现。这样,就可以适当弥补MatCap太过单一整体光照表现的短板。

关于MatCap,《UnityShaders and Effects Cookbook》一书的Chapter 5: LightingModels中,The Lit Sphere lighting model一节也有一些涉及。

二、MatCap贴图的获取

需要使用基于MatCap Shader,合适的MatCap 贴图必不可少。显而易见,MatCap贴图的获取,一般来说有两种方式。

  1. 自己制作。对着3D软件中的材质球截图。

  2. 从网络上获取。在网络上使用“matcap“等关键字搜索后获得。

这边提供几个可以获取MatCap贴图的网址:

[1] https://www.pinterest.com/evayali/matcap/

[2]https://www.google.com.hk/search?q=MatCap&newwindow=1&safe=strict&hl=zh-CN&biw=1575&bih=833&tbm=isch&tbo=u&source=univ&sa=X&ved=0ahUKEwju8JDTpZnSAhUGn5QKHawODTIQsAQIIg

[3]http://pixologic.com/zbrush/downloadcenter/library/#prettyPhoto

三、基于MatCap实现Physically Based Shading的思路简析

关于基于MatCap思想实现Physicallybased Shading,这篇文章(http://blog.csdn.net/ndsc_dw/article/details/50700201)提供了一定的思路,简单来说,就是用几张MatCap贴图来提供PBS需要的光滑度和金属度,来模拟出PBS的效果。继续展开下去就脱离本文的主线了,有兴趣的朋友可以深入进行了解。

四、基于MatCap思想的车漆Shader实现

此车漆Shader,除了用到MatCap,主要还需要提供一个Reflection Cube Map作为反射的颜色指导,就可以适当弥补MatCap太过单一的整体光照表现的短板,实现非常真实且高效的车漆Shader效果。

此Shader赋给Material后,Material在Inspector中的属性表现如下:

其中的MatCap贴图为:

Shader源码如下:

[cpp] view plain copy

Shader "ShaderPrac/Car Paint Shader"
{
Properties
{
//主颜色
_MainColor("Main Color", Color) = (1.0, 1.0, 1.0, 1.0)
//细节颜色
_DetailColor("Detail Color", Color) = (1.0, 1.0, 1.0, 1.0)
//细节纹理
_DetailTex("Detail Textrue", 2D) = "white" {}
//细节纹理深度偏移
_DetailTexDepthOffset("Detail Textrue Depth Offset", Float) = 1.0
//漫反射颜色
_DiffuseColor("Diffuse Color", Color) = (0.0, 0.0, 0.0, 0.0)
//漫反射纹理
_DiffuseTex("Diffuse Textrue", 2D) = "white" {}
//Material Capture纹理
_MatCap("MatCap", 2D) = "white" {}
//反射颜色
_ReflectionColor("Reflection Color", Color) = (0.2, 0.2, 0.2, 1.0)
//反射立方体贴图
_ReflectionMap("Reflection Cube Map", Cube) = "" {}
//反射强度
_ReflectionStrength("Reflection Strength", Range(0.0, 1.0)) = 0.5
}

SubShader  
{  
    Tags  
    {  
        "Queue" = "Geometry"  
        "RenderType" = "Opaque"  
    }  

    Pass  
    {  
        Blend Off  
        Cull Back  
        ZWrite On  

        CGPROGRAM  
        #include "UnityCG.cginc"  
        #pragma fragment frag  
        #pragma vertex vert  

        float4 _MainColor;  
        float4 _DetailColor;  
        sampler2D _DetailTex;  
        float4 _DetailTex_ST;  
        float _DetailTexDepthOffset;  
        float4 _DiffuseColor;  
        sampler2D _DiffuseTex;  
        float4 _DiffuseTex_ST;  
        sampler2D _MatCap;  
        float4 _ReflectionColor;  
        samplerCUBE _ReflectionMap;  
        float _ReflectionStrength;  

        //顶点输入结构  
        struct VertexInput  
        {  
            float3 normal : NORMAL;  
            float4 position : POSITION;  
            float2 UVCoordsChannel1: TEXCOORD0;  
        };  

        //顶点输出(片元输入)结构  
        struct VertexToFragment  
        {  
            float3 detailUVCoordsAndDepth : TEXCOORD0;  
            float4 diffuseUVAndMatCapCoords : TEXCOORD1;  
            float4 position : SV_POSITION;  
            float3 worldSpaceReflectionVector : TEXCOORD2;  
        };  

        //------------------------------------------------------------  
        // 顶点着色器  
        //------------------------------------------------------------  
        VertexToFragment vert(VertexInput input)  
        {  
            VertexToFragment output;  

            //漫反射UV坐标准备:存储于TEXCOORD1的前两个坐标xy。  
            output.diffuseUVAndMatCapCoords.xy = TRANSFORM_TEX(input.UVCoordsChannel1, _DiffuseTex);  

            //MatCap坐标准备:将法线从模型空间转换到观察空间,存储于TEXCOORD1的后两个纹理坐标zw  
            output.diffuseUVAndMatCapCoords.z = dot(normalize(UNITY_MATRIX_IT_MV[0].xyz), normalize(input.normal));  
            output.diffuseUVAndMatCapCoords.w = dot(normalize(UNITY_MATRIX_IT_MV[1].xyz), normalize(input.normal));  
            //归一化的法线值区间[-1,1]转换到适用于纹理的区间[0,1]  
            output.diffuseUVAndMatCapCoords.zw = output.diffuseUVAndMatCapCoords.zw * 0.5 + 0.5;  

            //坐标变换  
            output.position = mul(UNITY_MATRIX_MVP, input.position);  

            //细节纹理准备准备UV,存储于TEXCOORD0的前两个坐标xy  
            output.detailUVCoordsAndDepth.xy = TRANSFORM_TEX(input.UVCoordsChannel1, _DetailTex);  
              
            //深度信息准备,存储于TEXCOORD0的第三个坐标z  
            output.detailUVCoordsAndDepth.z = output.position.z;  

            //世界空间位置  
            float3 worldSpacePosition = mul(unity_ObjectToWorld, input.position).xyz;  

            //世界空间法线  
            float3 worldSpaceNormal = normalize(mul((float3x3)unity_ObjectToWorld, input.normal));  

            //世界空间反射向量  
            output.worldSpaceReflectionVector = reflect(worldSpacePosition - _WorldSpaceCameraPos.xyz, worldSpaceNormal);  
              
            return output;  
        }  

        //------------------------------------------------------------  
        // 片元着色器  
        //------------------------------------------------------------  
        float4 frag(VertexToFragment input) : COLOR  
        {  
            //镜面反射颜色  
            float3 reflectionColor = texCUBE(_ReflectionMap, input.worldSpaceReflectionVector).rgb * _ReflectionColor.rgb;  

            //漫反射颜色  
            float4 diffuseColor = tex2D(_DiffuseTex, input.diffuseUVAndMatCapCoords.xy) * _DiffuseColor;  

            //主颜色  
            float3 mainColor = lerp(lerp(_MainColor.rgb, diffuseColor.rgb, diffuseColor.a), reflectionColor, _ReflectionStrength);  

            //细节纹理  
            float3 detailMask = tex2D(_DetailTex, input.detailUVCoordsAndDepth.xy).rgb;  

            //细节颜色  
            float3 detailColor = lerp(_DetailColor.rgb, mainColor, detailMask);  

            //细节颜色和主颜色进行插值,成为新的主颜色  
            mainColor = lerp(detailColor, mainColor, saturate(input.detailUVCoordsAndDepth.z * _DetailTexDepthOffset));  

            //从提供的MatCap纹理中,提取出对应光照信息  
            float3 matCapColor = tex2D(_MatCap, input.diffuseUVAndMatCapCoords.zw).rgb;  

            //最终颜色  
            float4 finalColor=float4(mainColor * matCapColor * 2.0, _MainColor.a);  

            return finalColor;  
        }  

        ENDCG  
    }  
}  

Fallback "VertexLit"  

}

Shader注释已经比较详细,下面对代码中也许会不太理解,需要注意的地方进行说明。

要使用MatCap贴图,主要是将法线从模型空间转换到视图空间,并切换到适合提取纹理UV的区域[0,1]。(需要将法线从模型空间转换到视图空间,关于一些推导可以参考http://www.lighthouse3d.com/tutorials/glsl-12-tutorial/the-normal-matrix或者http://www.cnblogs.com/flytrace/p/3379816.html

Unity内置的矩阵UNITY_MATRIX_IT_MV,是UNITY_MATRIX_MV的逆转置矩阵,其作用正是将法线从模型空间转换到观察空间。于是顶点着色器vert中的这两句代码就很容易理解了:

[cpp] view plain copy

//MatCap坐标准备:将法线从模型空间转换到观察空间,存储于TEXCOORD1的后两个纹理坐标zw
output.diffuseUVAndMatCapCoords.z =dot(normalize(UNITY_MATRIX_IT_MV[0].xyz), normalize(input.normal));
output.diffuseUVAndMatCapCoords.w= dot(normalize(UNITY_MATRIX_IT_MV[1].xyz), normalize(input.normal));

而得到的视图空间的法线,区域是[-1,1],要转换到提取纹理UV的区域[0,1],就需要乘以0.5并加上0.5,那么顶点着色器vert中接下来的的这句代码也就可以理解了:

[cpp] view plain copy

//归一化的法线值区间[-1,1]转换到适用于纹理的区间[0,1]
output.diffuseUVAndMatCapCoords.zw= output.diffuseUVAndMatCapCoords.zw * 0.5 + 0.5;

稍后,在片元着色器frag中,用在顶点着色器vert中准备好的法线转换成的UV值,提取出MatCap的光照细节即可:

[cpp] view plain copy

//从提供的MatCap纹理中,提取出对应光照信息
float3matCapColor = tex2D(_MatCap, input.diffuseUVAndMatCapCoords.zw).rgb;

此Car Paint Shader其他实现,主要就是一些基本的Shader知识点的配合,如顶点坐标转换,反射,漫反射,细节纹理的计算,都是比较基础的内容,这里就不再赘述,直接看Shader源码即可。

最后看几张截图,然后就是相关Shader与MatCap资源的下载:

五、Shader源码与MatCap资源下载

从Github下载:

【Github】Shader源码与相关MatCap资源下载

参考与延伸

[1] http://wiki.unity3d.com/index.php/MatCap

[2] https://www.assetstore.unity3d.com/en/#!/content/8221

[3] http://blog.csdn.net/cubesky/article/details/38682975

[4] https://www.assetstore.unity3d.com/en/#!/content/76361

[5] http://www.cnblogs.com/flytrace/p/3379816.html

[6] http://www.cnblogs.com/flytrace/p/3395911.html

[[7] http://digitalrune.github.io/DigitalRune-Documentation/html/9a8c8b37-b996-477a-aeab-5d92714be3ca.htm](file:///C:/Users/Administrator/Google%20%E4%BA%91%E7%AB%AF%E7%A1%AC%E7%9B%98/Shader/blog/%5b7%5d%20http:/digitalrune.github.io/DigitalRune-Documentation/html/9a8c8b37-b996-477a-aeab-5d92714be3ca.htm)

[8] https://forum.unity3d.com/threads/_object2world-or-unity_matrix_it_mv.112446/

[9]http://www.lighthouse3d.com/tutorials/glsl-12-tutorial/the-normal-matrix

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,698评论 5 463
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,711评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,795评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,699评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,567评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,423评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,822评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,467评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,755评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,789评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,577评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,418评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,837评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,054评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,355评论 1 254
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,820评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,027评论 2 338

推荐阅读更多精彩内容

  • <转>我也忘了转自哪里,抱歉,感谢原作者 什么是Shader Shader(着色器)是一段能够针对3D对象进行操作...
    星易乾川阅读 5,558评论 1 16
  • Babybus-u3d技术交流-【浅墨Unity3D Shader编程】之二 雪山飞狐篇:Unity的基本Shad...
    Babybus_Unity阅读 5,618评论 4 61
  • 转载注明出处:点击打开链接 Shader(着色器)是一段能够针对3D对象进行操作、并被GPU所执行的程序。Shad...
    游戏开发小Y阅读 3,309评论 0 4
  • 两条车辙辗出一个人生 身后长长的, 交织的锁链 是梦的裂痕 栓起归乡人的热恋 奔赴似水的流年 皎洁的月色 混着白雪...
    圩原君阅读 179评论 0 0
  • 没有当过兵的人,永远无法理解军人的情感;没有在部队摸爬滚打过的人,自然也没法理解战友之间的深情厚谊。作为一名曾经的...
    _小六阅读 733评论 0 4