Integrating Bakery shader features to custom shaders
If you have a custom shader but want to add support for e.g.
- SH lightmaps;
- Per-vertex lightmaps;
- Volume lighting
- ...etc,
...there are a few steps you need to take:
Contents
Include the functions
1. You can use existing functions from Bakery.cginc, but the easiest starting point is including BakeryDecodeLightmap.hlsl which comes with the URP graph package (starting with URP graphs (rev 5.1) (16.0.4)). The latest package is always listed and freely downloadable from the main page. Unpack the package, copy the file to your shaders' folder and include it:
Select and call a function
2. Select a function for the desired feature and call it. For example:
(In the following functions normalWorld and viewDir are always world-space).
Computes final lightmap UV by applying renderer.lightmapScaleOffset to UV2. Following functions expect lightmapUV to be transformed this way.
Computes approximate specular from a directional lightmap. color will contain new specular highlights.
Samples Bakery SH lightmaps. L0 must be the default lightmap (decoded unity_Lightmap) value. sh will contain new diffuse lighting.
Samples Bakery MonoSH lightmaps. sh will contain new diffuse lighting.
Samples Bakery SH lightmaps and computes approximate specular from them. L0 must be the default lightmap (decoded unity_Lightmap) value. diffuseSH will contain new diffuse lighting; specularSH will contain new specular highlights.
Samples Bakery MonoSH lightmaps and computes approximate specular from them. diffuseSH will contain new diffuse lighting; specularSH will contain new specular highlights.
Samples Bakery Volumes. sh will contain new diffuse lighting. supportBakedVolumeRotation (added after rev 6.0) is either 0 (no rotation allowed) or 1 (enabled).
Samples Bakery Volumes and computes approximate specular from them. diffuseSH will contain new diffuse lighting; specularSH will contain new specular highlights. supportBakedVolumeRotation (added after rev 6.0) is either 0 (no rotation allowed) or 1 (enabled).
Samples light probes in a non-linear way, fixing negative color artifacts. color will contain new diffuse lighting.
Use the computed data
3. Given new diffuse/specular lighting, apply it to your shading calculations. The simplest approach would be:
However, if you want to take fresnel, metalness, specular color, and other properties into account, you can use this complex function:
After that just output (newDiffuse + newSpecular).
If you use a surface shader or a shader graph that doesn't allow outputting final color, a typical workaround is:
- Set Occlusion to 0 to cancel built-in indirect lighting and reflections.
- Add reflections back to newSpecular. You can use this function:
lod parameter can be computed with:
- Output final computed color to Emission.
Examples
Minimal unlit shader using SH lightmaps
Minimal surface shader using SH lightmaps
Minimal surface shader using SH lightmaps, with specular and reflections
URP Shader Graph integration
If you have an existing URP graph, most Bakery features can be added there in a following way:
- Set Ambient Occlusion to 0 to disable existing indirect lighting.
- Create a Custom Function node.
- In Node Settings:
- Set Source to BakeryDecodeLightmap.hlsl
- Set Name to the desired function name (see above), but without "_float". E.g., instead of "BakeryVolume_float", write "BakeryVolume".
- Add Inputs of the function. For BakeryVolume, these are "posWorld" (Vector3), "normalWorld" (Vector3) and supportBakedVolumeRotation (Float).
- Add Outputs of the function. For BakeryVolume, it's just "sh" (Vector3).
- Connect graph data to the inputs. E.g. for BakeryVolume, use a Position node (Space = World) for posWorld and a Transform node (Tangent -> World, Type = Normal) to convert your Tangent normal to World space and use it in normalWorld (or compute world-space normal initially).
- Output of the node is lighting color. Some functions have two outputs (diffuse and specular). For diffuse, mix final color in the following way:
- Add a Multiply node and multiply Custom Function output with your albedo.
- Add another Multiply node and multiply previous result with your ambient occlusion.
- Add an Add node to add your Emission to the previous value (if there is any).
- Connect final output to Emission.
Surface shader workarounds
It has been observed that on some Unity versions, not using any value from the Input structure in a literal assignment (such as "value = func(IN.uv)") results in Unity removing this value (setting it to 1.0). For example the surface shader parser ignores syntax like "func(IN.uv, out value)". If the steps above don't work in a surface shader, you might need to force Unity to keep the input values. One way of doing it is simply:
struct Input { float2 uv_MainTex; float2 uv2_OtherTex; }; ... float2 lightmapUV; LightmapUV_float(IN.uv2_OtherTex, lightmapUV); // Unity may destroy uv2_OtherTex and consequently lightmapUV, because it doesn't parse it as an assignment ... o.Emission += IN.uv2_OtherTex.xxx * 0.00001; // hack to force Unity never skip uv2_OtherTex during parsing