22. Unity Built-in LightingMap Baking System#
Pre-baking Setup#
Setting up Skybox#
- Path: Windows → Rendering → Lighting Settings → Skybox Material
Setting Scene Objects as Static#
- Enable Contribute GI and Reflection Probe Static
- Static option descriptions:
- Contribute GI: Responds to global illumination
- Occluder/Occludee Static: Responds to occlusion culling (OccCulling)
- Batching Static: Batching optimization
- Navigation Static/Off Mesh Link Generation: Navigation related
- Reflection Probe Static: Determines whether objects appear in reflection probe records
Main Directional Light, Emission Material, Reflection Probe Settings#
- Set emission material Global Illumination to Baked, Color to HDR
LightingMode Settings#
Path: Windows → Rendering → Lighting Settings → MixedLighting → LightingMode
LightingMode options:
- Baked Indirect
- Subtractive
- ShadowMask
- Directional Light Mode:
- RealTime
- Mixed
- Baked
Note: ShadowMask Mode needs to be enabled in ProjectSetting → Quality → Shadow → Shadowmask Mode. ShadowMask Mode additionally generates a Shadowmask texture.
Combination List#
- Bake Indirect
- Runtime LM = GI = EmitLighting + SkyLighting Shadows: Real-time shadows
- Mixed LM = GI = EmitLighting + SkyLighting + LightsGI Shadows: Real-time shadows
- Baked LM = GI + DL = (EmitLighting + SkyLighting + LightsGI) + LightsLighting Shadows: Static objects: shadows written to LM, Dynamic objects: none
- Subtractive
- Runtime LM = GI = EmitLighting + SkyLighting Shadows: Real-time shadows, RealTime Shadow Color setting disabled
- Mixed LM = GI + DL = (EmitLighting + SkyLighting + LightsGI) + LightsLighting Shadows: Static objects: shadows written to LM, Dynamic objects: real-time; RealTime Shadow Color setting effective
- Baked LM = GI + DL = (EmitLighting + SkyLighting + LightsGI) + LightsLighting Shadows: Static objects: shadows written to LM, Dynamic objects: none; RealTime Shadow Color setting disabled
- ShadowMask
- Runtime LM-light = GI = EmitLighting + SkyLighting LM-shadowmask = null Shadows: Real-time shadows
- Mixed LM-light = GI = EmitLighting + SkyLighting + LightsGI LM-shadowmask = LightsShadow Shadows: Static objects: shadows written to LM, Dynamic objects: real-time shadows
- Baked LM = GI + DL = (EmitLighting + SkyLighting + LightsGI) + LightsLighting LM-shadowmask = null Shadows: Static objects: shadows written to LM, Dynamic objects: none
Lighting Explanation#
- EmitLighting represents lighting caused by emission materials
- SkyLighting represents lighting caused by skybox
- LightsGI represents bounce light caused by main light
- LightsLighting represents lighting caused by main light
- LM = Lightmap, DL = DirectLighting
Note: Subtractive and ShadowMask handle real-time shadows for dynamic objects differently, ShadowMask provides more natural shadow mixing for static and dynamic objects.
Lightmapping Settings#
- Lightmapper: Progressive CPU
- Lightmap Resolution: 40
- Directional Mode: Non-Directional (generally not recommended, effect not obvious, energy consumption doubles)
Common Solutions#
- Full real-time lighting: No Lightmap needed
- Full real-time direct lighting + Baked Indirect
- Static objects baked, dynamic objects real-time (Subtractive/ShadowMask)
- Auxiliary lights only for baking (Baked/Mixed) Mixed determines whether to affect dynamic objects
- Effects RealTime
Batching Strategy and Baking Process Analysis#
Definition#
Combining several batches of rendering with the same rendering process to save resources and increase speed. Generally, shared Material with same parameters can be batched.
Common Strategies#
- Unity static settings
- Unity SRPBatching (same Shader can be batched)
- GPU Instancing
- Manual batching (merge models in DDC software)
FrameDebugger#
Windows → Analysis → FrameDebugger
FrameDebugger can view batching and rendering process
23. External LightingMap Baking and Custom Shader#
External Baking Process#
- Use 3dsMax/MAYA to bake LightingMap, need to create UV2 for models
- Textures:
- AO Map replaces SkyLighting
- EmitLighting
- LightsShadow
- LightGI + LightsLighting
- Alpha
- Use SD’s Blur HQ node to remove noise, merge textures to save resources
Lighting Composition#
flowchart LR Lighting --> SimpleLightSource SimpleLightSource --> MainDirectionalLight MainDirectionalLight --> DiffuseReflection MainDirectionalLight --> SpecularReflection DiffuseReflection --> Occlusion SpecularReflection --> Occlusion Occlusion --> Result Lighting --> ComplexEnvironment ComplexEnvironment --> SkyLight ComplexEnvironment --> Emission ComplexEnvironment --> OtherAmbientLight SkyLight --> DiffuseReflectionCubemap Emission --> DiffuseReflection1Col OtherAmbientLight --> DiffuseReflection DiffuseReflectionCubemap --> Occlusion DiffuseReflection1Col --> Occlusion DiffuseReflection --> Ignore SkyLight --> SpecularReflectionCubemap Emission --> SpecularReflectionCubemap OtherAmbientLight --> SpecularReflectionCubemap SpecularReflectionCubemap --> SurfaceOcclusion SpecularReflectionCubemap --> FresnelAttenuation SurfaceOcclusion --> Result FresnelAttenuation --> Result Occlusion --> Result
Shader Core Structure#
Shader "Zhuangdong/AP1/L15/L15_LightingMap" {
Properties{
// ... Texture and parameter declarations ...
}
SubShader{
Tags { "RenderType" = "Opaque" }
Pass {
Name "FORWARD"
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
// ... Using orge case as template, vertex shader removes TRANSFER_VERTEX_TO_FRAGMENT(o) ...
VertexOutput vert(VertexInput v) {
...
//Case needs 2 UVs, here merge 2 UVs into one float4, saving TEXCOORD
o.uvs = float4(v.uv0, v.uv1);
...
}
float3 DecodeNormal (float2 maskXY) {
//Texture only has RG two channels, we need to supplement B channel
float2 nDirTSxy = maskXY * 2.0 - 1.0;
//Texture value range is (0,1), normal is vector, vector value range is (-1,1)
float nDirTSz = sqrt(1.0 - (nDirTSxy.x * nDirTSxy.x + nDirTSxy.y * nDirTSxy.y));
//sqrt takes square root, x squared + y squared + z squared = 1
return float3(nDirTSxy, nDirTSz);
}
float4 frag(VertexOutput i) : COLOR {
// ... Sampling, vectors, dot products ...
//Extract surface information
float occlusion = var_MaskTex.r;
float matMask = var_MaskTex.g;//Metal texture
float3 diffCol = var_MainTex.rgb * lerp(1.0, _MetalDarken, pow(matMask, 5.0));
//Here add _MetalDarken to metal, reducing metal part diff
float specPow = max(1.0, lerp(_SpecParams.x, _SpecParams.z, matMask));
float specInt = max(0.0, lerp(_SpecParams.y, _SpecParams.w, matMask));
float reflectMip = clamp(lerp(_EnvReflectParams.x, _EnvReflectParams.z, matMask), 0.0, 7.0);
//clamp is clamp(value,min,max) which limits value to (min,max)
float reflectInt = max(0.0, lerp(_EnvReflectParams.y, _EnvReflectParams.w, matMask));
float fresnel = lerp(pow(1.0 - max(0.0, ndotv), _FresnelPow), 1.0, matMask);
//Distinguish Fresnel intensity between metal and non-metal
//Extract lighting information
float skyLightOcc = var_LightMap.r;
float emitLightingInt = var_LightMap.g;
float mainLightGIInt = pow(var_LightMap.b, _GIpow);
float mainLightShadow = var_LightMap.a;
//Sample texture Cube
float3 var_SkyCube = texCUBElod(_SkyCube, float4(vrDirWS, 7.0)).rgb;
float3 var_EnvCube = texCUBElod(_EnvCube, float4(vrDirWS, reflectMip)).rgb;
//MainLight
//Diff
float3 halfShadowCol = lerp(_HalfShadowCol.rgb, _MainLightCol, mainLightShadow);
//First assign colors to shadow and main light
float3 mainLightCol = lerp(_MainLightCol, halfShadowCol, _HalfShadowCol.a) * mainLightShadow;
//Then use shadow mask to remove shadow color, leaving main light color and penumbra color, here _HalfShadowCol.a controls penumbra intensity
float3 mainLightDiff = diffCol * mainLightCol * max(0.0, nDotl);
//DiffCol*Lambert*MainLight
//Spec
float3 mainLightSpec = mainLightCol * pow(max(0.0, vDotr), specPow) * specInt;//Phong
//GI
float3 mainLightGI = _GICol * occlusion * mainLightGIInt * _GIInt;
//GI is the bounce light of main light, here add AO for variation
//Mixed
float3 mainLight = (mainLightDiff + mainLightSpec + mainLightGI * _MainLightGIOn) * _MainLightOn;
//Here GI already has ambient AO information, so no need to *LM
//OtherLight
float3 skyLightDiff = diffCol * var_SkyCube * _SkyCol * _SkyLightInt * skyLightOcc * occlusion;
//Here is object diffuse * sky diffuse * LM(LightingMap)
float3 emitLightDiff = diffCol * _EmissionCol * emitLightingInt * occlusion;
//Same as above, this part is not affected by ambient AO, so no need to *_SkyLightOcc
//OtherEnvSpec
float3 envLightSpec = var_EnvCube * reflectInt * fresnel * occlusion;
float3 OtherLight = skyLightDiff * _SkyLightOn + emitLightDiff * _EmitLightOn + envLightSpec * _EnvReflectOn;
float3 finalRGB = mainLight + OtherLight;
return float4(finalRGB, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
Key Parameter Explanation#
_SpecParams
- x: Non-metal specular power
- y: Non-metal specular intensity
- z: Metal specular power
- w: Metal specular intensity
_EnvReflectParams
- x: Non-metal Cube sampling Mip value
- y: Non-metal reflection intensity
- z: Metal Cube sampling Mip value
- w: Metal reflection intensity
Key Points Summary#
- Normal map only has RG, need to manually supplement B channel
- Penumbra coloring
- Metal/non-metal parameters separately control specular and reflection
- Synthesize lighting according to lighting composition
24-26 LightingController Custom Baker and Multi-Script Collaboration#
Due to the extensive code content in this case, the explanation focuses on summaries. For source code, please download from the tutorial location.
This case implements a custom baker for completing custom baking, result preview, and material editing functions. The baking process generates three textures corresponding to main light, sky light, and emission light sources, and finally merges these three textures to output the final LightingMap. After baking is complete, you can preview the independent effects of each texture separately and flexibly adjust the final image performance through global parameters.
Main Light
Sky Light
Emission Light
Functions and File Structure#
LightingController
: Main script responsible for the entire baking process.LightingControllerGUI
: Custom baking panel (UI), making operations more intuitive.EmissionShaderGUI
: Allows material spheres to set emission participation in GI in the Inspector interface.Building/EmitLight/Sky
: Respectively correspond to building, emission, and skybox Shaders.
Global Parameter Brief Explanation#
MetalDarken
: Darkness of metal parts (affects metal surface reflection intensity).MainLightCol
: Main light color.SpecParams
: Specular parameters (x/z are specular power, y/w are specular intensity, respectively corresponding to metal/non-metal).SkyLightInt
: Sky light intensity.ReflectParams
: Reflection parameters (x/z are CubeMap Mip, y/w are reflection intensity).FresnelPow
: Fresnel phenomenon intensity.EmissionCol
: Emission color.
Baking Process#
- Collect scene baking information: including texture count, paths, texture objects
- Create cache: Prepare cache for main light, sky light, emission GI and final synthesized Lightmap respectively.
- Bake: Bake main light, sky light, emission GI in sequence, store each step result in cache.
- Synthesize Lightmap: Combine the results of three baking steps into one final Lightmap.
- Replace old Lightmap: Overwrite the scene’s original lighting textures with the newly synthesized Lightmap.
- Reset scene lighting environment: Modify scene settings.
- Update global parameters: Synchronize parameters to all related materials.
- Bake reflection probes: Make reflection effects display correctly.
Core Code Example#
public void MultiBake()
{
var buffer = new LightmapsBuffer();
Bake(BakeMode.BakeMainLight);
var info = new LightmapsInfo(LightmapSettings.lightmaps);
buffer.WriteBuffer(info, LightmapsBuffer.BufferType.MainLight);
Bake(BakeMode.BakeSkyLight);
buffer.WriteBuffer(info, LightmapsBuffer.BufferType.SkyLight);
Bake(BakeMode.BakeEmissionGI);
buffer.WriteBuffer(info, LightmapsBuffer.BufferType.EmissionGI);
buffer.CreateLightmaps();
buffer.OverrideLightmaps(info);
buffer.Clear();
ArrangeBakeScene(BakeMode.Default);
UpdateGlobalProperties();
BakeReflectProbe();
}
LightingController#
Class and Inheritance, Methods and Lifecycle#
public class LightingController : MonoBehaviour
class
: Can be understood as a “blueprint”, like Shader is a blueprint, Material is an “item” made according to the blueprint.Inheritance
: Like “vehicle” is the parent class, “car” is the child class, child class automatically has the basic functions of parent class.MonoBehaviour
: All Unity scripts that can be attached to objects must inherit from it to participate in the game lifecycle.[ExecuteInEditMode]
: Generally, scripts only run in PlayMode, this directive makes the script also run in preview window
private void OnEnable() {UpdateGlobalProperties();};
void
is different from methods in previous cases, void functions don’t return values, they are mainly used to perform specific operations.OnEnable
means this function starts running at the OnEnable stage of the game lifecycle
UpdateGlobalProperties() method is written after OnEnable, which is allowed in C#
LightmapsInfo#
To understand the following concepts to understand the role of LightmapsInfo
LightmapData, struct, constructor, Dictionary and foreach loop
LightmapData#
LightmapData[]
is Unity’s data type, it’s the collection of all lighting textures in the current scenelightmapsData
is a custom name, representingLightmapData[]
, used for function internal operationsLightmapsData[]
contains multiple instances, each instance represents each Lightingmap, each instance contains:- lightmapColor
- lightmapDir
- shadowMask
- occlusionMask
Struct and Constructor#
Struct#
Can be understood as a “container” that can hold different types of data.
Syntax#
public/private name (same as class/struct name) (type, parameter name)
Constructor#
A special function used to initialize struct (or class), with the same name as the struct.
Example#
public struct LightmapsInfo {
public readonly Dictionary<string, Texture2D> lightmapsInfo;
public LightmapsInfo(LightmapData[] lightmapsData) {
// Here all content will be automatically initialized
}
}
public class Person {
public string Name;
public int Age;
// Constructor
public Person(string name, int age) {
Name = name;
Age = age;
}
}
- Without constructor, you need to manually assign values one by one, very tedious.
- With constructor, just
new LightmapsInfo(parameters)
, all content automatically arranged.
Multiple Uses of Constructor#
- Constructor can have multiple, with different parameters or methods.
- Can also do parameter checking in constructor to prevent errors.
Example#
public LightmapsInfo(LightmapData[] lightmapsData){
if(lightmapsData == null) {
// Do some error handling
}
// Other initialization content
}
Dictionary#
Dictionary
is a “key-value pair” container that can quickly find corresponding content through “names”.- For example:
Dictionary<string, Texture2D>
, can use string names to find corresponding texture paths and objects.
Case#
Dictionary<string, Texture2D> myDict = new Dictionary<string, Texture2D>();
myDict["MainLight"] = mainLightTex;
Texture2D tex = myDict["MainLight"];
foreach Loop#
foreach
is used to traverse collections (such as arrays, List, Dictionary, etc.)
Example#
//data represents instance
foreach (var data in lightmapsData) {
var texture = data.lightmapColor;
path = AssetDatabase.GetAssetPath(texture);
lightmapsInfo.Add(path, texture);
}
int[] numbers = { 1, 2, 3, 4, 5 };
foreach (int num in numbers){
Debug.Log(num); // Output: 1, 2, 3, 4, 5
}
LightmapsBuffer Cache#
In Unity’s lighting baking process, we need to cache, synthesize and save different types of lighting textures.
LightmapsBuffer: Role and Implementation of Cache#
Cache (Buffer)#
Can be understood as a “storage box” used to temporarily save different types of lighting textures, convenient for subsequent processing and transfer.
Enum#
Used to define a set of constants, making code clearer and easier to understand.
public enum BufferType {
MainLight, // Main light lighting: BufferA
SkyLight, // Sky light lighting: BufferB
EmissionGI, // Emission GI: BufferC
Lightmap // Synthesized Lightmap
}
// Create cache for different types of lighting respectively
private Texture2D[] _bufferA; // Main light
private Texture2D[] _bufferB; // Sky light
private Texture2D[] _bufferC; // Emission
private Texture2D[] _lightmap; // Final synthesis
Clear Cache (ClearBuffer & Clear)#
switch-case
: Execute different clearing logic according to different types (BufferType).void(type parameter name)
:- If it’s a single data, parameter name is the temporary name of this data in the function
- If it’s a collection, parameter name is the temporary name of this collection in the function
- If it’s an enum, parameter name is the temporary name of elements in the enum.
Write Cache (WriteBuffer)#
- First judge the type, if it’s the final Lightmap then return directly.
- Clear the corresponding type cache.
- Create new cache and copy textures from info.
if (type == BufferType.Lightmap) return;
// Clear cache
ClearBuffer(type);
// Create cache and copy textures from info
var lightmapsCount = info.lightmapsCount;
var buffer = new Texture2D[lightmapsCount];
for (var i = 0; i < lightmapsCount; i++) {
var lightmap = info.lightmapsInfo.Values.ElementAt(i);
buffer[i] = new Texture2D(lightmap.width, lightmap.height, lightmap.format, false);
Graphics.CopyTexture(lightmap, 0, 0, buffer[i], 0, 0);
}
info.lightmapsInfo.Values.ElementAt(i)
: Take out the i-th texture from the dictionary in order.Texture2D(texture width, height, format (default RGB24), whether to generate mipmap)
Graphics.CopyTexture
: Unity API, used for efficient texture data copying.
Synthesize Lightmap (CreateLightmaps)#
- Read pixels from main light, sky light, emission three types of cache in sequence, synthesize to a new Lightmap.
for (var x = 0; x < width; x++) {
for (var y = 0; y < height; y++) {
var colA = _bufferA[i].GetPixel(x, y);
var colB = _bufferB[i].GetPixel(x, y);
var colC = _bufferC[i].GetPixel(x, y);
var newCol = new Color(colA.r, colB.g, colC.b, 1.0f);
lightmap.SetPixel(x, y, newCol.linear);
}
}
- Each pixel point takes corresponding colors from three cache textures, synthesizes to the final Lightmap.
- Note: Cache saves Texture2D objects, need to read and write pixel by pixel.
Save Lightmap (OverrideLightmaps)#
- Encode the synthesized Lightmap to EXR format (supports HDR) and write to disk.
- Refresh Unity resource database to make new files recognized.
for (var i = 0; i < lightmapsCount; i++) {
var bytes = _lightmap[i].EncodeToEXR(Texture2D.EXRFlags.CompressZIP);
File.WriteAllBytes(lightmapsInfo.Keys.ElementAt(i), bytes);
AssetDatabase.Refresh();
}
EncodeToEXR
: Unity API, encodes texture to EXR format, EXR is Unity lighting system’s dedicated formatTexture2D.EXRFlags.CompressZip
compress to zipFile.WriteAllBytes
: .NET API, writes byte array to file.lightmapsInfo.Keys.ElementAt(i)
get the i-th key from dictionary lightmapsInfo, i.e., pathAssetDatabase.Refresh()
: Refresh resource database (only available in editor mode).
Baking Mode (BakeMode) and Scene Settings (ArrangeBakeScene)#
BakeMode
: Use enum to define different baking modes (all, main light, sky light, emission).ArrangeBakeScene
: Set scene parameters according to different modes, such as ambient light type and intensity.
RenderSettings.ambientMode = AmbientMode.Skybox;
RenderSettings.ambientIntensity = 1.0f;
ambientMode
determines ambient light type (skybox, gradient, single color).ambientIntensity
controls ambient light intensity (effective after baking).
Corresponds to lightingSetting → lighting → Scene → Environment Lighting → Source & Intensity Multiplier
Set main light as static:
var staticFlags = StaticEditorFlags.ContributeGI | StaticEditorFlags.ReflectionProbeStatic;
GameObjectUtility.SetStaticEditorFlags(mainlight.gameObject, staticFlags);
Execute Baking (Bake) and Reflection Probe Baking (BakeReflectProbe)#
Bake
directly calls Unity API for lighting baking.
public void Bake(BakeMode mode) {
Lightmapping.Clear();
Lightmapping.Bake();
}
BakeReflectProbe
traverses all reflection probes in the scene, bakes and saves them one by one.
private void BakeReflectProbe() {
var allProbe = FindObjectsOfType<ReflectionProbe>();
foreach (var probe in allProbe) {
var path = AssetDatabase.GetAssetPath(probe.texture);
Lightmapping.BakeReflectionProbe(probe, path);
}
AssetDatabase.Refresh();
}
FindObjectsOfType<>()
: Find all reflection probes in the scene.AssetDatabase.GetAssetPath()
get these reflection probes’ textures and pathsLightmapping.BakeReflectionProbe()
: Unity API, bake reflection probe.
If you don’t customize storage path, reflection probe baked Cubemap defaults to LightingData
LightingControllerGUI: Custom Baking Panel#
After understanding the baking process and methods, we also need a custom UI panel to make baking operations and parameter adjustments more intuitive. Below introduces the implementation ideas and key code of LightingControllerGUI.
Basic Structure of Editor Class#
UnityEditor.Editor
: Custom editor class must inherit fromEditor
and implementOnInspectorGUI()
method.OnInspectorGUI()
: Used to draw the content of Inspector panel.
public class LightingControllerGUI : Editor
{
public override void OnInspectorGUI()
{
var controller = target as LightingController;
if (controller == null) return;
DrawFunctionButtons(controller);
DrawGlobalProperties(controller);
}
}
override
when methods in class are set as virtual or abstract, can only use override to reference and override execution contenttarget as LightingController
: Get the object bound to current Inspector, must be MonoBehaviour derived class.DrawFunctionButtons
: Draw baking-related operation buttons.DrawGlobalProperties
: Draw global parameter adjustment controls.
Drawing Function Buttons#
- Use
GUILayout.Button("button name")
to create buttons, execute corresponding methods when clicked. EditorGUILayout.BeginHorizontal()
andEndHorizontal()
are used to make multiple buttons display in the same row.
if (GUILayout.Button("Forbidden Art·Multiple Baking"))
controller.MultiBake();
EditorGUILayout.BeginHorizontal();
// Can add more buttons here
EditorGUILayout.EndHorizontal();
Global Parameter Adjustment and Monitoring#
- Use
EditorGUILayout.BeginFoldoutHeaderGroup
to create collapsible parameter groups. - Use
EditorGUILayout.Slider
to create draggable sliders for easy parameter adjustment. - Use
EditorGUI.BeginChangeCheck()
andEndChangeCheck()
to monitor parameter changes, automatically update when changed.
EditorGUI.BeginChangeCheck();
_groupAToggle = EditorGUILayout.BeginFoldoutHeaderGroup(_groupAToggle, "Material Properties");
if (_groupAToggle)
//Equivalent to if (groupAToggle == true)
{
controller.metalDarken = EditorGUILayout.Slider(
"Metal Darkening",
controller.metalDarken,
0.0f, 5.0f);
}
EditorGUILayout.EndFoldoutHeaderGroup();
if (EditorGUI.EndChangeCheck())
{
controller.UpdateGlobalProperties();
EditorUtility.SetDirty(controller);
}
_groupAToggle
: Used to control the expand/collapse of foldout group, defaults tofalse
when not assigned.EditorUtility.SetDirty(controller)
: Mark object as “modified”, ensure parameter changes can be recorded by Unity.
EmissionShaderGUI: Custom Emission Parameter Panel#
Through custom ShaderGUI
, we can make the material sphere’s Inspector panel appear “Enable Emission GI” option, convenient for controlling whether emission participates in global illumination (GI).
Basic Structure#
Inherit ShaderGUI#
When customizing material Inspector panel, need to inherit ShaderGUI
class.
public class EmissionShaderGUI : ShaderGUI
{
// Specific implementation see below
}
After inheritance, Unity will automatically get the Shader and Material information using this script.
Get Current Material#
Get the current material instance being edited through materialEditor.target as Material
.
var material = materialEditor.target as Material;
Use Default Inspector Interface#
If you don’t need to customize complex UI, you can directly call the base class’s OnGUI
, this will display the default property panel.
base.OnGUI(materialEditor, properties);
Add Emission GI Switch#
Use EditorGUILayout.Toggle
to create a checkbox, control whether emission participates in global illumination.
Toggle has 4 parameters
- label text content
- value condition
- style button style
- option button size
var ifEmissionGIOn = EditorGUILayout.Toggle(
"Enable Emission GI",
material.globalIlluminationFlags == MaterialGlobalIlluminationFlags.AnyEmissive);
material.globalIlluminationFlags = ifEmissionGIOn
? MaterialGlobalIlluminationFlags.AnyEmissive
: MaterialGlobalIlluminationFlags.EmissiveIsBlack;
EditorGUILayout.Toggle("interface text", condition)
: Display checkbox state according to condition.- Ternary operator
condition ? resultA : resultB
, equivalent to if-else statement:
if (ifEmissionGIOn)
material.globalIlluminationFlags = MaterialGlobalIlluminationFlags.AnyEmissive;
else
material.globalIlluminationFlags = MaterialGlobalIlluminationFlags.EmissiveIsBlack;
Shader Explanation and Case Analysis#
In this case, Shader has several key differences compared to other cases:
- Lightmap UV calculation method
- MetaPass implementation
- Branch declaration (shader_feature)
- Custom ShadowCaster
Standard Lightmap UV Calculation#
In Unity, Lightmap UV coordinates are usually calculated like this:
float2 lmUV = v.uv1.xy * unity_LightmapST.xy + unity_LightmapST.zw;
uv1
is the model’s second set of UV (specifically for lighting textures).unity_LightmapST
is the scale/offset parameter automatically passed by Unity.
MetaPass: Shading Channel Dedicated to Baking#
MetaPass is Unity lighting baking system’s dedicated Shader channel. Only Shaders with MetaPass can participate in lighting baking.
Case Code#
//Building.Shader's MetaPass
Pass {
Name "META"
Tags { "LightMode" = "Meta" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "UnityMetaPass.cginc"
#pragma shader_feature __ _BAKE_MAINLIGHT _BAKE_SKYLIGHT _BAKE_EMISSIONGI
struct VertexInput {
float4 vertex : POSITION;
float2 uv0 : TEXCOORD0;
float2 uv1 : TEXCOORD1;
float2 uv2 : TEXCOORD2;
};
struct VertexOutput {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
VertexOutput vert (VertexInput v) {
VertexOutput o = (VertexOutput)0;
o.pos = UnityMetaVertexPosition(v.vertex, v.uv1, v.uv2, unity_LightmapST, unity_DynamicLightmapST);
o.uv = v.uv0;
return o;
}
float4 frag(VertexOutput i) : COLOR {
UnityMetaInput metaIN;
UNITY_INITIALIZE_OUTPUT(UnityMetaInput, metaIN);
metaIN.Albedo = Luminance(tex2D(_MainTex, i.uv).rgb);
metaIN.SpecularColor = 0.0f;
metaIN.Emission = 0.0f;
return UnityMetaFragment(metaIN);
}
ENDCG
}
Key Points Explanation#
UnityStandardMeta.cginc
PBR process META Pass dedicatedUnityMetaPass.cginc
belongs to part ofUnityStandardMeta.cginc
, since the case is not PBR process, for optimization consideration, only reference this cginc. Compared toUnityStandardMeta.cginc
need to customize vertex shader partUnityMetaVertexPosition
: Convert vertex and UV to baking space.UnityMetaInput
: Contains array of baking required data like Albedo, SpecularColor, Emission.UNITY_INITIALIZE_OUTPUT
initializeUnityMetaInput
Luminance
: Convert color to grayscale, ensure baking only considers brightness, not affected by specular and emission.
Branch Control (shader_feature)#
//LightingController.cs
...
case BakeMode.Default:
// Turn off main light
mainlight.enabled = true;
// Set environment
RenderSettings.ambientMode = AmbientMode.Skybox;
RenderSettings.ambientIntensity = 1.0f;
// Set Shader global branches
Shader.DisableKeyword("_BAKE_MAINLIGHT");
Shader.DisableKeyword("_BAKE_SKYLIGHT");
Shader.DisableKeyword("_BAKE_EMISSIONGI");
break;
...
#pragma shader_feature __ _BAKE_MAINLIGHT _BAKE_SKYLIGHT _BAKE_EMISSIONGI
- Need to set global branches in both script and Shader
- Through branch declaration, can make Shader output different content for different baking modes.
- Use
#if defined(...)
for branch judgment in code:
#if defined (_BAKE_EMISSIONGI)
metaIN.Emission = opacity;
#elif defined (_BAKE_MAINLIGHT) || defined (_BAKE_SKYLIGHT)
metaIN.Emission = 0.0f;
#endif
Skybox Special Note#
- Skybox’s Shader doesn’t need MetaPass, Unity internally handles skybox lighting baking automatically.
ShadowCaster: Custom Shadow Pass#
Since this case scene is non-enclosed box-shaped and normals face inward, Unity’s default ShadowCaster cannot project correctly. Need to customize ShadowCaster Pass.
Case Code#
Pass {
Name "ShadowCaster"
Tags { "LightMode" = "ShadowCaster" }
ZWrite On ZTest LEqual Cull off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_shadowcaster
#include "UnityCG.cginc"
struct v2f {
V2F_SHADOW_CASTER;
};
v2f vert(appdata_base v) {
v2f o;
TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
return o;
}
float4 frag(v2f i) : SV_Target {
SHADOW_CASTER_FRAGMENT(i)
}
ENDCG
}
Key Points Explanation#
appdata_base
: Unity predefined vertex input structure, contains POSITION, NORMAL, TEXCOORD0.V2F_SHADOW_CASTER
: Output structure, contains shadow projection required clip space position and auxiliary vectors, equivalent to:- float4 pos : SV_POSITION;
- float3 vec : TEXCOORD0;
TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
: Calculate shadow offset, ensure shadow projection is correct.
TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
float4 clipPos = UnityClipSpaceShadowCasterPos(v.vertex, v.normal); \
o.pos = UnityApplyLinearShadowBias(clipPos); \
o.vec = ComputeOffsetData(clipPos);
SHADOW_CASTER_FRAGMENT(i)
Unity built-in macro, calculate shadow casting in fragment shaderCull off
: Turn off culling, ensure inner surfaces can also project.
uv0: TEXCOORD0 in VertexInput will automatically be recognized as the model’s first set of UV, in VertexOutput, TEXCOORD0 is a general interpolation register, it’s a developer-defined data channel, need to manually calculate and assign from input data in vert
The translation is now complete, including all Chinese comments and technical terms translated to English while maintaining the technical accuracy and context of the original content.