《Unity Shaders and Effects Cookbook》一书(感谢原书作者),同一时候会加上一点个人理解或拓展。

这里是本书全部的插图。这里是本书所需的代码和资源(当然你也能够从官网下载)。

========================================== 切割线 ==========================================

写在前面

照亮的球体(Lit Sphere。翻译过来非常怪)类型的光照模型是一种非常有趣的基于图像的光照。

实际上,我们能够使用一张2D贴图来完整地烘焙我们的光照。你能够得到Zbrush这个软件实现的同样效果。假设你对Zbrush的MatCaps(Material Captures)非常熟悉,那么恭喜你。被照亮的球体是同样的实现原理。我们能够创建一个贴图,然后全然照搬各种烘焙类型,像漫反射,镜面反射,反射以及边缘光照等效果。然后再使用它来创建我们的Shader。

这样的方法唯一的缺点就是。由于我们将光照全然烘焙到了贴图上,因此无法改变光照,除非你依据你的环境替换还有一张贴图。就像我们在反射一章中的简单的Cubemap反射一样。也就是说。这个Shader不会依据你环境中的光照做出不论什么变化,也不会再你移动你的视角时产生不论什么改变。下图展示了一个Lit Sphere贴图。它通常被称为一个Sphere Map:

这意味着。这样的使用贴图实现的Shader适合创造非常好的透视效果。或者甚至能够用于你的游戏中那些像电影画面似的、镜头位置被固定住而你又须要复杂的光照的场景中。

用更易懂的话来说,当给定一张贴图后。使用它渲染得到的画面将和贴图看起来一样,就像把贴图投影到了模型上一样。

准备工作

我们须要学习如何创建一个能够被用于我们的光照模型的贴图。我们能够使用Photoshop,可是我们也能够使用一个更小并且免费的工具,叫做Macrea(仅适用于Windows系统)。

这个是一个非常棒的免费软件能够帮你创建这些Lit Sphere maps。作者建议观看Vimeo上的视频(须要翻墙)来帮助你熟悉MaCrea的界面和工作流程。

一旦你熟悉了创建这些Sphere maps的流程,我们能够開始剩下的部分。以下的截图显示了Macrea的界面以及一个用它创建的完整的Lit Sphere。
当然。你也能够在最上面提到的本书资源里找到本章(第五章)使用的贴图。
在Unity里,做例如以下准备:
  1. 创建一个新的场景以及一些对象,一个平面以及一个平行光。
  2. 创建一个新的Shader 和材质,然后将你的Shader赋给新的材质。

实现和解释

有了我们的场景资源,我们就能够開始编写我们的Lit Sphere shading model了。

  1. 和之前一样。我们须要一些properties传递给Surface Shader。以便我们能够让这个Shader的用户更改贴图以及一些变量。

    因此,我们首先加入以下的代码到Properties块:

    1. Properties {
    2. _MainTint ("Diffuse Tint", Color) = (1,1,1,1)
    3. _MainTex ("Base (RGB)", 2D) = "white" {}
    4. _NormalMap ("Normal Map", 2D) = "bump" {}
    5. }
  2. 由于我们的Shader仅使用贴图来照亮我们的模型,因此我们不须要内置的Lambert光照函数。而须要声明我们自己的Unlit光照函数。

    我们还须要写一个顶点函数:

    1. CGPROGRAM
    2. #pragma surface surf Unlit vertex:vert

    解释:鉴于有些童鞋忘了或者没有看过之前的内容,这里说明一下上面这句声明的意思。

    它表明我们将使用名为surf的Surface Shader function。以及名为Unlit的自己定义光照函数,还有一个名为vert的顶点函数。本书第一次提到这个知识点是在这一节

  3. 和前面一样,我们须要在块中声明之前的properties,以便我们能够利用Inspector中的各个用户给定的数据。
    1. float4 _MainTint;
    2. sampler2D _MainTex;
    3. sampler2D _NormalMap;
  4. 接下来。我们创建一个新的光照函数。它相应上面提到的名为Unlit的光照模型。这样做是由于我们不想使用场景中的灯光影响我们的Shader。我们只想要得到对象的阴影,其它的交给贴图来照亮整个对象。

    因此,我们须要加入以下的光照函数(这里跟原书有一点不一样,我依据官方文档改动了返回类型):

    1. inline half4 LightingUnlit (SurfaceOutput s, fixed3 lightDir, fixed atten)
    2. {
    3. half4 c = half4(1,1,1,1);
    4. c.rgb = s.Albedo;
    5. c.a = s.Alpha;
    6. return c;
    7. }

    解释:同样,关于这里的内容能够參见这一节

  5. 如今,我们须要将一些额外的属性加入到Input结构体中。以便我们能够将这些信息从vert()函数传递给surf()函数:
    1. struct Input {
    2. float2 uv_MainTex;
    3. float2 uv_NormalMap;
    4. float3 tan1;
    5. float3 tan2;
    6. };

    解释:你能够把Input结构体当成是顶点函数和Surface shading函数之间的纽带,它们之间的沟通都是靠Input结构体进行传递的。

  6. 为了正确的在Sphere map中查找。我们须要将切线旋转矩阵(the rotated tangent vector)和当前模型的逆转置模型视图矩阵(the inverse transpose model view matrix)相乘。这将会给你合适的想来应用到Sphere map贴图中。假设你还是不明确也不要紧。在后面我们会进一步说明。

    1. void vert (inout appdata_full v, out Input o)
    2. {
    3. UNITY_INITIALIZE_OUTPUT(Input,o);
    4.  
    5. TANGENT_SPACE_ROTATION;
    6. o.tan1 = mul(rotation, UNITY_MATRIX_IT_MV[0].xyz);
    7. o.tan2 = mul(rotation, UNITY_MATRIX_IT_MV[1].xyz);
    8. }

    解释:vert()函数是这个光照模型横纵真正起作用的地方。我们使用模型的切线旋转矩阵和它的逆转置模型视图进行相乘。它的结果将在以下被用于查找Sphere map中相应的位置。那么,逆转置模型视图矩阵是从哪里产生的呢?事实上这是Unity提供的还有一个内置參数。因此我们不须要再自己计算它了。

    实际上Unity提供了绝大多数在标准的CGFX shaders中常见的转换矩阵。这是我们使用Surface Shader的一个优点之中的一个,我们不须要自己计算它们了。而只须要调用内置的參数。

    原书并没有给出这一步的解释,我这里补充一下。这个shader的精髓就在于它是像投影一样。全然平铺在Sphere上的。我们能够想象它的本质。就是在Eye Space中,依据顶点法线在X和Y轴上的投影作为UV坐标,对纹理进行採样。

    而作者在这里选择把计算统一到Tangent Space中。因此。这里的tan1和tan2就分别相应了在Tangent Space中。Eye Space的X轴和Y轴方向。

    这样的写法是推导了一步后的结果,原始的计算代码例如以下:

    1. o.tan1 = mul(rotation, mul(float3(1.0f, 0.0, 0.0f), (float3x3)UNITY_MATRIX_IT_MV));
    2. o.tan2 = mul(rotation, mul(float3(0.0f, 1.0, 0.0f), (float3x3)UNITY_MATRIX_IT_MV));

    rotation负责把方向从Object Space转换到Tangent Space。因此我们首先须要把Eye Space的X和Y轴转换到Object Space中, mul(float3(1.0f, 0.0, 0.0f), (float3x3)UNITY_MATRIX_IT_MV)负责这个转换步骤。这是由于。假设我们想要把X轴从Eye Space转换到Tangent Space,就须要从Eye Space到Tangent Space的变换矩阵。即我们希望有UNITY_MATRIX_MV的逆矩阵。

    然而,Unity没有直接提供这个矩阵么认识提供了UNITY_MATRIX_IT_MV,即UNITY_MATRIX_MV的逆转置矩阵。但我们仍然能够通过mul中交换变换矩阵和向量的位置,来得到同样的效果。

    然后在通过外层的mul把方向从Object Space转换到Tangent Space就可以。还是不明确的能够看一下这篇文章

  7. 最后,我们须要实现我们的surf()函数。进行一些计算来产生相应的Sphere map中的查找值,并把它们赋给我们的结构体。

    同样,这个部分将在后面进行说明:

    1. void surf (Input IN, inout SurfaceOutput o)
    2. {
    3. float3 normals = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap));
    4. o.Normal = normals;
    5.  
    6. float2 litSphereUV;
    7. litSphereUV.x = dot(IN.tan1, o.Normal);
    8. litSphereUV.y = dot(IN.tan2, o.Normal);
    9.  
    10. half4 c = tex2D (_MainTex, litSphereUV*0.5+0.5);
    11. o.Albedo = c.rgb * _MainTint;
    12. o.Alpha = c.a;
    13. }
下图显示了上述Shader的渲染结果。

完整代码

  1. Shader "Custom/LitSphere" {
  2. Properties {
  3. _MainTint ("Diffuse Tint", Color) = (1,1,1,1)
  4. _MainTex ("Base (RGB)", 2D) = "white" {}
  5. _NormalMap ("Normal Map", 2D) = "bump" {}
  6. }
  7. SubShader {
  8. Tags { "RenderType"="Opaque" }
  9. LOD 200
  10.  
  11. CGPROGRAM
  12. #pragma surface surf Unlit vertex:vert
  13.  
  14. float4 _MainTint;
  15. sampler2D _MainTex;
  16. sampler2D _NormalMap;
  17.  
  18. inline half4 LightingUnlit (SurfaceOutput s, fixed3 lightDir, fixed atten)
  19. {
  20. half4 c = half4(1,1,1,1);
  21. c.rgb = s.Albedo;
  22. c.a = s.Alpha;
  23. return c;
  24. }
  25.  
  26. struct Input {
  27. float2 uv_MainTex;
  28. float2 uv_NormalMap;
  29. float3 tan1;
  30. float3 tan2;
  31. };
  32.  
  33. void vert (inout appdata_full v, out Input o)
  34. {
  35. UNITY_INITIALIZE_OUTPUT(Input,o);
  36.  
  37. TANGENT_SPACE_ROTATION;
  38. o.tan1 = mul(rotation, UNITY_MATRIX_IT_MV[0].xyz);
  39. o.tan2 = mul(rotation, UNITY_MATRIX_IT_MV[1].xyz);
  40. }
  41.  
  42. void surf (Input IN, inout SurfaceOutput o)
  43. {
  44. float3 normals = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap));
  45. o.Normal = normals;
  46.  
  47. float2 litSphereUV;
  48. litSphereUV.x = dot(IN.tan1, o.Normal);
  49. litSphereUV.y = dot(IN.tan2, o.Normal);
  50.  
  51. half4 c = tex2D (_MainTex, litSphereUV*0.5+0.5);
  52. o.Albedo = c.rgb * _MainTint;
  53. o.Alpha = c.a;
  54. }
  55. ENDCG
  56. }
  57. FallBack "Diffuse"
  58. }

写在最后

上述是一种简单但从视觉上看起来不错的方法来得到复杂的光照效果。这样的技术的缺点就是它不能依据真实的灯光实时更新。它的光源看起来就像是被固定在摄像机朝向的某个位置上,就像贴图在视图上被投影到对象上一样。
以下给出一些关于本小节内容的连接。

【Unity Shaders】Lighting Models —— 灯型号Lit Sphere的更多相关文章

  1. 【Unity Shaders】Lighting Models —— 光照模型之Lit Sphere

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  2. 【Unity Shaders】Lighting Models 介绍

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  3. 【Unity Shaders】Lighting Models —— 衣服着色器

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  4. 【Unity Shaders】Diffuse Shading——创建一个自定义的diffuse lighting model(漫反射光照模型)

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  5. 【Unity Shaders】学习笔记——SurfaceShader(三)BasicDiffuse和HalfLambert

    [Unity Shaders]学习笔记——SurfaceShader(三)BasicDiffuse和HalfLambert 转载请注明出处:http://www.cnblogs.com/-867259 ...

  6. 【Unity Shaders】使用CgInclude让你的Shader模块化——创建CgInclude文件存储光照模型

    本系列主要參考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同一时候会加上一点个人理解或拓展. 这里是本书全部的插图. 这里是本书所需的代码 ...

  7. 【Unity Shaders】法线纹理(Normal Mapping)的实现细节

    写在前面 写这篇的目的是为了总结我长期以来的混乱.虽然题目是"法线纹理的实现细节",但其实我想讲的是如何在shader中编程正确使用法线进行光照计算.这里面最让人头大的就是各种矩阵 ...

  8. 【Unity Shaders】学习笔记——SurfaceShader(十一)光照模型

    [Unity Shaders]学习笔记——SurfaceShader(十一)光照模型 转载请注明出处:http://www.cnblogs.com/-867259206/p/5664792.html ...

  9. 【Unity Shaders】学习笔记——SurfaceShader(十)镜面反射

    [Unity Shaders]学习笔记——SurfaceShader(十)镜面反射 如果你想从零开始学习Unity Shader,那么你可以看看本系列的文章入门,你只需要稍微有点编程的概念就可以. 水 ...

随机推荐

  1. 83.#pragma详解

    创建数据段 //创建数据段 #pragma data_seg("fangfangdata") ; #pragma data_seg() 与数据段连接,实现数据通信,分享 //实现数 ...

  2. 15、python学习手册之:python语句、赋值、表达式和打印

    1.语句的另一个特殊规则是用一对括号把语句括起来就可以:括号().方括号[].字典的大括号{}.任何括在这些符号里的程序代码都可横跨好几行. 2.括号是可以包含一切的,因为任何表达式都可以包含在内,只 ...

  3. Docker---(4)Docker 部署spring web项目

    原文:Docker---(4)Docker 部署spring web项目 版权声明:欢迎转载,请标明出处,如有问题,欢迎指正!谢谢!微信:w1186355422 https://blog.csdn.n ...

  4. 代码高亮显示——google-code-prettify

    先放着,搭建完HEXO博客再来写这篇. https://code.google.com/archive/p/google-code-prettify/

  5. Android 自己定义主菜单

    本文介绍一个超简单的自己定义主菜单.效果例如以下: 原理:事实上就是对原生的Dialog的一个简单的封装.并加上显示和隐藏的动画效果.再给控件加上回调事件. TestDialog.java publi ...

  6. amazeui学习笔记--js插件(UI增强3)--折叠面板Collapse

    amazeui学习笔记--js插件(UI增强3)--折叠面板Collapse 一.总结 注意点: 1.data-am-collapse:这个东西就是展开折叠事件 2.am-collapse(包括其下属 ...

  7. ZOJ 2476 Total Amount 字符串

    http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=1476 题目大意: 给你n串数字组成的字符串,要求输出他们相加的和. 如:n=  ...

  8. matlab 程序发布

    将matlab程序发布为可执行程序包 说明,这种可执行程序包可以在没有安装matlab的计算机上运行. 1. 打开Applicaiton Compler 如果下拉列表中没有这个APPLICATIOND ...

  9. [TypeStyle] Use TypeStyle keyframes to create CSS animations

    We cover CSS keyframes and how to create them using TypeStyle. We then show how to use the keyframes ...

  10. node-sass的安装问题

    1.认识node-sass 我觉得要解决node-sass的问题,你首先至少要简单的了解node-sass是个什么东西?为什么要安装它? 对于在项目中使用sass的语法的时候,需要通过sass-loa ...