本系列主要参考《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块:
    	Properties {
    _MainTint ("Diffuse Tint", Color) = (1,1,1,1)
    _MainTex ("Base (RGB)", 2D) = "white" {}
    _NormalMap ("Normal Map", 2D) = "bump" {}
    }
  2. 因为我们的Shader仅使用贴图来照亮我们的模型,因此我们不需要内置的Lambert光照函数,而需要声明我们自己的Unlit光照函数。我们还需要写一个顶点函数:
    		CGPROGRAM
    #pragma surface surf Unlit vertex:vert

    解释:鉴于有些童鞋忘了或者没有看过之前的内容,这里说明一下上面这句声明的意思。它表明我们将使用名为surf的Surface Shader function,以及名为Unlit的自定义光照函数,还有一个名为vert的顶点函数。本书第一次提到这个知识点是在这一节

  3. 和前面一样,我们需要在块中声明之前的properties,以便我们可以利用Inspector中的各个用户给定的数据。
    		float4 _MainTint;
    sampler2D _MainTex;
    sampler2D _NormalMap;
  4. 接下来,我们创建一个新的光照函数,它对应上面提到的名为Unlit的光照模型。这样做是因为我们不想使用场景中的灯光影响我们的Shader。我们仅仅想要得到对象的阴影,其他的交给贴图来照亮整个对象。因此,我们需要添加下面的光照函数(这里跟原书有一点不一样,我根据官方文档修改了返回类型):
    		inline half4 LightingUnlit (SurfaceOutput s, fixed3 lightDir, fixed atten)
    {
    half4 c = half4(1,1,1,1);
    c.rgb = s.Albedo;
    c.a = s.Alpha;
    return c;
    }

    解释:同样,关于这里的内容可以参见这一节

  5. 现在,我们需要将一些额外的属性加入到Input结构体中,以便我们可以将这些信息从vert()函数传递给surf()函数:
    		struct Input {
    float2 uv_MainTex;
    float2 uv_NormalMap;
    float3 tan1;
    float3 tan2;
    };

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

  6. 为了正确的在Sphere map中查找,我们需要将切线旋转矩阵(the rotated tangent vector)和当前模型的逆转置模型视图矩阵(the inverse transpose model view matrix)相乘。这将会给你合适的想来应用到Sphere map贴图中。如果你还是不明白也不要紧,在后面我们会进一步说明。
    		void vert (inout appdata_full v, out Input o)
    {
    UNITY_INITIALIZE_OUTPUT(Input,o); TANGENT_SPACE_ROTATION;
    o.tan1 = mul(rotation, UNITY_MATRIX_IT_MV[0].xyz);
    o.tan2 = mul(rotation, UNITY_MATRIX_IT_MV[1].xyz);
    }

    解释: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轴方向。这种写法是推导了一步后的结果,原始的计算代码如下:

    o.tan1 = mul(rotation, mul(float3(1.0f, 0.0, 0.0f), (float3x3)UNITY_MATRIX_IT_MV));
    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中的查找值,并把它们赋给我们的结构体。同样,这个部分将在后面进行说明:
    		void surf (Input IN, inout SurfaceOutput o)
    {
    float3 normals = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap));
    o.Normal = normals; float2 litSphereUV;
    litSphereUV.x = dot(IN.tan1, o.Normal);
    litSphereUV.y = dot(IN.tan2, o.Normal); half4 c = tex2D (_MainTex, litSphereUV*0.5+0.5);
    o.Albedo = c.rgb * _MainTint;
    o.Alpha = c.a;
    }
下图显示了上述Shader的渲染结果。

完整代码

Shader "Custom/LitSphere" {
Properties {
_MainTint ("Diffuse Tint", Color) = (1,1,1,1)
_MainTex ("Base (RGB)", 2D) = "white" {}
_NormalMap ("Normal Map", 2D) = "bump" {}
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200 CGPROGRAM
#pragma surface surf Unlit vertex:vert float4 _MainTint;
sampler2D _MainTex;
sampler2D _NormalMap; inline half4 LightingUnlit (SurfaceOutput s, fixed3 lightDir, fixed atten)
{
half4 c = half4(1,1,1,1);
c.rgb = s.Albedo;
c.a = s.Alpha;
return c;
} struct Input {
float2 uv_MainTex;
float2 uv_NormalMap;
float3 tan1;
float3 tan2;
}; void vert (inout appdata_full v, out Input o)
{
UNITY_INITIALIZE_OUTPUT(Input,o); TANGENT_SPACE_ROTATION;
o.tan1 = mul(rotation, UNITY_MATRIX_IT_MV[0].xyz);
o.tan2 = mul(rotation, UNITY_MATRIX_IT_MV[1].xyz);
} void surf (Input IN, inout SurfaceOutput o)
{
float3 normals = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap));
o.Normal = normals; float2 litSphereUV;
litSphereUV.x = dot(IN.tan1, o.Normal);
litSphereUV.y = dot(IN.tan2, o.Normal); half4 c = tex2D (_MainTex, litSphereUV*0.5+0.5);
o.Albedo = c.rgb * _MainTint;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}

写在最后

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

【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 Shader——Writing Surface Shaders(2)——Custom Lighting models in Surface Shaders

    Surface Shader中的自定义光照模型 当你在编写 Surface Shaders 时,是在描述一个表面的属性(反射颜色.法线……),而且光的交互过程是由一个光照模型来计算的.内建的光照模型有 ...

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

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

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

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

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

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

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

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

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

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

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

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

随机推荐

  1. Spring MVC页面重定向

    以下示例显示如何编写一个简单的基于Web的重定向应用程序,这个应用程序使用重定向将http请求传输到另一个页面. 基于Spring MVC - Hello World实例章节中代码,创建创建一个名称为 ...

  2. PTA 社交网络图中结点的“重要性”计算(30 分)

    7-12 社交网络图中结点的“重要性”计算(30 分) 在社交网络中,个人或单位(结点)之间通过某些关系(边)联系起来.他们受到这些关系的影响,这种影响可以理解为网络中相互连接的结点之间蔓延的一种相互 ...

  3. 保留键的情况下取字典中最大的值(max\zip函数的联合使用)

    在我们平常想要获取字典中value最大或者最小的值的时候,常常使用如下函数: testDict = {"age1":18,"age2":20,"age ...

  4. MySQL备忘录

    1 数据库概念(了解) 1.1 什么是数据库 数据库就是用来存储和管理数据的仓库! 数据库存储数据的优先: l 可存储大量数据: l 方便检索: l 保持数据的一致性.完整性: l 安全,可共享: l ...

  5. Linux下文件的mtime/atime/ctime研究

    概述 在Linux下,对于某一个文件或文件夹时间的描述有三种:文件修改时间mtime,文件访问时间atime,文件状态改变时间ctime.在Linux下无法获取到文件的创建时间,因为根本就没有保存这个 ...

  6. Weblogic 12c 集群部署和session复制

    在上一篇Weblogic12c集群搭建的基础上,这一篇介绍Weblogic12c集群应用的部署和session复制. 1.启动服务 首先在weblogic12c控制台,启动受托管服务server1.s ...

  7. Python实现八大排序算法(转载)+ 桶排序(原创)

    插入排序 核心思想 代码实现 希尔排序 核心思想 代码实现 冒泡排序 核心思想 代码实现 快速排序 核心思想 代码实现 直接选择排序 核心思想 代码实现 堆排序 核心思想 代码实现 归并排序 核心思想 ...

  8. ROS机器人程序设计(原书第2版)学习镜像分享及使用说明

    ROS机器人程序设计(原书第2版)学习镜像分享及使用说明 系统用于ROS爱好者学习交流,也可用于其他用途,并不局限于ROS. 这款镜像文件是基于一年前的Ubuntu ROS Arduino Gazeb ...

  9. WmS详解(一)之token到底是什么?基于Android7.0源码

    做Android有些年头了,Framework层三大核心View系统,WmS.AmS最近在研究中,这三大块,每一块都够写一个小册子来介绍,其中View系统的介绍,我之前有一个系列的博客(不过由于时间原 ...

  10. Dynamics CRM2016 时间字段属性中的新增行为

    之前的博客中有特地介绍过CRM中的时间字段以及它在不同的应用场景中涉及的时制转换,而CRM2016又给时间字段添加了新的行为,具体见下属截图,简单介绍下每个图中对应的行为的意思,最后会做demo来具体 ...