untiy 3d ShaderLab_第6章_VertexLit渲染路径_4_顶点照明和Unity存放光源的第三种方式

6.4顶点照明和Unity存放光源的第三种方式

6.4.1   Unity为Vertex Pass准备的光源

是不是绝望了?世界没有光明怎么能行呢?当然不行,光明马上就来,Unity把它放到了unity_LightPosition[4]数组中。

可以简单地告诉你一个结果:在LightMode = Vertex的Pass内,unity_LightPosition[4]和unity_LightColor[4]是存取光源数据最可靠的第一首选手段,无论在Camera的RenderingPath为何,VertexLit,Forward还是Deferred,Unity都会及时、准确地更新此数组中的光源信息。

6.4.2设计用于检测的场景

打开Lab_ 3文件夹下的场景,其编辑器中Game视日的截图所示。上面蓝色区域的右下角是6个表明了灰度颜色和数字之间关系的图例,而最左边是场景中4个点光源在WorldSpace中的xyz坐标,在上面的是4个点光源的3个灰度材质球的表示,在下面的是4个点光源的一般数字(x,
y, x)表示。从上往下依次是黄色点光源Yellow (0.7,  0.5,  0.2)、绿色点光源Green (0.7 ,0.5 , 0.5、红色点光源Red ( 0.5 ,0.2, 0.2)以及蓝色点光源B1ue(0.2, 0.5,0.7)。之所以这样从黄色到蓝色排列,是因为我们可以根据Unity计算光照强度时所使用的Lunimance函数,来预测这4个颜色的点光源在unity_LightPosition[4]数组中的先后顺序。在这组灰度和数字对光源的世界坐标的表示的右边,是相对应的对光源在Camera空间的灰度和数字表示。因为场景中相机的坐标为(-0.3,-0.3,-0.3
) ,而且相机没有旋转,因此我们可以很简单地算出这4个点光源在Camera空间的xyz坐标,黄色点光源Yellow(1.0,0.8, 0.5)、绿色点光源Green(1.0,0.8 ,0.8 )、红色点光源Red (0.8,0.5,0.5)以及蓝色点光源(0.5,0.8,1.0) 4个点光源的WorldSpace和CameraSpace坐标都在(0,1)区间,可以被屏幕上的灰度正确表示,而且如果unity_LightPosition[4]中4个点光源的坐标是CameraSpace中的表示,那么灰度表示应该整体加亮0.3。将unity_LightPosition[4]数组中的每个向量的每个分量输出列Labes
3/ Shader文件夹下,如前两节一样都很简单,只是将对应的xyzw分量输出成灰度而己。除此之外,还输出了unity_LightAtten[4]数组到倒数第二排球体上,unity LightColor[4]到倒数第一排球体上。

6.4.3顶点照明中的点光源

首先我们将此场景的两个平行光关闭,简化下一步要分析问题的复杂性,然后将Camera的RenderingPath分别设为VertexLit、Forward、Deferred,并分别编译运行一下。我的测试结果是在这3种RenderingPath下,LightMode = Vertex的Pass内,unity_LightPosition[4]以及unity
LightColor[4]均包含正确的数据。而且根据灰度图的编码,我们可以很确定在这3种RenderingPath下,LightMode = Vertex的Pass内,unity_LightPosition[4]的数据都是Camera Space的坐标。

6.4.4计算顶点照明的ShaderVertexLights函数

在UnityCg.cginc中有一个函数引用了此变员,此函数如下:

float3 ShadeVertexLights(float4 vertex,float3 normal)
{
        float3 viewpos = mul(UNITY_MATRIX_MV,vertex).xyz;
        float3 viewN = mul((float3x3)UNITY_MATRIX_IT_MV,normal);
        float3 lightColor = UNITY_LIGHTMODEL_AMBIENT.xyz;
        for(int i=0;i<4;i++){
         float3 toLight = unity_LightPosition[i].xyz - viewpos.xyz*unity_LightPosition[i].w;
         float lengthSq = dot(toLight,toLight);
         float atten = 1.0 / (1.0+lengthSq * unity_LightAtten[i].z);
         float diff = max(0,dot(viewN,normalize(toLight)));
         LightColor += unity_LightColor[i].rgb * (diff * atten);

        }
        return lightColor;

}

首先我们可以发现顶点被这个矩阵UNITY_MATRIX_MV变换了,这说明float4 unity_LightPosition[4]中存储的确实是视空间中的光源估息,也就是当前Camera空间的光源的方向矢量或者位置。注意这个函数计算衰减的方式,如果你想让自己手动写的Shade对光照计算的结果和Unity一致,那么对衰减采用同样的计算方式很重要。

6.4.5顶点照明中的Pixel光源

现在的问题是:unity_LightPosition[4]只对Vertex点光源起作用么,对Direction光源起作用么,如果起作用,对应的方I句向量是World Space还是Camera Space。如果Camera有旋转的话,这一点很重要。我们现在把刚刚Build出来的程序再运行一次,改变光源的Pixel还是Vertex特性,我们会发现,在3个RenderingPath下
, unity_LightPosition[4]都有及时的更新,而且对于Pixel光源,其在unity_LightPosition[4]中的位置更靠前。

6.4.6顶点照明中的平行光

现在的问题是:unity_ LightPosition[4]对平行光起作用么?我们可以继续试一下,把某个点光源改为平行光,或者直接启用两个已经存在的平行光,我们会发现,在3个RenderingPath下,unity_LightPosition[4]和unity_LightColor[4]都有及时的光源数据。而你在实际操作时还会发现,在同为Pixel或者Vertex的情况下,平行光在unity_LightPosition[4]和unity_LightColor[4]中的排序更为靠前。而且我们所有的光源都有一个优点,就是它们的欧拉角都是(30,
300, 0),这个角度之所以好,因为它们表示为方向矢量之后都处于(0,1)区间,处于我们的屏幕色彩的(0,1 )表示范围内。

现在剩下的唯一问题就是如果是平行光,那么unity_LightPosition[4]中对应的方向矢量是在世界坐标还是CameraSpace内的表示,这个问题不像位置向量那么简单直观。仔细想想,其实也不是太难。

打开Lab 4文件夹下的场景,这个场景的构成基本和上一个测试场景一致,不同的是我将4个点光源都改成了平行光,然后将其中的红色和蓝色两个平行光光源绑定到相机下面成为其子物体,另外两个黄色和绿色的平行光则仍然留在世界坐标中。这样,如果unity_LightPosition[4]中的平行光方向矢量是相对于世界坐标而言的,那么当我们旋转相机时,黄色和绿色两个平行光的指示信号应该不会发生强弱变化,而红色和蓝色两个平行光的指示信号应该会对应做出信号强弱的变化。如果unity_LightPosition[4]中的平行光力方向向量为CameraSpace,则情况相反。场景的组织结构和初识状态如图6.11所示。

这次我们可以直接在编辑器中做这个测试,这样就剩下添加滑动条来控制相机角度的麻烦,

而且我保证结果可靠。

我们在编辑器中的任意一个相机角度旋转结果。结果显示,留在世界坐标系中的两个平行光的方向向量的指示信号在旋转相机时发生了变化,而作为Camera子物体的两个平行光的指示信号,则在Camera发生旋转时没有发生强弱变化。现在我可以负责任地告诉各位,在unity
LightPosition[4]中的平行光方向向量也是在CameraSpace 中的。

6.4.7顶点照明中的灯光信息小结

对于LightMode=Vertex的Pass来说,有效存取光源的方法是读取unity_LightPosition[4]和unity_LightColor[4],这两个数组可以保证在Unity的3个RenderingPath下都有效工作。需要注意的是,unity_LightPosition[4]中的点光源的位置向量和平行光的方向向量都处于CameraSpace中。如果各位想写一个只逐Vertex照明的Shader,又不想操作这些恼人的数组,可以直接使用UnityCG.cginc中的ShadeVertexLights函数。

其次需要牢记点,Unity不会针对不同的Pass及时清除一些无效数据,你可能会在unity_LightPos[X, Y,Z]0和_WorldSpaceLightPos0中取到某一个物体最后一次执行其他类型的Pass时残留的光源数据,所以在LightMode为Vertex的Pass内不要使用它们来进行光照计算。

6.4.8一个顶点照明的实现例子

当然,上面这种在比较抽象的使用颜色信号方式令人头大,没事,我还有一个在色彩上更直观的版本在Lighting/Lab_3c文件夹下面。该文件夹下的场景使用到了_Indicator文件夹里的unity_LightPosition_O.shader、unity_LightPosition_1.shader、unity_LightPosition_2.shader以及unity_LightPosition_3.shader,这4个材质对unity_LightPosition[4]数组里的光源在Vertex
Pass内做了一个简单的渲染,可以通过绿色控制面板上的按钮操作验证一下,其运行时的一个截图如图6.13

所示。

unity-LightPosition_ O.shader的代码如下:

hader "Tut/Lighting/VertexLit/Indicator/unity_LightPosition_0" {
	Properties {
		_Color ("Base Color", Color) =(1,1,1,1)
	}
	SubShader {
		pass{
		Tags{ "LightMode"="Vertex"}
		CGPROGRAM
		#pragma vertex vert
		#pragma fragment frag
		#include "UnityCG.cginc"
		#include "Lighting.cginc"

		uniform float4 _Color;

		struct vertOut{
			float4 pos:SV_POSITION;
			float4 color:COLOR;
		};
		vertOut vert(appdata_base v)
		{

			//unity_LightPosition in viewSpace,ie,the camera space
			float3 viewpos = mul (UNITY_MATRIX_MV, v.vertex).xyz;
			float3 viewN = mul ((float3x3)UNITY_MATRIX_IT_MV, v.normal);
			float3 lightColor = UNITY_LIGHTMODEL_AMBIENT.xyz;

			float3 toLight = unity_LightPosition[0].xyz - viewpos.xyz * unity_LightPosition[0].w;
			float lengthSq = dot(toLight, toLight);
			float atten = 4.0 / (1.0 + lengthSq * unity_LightAtten[0].z);
			float diff = max (0, dot (viewN, normalize(toLight)));
			lightColor += unity_LightColor[0].rgb * (diff * atten);

			vertOut o;
			o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
			o.color=float4(lightColor,1)*_Color;
			return o;
		}
		float4 frag(vertOut i):COLOR
		{
			return i.color;
		}
		ENDCG
		}//end pass
	}
}

时间: 2024-08-04 16:35:31

untiy 3d ShaderLab_第6章_VertexLit渲染路径_4_顶点照明和Unity存放光源的第三种方式的相关文章

untiy 3d ShaderLab_第7章_ Forward渲染路径_1_ForwardBase和ForwardAdd

第7章 Forward渲染路径 本章的主要目的是让大家在不使用Unity的Surface Shader时,也能够正确地存取Unity的光源数据,从而定制自己的Forward渲染路径的Shader.如果读者没有这个需求,或者己经了解了Unity是如何为Forward渲染路径存放光源数据的,那么就可以跳过本章. 7.1ForwardBase和ForwardAdd ForwardBase和ForwardAdd是专门为在Forward渲染路径下渲染物体而设计的两种Pass,其中ForwardBase会先

untiy 3d ShaderLab_第7章_ Forward渲染路径_2_Forward渲染路径下的重要光源

第7章 Forward渲染路径 本章的主要目的是让大家在不使用Unity的Surface Shader时,也能够正确地存取Unity的光源数据,从而定制自己的Forward渲染路径的Shader.如果读者没有这个需求,或者己经了解了Unity是如何为Forward渲染路径存放光源数据的,那么就可以跳过本章. 7.2Forward渲染路径下的重要光源 7.2.1 设计检测用的材质 其编辑器截图如图所示, _WorldSpaceLightPosO.x.shader文件,其主要代码如下: Shader

untiy 3d ShaderLab_第8章_3_ 单光贴图和Forward 渲染路径

8.3 单光贴图和Forward 渲染路径 8.3.1单光照贴图在VertexLit和Forward下面的不同表现 在单光照贴图的情况下,Camera的RenderingPath为VertexLit时,有一个不理想的地方就是被烘焙过的静态物体,默认的材质不会受到实时光照的影响.当然,可以通过提供自定义的材质改变这一行为,但是很麻烦.在RenderingPath为Forward时,这种麻烦就不会存在了,经过烘焙物体,Unity的默认材质会继续受到实时Pixel光源的影响. 8.3.2 准备可应用于

untiy 3d ShaderLab_第7章_ Forward渲染路径_4_Forward渲染路径总结

Forward渲染路径总结 现在可以总结一卜适应于Forward渲染路径的ForwardBase和ForwardAdd的情况. 7.4.1   Forward渲染路径下材质的适应性 这两类Pass不能在Camera为VertexLit时被演染,但可以被渲染路径为Forward和Deferred的Camera渲染. 7.4.2  Unity如何为Forward渲染路径设置光源 在ForwardBase的Pass内,_WorldSpaceLightPos0和_LightColor0只会含有第一个Pi

untiy 3d ShaderLab_第7章_ Forward渲染路径_3_重要光源在ForwardAdd内的执行

7.3重要光源在ForwardAdd内的执行 此外我们知道,根据Unity在关于Forward RenderingPath的官方帮助文档的陈述,除了第一个Pixel平行光之外,每个其他Pixel光源都会在个单独的ForwardAdd Pass内被执行,也就是说场景中的Pixel光源数为n,则ForwardAdd Pass会被执行n-1次.下面就来验证每一个其他Pixel光源是在ForwardAdd Pass内被执行的. 7.3.1设计用来检测Pixel光源的材质 Lab_1c文件夹下的场景,其编

第七章、函数基础之定义函数的三种方式03

目录 第七章.函数基础之定义函数的三种方式03 一.无参函数 二.有参函数 三.空函数 第七章.函数基础之定义函数的三种方式03 一.无参函数 定义函数时参数是函数体接收外部传值的一种媒介 在函数阶段括号没有参数就是无参函数.调用时不需要传入实参 如果函数体代码逻辑不需要依赖外部传入值,必须定义成无参函数 def func(): print('hello nick') func() # hello nick 二.有参函数 在函数阶段括号有参数就是有参函数.调用时需要传入实参 如果函数体代码逻辑需

第六章、创建多对多关系的三种方式

目录 第六章.创建多对多关系的三种方式 一.创建多对多关系的三种方式 1. 全自动(推荐指度**) 2. 纯手动(不推荐) 3. 半自动(******) 第六章.创建多对多关系的三种方式 一.创建多对多关系的三种方式 1. 全自动(推荐指度**) 全自动:利用 ManyToManyField 让 django 自动创建第三张表 优点:不需要手动创建第三张表 不足:由于第三张表不是你手动创建的,也就意味着第三张表中字段是固定的,无法做扩展 class Book(models.Model): ...

【REACT NATIVE 系列教程之四】刷新组件RENDER(重新渲染)的三种方式详解

本站文章均为 李华明Himi 原创,转载务必在明显处注明: 转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/react-native/2242.html 开发过游戏的都应该很清楚,"刷屏"是多么的重要.其实开发应用也如此,当组件的数据被修改后,如何及时更新组件呈现出最新的数据与效果一样至关重要. 那么这里Himi大概讲三种常用的方式: this.setState()  [最为常用] 这是在事件处理函数中和请求回调函数中触发 UI 更新的主要

Spring第三章:创建对象的三种方式

Spring 创建对象的三种方式 1. 通过构造方法创建 1.1 无参构造创建:默认情况. 1.2 有参构造创建:需要明确配置 1.2.1 需要在类中提供有参构造方法 package com.bjsxt.pojo; public class People { private int id; private String name; /** *注意这里一旦使用了有参的构造器之后就必须生成这个 * 无参的构造器不然spring会报错No matching constructor found in c