Shader that transforms a mercator projection to equirectangular? - unity3d

I am trying to make a shader in Unity taking a mercator projection texture as a source and converting it to an equirectangular projection texture.
Input example:
Output example:
This example does the opposite with an equirectangular as source.
If you look at the source of the above example:
// mercator
float latClamped = clamp(lat, -1.4835298641951802, 1.4835298641951802);
float yMerc = log(tan(PI / 4.0 + latClamped / 2.0)) / PI2;
float xMerc = xEqui / 2.0;
vec4 mercatorPos = vec4(xMerc, yMerc, 0.0, 1.0);
Can anyone help to reverse this so I'm able to go from a mercator map as a source to equirectangular (or even better, azimuthal).
Looking for a way to do 2D texture deformations going from x/y to longitude(x)/latitude(y) and back.
I appreciate your input.

If you want to output the equirectangular projection, you need to convert from equirectangular coordinates to mercator coordinates and then sample the mercator projection at those coordinates.
This is what it would look like in a fragment shader from uvs:
//uv to equirectangular
float lat = (uv.x) * 2 * PI; // from 0 to 2PI
float lon = (uv.y - .5f) * PI; // from -PI to PI
// equirectangular to mercator
float x = lat;
float y = log(tan(PI / 4. + lon / 2.));
// bring x,y into [0,1] range
x = x / (2*PI);
y = (y+PI) / (2*PI);
// sample mercator projection
fixed4 col = tex2D(_MainTex, float2(x,y));
The same thing applies to the azimuthal projection: You can go from azimuthal coordinates -> equirectangular -> mercator and sample the image. Or you can find a formula to go directly from azimuthal -> mercator. The wiki pages have a bunch of formulas to go back and forth between projections. Here is a full shader to play around with. Input is a mercator projection and outputs a equirectangular or azimuthal projection (choose from the dropdown menu)
Shader "Unlit/NewUnlitShader 1"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
[Enum(Equirectangular,0,Azimuthal,1)]
_Azimuthal("Projection", float) = 0
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float _Azimuthal;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
#define PI 3.141592653589793238462f
#define PI2 6.283185307179586476924f
float2 uvToEquirectangular(float2 uv) {
float lat = (uv.x) * PI2; // from 0 to 2PI
float lon = (uv.y - .5f) * PI; // from -PI to PI
return float2(lat, lon);
}
float2 uvAsAzimuthalToEquirectangular(float2 uv) {
float2 coord = (uv - .5) * 4;
float radius = length(coord);
float angle = atan2(coord.y, coord.x) + PI;
//formula from https://en.wikipedia.org/wiki/Lambert_azimuthal_equal-area_projection
float lat = angle;
float lon = 2 * acos(radius / 2.) - PI / 2;
return float2(lat, lon);
}
fixed4 frag(v2f i) : SV_Target
{
// get equirectangular coordinates
float2 coord = _Azimuthal ? uvAsAzimuthalToEquirectangular(i.uv) : uvToEquirectangular(i.uv);
// equirectangular to mercator
float x = coord.x;
float y = log(tan(PI / 4. + coord.y / 2.));
// brin x,y into [0,1] range
x = x / PI2;
y = (y + PI) / PI2;
fixed4 col = tex2D(_MainTex, float2(x,y));
// just to make it look nicer
col = _Azimuthal && length(i.uv*2-1) > 1 ? 1 : col;
return col;
}
ENDCG
}
}
}

Related

How to design a shader in Unity so it uses directions from the camera

I have found a few unique shaders from Shadertoy which I would like to impart an experience of 'sky' within models of buildings in Unity AR.
An example might be this one: https://www.shadertoy.com/view/4tdSWr which is just the sky looking up, or this one, https://www.shadertoy.com/view/4tdSWr which has some directional input from the mouse (click and drag) - the HLSL/unity version of this code except for changes to mouse input is at the end of this post.
Right now the clouds feel more like a green screen projection on the model, so there is no implication of direction or horizon if you are looking parallel to the plane the building is on. (ie, if I am standing with the clouds moving from right to left, as I turn left they don't appear to be moving from behind me and receding into the distance)
I have been trying to understand how to use the camera to 'rotate' the shader result so the direction of the camera is used to ensure the direction of the clouds movement. I would also like to use the angle of the camera with respect to the ground plane to impart a horizon when you are looking out towards the walls.
Any insight on how to do this would be great, especially if it is more than just a 'use _WorldSpaceCameraPos' or 'just add UNITY_MATRIX_MVP' as the results I found through excessive googling haven't really been that helpful so far.
The code for the second shader linked, adjusted for HLSL/Unity except for the mouse inputs is at the end of this post.
Shader "Unlit/skybox"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
iChannel0 ("noise-image", 2D) = "noise-image.png" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
sampler2D iChannel0;
float4 _MainTex_ST;
float random(in float2 uv)
{
return tex2D(iChannel0, uv / 64.).r;
}
float noise(in float2 uv)
{
float2 i = floor(uv);
float2 f = frac(uv);
f = f * f * (3. - 2. * f);
float lb = random(i + float2(0., 0.));
float rb = random(i + float2(1., 0.));
float lt = random(i + float2(0., 1.));
float rt = random(i + float2(1., 1.));
return lerp(lerp(lb, rb, f.x),
lerp(lt, rt, f.x), f.y);
}
#define OCTAVES 8
float fbm(in float2 uv)
{
float value = 0.;
float amplitude = .5;
for (int i = 0; i < OCTAVES; i++)
{
value += noise(uv) * amplitude;
amplitude *= .5;
uv *= 2.;
}
return value;
}
float3 Sky(in float3 ro, in float3 rd)
{
const float SC = 1e5;
// Calculate sky plane
float dist = (SC - ro.y) / rd.y;
float2 p = (ro + dist * rd).xz;
p *= 1.2 / SC;
// from iq's shader, https://www.shadertoy.com/view/MdX3Rr
float3 lightDir = normalize(float3(-.8, .15, -.3));
float sundot = clamp(dot(rd, lightDir), 0.0, 1.0);
float3 cloudCol = float3(1.,1.0,1.0);
//float3 skyCol = float3(.6, .71, .85) - rd.y * .2 * float3(1., .5, 1.) + .15 * .5;
float3 skyCol = float3(0.3,0.5,0.85) - rd.y*rd.y*0.5;
skyCol = lerp( skyCol, mul(0.85, float3(0.7,0.75,0.85)), pow( 1.0 - max(rd.y, 0.0), 4.0 ) );
// sun
float3 sun = mul(mul(0.25 , float3(1.0,0.7,0.4)) , pow( sundot,5.0 ));
sun += mul(mul(0.25 , float3(1.0,0.8,0.6)) , pow( sundot,64.0 ));
sun += mul(mul(0.2 , float3(1.0,0.8,0.6)) , pow( sundot,512.0 ));
skyCol += sun;
// clouds
float t = mul(_Time.y , 0.1);
float den = fbm(float2(p.x - t, p.y - t));
skyCol = lerp( skyCol, cloudCol, smoothstep(.4, .8, den));
// horizon
skyCol = lerp( skyCol, mul(0.68 , float3(.418, .394, .372)), pow( 1.0 - max(rd.y, 0.0), 16.0 ) );
return skyCol;
}
float3x3 setCamera( in float3 ro, in float3 ta, float cr )
{
float3 cw = normalize(ta-ro);
float3 cp = float3(sin(cr), cos(cr),0.0);
float3 cu = normalize( cross(cw,cp) );
float3 cv = normalize( cross(cu,cw) );
return float3x3( cu, cv, cw );
}
void mainImage( out float4 fragColor, in float2 fragCoord )
{
float2 uv = fragCoord.xy / _ScreenParams.xy;
uv -= 0.5;
uv.x *= _ScreenParams.x / _ScreenParams.y;
float2 mouse = iMouse.xy/_ScreenParams.xy;
float3 ro = float3(0.0, 0.0, 0.0);
float3 ta = float3(cos(mul(mouse.x , 6.28)), mul(mouse.y , 2.0), sin(mul(mouse.x , 6.28)));
float3x3 cam = setCamera(ro, ta, 0.0);
float3 rd = normalize(mul(cam , float3(uv, 1.0)));
float3 col = Sky(ro, rd);
fragColor = float4(float3(col),1.0);
}
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}

How render 3D render texture in a Unity3D custom shader

Hi is it possible to render a 3d render texture on a custom shader raymarching like a 3dtexture?
I use a 3D render texture because I calculate and set the color of the 3D volume in a compute shader. I set the rendertexture 3D as shown below:
output3DRenderTexture= new RenderTexture(m_CubeDim.x, m_CubeDim.y, 0, thisTexFormat);
outpuoutput3DRenderTextureRendTex.enableRandomWrite = true;
output3DRenderTexture.dimension = UnityEngine.Rendering.TextureDimension.Tex3D;
output3DRenderTexture.volumeDepth = m_CubeDim.z;
output3DRenderTexture.Create();
I populate the 3D RenderTexture data in a a compute shader and GetData helps me confirm the 3d render texture has all the correct color data.
I can successfully render if I replace the 3D renderTex in the custom shader's sampler3D for a 3DTexture I create with the Tex2D slices.
cubeRenderer.material.SetTexture("_MainTex", output3DRenderTexture);//this does not render
versus
cubeRenderer.material.SetTexture("_MainTex", outputTexture3D);//this renders
This post in 2016 seems to suggest it's possible to render 3d render textures in custom shaders but it may now be outdated, it doesn't work for me and no error shows either.
It seems to me there maybe a significant performance hit if create the Textures2D slices in GPU, carry on with the creation of the Texture3D on CPU and re-send this Tex3D to GPU for the custom shader to consume it. After all the 3D volume already existed in GPU except as RenderTexture set as Tex3D. Thank you!
Shader:
#include "UnityCG.cginc"
#define ITERATIONS 100
#define PI2 6.28318530718
half4 _Color;
sampler3D _MainTex;
half _Intensity, _Threshold;
half3 _SliceMin, _SliceMax;
float4x4 _AxisRotationMatrix;
float _Angle;
struct Ray {
float3 origin;
float3 dir;
};
struct AABB {
float3 min;
float3 max;
};
// https http.download.nvidia.com/developer/presentations/2005/GDC/Audio_and_Slides/VolumeRendering_files/GDC_2_files/GDC_2005_VolumeRenderingForGames_files/Slide0073.htm
bool intersect(Ray r, AABB aabb, out float t0, out float t1)
{
float3 invR = 1.0 / r.dir;
float3 tbot = invR * (aabb.min - r.origin);
float3 ttop = invR * (aabb.max - r.origin);
float3 tmin = min(ttop, tbot);
float3 tmax = max(ttop, tbot);
float2 t = max(tmin.xx, tmin.yz);
t0 = max(t.x, t.y);
t = min(tmax.xx, tmax.yz);
t1 = min(t.x, t.y);
return t0 <= t1;
}
float3 get_uv(float3 p) {
return (p + 0.5);
}
float sample_volume(float3 uv, float3 p)
{
float v = tex3D(_MainTex, uv).r * _Intensity;
return v;
}
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
float3 world : TEXCOORD1;
float3 local : TEXCOORD2;
};
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
o.world = mul(unity_ObjectToWorld, v.vertex).xyz;
o.local = v.vertex.xyz;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
Ray ray;
ray.origin = i.local;
// world space direction to object space
float3 dir = (i.world - _WorldSpaceCameraPos);
ray.dir = normalize(mul(unity_WorldToObject, dir));
AABB aabb;
aabb.min = float3(-0.5, -0.5, -0.5);
aabb.max = float3(0.5, 0.5, 0.5);
float tnear;
float tfar;
intersect(ray, aabb, tnear, tfar);
tnear = max(0.0, tnear);
// float3 start = ray.origin + ray.dir * tnear;
float3 start = ray.origin;
float3 end = ray.origin + ray.dir * tfar;
float dist = abs(tfar - tnear);
float step_size = dist / float(ITERATIONS);
float3 ds = normalize(end - start) * step_size;
float4 dst = float4(0, 0, 0, 0);
float3 p = start;
[unroll]
for (int iter = 0; iter < ITERATIONS; iter++)
{
float3 uv = get_uv(p);
float v = sample_volume(uv, p);
float4 src = float4(v, v, v, v);
src.a *= 0.5;
src.rgb *= src.a;
// blend
dst = (1.0 - dst.a) * src + dst;
p += ds;
if (dst.a > _Threshold) break;
}
return saturate(dst) * _Color;
}
#endif

Dynamically recalculating normals after vertex displacement

Can anyone let me know if I'm on the right tack with this: I have a vertex shader that bumps outward dynamically depending on a point passed in (think a mouse running under a rug). In order for the lighting to update properly, I need to recalculate the normals after modifying the vertex position. I have access to each vertex point as well as the origin.
My current thinking is I do some sort of math to determine the tangent / bitangent and use a cross product to determine the normal. My math skills aren't great, what would I need to do to determine those vectors?
Here's my current vert shader:
void vert(inout appdata_full v)
{
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
float distanceToLift = distance(worldPos, _LiftOrigin);
v.vertex.y = smoothstep(_LiftHeight, 0, distanceToLift / _LiftRadius) * 5;
}
A simple solution is covered in this tutorial by Ronja, which I'll summarize here with modifications which reflect your specific case.
First, find two points offset from your current point by a small amount of tangent and bitangent (which you can calculate from normal and tangent):
float3 posPlusTangent = v.vertex + v.tangent * 0.01;
worldPos = mul(unity_ObjectToWorld, posPlusTangent).xyz;
distanceToLift = distance(worldPos, _LiftOrigin);
posPlusTangent.y = smoothstep(_LiftHeight, 0, distanceToLift / _LiftRadius) * 5;
float3 bitangent = cross(v.normal, v.tangent);
float3 posPlusBitangent = v.vertex + bitangent * 0.01;
worldPos = mul(unity_ObjectToWorld, bitangent).xyz;
distanceToLift = distance(worldPos, _LiftOrigin);
posPlusBitangent.y = smoothstep(_LiftHeight, 0, distanceToLift / _LiftRadius) * 5;
Then, find the difference between these offsets and the new vertex pos to find the new tangent and bitangent, then do another cross product to find the resulting normal:
float3 modifiedTangent = posPlusTangent - v.vertex;
float3 modifiedBitangent = posPlusBitangent - v.vertex;
float3 modifiedNormal = cross(modifiedTangent, modifiedBitangent);
v.normal = normalize(modifiedNormal);
Altogether:
float find_offset(float3 localV)
{
float3 worldPos = mul(unity_ObjectToWorld, localV).xyz;
float distanceToLift = distance(worldPos, _LiftOrigin);
return smoothstep(_LiftHeight, 0, distanceToLift / _LiftRadius) * 5;
}
void vert(inout appdata_full v)
{
v.vertex.y = find_offset(v.vertex);
float3 posPlusTangent = v.vertex + v.tangent * 0.01;
posPlusTangent.y = find_offset(posPlusTangent);
float3 bitangent = cross(v.normal, v.tangent);
float3 posPlusBitangent = v.vertex + bitangent * 0.01;
posPlusTangent.y = find_offset(posPlusBitangent);
float3 modifiedTangent = posPlusTangent - v.vertex;
float3 modifiedBitangent = posPlusBitangent - v.vertex;
float3 modifiedNormal = cross(modifiedTangent, modifiedBitangent);
v.normal = normalize(modifiedNormal);
}
This is a method of approximation, but it may be good enough!

Image size independent shader

I'm trying to create a shader for an image material that draws a circle regardless of the aspect ratio of the image itself.
In Shadertoy (hlsl) I can do the following to create a round circle, regardless of aspect ratio:
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = fragCoord/iResolution.xy;
uv -= 0.5;
uv.x *= iResolution.x/iResolution.y; // < this compensates for the aspect ratio
float l = length(uv);
float s = smoothstep(0.5, 0.55, l);
vec4 col = vec4(s);
fragColor = vec4(col);
}
Which gives the following output
If I remove the line uv.x *= iResolution.x/iResolution.y; the circle will warp based on the current aspect ratio.
Now I want to create the same effect in Unity, so I tried the (to me seemingly) same approach.
_MainTex_TexelSize contains the width/height of the texture (from the docs):
{TextureName}_TexelSize - a float4 property contains texture size information:
- x contains 1.0/width
- y contains 1.0/height
- z contains width
- w contains height
Shader "Unlit/Shader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Blend SrcAlpha OneMinusSrcAlpha
Cull off
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _MainTex_TexelSize;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.uv -= 0.5;
o.uv.x *= _MainTex_TexelSize.z / _MainTex_TexelSize.w;
return o;
}
float DrawCircle(float2 uv, float radius, float fallOff)
{
float d = length(uv);
return smoothstep(radius, fallOff, d);
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
float c = DrawCircle(i.uv, 0.5, 0.55);
col = lerp(col, fixed4(1,0,0,1), c);
return col;
}
ENDCG
}
}
}
The shader compiles as is, but the circle will still stretch based on the aspect ratio of the image.
I thought this may be due to the way the uv's are set up using o.uv = TRANSFORM_TEX(v.uv, _MainTex); so I tried dividing that by the image's size:
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.uv / _MainTex_TexelSize.zw;
o.uv -= 0.5;
However this did nothing
and setting up the uv's differently like so
o.uv = v.uv / _MainTex_TexelSize.zw;
o.uv / _MainTex_TexelSize.zw;
o.uv -= 0.5;
results in the circle's center moving to the upper right, but still warp when the aspect ratio change.
What step am I missing/doing wrong to get the aspect ratio independent result like I get in shadertoy?
The aspect ratio of the input texture _MainTex has nothing to do with the aspect ratio of the output*. In the shadertoy example that output is the screen, and iResolution gives you the screen dimensions (the equivalent in unity is _ScreenParams). If you want to draw a quad that is not full screen, you have to match the quad aspect ratio with the _MainTex aspect ratio to use _MainTex_TexelSize, or else just provide the aspect ratio or dimensions in a shader property (that is basically what _ScreenParams does):
float _Aspect;
fixed4 frag(v2f i) : SV_Target
{
i.uv -= .5;
i.uv.x *= _Aspect;
fixed4 col = tex2D(_MainTex, i.uv);
float c = DrawCircle(i.uv, .5, .55);
col = lerp(col, fixed4(1,0,0,1), c);
return col;
}
You could calculate the aspect ratio with derivatives. Here dx and dy are the amount of uv change per pixel. This would also be useful if you want to have, for example, fallOff always be 10 pixels.
fixed4 frag(v2f i) : SV_Target
{
i.uv -= .5;
float dx = ddx(i.uv.x);
float dy = ddy(i.uv.y);
float aspect = dy/dx;
i.uv.x *= aspect;
fixed4 col = tex2D(_MainTex, i.uv);
float c = DrawCircle(i.uv, .5, .55);
col = lerp(col, fixed4(1,0,0,1), c);
return col;
}

Get the tangent from a normal direction cg unity3d

I am writing a shader in cg where I displace the vertexes. Because I displace the vertexes, I recalculate the normals so they point away from the surface and give that information to the fragment function. In the shader I also implemented a normal map, now am I wondering shouldn't I also recalculate the tangents? And is there a formula to calculate the tangent? I read that it is on a 90 degrees angle of the normal, could I use the cross product for that?
I want to pass on the right tangent to VOUT.tangentWorld. This is my vertex function:
VertexOutput vert (VertexInput i)
{
VertexOutput VOUT;
// put the vert in world space
float4 newVert = mul(_Object2World,i.vertex);
// create fake vertexes
float4 v1 = newVert + float4(0.05,0.0,0.0,0.0) ; // X
float4 v2 = newVert + float4(0.0,0.0,0.05,0.0) ; // Z
// assign the displacement map to uv coords
float4 disp = tex2Dlod(_Displacement, float4(newVert.x + (_Time.x * _Speed), newVert.z + (_Time.x * _Speed),0.0,0.0));
float4 disp2 = tex2Dlod(_Displacement, float4(v1.x + (_Time.x * _Speed), newVert.z + (_Time.x * _Speed),0.0,0.0));
float4 disp3 = tex2Dlod(_Displacement, float4(newVert.x + (_Time.x * _Speed), v2.z + (_Time.x * _Speed),0.0,0.0));
// offset the main vert
newVert.y += _Scale * disp.y;
// offset fake vertexes
v1 += _Scale * disp2.y;
v2 += _Scale * disp3.y;
// calculate the new normal direction
float3 newNor = cross(v2 - newVert, v1 - newVert);
// return world position of the vert for frag calculations
VOUT.posWorld = newVert;
// set the vert back in object space
float4 vertObjectSpace = mul(newVert,_World2Object);
// apply unity mvp matrix to the vert
VOUT.pos = mul(UNITY_MATRIX_MVP,vertObjectSpace);
//return the tex coords for frag calculations
VOUT.tex = i.texcoord;
// return normal, tangents, and binormal information for frag calculations
VOUT.normalWorld = normalize( mul(float4(newNor,0.0),_World2Object).xyz);
VOUT.tangentWorld = normalize( mul(_Object2World,i.tangent).xyz);
VOUT.binormalWorld = normalize( cross(VOUT.normalWorld, VOUT.tangentWorld) * i.tangent.w);
return VOUT;
}
Isn't it just the vector v2 - newVert or v1 - newVert because the point along the surface? And how do I know which one of the two it is?
I've used something like the below:
Vector3 tangent = Vector3.Cross( normal, Vector3.forward );
if( tangent.magnitude == 0 ) {
tangent = Vector3.Cross( normal, Vector3.up );
}
Source: http://answers.unity3d.com/questions/133680/how-do-you-find-the-tangent-from-a-given-normal.html