True Impostor技术原理总结与实践

True Impostor技术

基本介绍

Impostor,原意“伪装者”,是一种使用极简单的mesh来模拟真实mesh模型的一种优化技术,可以高效的在场景中绘制大量同类的模型而不需要绘制大量的多边形。Impostor技术是介于Billboard和mesh之间的一种模型,在节省顶点的同时实现模型全角度细节的展示。Impostor将2维纹理映射到一张矩形mesh上,模拟高精度模型的假象,实现方法可以是实时的,也可以预先计算好数据并保存在内存中。Impostor模型实际上只在某些角度下是完全精确的(采样角度),在相机移动角度改变的过渡过程中精度必定会降低,直到切换到另一个完全精确的角度。 Impostor支持原模型的自发光、反射、折射等特性,在采样足够的情况下基本可以以假乱真。

Billboard,Multi-Billboard

传统的Billboard公告牌技术原理很多简单,使用一张四边形mesh来展示一张纹理贴图,并监视相机的位置,通过差积运算得到方向向量让mesh始终朝向相机,实现伪装模型的效果,这种方法在远处的且是Y轴中心对称模型的情况下效果不错,例如一些单一的树木或者草。

单一的Billboard只能展示模型的一个面,之后衍生出像星形billboard的方法,例如模拟某种草,将多个billboard星状叠插在一起,展示模型更复杂的细节。与此同时随着叠加的billboard的数量增多,顶点的数量也会增多,mesh也会更加复杂,增加内存消耗。

Billboard只适用于远处的对模型细节要求不高的情况,可以显著降低性能要求。Billboard也是在很多需要降低性能要求,绘制大规模模型的情况下(例如草原、森林)普遍应用的一种方法,是一种image based renderring的思路。此外这种简单的billboard难以支持真正的光照阴影等效果。

Impostor纹理空间

纹理空间映射是Impostor技术的基础。映射主要分成三种:Spherical(球面空间),Octahedron(八面体空间),Hemi-Octahedron(半八面体空间)。

空间映射实现了二维数据和三维数据的相互转换,即数据的压缩解压过程。Impostor采样采集的实际是三维数据,但要通过压缩保存到一张二维纹理贴图上,最后在绘制Impostor的时候需要解压二维纹理数据实现不同角度展示对应的模型贴图。

如下图展示的是Spherical球面空间二维纹理的映射,在同一纬度上对应顶点的数量是相同的,导致越接近两极顶点越密集,两极的顶点对应在二维纹理的上下两边缘,顶点分布不均匀,导致纹理上下两边缘部分的数据过于密集大量重复造成精度浪费。
http

因此为了节约精度,会选择另外两种映射方法Octahedron和Hemi-Octahedron。

下面的gif动画展示了这两种空间映射的直观过程。可见这两种映射空间上顶点分布很均匀,避免了球面空间映射造成的精度浪费。此外,关于这两种的选择也要分情况:对于那些不需要展示模型底部的情况,为了进一步优化推荐选择Hemi-Octahedron空间映射。而如果需要全方位展示模型则只能选择Octahedron。

https://storage.googleapis.com/wzukusers/user-22455410/images/5aade979ada89fPZkHZz/Grid_Animation.gif

例如下面代码是Octahedron空间纹理映射变换函数:

			float2 VectortoOctahedron( float3 N )
			{
				N /= dot( 1.0, abs( N ) ); // 等同于:N/= abs(N.x)+abs(N.y)+abs(N.z)
				if( N.z <= 0 )
				{
				    N.xy = ( 1 - abs( N.yx ) ) * ( N.xy >= 0 ? 1.0 : -1.0 );
				}
				return N.xy;
			}
			
			float3 OctahedronToVector( float2 Oct )
			{
				float3 N = float3( Oct, 1.0 - dot( 1.0, abs( Oct ) ) );
				if(N.z< 0 )
				{
				    N.xy = ( 1 - abs( N.yx) ) * (N.xy >= 0 ? 1.0 : -1.0 );
				}
				return normalize( N);
			}

使用示例:VectortoOctahedron(objectCameraDirection.xzy)*0.5 + 0.5,其中objectCameraDirection是模型坐标系camear相对于模型的方向向量,计算结果即是二维纹理上对应的纹理坐标。

Impostor空间烘焙采样

Impostor采样主要根据设置的采样精度、纹理空间映射算法等均匀的从不同角度对模型进行截取快照,输出diffuse贴图(物体颜色和透明度)、法线贴图(用于之后计算光照阴影等)、Mask贴图(用于clip裁剪掉背景)等通道数据(一般设置的情况)。

Amplify Impostor插件烘焙Impostor的方法很简单,在模型上加上AmplifyImpostor.cs脚本,创建Impostor对象并设置到Impostor Assets上,然后设置纹理分辨率(Texture Size)、理采样帧数(Axis Frames),编辑Imspostor的mesh形状(Billboard mesh),以及选择烘焙设置(Bake Preset)等。最后点击Bake Impostor就可以实现一键烘焙。

在这里插入图片描述

例如选择基本的Custom Preset输出的结果为三张贴图:AlbedoAlpha、NormalDepth、Mask。
插件提供的另一种用于标准延迟渲染通道的StandardDefferd设置输出则为四张贴图:AlbedoAlpha、NormalDepth、SpecularSmoothness、EmissionOcclusion。

Amplify Impostor基本使用教程

视差映射POM

纹理空间是通过多角度采样得到的不同角度的纹理贴图,然后根据相机的位置来更换不同的贴图,但是在贴图直接更换的瞬间会有不连续的突变,为了实现平滑的过渡达到True Impostor的效果,需要加入视差映射POM(Parallax mapping)技术。

视差映射方法的基本原理是,根据当前相机位置确定当前对应的采样贴图片段,然后将当前贴图临近的贴图进行加权融合采样,实现平滑过渡,达到接近真实模型的效果。以Octahedron空间为例,每个贴图片段邻近的有三个片段,对三个邻近片段进行加权融合得到最终的采样颜色。

对整个纹理空间进行POM是一个比较耗费的过程,一般至少需要9x9的采样空间才能实现平滑过渡。

Billborad Impostor简化版实现

Demo下载

Impostor技术通过多角度采样纹理空间变换加上视差纹理映射POM实现Impostor模型不同视角的平滑展示。但是POM是一个比较费的过程,在游戏中如果不需要用Impostor来展示近处的细节模型(近处的模型直接替换真实的mesh模型),则可以去掉POM过程,在相机角度变换时直接替换纹理贴图即可。

Demo中使用AmplifyImpostor插件烘焙出三张贴图(bake preset选择custom)作为Impostor数据,demo中的BillboardImpostor.shader实现了简化版的Impostor,只加入了简单的漫反射效果。

完整代码如下:

// 用来展示BillboardImpostor模型
Shader "ImpostorDemo/BillboardImpostor"
{
	Properties
	{
		[NoScaleOffset]_Albedo("Impostor Albedo & Alpha", 2D) = "white" {}
		[NoScaleOffset]_Normals("Impostor Normal & Depth", 2D) = "white" {}
		[NoScaleOffset]_Mask("Mask", 2D) = "white" {}
		[HideInInspector]_AI_Frames("Impostor Frames", Float) = 0
		[HideInInspector]_AI_ImpostorSize("Impostor Size", Float) = 0
		_AI_Clip("Impostor Clip", Range( 0 , 1)) = 0.5
		
		_DiffuseColor ("DiffuseColor", Color) = (1,1,1,1)
		_Diffuse ("Diffuse", Range(0,1)) = 1.0
	}

	SubShader
	{
		CGINCLUDE
		#pragma target 3.0
		#define UNITY_SAMPLE_FULL_SH_PER_PIXEL 1
		ENDCG
		Tags { "RenderType"="Opaque" "Queue"="Geometry" "DisableBatching"="True" "ImpostorType"="Octahedron" }
		Cull Back
		Pass
		{
			ZWrite On
			Name "ForwardBase"
			Tags { "LightMode"="ForwardBase" }

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"
			#include "Lighting.cginc"
			
			uniform sampler2D _Albedo; // 颜色
			uniform sampler2D _Normals; // 法线
			uniform sampler2D _Mask;// mask
			uniform float _AI_Frames; // 切片规模
			uniform float _AI_ImpostorSize; // impostor resolution
			uniform float _AI_Clip; // 透明背景裁剪程度
			
			// 漫反射强度
			uniform float4 _DiffuseColor;
			uniform float _Diffuse;

			// Octahedron空间纹理映射变换
			float2 VectortoOctahedron( float3 N )
			{
				N /= dot( 1.0, abs( N ) ); // 等同于:N/= abs(N.x)+abs(N.y)+abs(N.z)
				if( N.z <= 0 )
				{
				    N.xy = ( 1 - abs( N.yx ) ) * ( N.xy >= 0 ? 1.0 : -1.0 );
				}
				return N.xy;
			}
			
			float3 OctahedronToVector( float2 Oct )
			{
				float3 N = float3( Oct, 1.0 - dot( 1.0, abs( Oct ) ) );
				if(N.z< 0 )
				{
				    N.xy = ( 1 - abs( N.yx) ) * (N.xy >= 0 ? 1.0 : -1.0 );
				}
				return normalize( N);
			}
			
			inline void OctaImpostorVertex( inout appdata_full v, inout float4 uvsFrame1)
			{
				float framesXY = _AI_Frames;
				float prevFrame = framesXY - 1;
				float2 fractions = 1.0 / float2( framesXY, prevFrame );
				float fractionsFrame = fractions.x;
				float fractionsPrevFrame = fractions.y;
				float UVscale = _AI_ImpostorSize;

				float3 worldOrigin = float3(unity_ObjectToWorld[0].w, unity_ObjectToWorld[1].w, unity_ObjectToWorld[2].w);

				float3 worldCameraPos = _WorldSpaceCameraPos;

				// 模型空间:origin到相机的方向
				float3 objectCameraDirection = normalize( mul( worldCameraPos - worldOrigin, (float3x3)unity_WorldToObject ) );
				// 模型空间:相机的位置
				float3 objectCameraPosition = mul( unity_WorldToObject, float4( worldCameraPos, 1 ) ).xyz;
				float3 upVector = float3( 0,1,0 );
				//float3 objectCameraDirection = UNITY_MATRIX_V[2].xyz;

				// 模型水平竖直向量
				float3 objectHorizontalVector = normalize( cross( objectCameraDirection, upVector ) );
				float3 objectVerticalVector = cross( objectHorizontalVector, objectCameraDirection );

				float2 uvExpansion = ( v.texcoord.xy - 0.5f ) * UVscale;
				float3 billboard = objectHorizontalVector * uvExpansion.x + objectVerticalVector * uvExpansion.y;

				float2 cameraPos = VectortoOctahedron(objectCameraDirection.xzy)*0.5 + 0.5;
				float colFrame = round(abs(cameraPos.x) * (framesXY-1));
				float rowFrame = round(abs(cameraPos.y) * (framesXY-1));
				// 纹理坐标
				uvsFrame1 = 0;
				uvsFrame1.xy = (v.texcoord.xy + float2(colFrame, rowFrame)) * fractionsFrame;

				// 顶点
				v.vertex.xyz = billboard;
				v.normal.xyz = objectCameraDirection;
			}

			struct v2f_surf {
				UNITY_POSITION(pos);
				float4 UVsFrame117 : TEXCOORD5;
				float4 viewPos17 : TEXCOORD6;
			};

		    // 顶点
			v2f_surf vert (appdata_full v ) {
				UNITY_SETUP_INSTANCE_ID(v);
				v2f_surf o;
				UNITY_INITIALIZE_OUTPUT(v2f_surf,o);
				UNITY_TRANSFER_INSTANCE_ID(v,o);
				UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
				OctaImpostorVertex( v, o.UVsFrame117 ); ///
				o.pos = UnityObjectToClipPos(v.vertex); // 顶点投影坐标

				// 漫反射
				float3 worldlight = normalize(_WorldSpaceLightPos0.xyz);
				float3 worldnormal = normalize(mul((float3x3)unity_ObjectToWorld,v.normal));
				float3 diffuse = _LightColor0.rgb * _DiffuseColor * _Diffuse * saturate(dot(worldnormal,worldlight));
				o.viewPos17 = float4(diffuse,0);
				return o;
			}

			// 片段着色
			fixed4 frag (v2f_surf IN) : SV_Target {				
				float4 blendedAlbedo = tex2D( _Albedo, float3( IN.UVsFrame117.xy, 0) );
				float alpha = blendedAlbedo.a - _AI_Clip;
				clip(alpha);
				return (blendedAlbedo + IN.viewPos17);
			}
			ENDCG
		}
		
	}
}

Demo场景TreeForest.scene中使用Billborad Impostor技术实现了简单的森林场景,制作了树木、石头和草的Impostor,近处展示真实的模型,远处展示Impostor模型。

在这里插入图片描述

总结

Impostor是一个介于billboard和mesh模型直接的一个技术产物,用于权衡增加模型细节和减少顶点数量的矛盾,加上这里实现的简化版本Billborad Impostor,他们之间的关系如下(3d模型细节从少到多):

Billborad — Multi Billborad — BillBoard Impostor — True Impostor — Mesh(model)

参考文章

Octahedral Impostor from Ryan Brucks

True Impostors - GPU Gems 3

©️2020 CSDN 皮肤主题: 点我我会动 设计师:上身试试 返回首页
实付 9.90元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值