Skip to content

Instantly share code, notes, and snippets.

@codewings
Created January 2, 2019 03:40
Show Gist options
  • Save codewings/58c6759b5a7b50a4c1b6aa65a9daafe1 to your computer and use it in GitHub Desktop.
Save codewings/58c6759b5a7b50a4c1b6aa65a9daafe1 to your computer and use it in GitHub Desktop.
Bake SH Coefficients from Cubemap
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
[ExecuteInEditMode]
public class SHLightingProbe : MonoBehaviour
{
public Vector4[] ProbeCoefficients = new Vector4[9]
{
new Vector4(1.933214f,1.862957f,1.624974f, 0),
new Vector4(-0.896699f,-0.8683487f,-0.8625721f, 0),
new Vector4(0.2199631f,0.3142451f,0.3953283f, 0),
new Vector4(-0.02590038f,-0.03190511f,-0.08994924f, 0),
new Vector4(0.097487f,0.1031978f,0.077221f, 0),
new Vector4(-0.2191523f,-0.2287068f,-0.2454807f, 0),
new Vector4(0.6572484f,0.5694487f,0.4614314f, 0),
new Vector4(0.06744754f,0.06944514f,0.0337911f, 0),
new Vector4(-0.1330402f,-0.1244516f,-0.1253917f, 0),
};
[Range(0, 2)]
public float Intensity;
public Cubemap EnvironmentCubemap;
public ReflectionProbe probe;
void ComputeCoefficientFromCube(Vector4[] coeff)
{
// step between two texels for range [0, 1]
float invWidth = 1.0f / ((float)EnvironmentCubemap.width);
// initial negative bound for range [-1, 1]
float negativeBound = -1.0f + invWidth;
// step between two texels for range [-1, 1]
float invWidthBy2 = 2.0f / ((float)EnvironmentCubemap.width);
float fWt = 0;
float[] resultR = new float[9];
float[] resultG = new float[9];
float[] resultB = new float[9];
for(int i = 0; i < 6; ++i)
{
for(int y=0; y < EnvironmentCubemap.width; y++)
{
// texture coordinate V in range [-1 to 1]
float fV = negativeBound + ((float)y) * invWidthBy2;
for(int x=0; x < EnvironmentCubemap.width; x++)
{
// texture coordinate U in range [-1 to 1]
float fU = negativeBound + ((float)x) * invWidthBy2;
// determine direction from center of cube texture to current texel
Vector3 dir = Vector3.zero;
CubemapFace face = CubemapFace.Unknown;
switch(i)
{
case 0: // POSITIVE_X:
dir.x = 1.0f;
dir.y = 1.0f - (invWidthBy2 * ((float)y) + invWidth);
dir.z = 1.0f - (invWidthBy2 * ((float)x) + invWidth);
dir = -dir;
face = (CubemapFace.PositiveX);
break;
case 1: // NEGATIVE_X:
dir.x = -1.0f;
dir.y = 1.0f - (invWidthBy2 * ((float)y) + invWidth);
dir.z = -1.0f + (invWidthBy2 * ((float)x) + invWidth);
dir = -dir;
face = (CubemapFace.NegativeX);
break;
case 2: // POSITIVE_Y:
dir.x = - 1.0f + (invWidthBy2 * ((float)x) + invWidth);
dir.y = 1.0f;
dir.z = - 1.0f + (invWidthBy2 * ((float)y) + invWidth);
dir = -dir;
face = (CubemapFace.PositiveY);
break;
case 3: // NEGATIVE_Y:
dir.x = - 1.0f + (invWidthBy2 * ((float)x) + invWidth);
dir.y = - 1.0f;
dir.z = 1.0f - (invWidthBy2 * ((float)y) + invWidth);
dir = -dir;
face = (CubemapFace.NegativeY);
break;
case 4: // POSITIVE_Z:
dir.x = - 1.0f + (invWidthBy2 * ((float)x) + invWidth);
dir.y = 1.0f - (invWidthBy2 * ((float)y) + invWidth);
dir.z = 1.0f;
face = (CubemapFace.PositiveZ);
break;
case 5: // NEGATIVE_Z:
dir.x = 1.0f - (invWidthBy2 * ((float)x) + invWidth);
dir.y = 1.0f - (invWidthBy2 * ((float)y) + invWidth);
dir.z = - 1.0f;
face = (CubemapFace.NegativeZ);
break;
default:
return;
}
// normalize direction
dir = Vector3.Normalize(dir);
// scale factor depending on distance from center of the face
float fDiffSolid = 4.0f / ((1.0f + fU*fU + fV*fV) * Mathf.Sqrt(1.0f + fU*fU + fV*fV));
fWt += fDiffSolid;
float[] sh = new float[9];
// calculate coefficients of spherical harmonics for current direction
sh[0] = 0.282095f;
sh[1] = 0.488603f * dir.y;
sh[2] = 0.488603f * dir.z;
sh[3] = 0.488603f * dir.x;
sh[4] = 1.092548f * dir.x*dir.y;
sh[5] = 1.092548f * dir.y*dir.z;
sh[6] = 0.315392f * (3.0f*dir.z*dir.z - 1.0f);
sh[7] = 1.092548f * dir.x * dir.z;
sh[8] = 0.546274f * (dir.x*dir.x - dir.y*dir.y);
// get color from texture and map to range [0, 1]
Color clr = EnvironmentCubemap.GetPixel(face, x, y);
// scale color and add to previously accumulated coefficients
for(uint order = 0; order < 9; ++order)
{
resultR[order] = resultR[order] + sh[order] * clr.r * fDiffSolid;
resultG[order] = resultG[order] + sh[order] * clr.g * fDiffSolid;
resultB[order] = resultB[order] + sh[order] * clr.b * fDiffSolid;
}
}
}
}
// final scale for coefficients
float fNormProj = (4.0f * Mathf.PI) / fWt;
for(uint order = 0; order < 9; ++order)
{
coeff[order].x = resultR[order] * fNormProj;
coeff[order].y = resultG[order] * fNormProj;
coeff[order].z = resultB[order] * fNormProj;
coeff[order].w = 0;
}
}
#if UNITY_EDITOR
[ContextMenu("Bake Coefficient")]
public void BakeCoefficient()
{
if (probe != null && probe.texture is Cubemap)
{
var path = "Assets/" + gameObject.name + ".png";
Lightmapping.BakeReflectionProbe(probe, path);
AssetDatabase.Refresh();
var importer = AssetImporter.GetAtPath(path) as TextureImporter;
var settings = new TextureImporterSettings();
importer.ReadTextureSettings(settings);
settings.cubemapConvolution = TextureImporterCubemapConvolution.None;
settings.readable = true;
importer.SetTextureSettings(settings);
importer.SaveAndReimport();
EnvironmentCubemap = AssetDatabase.LoadAssetAtPath<Cubemap>(path);
{
ComputeCoefficientFromCube(ProbeCoefficients);
}
DestroyImmediate(EnvironmentCubemap, true);
EnvironmentCubemap = null;
AssetDatabase.DeleteAsset(path);
return;
}
ComputeCoefficientFromCube(ProbeCoefficients);
}
#endif
void OnWillRenderObject()
{
var sharedMaterials = GetComponent<Renderer>().sharedMaterials;
foreach(var m in sharedMaterials)
{
m.SetVectorArray("_PrecomputedRadiance", ProbeCoefficients);
m.SetFloat("_Intensity", Intensity);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment