渲染8——反射

采样环境光
使用反射探针
创建粗糙和光滑镜子
立方体贴图采样
混合反射探针
本节是渲染课程的第八节。前一小结介绍的是阴影,本节主要介绍间接反射。
本课程使用unity版本5.4.0f3
这里写图片描述

1 环境映射
目前我们的shader对每个像素着色使用了环境光、漫反射、以及镜面反射。使用这三种技术,可以创建出很逼真的画面。但是这仅仅是对那些物体表面比较暗,不是很光滑才能很好的效果。但是对于表面光滑的物体表面就不那么理想了。

这里补充下environment light 和ambient light的是不是一个概念。在unity的https://docs.unity3d.com/Manual/GlobalIllumination.html
中介绍到:
diffuse environmental light also known as ambient light is light that is present all around the scene and does not come from any specific source object. the default value is skybox.
可以知道这两者是同一个意思。

锃亮的表面就像镜子那样。一块完美的镜子会反射所有的光线。这意味着没有任何的漫反射,仅仅有镜面反射。我们通过调节Metallic为1,Smoothness为0.95,其效果如下:
这里写图片描述
尽管这个球的漫反射的颜色设置为白色,但是表现出来的是黑色,除了一点的亮色。如果我们把Smoothness调节为1,那么连这个高光的白点也没有了。

这个并不像真正的镜子。镜子不会使全黑的,他会反射一切的东西。在这种情况下,他会反射天空盒,反射出来的是天空还有灰色的大地。

1.1 间接光反射
我们的球体表现出黑色,是因为我们仅仅包含了直接光。为了反射环境光,我们还要把间接光包含进去。很重要的一点是,这里使用间接光做镜面反射。在CreateIndirectLight函数中,我们自己配置了unity提供的UnityIndirect结构体的变量,都赋值了0。这就是为啥球体是黑色的原因了。
再次说明下,就是我们之前只计算了直接光,没有计算间接光。又因为设置Metallic=1,Smoothness=1,要明白这两个属性的含义。Metallic=1,表明只有镜面反射,ok,直接光的漫反色没有了。而Smoothness=1,表明直接光的镜面反射区域也为0,所以最终表现出来,直接光的返回的结果为黑色。

把场景的环境光强度设置为0,这样可以集中在镜面反射。我们再把材质设置为比较暗的材质,把Smoothness设置为0.5。然后计算间接光的镜面反射,这里为了明显,把间接光的镜面反射设置为红色。

UnityIndirect CreateIndirectLight (Interpolators i) {
	UnityIndirect indirectLight;
	indirectLight.diffuse = 0;
	indirectLight.specular = 0;

	#if defined(VERTEXLIGHT_ON)
		indirectLight.diffuse = i.vertexLightColor;
	#endif

	#if defined(FORWARD_BASE_PASS)
		indirectLight.diffuse += max(0, ShadeSH9(float4(i.normal, 1)));
		indirectLight.specular = float3(1, 0, 0);
	#endif

	return indirectLight;
}

在这里插入图片描述
球体拾取了红色。在这种情况下,红色反应了反射情况。我们的球体从中心反射环境光。很显然,它在编译反射的会更明显。这是因为当视角越浅反射越强。当在注视角的时候,反射越强,此时物体就会变成一个镜子一样。这个就是Fresnel反射。我们使用的使用的函数UNITY_BRDF_PBS 就是使用使用这个方式计算的。

1.2 采样环境光
为了反射环境光,我们需要采用天空和立方体贴图。unity把这个数据存储在unity_SpecCube0,此变量定义在UnityShaderVariables中。

采样立方体贴图,要使用一个3D向量,这个标记的是采样的方向向量。我们可以使用UNITY_SAMPLE_TEXCUBE 宏来采样,这个能屏蔽平台的差异。现在我们只是简单的使用法线向量来采样。

#if defined(FORWARD_BASE_PASS)
		indirectLight.diffuse += max(0, ShadeSH9(float4(i.normal, 1)));
		float3 envSample = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, i.normal);
		indirectLight.specular = envSample;
#endif

在这里插入图片描述
天空的颜色太亮了。但是因为立方体贴图包含了高动态范围的颜色,其亮度是可以超过1的。所以我们需要将采样的数据转换为RGB。

UnityCG包含了DecodeHDR函数,我们可以利用这个函数。HDR数据存储在四个通道使用的RGBM格式。所以我们采样的是float4值,然后转换。

indirectLight.diffuse += max(0, ShadeSH9(float4(i.normal, 1)));
float4 envSample = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, i.normal);
indirectLight.specular = DecodeHDR(envSample, unity_SpecCube0_HDR);

在这里插入图片描述

DecodeHDR函数是什么样子的呢?
RGBM包含了RGB是三个通道,外加一个M通道,它代表了基数。最终的RGB值是通过乘以xM^y来得到。其中x是缩放量,而y是指数。

1.3 反射跟踪
我们得到了正确的颜色,但是我们没有看到反射。因为我们使用的是球体的法线去采样环境光,这个透视不依赖于视角的方向。所以看起来环境光好好像画在了球体上。

为了得到真实的反射,我们需要使用从摄像机到表面像素点的向量,而且以法线为标准,进行反射。我们可以使用reflect函数计算反射向量,这个在第四节就介绍过。我们还需要一个视角向量,所以需要增加要给参数传递给CreateIndirectLight函数。

UnityIndirect CreateIndirectLight (Interpolators i, float3 viewDir) {
	…
	
	#if defined(FORWARD_BASE_PASS)
		indirectLight.diffuse += max(0, ShadeSH9(float4(i.normal, 1)));
		float3 reflectionDir = reflect(-viewDir, i.normal);
		float4 envSample = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, reflectionDir);
		indirectLight.specular = DecodeHDR(envSample, unity_SpecCube0_HDR);
	#endif

	return indirectLight;
}

…

float4 MyFragmentProgram (Interpolators i) : SV_TARGET {
	…

	return UNITY_BRDF_PBS(
		albedo, specularTint,
		oneMinusReflectivity, _Smoothness,
		i.normal, viewDir,
		CreateLight(i), CreateIndirectLight(i, viewDir)
	);
}

在这里插入图片描述

1.4 使用反射探针
反射天空盒已经完成了,但是如果能够反射周围的场景就更真实了。所以我们创建一个简单的建筑物场景。我使用了旋转的方块作为地板,在顶端使用几个立方体,然后把球体放在建筑物的中间。
在这里插入图片描述

为了能够反射周围的建筑物,我们首先要抓取它。这个可以使用反射探针做到,使用GameObject/Light/Refection Probe创建。创建一个反射探针,并把它放在球体的相同位置。
在这里插入图片描述

场景视图为了展示反射探针的存在。它的是否显示在可以通过scene视图的gizmo进行配置。因为gizmo阻挡了我们的球体,所以我们为了方便将其关闭掉,这里只是告诉你要能够知道在哪里关闭和显示。
在这里插入图片描述

反射探针将环境渲染到立方体贴图上。这也就意味着它要渲染场景6次,每次渲染到立方体的一个面。默认的情况下,它的类型是Baked。在这种模式下,立方体映射是在编辑器build的时候产生。这个贴图只包含静态的几何体。所以我们的建筑物在烘焙之前必须是静态的。

我们也可以把反射探针的模式改为Realtime模式。那么在运行的时候,可以实时渲染。
尽管实时探针可以很容易的设置,但是它的消耗是巨大的,特别是当update比较频繁的时候。当反射探针的模式为实时的时候,编辑器非运行状态是不会更新的。这时候无论你怎么编辑静态的物体,这个烘焙也不会自动更新。

物体不是必须要整体设置为静态的,我们可以只设置其Reflection Probe Static即可。
在这里插入图片描述

2 不太完美的反射
只有非常光滑的表面才能有强烈的反射。物体表面越粗糙,镜面反射就越少,而漫反射就会越多。暗淡的镜子,会反射出模糊的效果,那么我们怎么做到这个模糊的反射呢?

贴图可以有mipmaps,它是对于原图的低采样。当使用高的mipmaps图就可以产生模糊的效果。但是unity利用一个不同的算法来生成mipmap环境贴图。这个可以产生很好的模糊效果。
在这里插入图片描述
我们在哪里设置呢?我找了下Reflection Probe面板:
这里写图片描述

这里你可以设置下bake处理的贴图的精度。
精度为16的效果:
这里写图片描述

精度为128的效果:
这里写图片描述

2.1 粗糙的镜子
我们可以使用UNITY_SAMPLE_TEXCUBE_LOD 宏来采样立方体的贴图指定层级的mipmap。环境立方体贴图三次就近过滤,所以我们可以在相邻的mipmap进行混合。这个允许我们根据材质的光滑度来设置使用哪个mipmap。材质越粗糙,就要使用更高的mipmap。

由于粗糙度从0到1,我们必须需要用它来乘以mipmap的范围,unity也提供了UNITY_SPECCUBE_LOD_STEPS 宏来决定这个范围,如下:

	float roughness = 1 - _Smoothness;
		float4 envSample = UNITY_SAMPLE_TEXCUBE_LOD(
			unity_SpecCube0, reflectionDir, roughness * UNITY_SPECCUBE_LOD_STEPS
		);

在这里插入图片描述

事实上,粗糙度和mipmap的级别不是线性的。unity使用的转换公式为:
在这里插入图片描述
这里的r是原粗糙度。
在这里插入图片描述

float roughness = 1 - _Smoothness;
		roughness *= 1.7 - 0.7 * roughness;
		float4 envSample = UNITY_SAMPLE_TEXCUBE_LOD(
			unity_SpecCube0, reflectionDir, roughness * UNITY_SPECCUBE_LOD_STEPS
		);

在这里插入图片描述

2.2 凹凸镜子
除了使用光滑度来代表粗糙的镜子,我们还可以使用法线贴图来增加更大的变形。我们使用perturbed法线来决定法线的方向,这样也是可行的。
在这里插入图片描述

2.3 金属和非金属

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值