Last active
July 2, 2024 06:54
-
-
Save Farfarer/5664694 to your computer and use it in GitHub Desktop.
Create dynamic equirectangular maps for Unity. These have the benefit that, as they're flat images, you can sample lower mips to get blurry reflections. The straight cubemap version (detailed here: http://www.farfarer.com/blog/2011/07/25/dynamic-ambient-lighting-in-unity/ ) will give you hard seams when you sample mips from the cubemap. Although…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This takes in the cubemap generated by your cubemap camera and feeds back out an equirectangular image. | |
// Create a new material and give it this shader. Then give that material to the "cubemapToEquirectangularMateral" property of the dynamicAmbient.js script in this gist. | |
// You could probably abstract this to C#/JS code and feed it in a pre-baked cubemap to sample and then spit out an equirectangular map if you don't have render textures. | |
Shader "Custom/cubemapToEquirectangular" { | |
Properties { | |
_MainTex ("Cubemap (RGB)", CUBE) = "" {} | |
} | |
Subshader { | |
Pass { | |
ZTest Always Cull Off ZWrite Off | |
Fog { Mode off } | |
CGPROGRAM | |
#pragma vertex vert | |
#pragma fragment frag | |
#pragma fragmentoption ARB_precision_hint_fastest | |
#include "UnityCG.cginc" | |
struct v2f { | |
float4 pos : POSITION; | |
float2 uv : TEXCOORD0; | |
}; | |
samplerCUBE _MainTex; | |
#define PI 3.141592653589793 | |
#define HALFPI 1.57079632679 | |
v2f vert( appdata_img v ) | |
{ | |
v2f o; | |
o.pos = mul(UNITY_MATRIX_MVP, v.vertex); | |
float2 uv = v.texcoord.xy * 2 - 1; | |
uv *= float2(PI, HALFPI); | |
o.uv = uv; | |
return o; | |
} | |
fixed4 frag(v2f i) : COLOR | |
{ | |
float cosy = cos(i.uv.y); | |
float3 normal = float3(0,0,0); | |
normal.x = cos(i.uv.x) * cosy; | |
normal.y = i.uv.y; | |
normal.z = cos(i.uv.x - HALFPI) * cosy; | |
return texCUBE(_MainTex, normal); | |
} | |
ENDCG | |
} | |
} | |
Fallback Off | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Wizard to convert a cubemap to an equirectangular cubemap. | |
// Put this into an /Editor folder | |
// Run it from Tools > Cubemap to Equirectangular Map | |
using UnityEditor; | |
using UnityEngine; | |
using System.IO; | |
class CubemapToEquirectangularWizard : ScriptableWizard { | |
public Cubemap cubeMap = null; | |
public int equirectangularWidth = 2048; | |
public int equirectangularHeight = 1024; | |
private Material cubemapToEquirectangularMaterial; | |
private Shader cubemapToEquirectangularShader; | |
[MenuItem ("Tools/Cubemap to Equirectangular Map")] | |
static void CreateWizard () { | |
ScriptableWizard.DisplayWizard<CubemapToEquirectangularWizard>("Cubemap to Equirectangular Map", "Convert"); | |
} | |
void OnWizardCreate () { | |
bool goodToGo = true; | |
cubemapToEquirectangularShader = Shader.Find("Custom/cubemapToEquirectangular"); | |
if ( cubemapToEquirectangularShader == null ) | |
{ | |
Debug.LogWarning ( "Couldn't find the shader \"Custom/cubemapToEquirectangular\", do you have it in your project?\nYou can get it here; https://gist.github.com/Farfarer/5664694#file-cubemaptoequirectangular-shader"); | |
goodToGo = false; | |
} | |
else { | |
cubemapToEquirectangularMaterial = new Material( cubemapToEquirectangularShader ); | |
} | |
if ( cubeMap == null ) | |
{ | |
Debug.LogWarning ( "You must specify a cubemap."); | |
goodToGo = false; | |
} | |
else if ( equirectangularWidth < 1 ) | |
{ | |
Debug.LogWarning ( "Width must be greater than 0."); | |
goodToGo = false; | |
} | |
else if ( equirectangularHeight < 1 ) | |
{ | |
Debug.LogWarning ( "Height must be greater than 0."); | |
goodToGo = false; | |
} | |
if (goodToGo) { | |
// Go to gamma space. | |
ColorSpace originalColorSpace = PlayerSettings.colorSpace; | |
PlayerSettings.colorSpace = ColorSpace.Gamma; | |
// Do the conversion. | |
RenderTexture rtex_equi = new RenderTexture ( equirectangularWidth, equirectangularHeight, 24 ); | |
Graphics.Blit (cubeMap, rtex_equi, cubemapToEquirectangularMaterial); | |
Texture2D equiMap = new Texture2D(equirectangularWidth, equirectangularHeight, TextureFormat.ARGB32, false); | |
//equiMap.SetPixels(rtex_equiPixels); | |
equiMap.ReadPixels(new Rect(0, 0, equirectangularWidth, equirectangularHeight), 0, 0, false); | |
equiMap.Apply(); | |
byte[] bytes = equiMap.EncodeToPNG(); | |
DestroyImmediate(equiMap); | |
string assetPath = AssetDatabase.GetAssetPath(cubeMap); | |
string assetDir = Path.GetDirectoryName(assetPath); | |
string assetName = Path.GetFileNameWithoutExtension(assetPath) + "_equirectangular.png"; | |
string newAsset = Path.Combine(assetDir, assetName); | |
File.WriteAllBytes(newAsset, bytes); | |
// Import the new texture. | |
AssetDatabase.ImportAsset(newAsset); | |
Debug.Log ("Equirectangular map saved to " + newAsset); | |
// Go to whatever the color space was before. | |
PlayerSettings.colorSpace = originalColorSpace; | |
} | |
} | |
void OnWizardUpdate () { | |
helpString = "Converts a cubemap into an equirectangular map."; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Apply this script to the camera you'll use to generate your cubemaps. | |
@script ExecuteInEditMode | |
private var cam : Camera; | |
public var target : Transform; | |
private var tr : Transform; | |
public var cubemapSize : int = 512; | |
public var oneFacePerFrame : boolean = false; | |
public var offset : Vector3 = Vector3.zero; | |
private var rtex : RenderTexture; | |
public var createEquirectangularMap = true; | |
public var equirectangularSize : int = 1024; | |
public var cubemapToEquirectangularMateral : Material = null; | |
private var rtex_equi : RenderTexture; | |
function Start () { | |
cam = camera; | |
cam.enabled = false; | |
// render all six faces at startup | |
UpdateCubemap( 63 ); | |
} | |
function LateUpdate () { | |
if ( oneFacePerFrame ) { | |
var faceToRender = Time.frameCount % 6; | |
var faceMask = 1 << faceToRender; | |
UpdateCubemap ( faceMask ); | |
} else { | |
UpdateCubemap ( 63 ); // all six faces | |
} | |
} | |
function UpdateCubemap ( faceMask : int ) { | |
if ( !tr ) { | |
tr = transform; | |
tr.rotation = Quaternion.identity; | |
} | |
if ( !rtex ) { | |
rtex = new RenderTexture ( cubemapSize, cubemapSize, 16 ); | |
rtex.isPowerOfTwo = true; | |
rtex.isCubemap = true; | |
rtex.useMipMap = false; | |
rtex.hideFlags = HideFlags.HideAndDontSave; | |
rtex.SetGlobalShaderProperty ( "_WorldCube" ); | |
} | |
tr.position = target.position + offset; | |
cam.RenderToCubemap ( rtex, faceMask ); | |
if ( !rtex_equi ) { | |
rtex_equi = new RenderTexture ( equirectangularSize * 2, equirectangularSize, 16 ); | |
rtex_equi.isPowerOfTwo = true; | |
rtex_equi.isCubemap = false; | |
rtex_equi.useMipMap = true; | |
rtex_equi.wrapMode = TextureWrapMode.Repeat; | |
rtex_equi.hideFlags = HideFlags.HideAndDontSave; | |
rtex_equi.filterMode = FilterMode.Trilinear; | |
rtex_equi.SetGlobalShaderProperty ( "_Equirectangular" ); | |
var mipNum : float = Mathf.Ceil(Mathf.Log(equirectangularSize * 2)) + 1; | |
Shader.SetGlobalFloat("_EquirectangularBlur", mipNum); | |
} | |
if (createEquirectangularMap) { | |
Graphics.Blit (rtex, rtex_equi, cubemapToEquirectangularMateral); | |
} | |
} | |
function OnDisable () { | |
DestroyImmediate ( rtex ); | |
DestroyImmediate ( rtex_equi ); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This shader samples the equirectangular map to get an ambient and a specular value from it. | |
Shader "Custom/Equirectangular/DynamicAmbient" { | |
Properties { | |
_Color ("Main Color", Color) = (1,1,1,1) | |
_MainTex ("Diffuse (RGB) Alpha (A)", 2D) = "gray" {} | |
_SpecularTex ("Specular (R) Gloss (B) Fresnel (B)", 2D) = "gray" {} | |
_BumpMap ("Normal (Normal)", 2D) = "bump" {} | |
} | |
SubShader{ | |
Pass { | |
Tags {"LightMode" = "Always"} | |
CGPROGRAM | |
#pragma vertex vert | |
#pragma fragment frag | |
#pragma fragmentoption ARB_precision_hint_fastest | |
#pragma glsl | |
#pragma target 3.0 | |
#include "UnityCG.cginc" | |
struct appdata { | |
float4 vertex : POSITION; | |
float4 tangent : TANGENT; | |
float3 normal : NORMAL; | |
float4 texcoord : TEXCOORD0; | |
}; | |
struct v2f | |
{ | |
float4 pos : SV_POSITION; | |
float2 uv : TEXCOORD0; | |
float3 normal : TEXCOORD2; | |
float3 tangent : TEXCOORD3; | |
float3 binormal : TEXCOORD4; | |
float3 viewDir : TEXCOORD5; | |
}; | |
v2f vert (appdata v) | |
{ | |
v2f o; | |
o.pos = mul(UNITY_MATRIX_MVP, v.vertex); | |
o.uv = v.texcoord.xy; | |
o.normal = v.normal; | |
o.tangent = v.tangent; | |
o.binormal = cross(v.normal, v.tangent.xyz) * v.tangent.w; | |
o.viewDir = WorldSpaceViewDir(v.vertex); | |
return o; | |
} | |
sampler2D _MainTex, _SpecularTex, _BumpMap; | |
sampler2D _Equirectangular; | |
float _EquirectangularBlur; | |
#define PI 3.141592653589793 | |
inline float3 TangentToWorld(float3 tSpace, float3 normal, float3 tangent, float3 binormal) | |
{ | |
float3 normalO = (tangent * tSpace.x) + (binormal * tSpace.y) + (normal * tSpace.z); | |
return normalize(mul((float3x3)_Object2World, normalO)); | |
} | |
inline float2 RadialCoords(float3 a_coords) | |
{ | |
float lon = atan2(a_coords.z, a_coords.x); | |
float lat = acos(a_coords.y); | |
float2 sphereCoords = float2(lon, lat) * (1.0 / PI); | |
return float2(sphereCoords.x * 0.5 + 0.5, 1 - sphereCoords.y); | |
} | |
float4 frag(v2f IN) : COLOR | |
{ | |
// Normalisation. | |
IN.viewDir = normalize (IN.viewDir); | |
// Textures. | |
fixed3 albedo = tex2D(_MainTex, IN.uv).rgb; | |
float3 normal = UnpackNormal(tex2D(_BumpMap, IN.uv)); | |
float3 specular = tex2D(_SpecularTex, IN.uv).rgb; | |
// Vectors. | |
float3 normalW = TangentToWorld ( normal, IN.normal, IN.tangent, IN.binormal); | |
float2 ambCoords = RadialCoords(normalW); | |
float3 refl = -reflect(IN.viewDir, normalW); | |
float2 specCoords = RadialCoords(refl); | |
// Mip values. Sampling the lowest mip gives a uniform colour, so I'm sampling second lowest mip. | |
float ambMip = _EquirectangularBlur - 1; | |
float specMip = (1 - specular.g) * _EquirectangularBlur; | |
// Ambient. | |
float3 amb = tex2Dlod(_Equirectangular, float4(ambCoords.xy, 0, ambMip)).rgb; | |
// Specular. | |
float3 spec = tex2Dlod(_Equirectangular, float4(specCoords.xy, 0, specMip)).rgb; | |
// Fresnel. | |
float VdotN = dot( IN.viewDir, normalW ); | |
float fresnel = pow( abs(1.0 - VdotN), 5.0 ); | |
fresnel += specular.b * ( 1.0 - fresnel ); | |
float specMultiplier = fresnel * specular.r; | |
// Result. | |
float4 c; | |
c.rgb = albedo * amb + spec * specMultiplier; | |
c.a = 1.0; | |
return c; | |
} | |
ENDCG | |
} | |
} | |
FallBack "VertexLit" | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hey,
I'm getting distortions around the north and south pole of my equirectangular image. I made the cubemap by rendering a camera to the cubemap from within an inverted 2x2x2 cube with a checkers texture on it. The image below shows what a skybox looks like when it is using the image imported as a cubemap.