营销型网站建设一般包含哪些内容/引流最好的推广方法
本节,我们将跟随数据流向讲解UEP管线中的烘培光照。
文章目录
- 一、URP烘培光照
- 1. 搭建场景
- 2. 烘培光照参数设置
- MixedLight光照设置:
- 直观感受
- Lightmapping Settings参数设置:
- 3. 我们如何记录次表面光源颜色
- 首先我们提取出相关URP代码,便于测试
- 之后进入Shader
- UnityMetaVertexPosition
- 4. 使用光照贴图
- 二、光照探针
- 1. 添加光照探针并获取烘培结果
- 2. 获取烘培的球谐系数
- 3. 计算球谐光照
- 4. 使用球谐光照
- 三、光照探针代理体LPPV
- 参考
一、URP烘培光照
1. 搭建场景
将所有需要烘培的物体设置为ContributeGI(下面两种方法都可)
将光源设置为Mixed
2. 烘培光照参数设置
添加新的Lighting Settings
点击 Generate Lighting 烘培
一般GPU烘培比CPU烘培块,至于具体该怎么选择,以及有什么区别,可参照
unity渐进式烘焙Progressive CPU和GPU
或Unity官方文档:The Progressive GPU Lightmapper
烘培后的光照会被保存在Scene同等目录下的同名文件下
使用烘培光照
- 首先需要确保烘培物体开启contribute GI
- 其次、烘培物体必须使用内置材质、或标准Shader、或自定义Shader中拥有Meta Pass。
- 之后需要定义光源为烘培类型(Mixed、Baked),可以在Window > Rendering > Light Explorer中做整体调整。
对当前场景做预烘培光照,需要打开Window > Rendering > Lighting
MixedLight光照设置:
- Baked Indirect【阴影全部由shadowMap生成】: 烘培间接光就像是 实时光照+额外的间接光,但是在阴影距离之外没有阴影显示 (因为实时光只生成阴影范围之内的阴影)。
\quad 烘焙间接模式的一个很好的例子是,如果你正在制作一款室内射击游戏或冒险游戏,设置在与走廊相连的房间里。观看距离是有限的,所以所有可见的东西通常都应该在阴影距离之内。这个模式对于建立一个有雾的室外场景也很有用,因为你可以用雾来隐藏远处缺失的阴影。
【使用:中档pc和高端移动设备】
- Shadowmask:烘培阴影遮罩贴图,可以保存静态物体之间的阴影关系。
Shadowmask是一种纹理,它与相应的光贴图共享相同的UV布局和分辨率。它为每个texel最多存储4个光源的遮挡信息,因为纹理在当前gpu上最多限制为4个通道。- Distance Shadowmask【实时阴影距离以外仍有静态阴影】:他与Baked Indirect的区别是,Distance Shadowmask可以在实时光照阴影距离之外,对静态物体使用烘培阴影,对动态物体使用光照探针阴影。Distance Shadowmask模式的一个很好的例子是,如果你正在构建一个开放的世界场景,其中阴影一直延伸到地平线,复杂的静态网格在移动角色上投射实时阴影。
【使用:高端PC、新一代设备】 - Shadowmask【动态物体可以接收到静态物体(Light Probe)阴影;静态物体使用阴影遮蔽贴图获取静态物体的阴影投影】:Shadowmask可能有用的一个很好的例子是,如果你正在构建一个几乎完全静态的场景,使用镜面材料,柔和的烘烤阴影和动态阴影接受物体,但不要太靠近相机。另一个很好的例子是一个开放世界场景,它的阴影一直延伸到地平线,但没有像昼夜循环这样的动态照明。
【使用:中低端PC、移动设备】
- Distance Shadowmask【实时阴影距离以外仍有静态阴影】:他与Baked Indirect的区别是,Distance Shadowmask可以在实时光照阴影距离之外,对静态物体使用烘培阴影,对动态物体使用光照探针阴影。Distance Shadowmask模式的一个很好的例子是,如果你正在构建一个开放的世界场景,其中阴影一直延伸到地平线,复杂的静态网格在移动角色上投射实时阴影。
Distance Shadowmask 和 Shadowmask类型的切换,在Project Settings>Quality中导航到Shadows,其中Shadowmask Mode选项可以切换Distance Shadowmask 和 Shadowmask。
该类型会额外生成一个LightMap贴图 Lightmap-x_comp_shadowmask
- Subtractive:不仅仅烘培间接光,还烘培直接光,静态物体无法接受(除了主光源外)动态物体的阴影,动态物体只能通过光照探针得到静态物体的阴影。当你正在构建带有外部关卡和很少动态GameObjects的格子阴影(即卡通风格)游戏时,Subtractive模式便会发挥作用。
【使用:低端移动设备】
直观感受
除两个绿色小球外,其余物体都是静态物体。场景中无光照探针。场景中只有主光源,设置为Mixed。
Baked Indirect:实时阴影距离以外无阴影。其他阴影实时生成。
Shadowmask:
实时阴影距离以外有静态物体阴影,无动态物体阴影。
动态物体上无静态物体阴影。
静态物体阴影为预烘培阴影,移动后阴影错误。
Distance Shadowmask:
实时阴影距离以外有静态物体阴影,无动态物体阴影。
动态物体可接受静态物体阴影
静态物体移动后,阴影随之移动
Subtractive:
实时物体阴影由静态物体阴影和动态物体阴影混合得到,阴影混合效果并不好。
静态物体移动阴影错误。
以上效果可以得出
阴影质量: Distance Shadowmask > Baked Indirect > Shadowmask > Subtractive
Lightmapping Settings参数设置:
其中,Lightmapping Settings参数包括:
-
Lightmap Resolution【采样数】:每单位(unit,一般为1m)像素分辨率。值越高,烘培时间越长。
-
Lightmap Padding(填充???):在烘培光照中设置形状之间的纹理的分隔【默认为2】
-
LightmapSize【光照贴图大小】:光照贴图大小,整合了多个OBJ的纹理光照贴图
-
Compress Lightmaps【压缩】:是否压缩光照贴图。较低质量的压缩减少了内存和存储需求,但代价是更多的可视化工件。更高质量的压缩需要更多的内存和存储空间,但可以提供更好的视觉效果。
-
Ambient Occlusion【增加了环境光遮蔽效果、边缘缝隙会更暗一点】:指定是否包含环境遮挡或不在烘烤光图结果中。当光线反射到它们上时,启用此功能可以模拟物体的裂缝和缝隙中发生的柔和阴影。
-
Directional Mode【是否烘培镜面反射】:控制烘烤和实时光贴图是否存储来自照明环境的定向照明信息。该贴图存储主要的入射方向以及一个因子:记录有多少光是从主入射方向射入的,其他光照则认为均匀的来自法线半球,这些数据可以用来实时计算材质的镜面反射,但看起来仍像纯粹的漫反射。
Non-Directional
Directional
我没看出来区别在哪,但是Directional会多一个光照贴图:Lightmap-0_comp_dir
-
Indirect Intensity【光线反射强度】:Unity提供的参考值是在0-5。小于1,反射后光照强度衰减、大于1,反射光强总和大于1。基于物理的情况下,该值应该为一个小于1的数,但特定情况下,使用较大的值能得到更好的效果。
-
Albedo Boost【值越大,反射光颜色约趋向于白色】:通过增强场景中材料的反照率来控制表面之间反弹的光量。增加这个值,在间接光计算中,反照率值趋向白色。默认值是物理精确值。
-
最后一个选项 Lightmap Parameters 为每个Obj的默认光照贴图数据,默认值可以通过Create>LightmapParameters创建,再在Lighting窗口的Lightmap Parameters选项中设置自定义的文件。
烘培主要包含如下参数
这些参数大多都不需要调整,使用默认值已经可以解决大部分项目需求。
3. 我们如何记录次表面光源颜色
首先我们设置烘培模式为 Backed Indirect 或 shadowmask,我们关闭主光源(直接光照),发现地表的绿色映射到了物体上。
间接光照的颜色是通过物体材质中Shader的Meta Pass来设置的。
我们转到Lit.shader的Meta Pass
首先我们提取出相关URP代码,便于测试
之后进入Shader
#pragma vertex UniversalVertexMeta
#pragma fragment UniversalFragmentMetaLit#include "Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl"
#include "MyLitMetaPass.hlsl"
首先,我们来看顶点着色器的输入,Meta Pass的输入为顶点、法线和3个UV
。。。
struct Attributes
{float4 positionOS : POSITION;float3 normalOS : NORMAL;float2 uv0 : TEXCOORD0;//BaseMap的UVfloat2 uv1 : TEXCOORD1;//Lightmap的UVfloat2 uv2 : TEXCOORD2;//DynamicLightmap的UVUNITY_VERTEX_INPUT_INSTANCE_ID
};struct Varyings
{float4 positionCS : SV_POSITION;float2 uv : TEXCOORD0;
#ifdef EDITOR_VISUALIZATIONfloat2 VizUV : TEXCOORD1;float4 LightCoord : TEXCOORD2;
#endif
};
之后,进入顶点着色器
Varyings UniversalVertexMeta(Attributes input)
{Varyings output = (Varyings)0;output.positionCS = UnityMetaVertexPosition(input.positionOS.xyz, input.uv1, input.uv2);output.uv = TRANSFORM_TEX(input.uv0, _BaseMap);
#ifdef EDITOR_VISUALIZATIONUnityEditorVizData(input.positionOS.xyz, input.uv0, input.uv1, input.uv2, output.VizUV, output.LightCoord);
#endifreturn output;
}
UnityMetaVertexPosition
UnityMetaVertexPosition 函数定义在 Core/ShaderLibrary/MetaPass.hlsl
,我们进入文件查看。
其中unity_LightmapST
和unity_DynamicLightmapST
定义在UnityInput.hlsl中。
引用自:MyLit.shader -> LitInput.hlsl -> Core.hlsl -> input.hlsl -> UnityInput.hlsl
float4 unity_LightmapST;
float4 unity_DynamicLightmapST;
该变量为Unity引擎自动帮我们赋值,
unity_LightmapST保存了Baked LightMap的UV_ST。
unity_DynamicLightmapST保存了Realtime Global Illumination的LightMap的UV_ST
DynamicLightmap也是用于静态物体,区别是在运行时可以改变光照的intensity和direction。需要在LIGHT SETTING面板里勾选Realtime Global Illumination。
UnityMetaVertexPosition 输入顶点坐标,以及unity_Lightmap,unity_DynamicLightmap的UV坐标,得到光照贴图中数据的裁剪坐标。
unity_MetaVertexControl
的定义在MetaPass.hlsl中
CBUFFER_START(UnityMetaPass)// x = use uv1 as raster position// y = use uv2 as raster positionbool4 unity_MetaVertexControl;// x = return albedo// y = return normalbool4 unity_MetaFragmentControl;// Control which VisualizationMode we will// display in the editorint unity_VisualizationMode;
CBUFFER_END
unity_MetaVertexControl
变量用来区别是LightMap还是dynamicLightmap。因此启用实时全局光照会替代烘培全局光照作为最终结果。
unity_MetaFragmentControl
变量用来区别烘焙表面颜色还是自发光颜色。
EDITOR_VISUALIZATION
宏为编辑模式可视化,用于渲染材质验证器。该模式具体使用参照
基于物理的渲染材质验证器
可以简单的理解为,当我们在编辑器模式下更改了Shading Mode,则直接输出物体的裁剪坐标。
否则输出LightMap中得到的数据。
我的Unity版本(2021.3.31f1)中,与Unity文档2022.1版本shadingMode显示不同,至于哪些Shading Mode会启用该宏定义,在此处不再做测试。
float4 UnityMetaVertexPosition(float3 vertex, float2 uv1, float2 uv2)
{return UnityMetaVertexPosition(vertex, uv1, uv2, unity_LightmapST, unity_DynamicLightmapST);
}float4 UnityMetaVertexPosition(float3 vertex, float2 uv1, float2 uv2, float4 lightmapST, float4 dynlightmapST)
{
#ifndef EDITOR_VISUALIZATIONif (unity_MetaVertexControl.x){vertex.xy = uv1 * lightmapST.xy + lightmapST.zw;// OpenGL right now needs to actually use incoming vertex position,// so use it in a very dummy wayvertex.z = vertex.z > 0 ? REAL_MIN : 0.0f;}if (unity_MetaVertexControl.y){vertex.xy = uv2 * dynlightmapST.xy + dynlightmapST.zw;// OpenGL right now needs to actually use incoming vertex position,// so use it in a very dummy wayvertex.z = vertex.z > 0 ? REAL_MIN : 0.0f;}return TransformWorldToHClip(vertex);
#elsereturn TransformObjectToHClip(vertex);
#endif
}
这里,顶点不再是模型的顶点坐标,而是该模型顶点在LightMap中的UV坐标,最终结果要将该UV坐标从世界坐标转为裁剪坐标。【不知道这么做有什么道理!!!!】
我猜测:这里的 ViewMatrix 和 ProjectionMatrix 都是单位矩阵,并不会对UV坐标有改变,因为将UV坐标从世界空间转换到裁剪空间没有道理。但这仅仅是猜测!!!!!!!!!
继续回到顶点着色器
Varyings UniversalVertexMeta(Attributes input)
{Varyings output = (Varyings)0;output.positionCS = UnityMetaVertexPosition(input.positionOS.xyz, input.uv1, input.uv2);output.uv = TRANSFORM_TEX(input.uv0, _BaseMap);
#ifdef EDITOR_VISUALIZATIONUnityEditorVizData(input.positionOS.xyz, input.uv0, input.uv1, input.uv2, output.VizUV, output.LightCoord);
#endifreturn output;
}
output.positionCS
记录了LightMapUV坐标在的裁剪空间下的坐标。
output.uv
记录了_BaseMap的UV坐标。
这里忽略EDITOR_VISUALIZATION被定义的代码。
#define TRANSFORM_TEX(tex,name) (tex.xy * name##_ST.xy + name##_ST.zw)
我们得到片元着色器输入数据如下
struct Varyings
{float4 positionCS : SV_POSITION;float2 uv : TEXCOORD0;
#ifdef EDITOR_VISUALIZATIONfloat2 VizUV : TEXCOORD1;float4 LightCoord : TEXCOORD2;
#endif
进入片元着色器:
half4 UniversalFragmentMetaLit(Varyings input) : SV_Target
{SurfaceData surfaceData;InitializeStandardLitSurfaceData(input.uv, surfaceData);BRDFData brdfData;InitializeBRDFData(surfaceData.albedo, surfaceData.metallic, surfaceData.specular, surfaceData.smoothness, surfaceData.alpha, brdfData);MetaInput metaInput;metaInput.Albedo = brdfData.diffuse + brdfData.specular * brdfData.roughness * 0.5;metaInput.Emission = surfaceData.emission;return UniversalFragmentMeta(input, metaInput);
}
计算得到 metaInput
,MetaInput定义在MetaInput.hlsl
#define MetaInput UnityMetaInput
#define MetaFragment UnityMetaFragment
UnityMetaInput结构体定义在MetaPass.hlsl中,保存了颜色和自发光颜色。
struct UnityMetaInput
{half3 Albedo;half3 Emission;
#ifdef EDITOR_VISUALIZATIONfloat2 VizUV;float4 LightCoord;
#endif
};
然后,将顶点着色器输入Varyings input
,和UnityMetaInput metaInput
传给UniversalFragmentMeta
函数。函数在UniversalMetaPass.hlsl
内
half4 UniversalFragmentMeta(Varyings fragIn, MetaInput metaInput)
{
#ifdef EDITOR_VISUALIZATIONmetaInput.VizUV = fragIn.VizUV;metaInput.LightCoord = fragIn.LightCoord;
#endifreturn UnityMetaFragment(metaInput);
}
UniversalFragmentMeta()
函数又调用了UnityMetaFragment
,UnityMetaFragment
定义在MetaPass.hlsl中
//如下代码删除了EDITOR_VISUALIZATION定义部分,我们只关注正常烘培。
float unity_OneOverOutputBoost;
float unity_MaxOutputValue;
float unity_UseLinearSpace;
half4 UnityMetaFragment (UnityMetaInput IN)
{half4 res = 0;//`unity_MetaFragmentControl`变量用来区别烘焙表面颜色还是自发光颜色。// x:烘焙表面颜色if (unity_MetaFragmentControl.x){res = half4(IN.Albedo,1);// Apply Albedo Boost from LightmapSettings.res.rgb = clamp( pow( abs(res.rgb) , saturate(unity_OneOverOutputBoost) ), 0, unity_MaxOutputValue);}// y:烘培自发光if (unity_MetaFragmentControl.y){half3 emission;if (unity_UseLinearSpace)emission = IN.Emission;elseemission = Gamma20ToLinear(IN.Emission);res = half4(emission, 1.0);}return res;
}
unity_OneOverOutputBoost 和 unity_MaxOutputValue 用来定义烘焙时的光强。
该值定义在Lighting窗口>Scene中
按照 Unity 的解释说明,我猜测:
- Albedo Boost的值被1除,传入unity_OneOverOutputBoost,标识指数强度值;
- Indirect Intensity的值,传入unity_MaxOutputValue, 规定了反射可以达到的最大亮度。
如果打开了烘培自发光,在Shader编辑器中打开Emission,设置为Baked,Shader会写入_EmissionColor值,并设置Flag。
m.globalIlluminationFlags &=~MaterialGlobalIlluminationFlags.EmissiveIsBlack;
Unity会根据这个Flag,自动设置unity_MetaFragmentControl.y
的值。
根据下图可以看到: 左边自发光的小黄球(静态物体) 的自发光颜色照射在了周围静态物体上,但 右边自发光的小黄球 是动态物体,因此,即使自发光并设置为Baked属性,也并没有烘培光照,这符合我们的想法。
无论是烘培光还是自发光,都只是将当前片元的出射光/自发光作为输出。他将指定该片元在烘培系统中以什么颜色作为光子基本颜色来映射。
总结:次表面光源颜色为表面接收到光源后的漫反射颜色,如果有自发光。则用自发光颜色替代。Unity将使用这些次表面光源或自发光光源,做间接光照烘培(我猜测应该是光子映射,而这些光子就是映射在网络上的光照贴图的一个个纹素)
4. 使用光照贴图
要想获取到光照贴图的数据,必须告诉Unity:perObjectData = PerObjectData.Lightmaps
var drawingSettings = new DrawingSettings(unlitShaderTagId, sortingSettings)
{...perObjectData = PerObjectData.Lightmaps
};
在URP中,默认开启大多数配置,可在UniversalRenderPipelien.cs > RenderSingleCamera() > InitializeRenderingData()>GetPerObjectLightFlags()中找到此设置。
static void InitializeRenderingData(UniversalRenderPipelineAsset settings, ref CameraData cameraData, ref CullingResults cullResults,bool anyPostProcessingEnabled, out RenderingData renderingData)
{renderingData.perObjectData = GetPerObjectLightFlags(renderingData.lightData.additionalLightsCount);
}static PerObjectData GetPerObjectLightFlags(int additionalLightsCount)
{var configuration = PerObjectData.ReflectionProbes | PerObjectData.Lightmaps | PerObjectData.LightProbe | PerObjectData.LightData | PerObjectData.OcclusionProbe | PerObjectData.ShadowMask;
}
一旦开启该配置PerObjectData.Lightmaps,Unity会将对有LightMap的物体使用含有LIGHTMAP_ON宏定义的Shader变体。
我们需要在Shader中定义宏:
#pragma multi_compile _ LIGHTMAP_ON
在URP管线中,只有UniversalForward、UniversalGBuffer两个Pass有关于LIGHTMAP_ON的宏定义,当定义了LIGHTMAP_ON,说明该物体网络要使用烘培光进行渲染。
我们只关心前向渲染的结果。
同样,我们将URP中的LitShader代码复制出来,便于修改
我们在增加光照贴图后,Unity会将UV数据和顶点数据打包一起发送。
struct Attributes
{float2 staticLightmapUV : TEXCOORD1;float2 dynamicLightmapUV : TEXCOORD2;
};
我们通过TEXCOORD1
和TEXCOORD2
可以获取到烘培光照贴图的uv和实时全局光照的UV。
在顶点着色器中,通过宏,将数据传入片元着色器。
// 处理烘培光照OUTPUT_LIGHTMAP_UV(input.staticLightmapUV, unity_LightmapST, output.staticLightmapUV);
// 处理实时全局光照
#ifdef DYNAMICLIGHTMAP_ONoutput.dynamicLightmapUV = input.dynamicLightmapUV.xy * unity_DynamicLightmapST.xy + unity_DynamicLightmapST.zw;
#endif
该宏定义在Lighting.hlsl中,如果启用了光照贴图,则该函数将经过了ST变换的UV坐标传递到片元着色器;否则,将LightMap宏置为空,并使用球谐函数作为全局光照数据。
#if defined(LIGHTMAP_ON)#define DECLARE_LIGHTMAP_OR_SH(lmName, shName, index) float2 lmName : TEXCOORD##index#define OUTPUT_LIGHTMAP_UV(lightmapUV, lightmapScaleOffset, OUT) OUT.xy = lightmapUV.xy * lightmapScaleOffset.xy + lightmapScaleOffset.zw;#define OUTPUT_SH(normalWS, OUT)
#else#define DECLARE_LIGHTMAP_OR_SH(lmName, shName, index) half3 shName : TEXCOORD##index#define OUTPUT_LIGHTMAP_UV(lightmapUV, lightmapScaleOffset, OUT)#define OUTPUT_SH(normalWS, OUT) OUT.xyz = SampleSHVertex(normalWS)
#endif
传入片元着色器后,初始化InputData,其中根据光照贴图设置half3 bakedGI参数。
void InitializeInputData(Varyings input, half3 normalTS, out InputData inputData)
{
#if defined(DYNAMICLIGHTMAP_ON)inputData.bakedGI = SAMPLE_GI(input.staticLightmapUV, input.dynamicLightmapUV, input.vertexSH, inputData.normalWS);
#elseinputData.bakedGI = SAMPLE_GI(input.staticLightmapUV, input.vertexSH, inputData.normalWS);
#endif
}
其中,SAMPLE_GI
宏定义在GlobalIllumination.hlsl中
// We either sample GI from baked lightmap or from probes.
// If lightmap: sampleData.xy = lightmapUV
// If probe: sampleData.xyz = L2 SH terms
#if defined(LIGHTMAP_ON) && defined(DYNAMICLIGHTMAP_ON)
#define SAMPLE_GI(staticLmName, dynamicLmName, shName, normalWSName) SampleLightmap(staticLmName, dynamicLmName, normalWSName)
#elif defined(DYNAMICLIGHTMAP_ON)
#define SAMPLE_GI(staticLmName, dynamicLmName, shName, normalWSName) SampleLightmap(0, dynamicLmName, normalWSName)
#elif defined(LIGHTMAP_ON)
#define SAMPLE_GI(staticLmName, shName, normalWSName) SampleLightmap(staticLmName, 0, normalWSName)
#else
#define SAMPLE_GI(staticLmName, shName, normalWSName) SampleSHPixel(shName, normalWSName)
#endif
- 如果定义了光照贴图或实时光照贴图,则使用函数SampleLightmap;
- 若未使用光照贴图,则使用SampleSHPixel。
核心代码:
// Sample baked and/or realtime lightmap. Non-Direction and Directional if available.
half3 SampleLightmap(float2 staticLightmapUV, float2 dynamicLightmapUV, half3 normalWS)
{
#ifdef UNITY_LIGHTMAP_FULL_HDRbool encodedLightmap = false;
#elsebool encodedLightmap = true;
#endifhalf4 decodeInstructions = half4(LIGHTMAP_HDR_MULTIPLIER, LIGHTMAP_HDR_EXPONENT, 0.0h, 0.0h);// The shader library sample lightmap functions transform the lightmap uv coords to apply bias and scale.// However, universal pipeline already transformed those coords in vertex. We pass half4(1, 1, 0, 0) and// the compiler will optimize the transform away.half4 transformCoords = half4(1, 1, 0, 0);float3 diffuseLighting = 0;#if defined(LIGHTMAP_ON) && defined(DIRLIGHTMAP_COMBINED)diffuseLighting = SampleDirectionalLightmap(TEXTURE2D_LIGHTMAP_ARGS(LIGHTMAP_NAME, LIGHTMAP_SAMPLER_NAME),TEXTURE2D_LIGHTMAP_ARGS(LIGHTMAP_INDIRECTION_NAME, LIGHTMAP_SAMPLER_NAME),LIGHTMAP_SAMPLE_EXTRA_ARGS, transformCoords, normalWS, encodedLightmap, decodeInstructions);
#elif defined(LIGHTMAP_ON)diffuseLighting = SampleSingleLightmap(TEXTURE2D_LIGHTMAP_ARGS(LIGHTMAP_NAME, LIGHTMAP_SAMPLER_NAME), LIGHTMAP_SAMPLE_EXTRA_ARGS, transformCoords, encodedLightmap, decodeInstructions);
#endif#if defined(DYNAMICLIGHTMAP_ON) && defined(DIRLIGHTMAP_COMBINED)diffuseLighting += SampleDirectionalLightmap(TEXTURE2D_ARGS(unity_DynamicLightmap, samplerunity_DynamicLightmap),TEXTURE2D_ARGS(unity_DynamicDirectionality, samplerunity_DynamicLightmap),dynamicLightmapUV, transformCoords, normalWS, false, decodeInstructions);
#elif defined(DYNAMICLIGHTMAP_ON)diffuseLighting += SampleSingleLightmap(TEXTURE2D_ARGS(unity_DynamicLightmap, samplerunity_DynamicLightmap),dynamicLightmapUV, transformCoords, false, decodeInstructions);
#endifreturn diffuseLighting;
}
- 是否解码贴图数据
- 采样光照贴图,并根据光照贴图类型是否为方向贴图,决定是否使用方向采样(个人猜测,此处使用2次球谐实现)
- 采样实时光照贴图,同样根据贴图类型进行不同的采样,之后将结果附加在光照贴图采样之上。
当下,我们只考虑开启最基础的LIGHTMAP_ON
宏。
SampleSingleLightmap函数在EntityLighting.hlsl中,函数除了基础的采样函数外,只增加了解码功能,并无特别之处。
real3 SampleSingleLightmap(TEXTURE2D_LIGHTMAP_PARAM(lightmapTex, lightmapSampler), LIGHTMAP_EXTRA_ARGS, float4 transform, bool encodedLightmap, real4 decodeInstructions)
{// transform is scale and biasuv = uv * transform.xy + transform.zw;real3 illuminance = real3(0.0, 0.0, 0.0);// Remark: baked lightmap is RGBM for now, dynamic lightmap is RGB9E5if (encodedLightmap){real4 encodedIlluminance = SAMPLE_TEXTURE2D_LIGHTMAP(lightmapTex, lightmapSampler, LIGHTMAP_EXTRA_ARGS_USE).rgba;illuminance = DecodeLightmap(encodedIlluminance, decodeInstructions);}else{illuminance = SAMPLE_TEXTURE2D_LIGHTMAP(lightmapTex, lightmapSampler, LIGHTMAP_EXTRA_ARGS_USE).rgb;}return illuminance;
}
最终,采样结果传输路线为 illuminance -> diffuseLighting -> inputData.bakedGI.
之后,在Fragment中的如下函数中进行光照计算,UV数据保存在inputData中
half4 color = UniversalFragmentPBR(inputData, surfaceData);
在Lighting.hlsl中找到该函数
half4 UniversalFragmentPBR(InputData inputData, SurfaceData surfaceData)
{...MixRealtimeAndBakedGI(mainLight, inputData.normalWS, inputData.bakedGI);...
}
进入MixRealtimeAndBakedGI,该函数混合实时光和烘培光,主要是通过实时阴影计算,减去光照贴图的暗部亮度。
void MixRealtimeAndBakedGI(inout Light light, half3 normalWS, inout half3 bakedGI)
{
#if defined(LIGHTMAP_ON) && defined(_MIXED_LIGHTING_SUBTRACTIVE)bakedGI = SubtractDirectMainLightFromLightmap(light, normalWS, bakedGI);
#endif
}
进入SubtractDirectMainLightFromLightmap,为烘培光照添加主光照阴影
half3 SubtractDirectMainLightFromLightmap(Light mainLight, half3 normalWS, half3 bakedGI)
{// Let's try to make realtime shadows work on a surface, which already contains// baked lighting and shadowing from the main sun light.// Summary:// 1) Calculate possible value in the shadow by subtracting estimated light contribution from the places occluded by realtime shadow:// a) preserves other baked lights and light bounces// b) eliminates shadows on the geometry facing away from the light// 2) Clamp against user defined ShadowColor.// 3) Pick original lightmap value, if it is the darkest one.// 1) Gives good estimate of illumination as if light would've been shadowed during the bake.// We only subtract the main direction light. This is accounted in the contribution term below.half shadowStrength = GetMainLightShadowStrength();half contributionTerm = saturate(dot(mainLight.direction, normalWS));half3 lambert = mainLight.color * contributionTerm;half3 estimatedLightContributionMaskedByInverseOfShadow = lambert * (1.0 - mainLight.shadowAttenuation);half3 subtractedLightmap = bakedGI - estimatedLightContributionMaskedByInverseOfShadow;// 2) Allows user to define overall ambient of the scene and control situation when realtime shadow becomes too dark.half3 realtimeShadow = max(subtractedLightmap, _SubtractiveShadowColor.xyz);realtimeShadow = lerp(bakedGI, realtimeShadow, shadowStrength);// 3) Pick darkest colorreturn min(bakedGI, realtimeShadow);
}
二、光照探针
1. 添加光照探针并获取烘培结果
使用光照探针,增加光照探针组件即可
添加组件后,可编辑探针位置,之后点击烘培,即可对光照探针赋值。
赋值的结果为球谐函数系数。
2. 获取烘培的球谐系数
我们通过设置PerObjectData.LightProbe
告诉Unity获取系数。
系数保存在UnityInput.hlsl中,为3阶球谐(单通道9个系数,故rgb三通道一共27个系数),故这里使用 4 * 7 = 28个存储空间。
CBUFFER_START(UnityPerDraw)// SH block featurereal4 unity_SHAr;real4 unity_SHAg;real4 unity_SHAb;real4 unity_SHBr;real4 unity_SHBg;real4 unity_SHBb;real4 unity_SHC;
CBUFFER_END
3. 计算球谐光照
计算球谐函数分为 顶点着色计算 和 片元着色计算 ,函数在GlobalIllumination.hlsl中。
// SH Vertex Evaluation. Depending on target SH sampling might be
// done completely per vertex or mixed with L2 term per vertex and L0, L1
// per pixel. See SampleSHPixel
half3 SampleSHVertex(half3 normalWS)
{
#if defined(EVALUATE_SH_VERTEX)return SampleSH(normalWS);
#elif defined(EVALUATE_SH_MIXED)// no max since this is only L2 contributionreturn SHEvalLinearL2(normalWS, unity_SHBr, unity_SHBg, unity_SHBb, unity_SHC);
#endif// Fully per-pixel. Nothing to compute.return half3(0.0, 0.0, 0.0);
}// SH Pixel Evaluation. Depending on target SH sampling might be done
// mixed or fully in pixel. See SampleSHVertex
half3 SampleSHPixel(half3 L2Term, half3 normalWS)
{
#if defined(EVALUATE_SH_VERTEX)return L2Term;
#elif defined(EVALUATE_SH_MIXED)half3 res = L2Term + SHEvalLinearL0L1(normalWS, unity_SHAr, unity_SHAg, unity_SHAb);
#ifdef UNITY_COLORSPACE_GAMMAres = LinearToSRGB(res);
#endifreturn max(half3(0, 0, 0), res);
#endif// Default: Evaluate SH fully per-pixelreturn SampleSH(normalWS);
}
球谐计算调用
half3 SampleSH(half3 normalWS)
{// LPPV is not supported in Ligthweight Pipelinereal4 SHCoefficients[7];SHCoefficients[0] = unity_SHAr;SHCoefficients[1] = unity_SHAg;SHCoefficients[2] = unity_SHAb;SHCoefficients[3] = unity_SHBr;SHCoefficients[4] = unity_SHBg;SHCoefficients[5] = unity_SHBb;SHCoefficients[6] = unity_SHC;return max(half3(0, 0, 0), SampleSH9(SHCoefficients, normalWS));
}
最终通过SampleSH9
函数计算,该函数定义在core/EntityLighting.hlsl中
SHEvalLinearL0L1
函数计算0、1阶,SHEvalLinearL2
函数计算2阶,最终将线性空间转化为SRGB空间。
float3 SampleSH9(float4 SHCoefficients[7], float3 N)
{float4 shAr = SHCoefficients[0];float4 shAg = SHCoefficients[1];float4 shAb = SHCoefficients[2];float4 shBr = SHCoefficients[3];float4 shBg = SHCoefficients[4];float4 shBb = SHCoefficients[5];float4 shCr = SHCoefficients[6];// Linear + constant polynomial termsfloat3 res = SHEvalLinearL0L1(N, shAr, shAg, shAb);// Quadratic polynomialsres += SHEvalLinearL2(N, shBr, shBg, shBb, shCr);#ifdef UNITY_COLORSPACE_GAMMAres = LinearToSRGB(res);
#endifreturn res;
}
球谐调用:在Globalillumination.hlsl中有宏如下
// We either sample GI from baked lightmap or from probes.
// If lightmap: sampleData.xy = lightmapUV
// If probe: sampleData.xyz = L2 SH terms
#if defined(LIGHTMAP_ON) && defined(DYNAMICLIGHTMAP_ON)
#define SAMPLE_GI(staticLmName, dynamicLmName, shName, normalWSName) SampleLightmap(staticLmName, dynamicLmName, normalWSName)
#elif defined(DYNAMICLIGHTMAP_ON)
#define SAMPLE_GI(staticLmName, dynamicLmName, shName, normalWSName) SampleLightmap(0, dynamicLmName, normalWSName)
#elif defined(LIGHTMAP_ON)
#define SAMPLE_GI(staticLmName, shName, normalWSName) SampleLightmap(staticLmName, 0, normalWSName)
#else
#define SAMPLE_GI(staticLmName, shName, normalWSName) SampleSHPixel(shName, normalWSName)
#endif
当未定义光照贴图和实时全局光照时,使用球谐函数赋值全局光照。
4. 使用球谐光照
在物体的Lighting一栏中,打开使用全局光照,并且使用LightProbes
三、光照探针代理体LPPV
个人认为LPPV解决的是:大体积物体球谐着色错误的问题。因为大型物体只能通过物体位置输入球谐系数,而不能通过实际片元位置,得到最邻近的探针。
因此需要使用代理探针,作为物体位置的代理点,进行计算。
然而我的硬件不支持LPPV,这里不做完整介绍。Unity好像也逐渐不再使用改方法。
参考
Unity官方文档
shiomi:Unity SRP 学习笔记(一):PBR
Baked Light Light Maps and Probes:Catlike
相关文章:

Unity SRP 管线【第五讲:URP烘培光照】
本节,我们将跟随数据流向讲解UEP管线中的烘培光照。 文章目录 一、URP烘培光照1. 搭建场景2. 烘培光照参数设置MixedLight光照设置:直观感受 Lightmapping Settings参数设置: 3. 我们如何记录次表面光源颜色首先我们提取出相关URP代码&#…...

Mysql运维篇(一) 日志类型
一路走来,所有遇到的人,帮助过我的、伤害过我的都是朋友,没有一个是敌人,如有侵权请留言,我及时删除。 一、mysql相关日志 首先,我们能接触到的,一般我们排查慢查询时,会去看慢查询日志。如果做过数据备份会恢复的,可能接触或用过BinLog。那还有其他的吗?对MySQL原理…...

【C语言】结构体与内存操作函数 总结
结构体 一、结构体简介 C 语言内置的数据类型,除了最基本的几种原始类型,只有数组属于复合类型,可以同时包含多个值,但是只能包含相同类型的数据,实际使用中并不够用。 实际使用中,主要有下面两种情况&a…...

第12章_集合框架(Collection接口,Iterator接口,List,Set,Map,Collections工具类)
文章目录 第12章_集合框架本章专题与脉络1. 集合框架概述1.1 生活中的容器1.2 数组的特点与弊端1.3 Java集合框架体系1.4 集合的使用场景 2. Collection接口及方法2.1 添加2.2 判断2.3 删除2.4 其它 3. Iterator(迭代器)接口3.1 Iterator接口3.2 迭代器的执行原理3.3 foreach循…...

C语言进阶——数据结构之链表(续)
前言 hello,大家好呀,我是Humble,本篇博客承接之前的C语言进阶——数据结构之链表 的内容(没看过的小伙伴可以从我创建的专栏C语言进阶之数据结构 找到那篇文章并阅读后在回来哦~),上次我们重点说了链表中…...

数据库课程设计-图书管理系统数据库设计
目录 一、实验目的 二、实验内容 三、实验要求 四、实验设计 4.1需求分析 4.1.1系统目标 4.1.2功能需求 4.1.3性能需求 4.14界面需求 4.2概念模型设计 4.2.1 实体及联系 4.2.2 E-R图 4.3 逻辑设计 4.3.1 E-R模型向关系模型的转换 4.3.2 数据库逻辑结构 4.3.3数据库模型函数依赖…...

【超简版,代码可用!】【0基础Python爬虫入门——下载歌曲/视频】
安装第三方模块— requests 完成图片操作后输入:pip install requests 科普: get:公开数据 post:加密 ,个人信息 进入某音乐网页,打开开发者工具F12 选择网络,再选择—>媒体——>获取URL【先完成刷新页面】 科…...

CommunityToolkit.Mvvm支持环境
引言 CommunityToolkit.Mvvm 包(又名 MVVM 工具包,以前称为 Microsoft.Toolkit.Mvvm)是一个现代、快速和模块化的 MVVM 库。 它是 .NET 社区工具包的一部分,其中一条原则是: 独立于平台和运行时 - .NET Standard 2.0…...

探讨品牌设计的本质,为企业形象注入活力!
品牌设计作为一个行业,首先需要明确行业的本质和意义。由于越来越多的咨询公司和营销公司对品牌有不同的理解和解释,因为他们服务的内容和专业水平不同,什么是品牌的定义越来越复杂,逐渐成为一种神秘的知识。例如,特劳…...

【Maven】-- 打包添加时间戳的两种方法
一、需求 在执行 mvn clean package -Dmaven.test.skiptrue 后,生成的 jar 包带有自定义系统时间。 二、实现 方法一:使用自带属性(不推荐) 使用系统时间戳,但有一个问题,就是默认使用 UTC0 的时区。举例…...

图片分类: 多类别
最近需要训练一个有200多类的图片分类网络,搜了一遍,发现居然没有很合适用的开源项目,于是自己简单撸了一个轮子,项目地址: https://github.com/xuduo35/imgcls_pytorch。支持如下backbone: alexnetresnet18,resnet34,resnet50,r…...

python 抓包tcp数据拷贝转发
在Python中,你可以使用scapy库进行抓包,使用shutil或io库进行数据的拷贝,以及使用socket库进行数据转发。下面是一个简单的示例,展示了如何进行这些操作: 首先,你需要安装必要的库。你可以使用pip来安装它…...

ubuntu 各版本图形界面和命令行切换快捷键介绍
文章目录 前言一、ubuntu 图形界面和命令行模式切换的快捷键1. ubuntu 16.042. ubuntu 18.043. ubuntu 20.044. ubuntu 22.04 总结 前言 本文主要介绍如何使用快捷键进行ubuntu 的图形界面和命令行模式切换,涉及如下 几个ubuntu 版本 ubuntu16.04 ubuntu18.04 ubun…...

基于SpringBoot Vue博物馆管理系统
大家好✌!我是Dwzun。很高兴你能来阅读我,我会陆续更新Java后端、前端、数据库、项目案例等相关知识点总结,还为大家分享优质的实战项目,本人在Java项目开发领域有多年的经验,陆续会更新更多优质的Java实战项目&#x…...

关于预检请求
基本概述 预检请求(Preflight Request)是一种由浏览器自动发起的请求,用于检查实际请求是否安全可行。这种请求通常在跨域请求(CORS)中出现,并且只在某些特定条件下触发。以下是触发预检请求的具体条件&am…...

cookie in selenium 定时更新token
1.selenium添加cookie访问 需要登录才能访问的链接 selenium 访问 “https://developer.org.com”,如果没登陆,则跳转到"https://console.org.com/login",此时selenium取到的cookie的domain是:.console.org.com。 而domain 是 .c…...

【MIdjourney】一些材质相关的关键词
1.多维剪纸(Multidimensional papercut) "Multidimensional papercut"(多维剪纸)是一种剪纸艺术形式,通过多层次的剪纸技巧和设计来创造出立体感和深度感。这种艺术形式通常涉及在不同的纸层上剪裁不同的图案,并将它们…...

递归组件怎么实现无线滚动
递归组件实现无限滚动的方法通常涉及到对数据的递归处理和组件的自我调用。以下是一个简单的示例,展示如何使用递归组件实现无限滚动: 首先,定义一个递归组件,该组件可以调用自己来渲染下一组数据。假设我们要展示一个滚动列表&a…...

致远OA如何开发 第十篇 数据库
数据库 此栏目技术支持 技术大佬对栏目文章的支持 特别感谢 如何编写dao实现数据的增删改查 新建文件 实现下面的方法即可,具体的sql操作需要自己组装 其中JDBCAgent 是致远封装过的工具Overridepublic void addData(String dataId, String agentId) {try (JDBC…...

信息检索与数据挖掘 | (十)线性回归与逻辑回归
文章目录 📚线性回归算法流程📚Bias and variance📚过拟合&欠拟合📚逻辑回归算法流程 📚线性回归算法流程 ybwx 使用loss function L来评估函数的好坏 从而我们要选择使L最小的模型参数w,b 使用梯度下降的方法…...

【issue-halcon例程学习】measure_arc.hdev
例程功能 检查倒角后铸件的细长孔之间的距离。 代码如下 read_image (Zeiss1, zeiss1) get_image_size (Zeiss1, Width, Height) dev_close_window () dev_open_window (0, 0, Width / 2, Height / 2, black, WindowHandle) set_display_font (WindowHandle, 14, mono, true,…...

RKE快速搭建离线k8s集群并用rancher管理界面
转载说明:如果您喜欢这篇文章并打算转载它,请私信作者取得授权。感谢您喜爱本文,请文明转载,谢谢。 本文记录使用RKE快速搭建一套k8s集群过程,使用的rancher老版本2.5.7(当前最新版为2.7)。适用…...

代码随想录算法训练营第十四天|● 理论基础 ● 递归遍历 ● 迭代遍历 ● 统一迭代
仅做学习笔记,详细请访问代码随想录 ● 理论基础 ● 递归遍历 ● 迭代遍历 ● 统一迭代 单层递归的逻辑就是按照中左右的顺序来处理的,这样二叉树的前序遍历,基本就写完了,再看一下完整代码: 前序遍历: …...

❤css实用
❤ css实用 渐变色边框(Gradient borders方法的汇总 5种) 给 border 设置渐变色是很常见的效果,实现这个效果有很多思路 1、使用 border-image 使用 css 的 border-image 属性给 border 绘制复杂图样 与 background-image 类似,我…...

web系统架构基于springCloud的各技术栈
博主目前开发的web系统架构是基于springCloud的一套微服务架构。 使用的技术栈:springbootmysqlclickhousepostgresqlredisrocketMqosseurekabase-gatewayapollodockernginxvue的一套web架构。 一、springboot3.0 特性:Spring Boot 3.0提供了许多新特性…...

【第十五课】数据结构:堆 (“堆”的介绍+主要操作 / acwing-838堆排序 / 时间复杂度的分析 / c++代码 )
目录 关于堆的一些知识的回顾 数据结构:堆的特点 "down" 和 "up":维护堆的性质 down up 数据结构:堆的主要操作 acwing-838堆排序 代码如下 时间复杂度分析 确实是在写的过程中频繁回顾了很多关于树的知识&…...

el-select选项过多导致页面卡顿,路由跳转卡顿
问题:el-select数据量太大,导致渲染过慢,或造成页面卡顿甚至于卡死 卡顿原因:DOM中数据过多,超过内存限制 解决方法: 1.使用Virtualized Select 虚拟化选择器,页面就不卡了 2.el-select做分…...

信息流广告参数回传工具怎么做联调
信息流广告在抖音等平台上越来越受到广告主的青睐,它能够在用户浏览内容的同时,以自然的方式展示广告,提高曝光率和点击率。然而,为了更好地评估广告效果,需要进行参数回传联调。本文将介绍一种实用的工具——数灵通外…...

matlab appdesigner系列-常用18-表格
表格,常用来导入外部表格数据 示例: 导入外界excel数据:data.xlsx 姓名年龄城市王一18长沙王二21上海王三56武汉王四47北京王五88成都王六23长春 操作步骤如下: 1)将表格拖拽到画布上 2)对app1右键进行…...

密码学的100个基本概念
密码学作为信息安全的基础,极为重要,本文分为上下两部分,总计10个章节,回顾了密码学的100个基本概念,供小伙伴们学习参考。本文将先介绍前五个章节的内容。 一、密码学历史 二、密码学基础 三、分组密码 四、序列密码 五、哈希…...