【Unity Shaders】Lighting Models —— 光照模型之Lit Sphere
本系列主要参考《Unity Shaders and Effects Cookbook》一书(感谢原书作者),同时会加上一点个人理解或拓展。
这里是本书所有的插图。这里是本书所需的代码和资源(当然你也可以从官网下载)。
========================================== 分割线 ==========================================
写在前面
准备工作
- 创建一个新的场景以及一些对象,一个平面以及一个平行光;
- 创建一个新的Shader 和材质,然后将你的Shader赋给新的材质。
实现和解释
- 和之前一样,我们需要一些properties传递给Surface Shader,以便我们可以让这个Shader的用户更改贴图以及一些变量。因此,我们首先添加下面的代码到Properties块:
Properties {
_MainTint ("Diffuse Tint", Color) = (1,1,1,1)
_MainTex ("Base (RGB)", 2D) = "white" {}
_NormalMap ("Normal Map", 2D) = "bump" {}
} - 因为我们的Shader仅使用贴图来照亮我们的模型,因此我们不需要内置的Lambert光照函数,而需要声明我们自己的Unlit光照函数。我们还需要写一个顶点函数:
CGPROGRAM
#pragma surface surf Unlit vertex:vert解释:鉴于有些童鞋忘了或者没有看过之前的内容,这里说明一下上面这句声明的意思。它表明我们将使用名为surf的Surface Shader function,以及名为Unlit的自定义光照函数,还有一个名为vert的顶点函数。本书第一次提到这个知识点是在这一节。
- 和前面一样,我们需要在块中声明之前的properties,以便我们可以利用Inspector中的各个用户给定的数据。
float4 _MainTint;
sampler2D _MainTex;
sampler2D _NormalMap; - 接下来,我们创建一个新的光照函数,它对应上面提到的名为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;
}解释:同样,关于这里的内容可以参见这一节。
- 现在,我们需要将一些额外的属性加入到Input结构体中,以便我们可以将这些信息从vert()函数传递给surf()函数:
struct Input {
float2 uv_MainTex;
float2 uv_NormalMap;
float3 tan1;
float3 tan2;
};解释:你可以把Input结构体当成是顶点函数和Surface shading函数之间的纽带,它们之间的沟通都是靠Input结构体进行传递的。
- 为了正确的在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即可。还是不明白的可以看一下这篇文章。
- 最后,我们需要实现我们的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 "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的更多相关文章
- 【Unity Shaders】Lighting Models —— 灯型号Lit Sphere
考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同一时候会加上一点个人理解或拓展. 这里是本书全部的插图.这里是本书所需的代码和资源(当然你 ...
- 【Unity Shaders】Lighting Models 介绍
本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...
- Unity Shader——Writing Surface Shaders(2)——Custom Lighting models in Surface Shaders
Surface Shader中的自定义光照模型 当你在编写 Surface Shaders 时,是在描述一个表面的属性(反射颜色.法线……),而且光的交互过程是由一个光照模型来计算的.内建的光照模型有 ...
- 【Unity Shaders】Lighting Models —— 衣服着色器
本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...
- 【Unity Shaders】Diffuse Shading——创建一个自定义的diffuse lighting model(漫反射光照模型)
本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...
- 【Unity Shaders】使用CgInclude让你的Shader模块化——创建CgInclude文件存储光照模型
本系列主要參考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同一时候会加上一点个人理解或拓展. 这里是本书全部的插图. 这里是本书所需的代码 ...
- 【Unity Shaders】学习笔记——SurfaceShader(十一)光照模型
[Unity Shaders]学习笔记——SurfaceShader(十一)光照模型 转载请注明出处:http://www.cnblogs.com/-867259206/p/5664792.html ...
- 【Unity Shaders】学习笔记——SurfaceShader(三)BasicDiffuse和HalfLambert
[Unity Shaders]学习笔记——SurfaceShader(三)BasicDiffuse和HalfLambert 转载请注明出处:http://www.cnblogs.com/-867259 ...
- 【Unity Shaders】法线纹理(Normal Mapping)的实现细节
写在前面 写这篇的目的是为了总结我长期以来的混乱.虽然题目是"法线纹理的实现细节",但其实我想讲的是如何在shader中编程正确使用法线进行光照计算.这里面最让人头大的就是各种矩阵 ...
随机推荐
- Spring MVC页面重定向
以下示例显示如何编写一个简单的基于Web的重定向应用程序,这个应用程序使用重定向将http请求传输到另一个页面. 基于Spring MVC - Hello World实例章节中代码,创建创建一个名称为 ...
- PTA 社交网络图中结点的“重要性”计算(30 分)
7-12 社交网络图中结点的“重要性”计算(30 分) 在社交网络中,个人或单位(结点)之间通过某些关系(边)联系起来.他们受到这些关系的影响,这种影响可以理解为网络中相互连接的结点之间蔓延的一种相互 ...
- 保留键的情况下取字典中最大的值(max\zip函数的联合使用)
在我们平常想要获取字典中value最大或者最小的值的时候,常常使用如下函数: testDict = {"age1":18,"age2":20,"age ...
- MySQL备忘录
1 数据库概念(了解) 1.1 什么是数据库 数据库就是用来存储和管理数据的仓库! 数据库存储数据的优先: l 可存储大量数据: l 方便检索: l 保持数据的一致性.完整性: l 安全,可共享: l ...
- Linux下文件的mtime/atime/ctime研究
概述 在Linux下,对于某一个文件或文件夹时间的描述有三种:文件修改时间mtime,文件访问时间atime,文件状态改变时间ctime.在Linux下无法获取到文件的创建时间,因为根本就没有保存这个 ...
- Weblogic 12c 集群部署和session复制
在上一篇Weblogic12c集群搭建的基础上,这一篇介绍Weblogic12c集群应用的部署和session复制. 1.启动服务 首先在weblogic12c控制台,启动受托管服务server1.s ...
- Python实现八大排序算法(转载)+ 桶排序(原创)
插入排序 核心思想 代码实现 希尔排序 核心思想 代码实现 冒泡排序 核心思想 代码实现 快速排序 核心思想 代码实现 直接选择排序 核心思想 代码实现 堆排序 核心思想 代码实现 归并排序 核心思想 ...
- ROS机器人程序设计(原书第2版)学习镜像分享及使用说明
ROS机器人程序设计(原书第2版)学习镜像分享及使用说明 系统用于ROS爱好者学习交流,也可用于其他用途,并不局限于ROS. 这款镜像文件是基于一年前的Ubuntu ROS Arduino Gazeb ...
- WmS详解(一)之token到底是什么?基于Android7.0源码
做Android有些年头了,Framework层三大核心View系统,WmS.AmS最近在研究中,这三大块,每一块都够写一个小册子来介绍,其中View系统的介绍,我之前有一个系列的博客(不过由于时间原 ...
- Dynamics CRM2016 时间字段属性中的新增行为
之前的博客中有特地介绍过CRM中的时间字段以及它在不同的应用场景中涉及的时制转换,而CRM2016又给时间字段添加了新的行为,具体见下属截图,简单介绍下每个图中对应的行为的意思,最后会做demo来具体 ...