AmplifyImpostors源码阅读
首先看一下点击Bake按钮后的执行流程:
1.AmplifyImpostorInspector部分
首先点击按钮设置了bakeTexture = true
if( GUILayout.Button( TextureIcon, "buttonright", GUILayout.Height( 24 ) ) )
{
// now recalculates texture and mesh every time because mesh might have changed
//if( m_instance.m_alphaTex == null )
//{
m_outdatedTexture = true;
m_recalculatePreviewTexture = true;
//} bakeTextures = true;
}
如果展开了BillboardMesh选项或是bakeTextures为true,则都会执行下面部分:
if( ( ( m_billboardMesh || m_recalculatePreviewTexture ) && m_instance.m_alphaTex == null ) || ( bakeTextures && m_recalculatePreviewTexture ) )
{
try
{
m_instance.RenderCombinedAlpha( m_currentData );
}
catch( Exception e )
{
Debug.LogWarning( "[AmplifyImpostors] Something went wrong with the mesh preview process, please contact support@amplify.pt with this log message.\n" + e.Message + e.StackTrace );
} if( m_instance.m_cutMode == CutMode.Automatic )
m_recalculateMesh = true;
m_recalculatePreviewTexture = false;
}
1.1 RenderCombinedAlpha
该函数会遍历一遍所有视角的模型,生成出覆盖范围最大的Bounds,并更新到这2个变量中:
m_xyFitSize = Mathf.Max(m_xyFitSize, frameBounds.size.x, frameBounds.size.y);
m_depthFitSize = Mathf.Max(m_depthFitSize, frameBounds.size.z);
通过RenderImpostor函数的combinedAlphas变量,将所有视角模型的alpha叠加在一张RT上,再通过这张叠加RT
修正原有Bounds:
m_xyFitSize *= maxBound;
m_depthFitSize *= maxBound;
接着得到哪张材质的索引对应传入RT集合的alpha材质:
bool standardRendering = m_data.Preset.BakeShader == null;
int alphaIndex = m_data.Preset.AlphaIndex;
if (standardRendering && m_renderPipelineInUse == RenderPipelineInUse.HDRP)
alphaIndex = 3;
else if (standardRendering)
alphaIndex = 2;
用深度图的边缘生成alpha:
RenderTexture tempTex = RenderTextureEx.GetTemporary(m_alphaGBuffers[3]);
Graphics.Blit(m_alphaGBuffers[3], tempTex);
packerMat.SetTexture("_A", tempTex);
Graphics.Blit(m_trueDepth, m_alphaGBuffers[3], packerMat, 11);
RenderTexture.ReleaseTemporary(tempTex);
shader:
Pass // copy depth 11
{
ZTest Always Cull Off ZWrite Off CGPROGRAM
#pragma target 3.0
#pragma vertex vert_img
#pragma fragment frag
#include "UnityCG.cginc" uniform sampler2D _MainTex;
uniform sampler2D _A; float4 frag( v2f_img i ) : SV_Target
{
float depth = SAMPLE_RAW_DEPTH_TEXTURE( _MainTex, i.uv ).r;
float3 color = tex2D( _A, i.uv ).rgb;
float alpha = 1 - step( depth, 0 ); return float4( color, alpha );
}
ENDCG
}
合并后的alpha会单独存下来,也就是每一个sheet格子的alpha叠在一起,这样做可以让最终生成面片的顶点合理覆盖:
1.2 GenerateAutomaticMesh
这个函数主要生成顶点,会存到AmplifyImpostorAsset的ShapePoints中。
这一步一定会设上triangulateMesh = true;
if (m_recalculateMesh && m_instance.m_alphaTex != null)
{
m_recalculateMesh = false;
m_instance.GenerateAutomaticMesh(m_currentData);
triangulateMesh = true;
EditorUtility.SetDirty(m_instance);
}
接着设置previewMesh:
if (triangulateMesh)
m_previewMesh = GeneratePreviewMesh(m_currentData.ShapePoints, true);
然后会将CutMode改为手动,允许用户二次修改:
if (autoChangeToManual /*&& Event.current.type == EventType.Layout*/ )
{
autoChangeToManual = false;
m_instance.m_cutMode = CutMode.Manual;
Event.current.Use();
}
最后进入DelayedBake,调用AmplifyImpostor的RenderAllDeferredGroups函数。
2.AmplifyImpostor部分
进入函数RenderAllDeferredGroups,前面都和之前操作差不多,直到调用到RenderImpostor:
if (impostorMaps)
{
commandBuffer.SetViewProjectionMatrices(V, P);
commandBuffer.SetViewport(new Rect((m_data.TexSize.x / hframes) * x, (m_data.TexSize.y / (vframes + (impostorType == ImpostorType.Spherical ? 1 : 0))) * y, (m_data.TexSize.x / m_data.HorizontalFrames), (m_data.TexSize.y / m_data.VerticalFrames)));
绘制时每个sheet的格子都存放对应角度的模型图片,通过SetViewport进行绘制目标区域的裁剪。
不同的ImpostorType对应绘制hframes、vframes的排布方式也不一样。
绘制代码基本的逻辑结构如下:
for (int x = 0; x < hframes; x++)
{
for (int y = 0; y <= vframes; y++)
{
if (impostorMaps)
{
commandBuffer.SetViewProjectionMatrices(V, P);
commandBuffer.SetViewport(new Rect((m_data.TexSize.x / hframes) * x, (m_data.TexSize.y / (vframes + (impostorType == ImpostorType.Spherical ? 1 : 0))) * y, (m_data.TexSize.x / m_data.HorizontalFrames), (m_data.TexSize.y / m_data.VerticalFrames))); if (standardrendering && m_renderPipelineInUse == RenderPipelineInUse.HDRP)
{
commandBuffer.SetGlobalMatrix("_ViewMatrix", V);
commandBuffer.SetGlobalMatrix("_InvViewMatrix", V.inverse);
commandBuffer.SetGlobalMatrix("_ProjMatrix", P);
commandBuffer.SetGlobalMatrix("_ViewProjMatrix", P * V);
commandBuffer.SetGlobalVector("_WorldSpaceCameraPos", Vector4.zero);
}
} for (int j = 0; j < validMeshesCount; j++)
{
commandBuffer.DrawRenderer...
}
}
}
Graphics.ExecuteCommandBuffer(commandAlphaBuffer);
优先绘制Y轴,其次X轴,每次绘制写入commandBuffer,最后统一在外部执行ExecuteCommandBuffer。
附一张测试例图方便参考:
2.1 Remapping
这一步工作主要是将深度通道塞进去。
合并Alpha:
// Switch alpha with occlusion
RenderTexture tempTex = RenderTexture.GetTemporary(m_rtGBuffers[0].width, m_rtGBuffers[0].height, m_rtGBuffers[0].depth, m_rtGBuffers[0].format);
RenderTexture tempTex2 = RenderTexture.GetTemporary(m_rtGBuffers[3].width, m_rtGBuffers[3].height, m_rtGBuffers[3].depth, m_rtGBuffers[3].format); packerMat.SetTexture("_A", m_rtGBuffers[2]);
Graphics.Blit(m_rtGBuffers[0], tempTex, packerMat, 4); //A.b
packerMat.SetTexture("_A", m_rtGBuffers[0]);
Graphics.Blit(m_rtGBuffers[3], tempTex2, packerMat, 4); //B.a
Graphics.Blit(tempTex, m_rtGBuffers[0]);
Graphics.Blit(tempTex2, m_rtGBuffers[3]);
RenderTexture.ReleaseTemporary(tempTex);
RenderTexture.ReleaseTemporary(tempTex2);
shader:
Pass // Copy Alpha 4
{
CGPROGRAM
#pragma target 3.0
#pragma vertex vert_img
#pragma fragment frag
#include "UnityCG.cginc" uniform sampler2D _MainTex;
uniform sampler2D _A; fixed4 frag (v2f_img i ) : SV_Target
{
float alpha = tex2D( _A, i.uv ).a;
fixed4 finalColor = (float4(tex2D( _MainTex, i.uv ).rgb , alpha));
return finalColor;
}
ENDCG
}
这一步会将RT[2]的alpha合并至RT[0],将RT[0]的alpha合并至RT[3]
接下来PackDepth,将深度信息写入RT[2]的A通道:
// Pack Depth
PackingRemapping(ref m_rtGBuffers[2], ref m_rtGBuffers[2], 0, packerMat, m_trueDepth);
m_trueDepth.Release();
m_trueDepth = null;
RT[2]存的是法线,a通道存深度后:
RT[0]的alpha:
FixAlbedo,m_rtGBuffers[1]对应extraTex参数,若传参会被设置到_A采样器。
// Fix Albedo
PackingRemapping(ref m_rtGBuffers[0], ref m_rtGBuffers[0], 5, packerMat, m_rtGBuffers[1]);
alb.rgb / (1-spec)不太清楚。
Pass // Fix albedo 5
{
CGPROGRAM
#pragma target 3.0
#pragma vertex vert_img
#pragma fragment frag
#include "UnityCG.cginc" uniform sampler2D _MainTex;
uniform sampler2D _A; //specular fixed4 frag (v2f_img i ) : SV_Target
{
float3 spec = tex2D( _A, i.uv ).rgb;
float4 alb = tex2D( _MainTex, i.uv );
alb.rgb = alb.rgb / (1-spec);
return alb;
}
ENDCG
}
存TGA(如果预设里勾选了TGA则调用该处,否则存PNG):
// TGA
for (int i = 0; i < outputList.Count; i++)
{
if (outputList[i].ImageFormat == ImageFormat.TGA)
PackingRemapping(ref m_rtGBuffers[i], ref m_rtGBuffers[i], 6, packerMat);
}
DilateShader边缘膨胀处理:
Shader dilateShader = AssetDatabase.LoadAssetAtPath<Shader>(AssetDatabase.GUIDToAssetPath(DilateGUID));
Debug.Log(dilateShader, dilateShader);
Material dilateMat = new Material(dilateShader); // Dilation
for (int i = 0; i < outputList.Count; i++)
{
if (outputList[i].Active)
DilateRenderTextureUsingMask(ref m_rtGBuffers[i], ref m_rtGBuffers[alphaIndex], m_data.PixelPadding, alphaIndex != i, dilateMat);
}
shader是沿着周围8个方向外拓一圈:
float4 frag_dilate( v2f_img i, bool alpha )
{
float2 offsets[ 8 ] =
{
float2( -1, -1 ),
float2( 0, -1 ),
float2( +1, -1 ),
float2( -1, 0 ),
float2( +1, 0 ),
float2( -1, +1 ),
float2( 0, +1 ),
float2( +1, +1 )
};
函数中会根据pixelBlend将这个shader调用N次:
for (int i = 0; i < pixelBleed; i++)
{
dilateMat.SetTexture("_MaskTex", dilatedMask); Graphics.Blit(mainTex, tempTex, dilateMat, alpha ? 1 : 0);
Graphics.Blit(tempTex, mainTex); Graphics.Blit(dilatedMask, tempMask, dilateMat, 1);
Graphics.Blit(tempMask, dilatedMask);
}
默认值是调用32次:
[SerializeField]
[Range( 0, 64 )]
public int PixelPadding = 32;
3.Shader渲染部分
Octahedron八面体方案和球面分别使用2个对外Shader,
八面体方案会采样3次做插值,球面则代码稍少,接下来只看球面部分。
3.1 SphereImpostorVertex
先看ForwardBase的pass:
顶点部分执行SphereImpostorVertex( v.vertex, v.normal, o.frameUVs, o.viewPos );
这个函数会处理Billboard的位置信息,并返回常规顶点信息和frameUVs信息。
得到相对相机位置,并转换至object空间,_Offset是实际模型中心偏移量,通过像素转顶点的方式计算得到
float3 objectCameraPosition = mul( ai_WorldToObject, float4( worldCameraPos, 1 ) ).xyz - _Offset.xyz; //ray origin
float3 objectCameraDirection = normalize( objectCameraPosition );
构建一组基向量:
float3 upVector = float3( 0,1,0 );
float3 objectHorizontalVector = normalize( cross( objectCameraDirection, upVector ) );
float3 objectVerticalVector = cross( objectHorizontalVector, objectCameraDirection );
横向信息用arctan2,变量名作者写错了
float verticalAngle = frac( atan2( -objectCameraDirection.z, -objectCameraDirection.x ) * AI_INV_TWO_PI ) * sizeX + 0.5;
纵向信息用acos将点乘转线性
float verticalDot = dot( objectCameraDirection, upVector );
float upAngle = ( acos( -verticalDot ) * AI_INV_PI ) + axisSizeFraction * 0.5f;
yRot构建的旋转矩阵用作细节修正
float yRot = sizeFraction.x * AI_PI * verticalDot * ( 2 * frac( verticalAngle ) - 1 ); // Billboard rotation
float2 uvExpansion = vertex.xy;
float cosY = cos( yRot );
float sinY = sin( yRot );
float2 uvRotator = mul( uvExpansion, float2x2( cosY, -sinY, sinY, cosY ) );
最后sizeFraction用于将坐标缩放为对应sheet内格子大小
float2 frameUV = ( ( uvExpansion * fractionsUVscale + 0.5 ) + relativeCoords ) * sizeFraction;
3.2 SphereImpostorFragment
frag一些逻辑都是常规操作,看下深度部分的处理,
离近了看会有真实深度的遮挡:
因为是正交相机拍摄,不存在DeviceDepth转线性EyeDepth。
深度赋值取的clipPos.z:
fixed4 frag_surf (v2f_surf IN, out float outDepth : SV_Depth ) : SV_Target {
...
IN.pos.zw = clipPos.zw;
outDepth = IN.pos.z;
_DepthSize读的是csharp变量m_depthFitSize,在烘焙时这个值是正交相机的远截面:
Matrix4x4 P = Matrix4x4.Ortho(-fitSize + m_pixelOffset.x, fitSize + m_pixelOffset.x, -fitSize + m_pixelOffset.y, fitSize + m_pixelOffset.y, 0, zFar: -m_depthFitSize);
最后深度计算这里,_DepthSize*0.5猜测是物体中心是z=0.5,是基于物体中心增加偏移深度,并且remapNormal.a之前已经随着法线做了-1 - 1的映射操作:
float4 remapNormal = normalSample * 2 - 1; // object normal is remapNormal.rgb
最后乘以length( ai_ObjectToWorld[ 2 ].xyz )其实是乘以Z轴的缩放,如果没有缩放改成1结果不变:
float depth = remapNormal.a * _DepthSize * 0.5 * length( ai_ObjectToWorld[ 2 ].xyz );
计算完后再将颜色和深度输出:
fixed4 frag_surf (v2f_surf IN, out float outDepth : SV_Depth ) : SV_Target {
UNITY_SETUP_INSTANCE_ID(IN);
SurfaceOutputStandardSpecular o;
UNITY_INITIALIZE_OUTPUT( SurfaceOutputStandardSpecular, o ); float4 clipPos;
float3 worldPos;
SphereImpostorFragment( o, clipPos, worldPos, IN.frameUVs, IN.viewPos );
IN.pos.zw = clipPos.zw; outDepth = IN.pos.z; UNITY_APPLY_DITHER_CROSSFADE(IN.pos.xy);
return float4( _ObjectId, _PassValue, 1.0, 1.0 );
}
阴影部分ShadowCaster pass用了同样的代码,因此impostor也有阴影。
AmplifyImpostors源码阅读的更多相关文章
- 【原】FMDB源码阅读(三)
[原]FMDB源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 FMDB比较优秀的地方就在于对多线程的处理.所以这一篇主要是研究FMDB的多线程处理的实现.而 ...
- 【原】FMDB源码阅读(二)
[原]FMDB源码阅读(二) 本文转载请注明出处 -- polobymulberry-博客园 1. 前言 上一篇只是简单地过了一下FMDB一个简单例子的基本流程,并没有涉及到FMDB的所有方方面面,比 ...
- 【原】FMDB源码阅读(一)
[原]FMDB源码阅读(一) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 说实话,之前的SDWebImage和AFNetworking这两个组件我还是使用过的,但是对于 ...
- 【原】AFNetworking源码阅读(六)
[原]AFNetworking源码阅读(六) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 这一篇的想讲的,一个就是分析一下AFSecurityPolicy文件,看看AF ...
- 【原】AFNetworking源码阅读(五)
[原]AFNetworking源码阅读(五) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇中提及到了Multipart Request的构建方法- [AFHTTP ...
- 【原】AFNetworking源码阅读(四)
[原]AFNetworking源码阅读(四) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇还遗留了很多问题,包括AFURLSessionManagerTaskDe ...
- 【原】AFNetworking源码阅读(三)
[原]AFNetworking源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇的话,主要是讲了如何通过构建一个request来生成一个data tas ...
- 【原】AFNetworking源码阅读(二)
[原]AFNetworking源码阅读(二) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇中我们在iOS Example代码中提到了AFHTTPSessionMa ...
- 【原】AFNetworking源码阅读(一)
[原]AFNetworking源码阅读(一) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 AFNetworking版本:3.0.4 由于我平常并没有经常使用AFNetw ...
- [PHP源码阅读]explode和implode函数
explode和implode函数主要用作字符串和数组间转换的操作,比如获取一段参数后根据某个字符分割字符串,或者将一个数组的结果使用一个字符合并成一个字符串输出.在PHP中经常会用到这两个函数,因此 ...
随机推荐
- numpy argsort排序如何让其稳定排序
numpy.argsort(a, axis=-1, kind=None, order=None) Parameters: aarray_like Array to sort. axis int or ...
- JVM(JAVA Virtual Machine)Java虚拟机
JVM的跨平台性 一次编写,到处运行 JVM将字节码文件编译成对应操作系统的机器码 JVM的语言无关性 JVM的内存区域 虚拟机栈:在JVM运行过程中存储当前线程运行方法所需的数据,指令.返回地址 本 ...
- 非常非常好用的一款账户密码保存工具-KeePass
非常非常好用的一款账户密码保存工具 下载地址: https://sourceforge.net/projects/keepass/files/KeePass%202.x/2.55/KeePass- ...
- 时隔半年 DotNetGuide 已突破了 6.6K + Star,持续更新,欢迎更多小伙伴PR投稿!
前言 记得今年5月份的时候 DotNetGuide GitHub才突破5k Star,经过持续不断地输出时隔半年 DotNetGuide 已突破了 6.6K + Star!并且由我创建的DotNetG ...
- 云原生爱好者周刊:Chaos Mesh 升级成为 CNCF 孵化项目
云原生一周动态要闻: Chaos Mesh 升级成为 CNCF 孵化项目 Zabbix Web Frontend 中发现安全漏洞 2021 年 Rust 调查报告出炉 Sysdig 2022 云原生安 ...
- 云原生周刊 | 人类、机器人与 Kubernetes
近日 Grafana 官网发表了一篇博客介绍了 2022 年比较有意思.脑洞大开的一些 Grafana 使用案例,比如监控特斯拉 Model 3 的充电状态.OTA 更新状况等等. 海事技术供应商 R ...
- 【2024】所有人都能看懂的 Win 11 安装/重装教程,跟着我一遍包成功
无论你因为系统坏掉想重装一下 Windows,或者只是想升级一下 Windows 都可以.虽然标题写的是 Win 11,不过实际上对于任何 Windows 系统都适用,不过现在 Win 11 已经相当 ...
- 一文彻底弄懂MySQL的优化
在企业级 Web 开发中,MySQL 优化是至关重要的,它直接影响系统的响应速度.可扩展性和整体性能.下面从不同角度,列出详细的 MySQL 优化技巧,涵盖查询优化.索引设计.表结构设计.配置调整等方 ...
- 【2024.09.15】NOIP2024 赛前集训(2)
[2024.09.15]NOIP2024 赛前集训(2) A 最大的难点戏剧性地变成了二叉搜索树是什么. 先根据已知序列把二叉树建出来,忘了二叉搜索树的移步 二叉搜索树 & 平衡树 - OI ...
- ABC343:起航
ABC343:起航 2024/3/2/22:53 有点儿晚了,简单总结一下. 前4题都很基础,一点点小思维,其中C题 边界又盲目追求刚刚好,WA了一次,总结经验,程序实际设计应该略微大于数据范围. E ...