Integrating Bakery shader features to custom shaders

From Bakery GPU Lightmapper: Wiki
Jump to navigation Jump to search

If you have a custom shader but want to add support for e.g.

...there are a few steps you need to take:

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:

#define NOURP // add this if you're not on URP
#define SURFACE // add this if it's a surface shader
#include "BakeryDecodeLightmap.hlsl"


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).


void LightmapUV_float(float2 uv, out float2 lightmapUV)

Computes final lightmap UV by applying renderer.lightmapScaleOffset to UV2. Following functions expect lightmapUV to be transformed this way.



void DirectionalSpecular_float(float2 lightmapUV, float3 normalWorld, float3 viewDir, float smoothness, out float3 color)

Computes approximate specular from a directional lightmap. color will contain new specular highlights.



BakerySH_float(float3 L0, float3 normalWorld, float2 lightmapUV, out float3 sh)

Samples Bakery SH lightmaps. L0 must be the default lightmap (decoded unity_Lightmap) value. sh will contain new diffuse lighting.



BakeryMonoSH_float(float3 normalWorld, float2 lightmapUV, out float3 sh) // samples Bakery MonoSH lightmaps

Samples Bakery MonoSH lightmaps. sh will contain new diffuse lighting.



void BakerySpecSHFull_float(float3 L0, float3 normalWorld, float2 lightmapUV, float3 viewDir, float smoothness, float3 albedo, float metalness, out float3 diffuseSH, out float3 specularSH)

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.



void BakerySpecMonoSHFull_float(float3 normalWorld, float2 lightmapUV, float3 viewDir, float smoothness, float3 albedo, float metalness, out float3 diffuseSH, out float3 specularSH)

Samples Bakery MonoSH lightmaps and computes approximate specular from them. diffuseSH will contain new diffuse lighting; specularSH will contain new specular highlights.



BakeryVolume_float(float3 posWorld, float3 normalWorld, float supportBakedVolumeRotation, out float3 sh)

Samples Bakery Volumes. sh will contain new diffuse lighting. supportBakedVolumeRotation (added after rev 6.0) is either 0 (no rotation allowed) or 1 (enabled).



void BakeryVolumeSpec_float(float3 posWorld, float3 normalWorld, float3 viewDir, float smoothness, float3 albedo, float metalness, float supportBakedVolumeRotation, out float3 diffuseSH, out float3 specularSH)

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).



void NonLinearLightProbe_float(float3 normalWorld, out float3 color)

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:

albedo * diffuseLighting + specularLighting

However, if you want to take fresnel, metalness, specular color, and other properties into account, you can use this complex function:

void WeightReflection2_float(float smoothness, float metallic, float occlusion, float3 baseColor, float3 worldPos, float3 normal, float3 viewDir, float3 diffuse, float3 specular, float3 specularColor, out float3 newDiffuse, out float3 newSpecular)

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:
void GetReflectionProjected_float(float3 worldPos, float3 viewDir, float3 normal, float lod, out float3 reflection)

lod parameter can be computed with:

void SmoothnessToMip_float(float smoothness, out float mip)
  • 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