unity shader development[6]

56 篇文章 3 订阅

镜面的实现

  在上一章中,我们解释了用于渲染的光照的基本理论,并从头开始在无光照度中实现了一个漫反射着色器。在这一章中,你将学习如何为该着色器添加一个Specular项。

基本照明的计算(第二部分)

  在上一章中,我们学习了漫反射近似的理论;现在轮到Specular 近似的时候了。

镜面

  只有当你的视角恰好与镜面的方向一致时,你才会看到镜面。这使得它依赖于视角。你可以在标准光照图中烘烤漫反射光照,但镜面光需要你在烘烤中使用一些技巧或实时计算。在烘焙中使用一些技巧,或者实时计算。

  我们可以使用的最简单的specular公式之一被称为Phong。

在这里插入图片描述
  这就是反射方向。它可以通过将法线和光线方向的点积乘以2和法线方向,然后减去光线方向而得到。在许多着色器语言中都有一个函数用于此,一般称为反射

清单 6-1. Phong的实现

float3 reflectionVector = reflect (-lightDir, normal);
float specDot = max(dot(reflectionVector, eyeDir), 0.0);
float spec = pow(specDot, specExponent);

  为了实现Phong(见清单6-1),你需要首先计算出镜子的反射方向。然后计算反射方向与视图方向的点积–正如我们所说的,镜面反射是与视图有关。然后你把这个值提升到你在着色器属性中选择的指数的幂。属性中选择的指数。这就控制了镜面的强度。你将在未来的一章中看到这种方法是如何,虽然 松散地基于物理现实,但实际上却违反了许多基于物理的着色规则。当你修正它以符合 PBS 原则时,即使是一个简单的 Phong 也会感觉更真实。

  现在我们已经介绍了Specular项的理论和实现,让我们将其付诸实践把它添加到漫反射着色器中。

你的第一个照明Unity着色器(第二部分)

  在这一节中,你将使用DiffuseShader并为其添加一个specular项。

实现Specular

  让我们创建一个新的材质,叫做SpecularMaterial。复制DiffuseShader,并调用复制的 SpecularShader。记得将着色器路径改为Custom/SpecularShader,否则你会有两个重叠的着色器路径名称。

  着色器的结构是不变的,所以不用担心这个问题。首先,在属性块中添加两个新的属性块。_SpecColor和_Shininess。_SpecColor是镜面的颜色,使用白色作为默认值。默认值。Shininess是镜面的强度,它是一个数字。

Properties
{
	_DiffuseTex ("Texture", 2D) = "white" {}
	_Color ("Color", Color) = (1,0,0,1)
	_Ambient ("Ambient", Range (0, 1)) = 0.25
	_SpecColor ("Specular Material Color", Color) = (1,1,1,1)
	_Shininess ("Shininess", Float) = 10
}

  接下来,向 v2f 结构添加一个成员。 您需要在顶点着色器中计算一个额外的值,然后通过 v2f 将它们传递给片段着色器。 这个额外的值是世界空间顶点位置,我们将其称为 vertexWorld:

struct v2f
{
	float2 uv : TEXCOORD0;
	float4 vertexClip : SV_POSITION;
	float4 vertexWorld : TEXCOORD1;
	float3 worldNormal : TEXCOORD2;
};

  这样做是因为您需要在片段着色器中计算光的方向。 你可以在顶点着色器中做到这一点,这被称为被顶点光照。 但不出所料,如果您在片段着色器中进行光照计算,结果看起来会更好。

  现在,在顶点着色器中填充该值:

v2f vert (appdata v)
{
	v2f o;
	o.vertexClip = UnityObjectToClipPos(v.vertex);
	o.vertexWorld = mul(unity_ObjectToWorld, v.vertex);
	o.uv = TRANSFORM_TEX(v.uv, _DiffuseTex);
	float3 worldNormal = UnityObjectToWorldNormal(v.normal);
	o.worldNormal = worldNormal;
	return o;
}

  在这一行中,我们使用矩阵乘法将局部空间顶点位置转换为世界空间顶点位置。 unity_ObjectToWorld 是您进行此转换所需的矩阵; 它包含在标准库中

  现在您已准备好开始将所需的行添加到片段函数中。 有几个值需要计算,例如归一化的世界空间法线、归一化的视图方向和归一化的光方向(参见清单 6-2)。

清单 6-2。 镜面反射计算所需的值

float3 normalDirection = normalize(i.worldNormal);
float3 viewDirection = normalize(UnityWorldSpaceViewDir(i.vertexWorld));
float3 lightDirection = normalize(UnityWorldSpaceLightDir(i.vertexWorld));

  为了达到最佳效果,所有的向量都需要在转换后进行归一化处理。根据不同的情况,你可能会避免这样做,但你有可能在你的照明中出现不良的伪影。请注意,所有这些值都在 相同的坐标空间,世界空间。

  在这里,我们使用了各种实用函数。 您可以使用 mul 和适当的矩阵,但 Unity 2017.x 无论如何都会用实用程序函数替换它们。 例如,如果您在 Unity 中保存和加载后使用 mul(UNITY_MATRIX_MVP, v.vertex),您会发现它被转换为 UnityObjectToClipPos(v.vertex) 并且此消息添加到文件的顶部:

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

  因此,一开始就不使用这些实用函数是很没有意义的。继续,在漫反射实现之后,你需要将Specular解释中的那些伪代码行翻译成有效的Unity着色器代码(见清单6-3)。

清单 6-3. 镜面计算

float3 reflectionDirection = reflect(-lightDirection, normalDirection);
float3 specularDot = max(0.0, dot(viewDirection, reflectionDirection));
float3 specular = pow(specularDot, _Shininess); 

  首先,你用反射函数找到反射方向。你需要lightDirection的负数,所以它从物体到光线。然后你计算viewDirection和reflectionDirection之间的点积,这与你在漫反射项中用来计算从表面反射多少光线的操作是一样的。

  在漫反射中,它在法线和光线方向之间。在这里,它是在镜面反射方向和视角方向之间,因为镜面项是与视角有关的。请注意,同样,点积值不能为负。点积值不能是负数。你不可能有负的光。

  然后你需要在最终的输出中加入镜面。对于漫反射,你要把它乘以表面的颜色。表面的颜色。对于镜面来说,这相当于乘以镜面的颜色。

float4 specularTerm = float4(specular, 1) * _SpecColor * _LightColor0; 

  你可能已经注意到了,我们并没有像你在做 "漫反射 "时那样,将 "镜面 "乘以 "表面 "的颜色。漫反射。那基本上会使它消失。这里面有一些物理原理,我们将在介绍基于物理的着色时解释。我们将在介绍基于物理的着色时解释。

清单6-4显示了完整的片段着色器。
最后,整个着色器显示在清单6-5中,以方便你使用。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
  这就结束了对非基于物理的Phong specular的介绍。到现在为止,你只在场景中支持一个灯光。场景中只支持一个灯光,但是通过添加一个ForwardAdd pass,你可以在一个场景中支持任意数量的 场景中的任何数量的灯光。让我们为这个着色器添加一个ForwardAdd通道。

支持不止一盏灯

  你需要注意的一件事是确保ForwardAdd是一个单独的通道,因为你不希望 因为你不希望做像添加环境这样的事情超过一次。复制镜面着色器,并将着色器的路径改为 Custom/SpecularShaderForwardAdd。

  你需要把目前停留在sub-shader中的标签和其他信息移到Pass中。并复制和粘贴当前的pass。将#pragma multi_compile_fwdbase加在ForwardBase pass的其他pragma之后(见清单6-6)。ForwardBase传递中的其他pragma之后(见清单6-6)。

在这里插入图片描述
  然后我们应该把第二个Pass的Tag改成ForwardAdd,在标签后面加上Blend One One一行,并在标签,并添加pragma #pragma multi_compile_fwdadd(见清单6-7)。

  ForwardAdd告诉编译器它应该在第一道光之后使用这道光,而Blend One One则设置了混合模式。混合模式基本上类似于Photoshop中的图层模式。我们有不同的图层,由不同的通道渲染,我们希望以一种合理的方式将它们混合在一起。请记住,混合模式比Photoshop中的模式要简单得多。计算公式是Blend SrcFactor DstFactor;我们对两个因素都使用了1,这意味着颜色是加法混合的。

  这些谚语是为了利用自动多编译系统的优势,该系统会编译所有的着色器的所有变体,这些变体需要特定的通道来工作。

清单6-7。为ForwardAdd通道进行设置
在这里插入图片描述
  这已经足够了,有了灯光后,首先影响的是结果。最后的修饰是将 _Ambient作为ForwardAdd通道中的最小值,这意味着我们不会再添加两次或更多的环境。(见清单6-8)。

清单6-8。避免将环境术语重新添加到ForwardAdd Pass中

//Diffuse implementation (Lambert)
float nl = max(0.0, dot(normalDirection, lightDirection));

  让我们尝试一下,在项目中添加一个不同的灯光,也许用两种不同的颜色来区分这两个灯光。然后将应用于鸭子的着色器改为图6-2中所示的新着色器。

在这里插入图片描述
  为了方便起见,清单6-9显示了生成的完整着色器,你可以看到它相当长 因为代码是重复的。

总结

  在本章中,你实现了最简单的Specular版本,Phong。然后你在着色器中加入了对 然后你在着色器中加入了对多个灯光的支持,由于代码的重复,这导致了一个很长的着色器。

  下一章将介绍表面着色器,它可以让你省去一些 的工作,并且在支持多个灯光时可以节省大量的代码行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值