Last active
September 19, 2022 23:19
-
-
Save lyuma/e755620e2abe2593de94aed5021cdd3f to your computer and use it in GitHub Desktop.
Port of https://github.com/SCRN-VRC/Raymarching-with-ShadowCaster to Godot 4
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
// WIP Godot port. Lighting function not yet implemented. Clouds need #define and are not transparent. | |
/* | |
MIT License | |
Godot Port Copyright (c) 2022 Lyuma | |
Copyright (c) 2022 SCRN-VRC | |
Permission is hereby granted, free of charge, to any person obtaining a copy | |
of this software and associated documentation files (the "Software"), to deal | |
in the Software without restriction, including without limitation the rights | |
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
copies of the Software, and to permit persons to whom the Software is | |
furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in all | |
copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
SOFTWARE. | |
https://bgolus.medium.com/rendering-a-sphere-on-a-quad-13c92025570c | |
https://www.shadertoy.com/view/WtlGWr | |
https://www.shadertoy.com/view/3tyyDz | |
https://github.com/netri/Neitri-Unity-Shaders | |
*/ | |
// Shader "SCRN/Dice" { | |
shader_type spatial; | |
render_mode skip_vertex_transform; | |
uniform bool use_depth_texture; | |
// Properties { | |
group_uniforms Circles_Color_Settings; | |
uniform sampler2D _CircleTex1 : source_color, hint_default_white; | |
uniform vec4 _CircleCol = vec4(3, 3, 3, 1); | |
uniform float _CircleTex1Scale = 7.0; | |
group_uniforms Frame_Color_Settings; | |
uniform sampler2D _Matcap1 : source_color, hint_default_white; | |
uniform vec4 _FrameCol = vec4(1.5, 1.5, 1.5, 1); | |
group_uniforms Dice_Settings; | |
uniform float _Smoothness : hint_range(0, 1) = 0.9; | |
uniform float _EdgeCut : hint_range(0, 2) = 0.781; | |
uniform float _EdgeRound : hint_range(0, 1) = 0.712; | |
group_uniforms Cloud_Settings; | |
// [KeywordEnum(On, Off)] _Cloud ("Cloud On", Float) = 0.0 | |
uniform bool _Cloud_On = false; | |
uniform vec4 _CloudColor = vec4(0.5, 0.5, 0.5, 1); | |
uniform vec4 _CloudGlowCol = vec4(0.74, 0.0, 3.2, 1); | |
uniform float _CloudScale : hint_range(1.0, 10.0) = 3.18; | |
uniform float _CloudNoiseScale : hint_range(1.0, 10.0) = 9.46; | |
uniform float _CloudSharpness : hint_range(0.0, 20.0) = 8.4; | |
uniform float _CloudShadow : hint_range(0.0, 1.0) = 0.517; | |
uniform vec4 _CloudOffset = vec4(0.05, -0.04, 0.07, 0); | |
group_uniforms Other_Settings; | |
uniform vec4 _GlowCol = vec4(2.48, 0.0, 4.54, 1); | |
uniform vec4 _Test = vec4(0, 0, 0, 0); | |
uniform sampler2D _NoiseTex: hint_default_black; | |
uniform samplerCube _CubeTex: hint_default_black; | |
// Performance heavy. | |
//#define ENABLE_CLOUDS | |
// Godot forces shaders which access DEPTH_TEXTURE to transparent. | |
//#define USE_DEPTH_TEXTURE | |
// should make shadow receiving work on mobile | |
//#if defined(UNITY_float_PRECISION_FRAGMENT_SHADER_REGISTERS) | |
//#undef UNITY_float_PRECISION_FRAGMENT_SHADER_REGISTERS | |
//#endif | |
////#include "./AudioLink/AudioLink.cginc" | |
// real check needed for enabling conservative depth | |
// requires Shader Model 5.0 | |
//#if SHADER_TARGET > 40 | |
//#define USE_CONSERVATIVE_DEPTH 1 | |
//#endif | |
//vec4 pos : SV_POSITION; | |
varying vec3 varying_rd; | |
varying vec3 varying_ro; | |
varying vec4 modelPos; | |
varying vec3 center; | |
varying float maxScale; | |
struct marchInOut | |
{ | |
vec3 ro; | |
vec3 rd; | |
vec3 pos; | |
vec3 norm; | |
vec4 col; | |
float depth; | |
float matID; | |
float dist; | |
}; | |
//#define DEBUG_VERTEX_SHADER | |
void vertex() { | |
// check if the current projection is orthographic or not from the current projection matrix | |
bool isOrtho = PROJECTION_MATRIX[3][3] > 0.5; | |
// viewer position, equivalent to _WorldSpaceCameraPos.xyz, but for the current view | |
vec3 worldSpaceViewerPos = (INV_VIEW_MATRIX)[3].xyz; | |
// view forward | |
vec3 worldSpaceViewForward = -(INV_VIEW_MATRIX)[2].xyz; | |
// pivot position | |
vec3 worldSpacePivotPos = (MODEL_MATRIX)[3].xyz; | |
// offset between pivot and camera | |
vec3 worldSpacePivotToView = worldSpacePivotPos - worldSpaceViewerPos; | |
// get the max object scale | |
vec3 scale = vec3( | |
length(transpose(MODEL_MATRIX)[0].xyz), | |
length(transpose(MODEL_MATRIX)[1].xyz), | |
length(transpose(MODEL_MATRIX)[2].xyz) | |
); | |
maxScale = max(abs(scale.x), max(abs(scale.y), abs(scale.z))); | |
// calculate a camera facing rotation matrix | |
vec3 up = (INV_VIEW_MATRIX)[1].xyz; | |
vec3 forward = isOrtho ? worldSpaceViewForward : normalize(worldSpacePivotToView); | |
vec3 right = normalize(cross(forward, up)); | |
up = cross(right, forward); | |
mat3 quadOrientationMatrix = transpose(mat3(right, up, forward)); | |
// use the max scale to figure out how big the quad needs to be to cover the entire sphere | |
// we're using a hardcoded object space radius of 0.5 in the fragment shader | |
float maxRadius = maxScale * 0.5; | |
// find the radius of a cone that contains the sphere with the point at the camera and the base at the pivot of the sphere | |
// this means the quad is always scaled to perfectly cover only the area the sphere is visible within | |
float quadScale = maxScale; | |
if (!isOrtho) | |
{ | |
// get the sine of the right triangle with the hyp of the sphere pivot distance and the opp of the sphere radius | |
float sinAngle = maxRadius / length(worldSpacePivotToView); | |
// convert to cosine | |
float cosAngle = sqrt(1.0 - sinAngle * sinAngle); | |
// convert to tangent | |
float tanAngle = sinAngle / cosAngle; | |
// basically this, but should be faster | |
//tanAngle = tan(asin(sinAngle)); | |
// get the opp of the right triangle with the 90 degree at the sphere pivot * 2 | |
quadScale = tanAngle * length(worldSpacePivotToView) * 2.0; | |
} | |
vec3 vvertex = VERTEX; | |
// flatten mesh, in case it's a cube or sloped quad mesh | |
vvertex.z = 0.0; | |
// calculate world space position for the camera facing quad | |
vec3 worldPos = (vvertex.xyz * quadScale) * quadOrientationMatrix + worldSpacePivotPos; | |
VERTEX = (VIEW_MATRIX * vec4(worldPos, 1.0)).xyz; | |
NORMAL = mat3(VIEW_MATRIX) * quadOrientationMatrix * NORMAL; | |
TANGENT = mat3(VIEW_MATRIX) * quadOrientationMatrix * TANGENT; | |
//POSITION = PROJECTION_MATRIX * vec4(VERTEX, 1.0); | |
// calculate world space view ray direction and origin for perspective or orthographic | |
vec3 worldSpaceRayOrigin = worldSpaceViewerPos; | |
vec3 worldSpaceRayDir = worldPos - worldSpaceRayOrigin; | |
if (isOrtho) | |
{ | |
worldSpaceRayDir = worldSpaceViewForward * -dot(worldSpacePivotToView, worldSpaceViewForward); | |
worldSpaceRayOrigin = worldPos - worldSpaceRayDir; | |
} | |
// scale the ray with the scale of the game object | |
worldSpaceRayDir = normalize(worldSpaceRayDir) * maxScale; | |
// output object space ray direction and origin | |
varying_rd = (inverse(MODEL_MATRIX) * vec4(worldSpaceRayDir, 0.0)).xyz; | |
varying_ro = (inverse(MODEL_MATRIX) * vec4(worldSpaceRayOrigin, 1.0)).xyz; | |
#if defined(USE_CONSERVATIVE_DEPTH) | |
worldPos += worldSpaceRayDir / dot(normalize(worldSpacePivotToView), worldSpaceRayDir) * maxRadius; | |
#endif | |
// setting up to read the depth pass | |
modelPos = (inverse(MODEL_MATRIX) * vec4(worldPos, 1.0)); | |
center = (MODEL_MATRIX * vec4(0., 0., 0., 1.)).xyz; | |
} | |
// https://www.shadertoy.com/view/WtlGWr | |
// positions of all the dimples in the dice | |
const vec3 dips[21] = | |
{ | |
// one | |
vec3( 0., 0., 0.31), | |
// two | |
vec3( 0.31, 0.0, 0.12), | |
vec3( 0.31, 0.0, -0.12), | |
// three | |
vec3( 0.12, 0.31, 0.12), | |
vec3( 0., 0.31, 0. ), | |
vec3(-0.12, 0.31, -0.12), | |
// four | |
vec3( 0.12, -0.31, 0.12), | |
vec3(-0.12, -0.31, 0.12), | |
vec3( 0.12, -0.31, -0.12), | |
vec3(-0.12, -0.31, -0.12), | |
// five | |
vec3(-0.31, 0., 0. ), | |
vec3(-0.31, -0.12, -0.12), | |
vec3(-0.31, -0.12, 0.12), | |
vec3(-0.31, 0.12, -0.12), | |
vec3(-0.31, 0.12, 0.12), | |
// six | |
vec3( 0.13, -0.13, -0.31), | |
vec3( 0.13, 0., -0.31), | |
vec3( 0.13, 0.13, -0.31), | |
vec3(-0.13, -0.13, -0.31), | |
vec3(-0.13, 0., -0.31), | |
vec3(-0.13, 0.13, -0.31) | |
}; | |
const float dipsR[21] = | |
{ | |
// one | |
0.12, | |
// two | |
0.08, 0.08, | |
// three | |
0.07, 0.07, 0.07, | |
// four | |
0.07, 0.07, 0.07, 0.07, | |
// five | |
0.06, 0.06, 0.06, 0.06, 0.06, | |
// six | |
0.06, 0.06, 0.06, 0.06, 0.06, 0.06 | |
}; | |
// https://catlikecoding.com/unity/tutorials/scriptable-render-pipeline/reflections/ | |
vec3 BoxProjection(vec3 direction, vec3 position, vec4 cubemapPosition, vec3 boxMin, vec3 boxMax) | |
{ | |
if (cubemapPosition.w > 0.0) { | |
vec3 factors = (mix(boxMin, boxMax, greaterThan(direction, vec3(0.0))) - position) / direction; | |
float scalar = min(min(factors.x, factors.y), factors.z); | |
direction = direction * scalar + (position - cubemapPosition.xyz); | |
} | |
return direction; | |
} | |
/* | |
vec3 refProbe(vec3 worldPos, vec3 reflVec) | |
{ | |
vec3 boxProject = BoxProjection(reflVec, worldPos, | |
unity_SpecCube0_ProbePosition, unity_SpecCube0_BoxMin, | |
unity_SpecCube0_BoxMax); | |
float roughness = 1.0 - _Smoothness; | |
roughness *= 1.7 - 0.7 * roughness; | |
vec4 boxProbe0 = UNITY_SAMPLE_TEXCUBE_LOD(unity_SpecCube0, boxProject, roughness); | |
boxProbe0.rgb = DecodeHDR(boxProbe0, unity_SpecCube0_HDR); | |
vec3 indirectSpecular; | |
float blend = unity_SpecCube0_BoxMin.w; | |
[branch] | |
if (blend < 0.99999) { | |
vec3 boxProject = BoxProjection( | |
reflVec, worldPos, | |
unity_SpecCube1_ProbePosition, | |
unity_SpecCube1_BoxMin, unity_SpecCube1_BoxMax | |
); | |
vec4 boxProbe1 = UNITY_SAMPLE_TEXCUBE_SAMPLER_LOD(unity_SpecCube1, unity_SpecCube0, boxProject, roughness); | |
boxProbe1.rgb = DecodeHDR(boxProbe1, unity_SpecCube1_HDR); | |
indirectSpecular = mix(boxProbe1.rgb, boxProbe0.rgb, blend); | |
} | |
else | |
{ | |
indirectSpecular = boxProbe0.rgb; | |
} | |
if (!any(indirectSpecular)) | |
{ | |
indirectSpecular = texCUBElod(_CubeTex, vec4(reflVec, roughness)); | |
} | |
return indirectSpecular; | |
} | |
*/ | |
vec3 refProbe(vec3 worldPos, vec3 reflVec) { | |
float roughness = 1.0 - _Smoothness; | |
roughness *= 1.7 - 0.7 * roughness; | |
return textureLod(_CubeTex, reflVec, roughness).rgb; | |
} | |
// https://mercury.sexy/hg_sdf/ | |
// Shortcut for 45-degrees rotation | |
void pR45(inout vec2 p) { | |
p = (p + vec2(p.y, -p.x)) * sqrt(0.5); | |
} | |
// Repeat around the origin by a fixed angle. | |
// For easier use, num of repetitions is use to specify the angle. | |
float pModPolar(inout vec2 p, float repetitions) { | |
float angle = 2.0*PI/repetitions; | |
float a = atan(p.y, p.x) + angle/2.0; | |
float r = length(p); | |
float c = floor(a/angle); | |
a = mod(a,angle) - angle/2.0; | |
p = vec2(cos(a), sin(a))*r; | |
// For an odd number of repetitions, fix cell index of the cell in -x direction | |
// (cell index would be e.g. -5 and 5 in the two halves of the cell): | |
if (abs(c) >= (repetitions/2.0)) c = abs(c); | |
return c; | |
} | |
float fOpIntersectionChamfer(float a, float b, float r) { | |
return max(max(a, b), (a + r + b)*sqrt(0.5)); | |
} | |
// Difference can be built from Intersection or Union: | |
float fOpDifferenceChamfer (float a, float b, float r) { | |
return fOpIntersectionChamfer(a, -b, r); | |
} | |
// https://www.shadertoy.com/view/wsSGDG | |
float sdOctahedron(vec3 p, float s) { | |
p = abs(p); | |
float m = (p.x + p.y + p.z - s) / 3.0; | |
vec3 o = p - m; | |
vec3 k = min(o, 0.0); | |
o = o + (k.x + k.y + k.z) * 0.5 - k * 1.5; | |
o = clamp(o, 0.0, s); | |
return length(p - o) * sign(m); | |
} | |
float sphere (vec3 p, float radius) { | |
return length(p) - radius ; | |
} | |
float box( vec3 p, vec3 b ) | |
{ | |
vec3 q = abs(p) - b; | |
return length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0); | |
} | |
/* | |
out: | |
x - distance of entire sdf | |
y - material ID | |
0 - default glass | |
1 - glowing border | |
2 - glowing circles | |
z - distance to glowing stuff | |
*/ | |
vec3 mapDice(vec3 p) | |
{ | |
float s = sdOctahedron(p, _EdgeRound); | |
float c = box(p, vec3(0.29)); | |
float dice = fOpIntersectionChamfer(s, c, 0.003); | |
// carve the box edges away | |
vec3 p2 = p; | |
pR45(p2.xz); | |
p2.xz *= _EdgeCut; | |
float sc = box(p2, vec3(0.29)); | |
dice = max(dice, sc); | |
p2 = p; | |
pR45(p2.xy); | |
p2.xy *= _EdgeCut; | |
sc = box(p2, vec3(0.29)); | |
dice = max(dice, sc); | |
p2 = p; | |
pR45(p2.yz); | |
p2.yz *= _EdgeCut; | |
sc = box(p2, vec3(0.29)); | |
dice = max(dice, sc); | |
// edge details | |
float bcut = box(p, vec3(0.27)); | |
s = sdOctahedron(p, _EdgeRound + 0.02); | |
bcut = max(bcut, s); | |
float ccut = box(p, vec3(0.257)); | |
ccut = fOpDifferenceChamfer(bcut, ccut, 0.005); | |
dice = min(dice, ccut); | |
float matID = 0.0; | |
float light = ccut * 5.0; | |
matID = dice == ccut ? 1.0 : matID; | |
// //short circuting for better performance | |
// if (dice > 0.01) return vec3(dice, matID, light); | |
float d = sphere(p + dips[0], dipsR[0]); | |
for (int i = 1; i < 21; i++) { | |
d = min(d, sphere(p + dips[i], dipsR[i])); | |
} | |
dice = max(dice, -d); | |
float dc = abs(dice + d - 0.01); | |
matID = dc < 0.01 ? 2.0 : matID; | |
matID += dc < 0.01 ? dc : 0.0; // store intensity | |
return vec3(dice, matID, light); | |
} | |
// faster 4 tap normals | |
vec3 diceNorm( in vec3 p ){ | |
const vec2 e = vec2(0.0015, -0.0015); | |
return normalize( | |
e.xyy*mapDice(p+e.xyy).x + | |
e.yyx*mapDice(p+e.yyx).x + | |
e.yxy*mapDice(p+e.yxy).x + | |
e.xxx*mapDice(p+e.xxx).x); | |
} | |
void marchOuter(inout marchInOut mI, float max_steps) | |
{ | |
vec3 p = mI.ro; | |
vec3 rd = mI.rd; | |
float t = 0.0; | |
bool hit = false; | |
for (float i = 0.; i < max_steps; i++) { | |
vec3 d = mapDice(p); | |
// more detail the closer | |
if (d.x < (0.0001 * (t + 1.0))) { | |
hit = true; | |
mI.matID = d.y; | |
break; | |
} | |
p += d.x * rd; | |
t += d.x; | |
if (any(greaterThan(abs(p), vec3(1.0)))) break; | |
} | |
mI.pos = p; | |
mI.col.a = hit ? 1.0 : 0.0; | |
mI.dist = t; | |
} | |
vec3 calculateWorldSpace(vec4 worldPos, vec4 screenPos, vec3 cameraPos, sampler2D depth_texture, mat4 xINV_PROJECTION_MATRIX) | |
{ | |
// Subtract camera position from vertex position in world | |
// to get a ray pointing from the camera to this vertex. | |
vec3 worldDir = worldPos.xyz / worldPos.w - cameraPos; | |
// Calculate screen UV | |
vec2 screenUV = screenPos.xy / screenPos.w; | |
// _ProjectionParams.x is 1.0 (or –1.0 if currently rendering with a flipped projection matrix) | |
// screenUV.y *= _ProjectionParams.x; | |
screenUV = screenUV * 0.5f + 0.5f; | |
// Read depth, linearizing into worldspace units. | |
float scrdepth = textureLod(depth_texture, screenUV, 0.0).r; | |
vec4 upos = xINV_PROJECTION_MATRIX * vec4(screenUV * 2.0 - 1.0, scrdepth * 2.0 - 1.0, 1.0); | |
float depth = upos.z / upos.w; //LinearEyeDepth(UNITY_SAMPLE_DEPTH(SampleScreenDepth(screenUV))) / screenPos.w; | |
// Advance by depth along our view ray from the camera position. | |
// This is the worldspace coordinate of the corresponding fragment | |
// we retrieved from the depth buffer. | |
return worldDir * depth; | |
} | |
// https://github.com/keijiro/BiplanarMapping | |
// Biplanar mapping for color texture | |
void Biplanar_float | |
(sampler2D tex, vec3 wpos, vec3 wnrm, out vec4 output) | |
{ | |
// Coordinate derivatives for texturing | |
vec3 p = wpos; | |
vec3 n = abs(wnrm); | |
vec3 dpdx = dFdx(p); | |
vec3 dpdy = dFdy(p); | |
// Major axis (in x; yz are following axis) | |
uvec3 ma = (n.x > n.y && n.x > n.z) ? uvec3(0, 1, 2) : | |
((n.y > n.z ) ? uvec3(1, 2, 0) : | |
uvec3(2, 0, 1)) ; | |
// Minor axis (in x; yz are following axis) | |
uvec3 mi = (n.x < n.y && n.x < n.z) ? uvec3(0, 1, 2) : | |
((n.y < n.z ) ? uvec3(1, 2, 0) : | |
uvec3(2, 0, 1)) ; | |
// Median axis (in x; yz are following axis) | |
uvec3 me = uvec3(3) - mi - ma; | |
// Project + fetch | |
vec4 x = textureGrad (tex, | |
vec2( p[ma.y], p[ma.z]), | |
vec2(dpdx[ma.y], dpdx[ma.z]), | |
vec2(dpdy[ma.y], dpdy[ma.z])); | |
vec4 y = textureGrad (tex, | |
vec2( p[me.y], p[me.z]), | |
vec2(dpdx[me.y], dpdx[me.z]), | |
vec2(dpdy[me.y], dpdy[me.z])); | |
// Blend factors | |
vec2 w = vec2(n[ma.x], n[me.x]); | |
// Make local support | |
w = clamp((w - 0.5773) / (1.0 - 0.5773), 0.0, 1.0); | |
// Blending | |
output = (x * w.x + y * w.y) / (w.x + w.y); | |
} | |
float noise(vec3 x) { | |
vec3 p = floor(x); | |
vec3 f = fract(x); | |
f = f*f*(3.0-2.0*f); | |
vec2 uv = (p.xy+vec2(37.0,239.0)*p.z) + f.xy; | |
vec2 rg = textureLod(_NoiseTex, vec2((uv+0.5)/256.0), 0).yx; | |
return mix( rg.x, rg.y, f.z )*2.0-1.0; | |
} | |
//https://iquilezles.org/articles/fbm | |
float fbm(in vec3 x) { | |
x -= TIME * _CloudOffset.xyz; | |
const float H = 1.0; | |
const int num_octaves = 5; | |
float G = exp2(-H); | |
float f = 1.0; | |
float a = 1.0; | |
float t = 0.0; | |
for(int i=0; i<num_octaves; i++) { | |
t += (i>2) ? a*noise(f*(x)): a*noise(f*x); | |
f *= 2.0; | |
a *= G; | |
} | |
return t; | |
} | |
float mapClouds(vec3 po) { | |
// if it's outside the dice bounds, ease off the density | |
float c = box(po, vec3(0.2)); | |
c = clamp(-(c * 0.5 - 0.5), 0.0, 1.0); | |
float sd = sphere(po, -_CloudScale); | |
sd += c * (_CloudSharpness * fbm(po * _CloudNoiseScale)); | |
return sd; | |
} | |
// faster 4 tap normals | |
vec3 cloudNorm( in vec3 p ){ | |
const vec2 e = vec2(0.01, -0.01); | |
return normalize( | |
e.xyy*mapClouds(p+e.xyy) + | |
e.yyx*mapClouds(p+e.yyx) + | |
e.yxy*mapClouds(p+e.yxy) + | |
e.xxx*mapClouds(p+e.xxx)); | |
} | |
struct LightInfo { | |
vec3 dir; | |
vec3 color; | |
bool isDirectionalLight; | |
}; | |
// https://www.shadertoy.com/view/sdjcRD | |
vec4 marchClouds( vec3 ro, inout vec3 rd, float mind, float maxd, float maxs, LightInfo light, float audio1) | |
{ | |
vec3 cloudColor = _CloudColor.rgb * _CloudColor.a; | |
vec3 lightVec = light.isDirectionalLight ? vec3(0.5, 0.0, 0.5) : light.dir; | |
const float steps = maxs; | |
const float shadowSteps = 10.; | |
const float invSteps = 1. / (steps); | |
const float invShadowSteps = 0.75 / (shadowSteps); | |
const float stepDistance = maxd * invSteps; | |
const float shadowStepSize = 0.75 * invShadowSteps; | |
vec3 lightColor = vec3(0.,0.,0.); | |
float lightPower = 1.0; | |
vec3 CurPos = ro; | |
float li = 0.0; | |
float t = mind; | |
for(float I = 0.0; I < steps; ++I) { | |
if (t > maxd) break; | |
float cursample = mapClouds( CurPos ) * 3.0; | |
if ( cursample > 0.01 ) { | |
vec3 lpos = CurPos; | |
float shadowDist = 0.; | |
for ( float S = 0.0 ; S < shadowSteps ; ++S ) { | |
lpos += lightVec * shadowStepSize ; | |
float lsample = (mapClouds( lpos )); | |
shadowDist += lsample * _CloudShadow; | |
} | |
float curdensity = clamp( cursample * invSteps, 0.0, 1.0 ); | |
float shadow = exp( - shadowDist * invShadowSteps ); | |
lightColor += shadow * curdensity * lightPower * (cloudColor); | |
lightPower *= (1. - curdensity); | |
// sample the dice again for the glow | |
float glow = mapDice(CurPos).z; | |
li += 0.1 / (1.0 + glow * glow * (200.)) * lightPower; | |
if ( lightPower < 0.01 ) { | |
break; | |
} | |
} | |
t += min(0.02 * exp(-0.2 * cursample), stepDistance); | |
CurPos = ro + rd * t; | |
} | |
float lightScale = dot(light.color, vec3(0.2125, 0.7154, 0.0721)); | |
lightScale = smoothstep(0.0, 2.0, 1.0 - (lightScale - 1.0)); | |
lightColor.rgb += _CloudGlowCol.rgb * (lightScale * _CloudGlowCol.a * li * (audio1 * 0.5 + 0.5)); | |
return vec4( lightColor , 1.0 - lightPower ); | |
} | |
// https://www.shadertoy.com/view/WsXSDH | |
// cheap AO using normals | |
float diceCheapAO(vec3 p, vec3 n) | |
{ | |
float a = .5+.5*mapDice(p+n*.04).x/.05; | |
a *= .6+.4*mapDice(p+n*.08).x/.1; | |
a *= .7+.3*mapDice(p+n*.16).x/.2; | |
return clamp(a * a, 0.0, 1.0); | |
} | |
// same idea for the AO | |
// since we know where the surface is, | |
// just sample from the surface back to the origin for a nice, cheap glow effect | |
vec3 diceCheapGlow(vec3 p, vec3 rd, float audio1) | |
{ | |
float li = 0.0; | |
for (float i = 0.0; i < 5.0; i++) | |
{ | |
vec3 d = mapDice(p + rd * (-0.1 * i)); | |
li += 0.1 / (1.0 + d.z * d.z * (1000.0 - 500.0 * audio1)); | |
} | |
return _GlowCol.rgb * li * _GlowCol.a; | |
} | |
vec3 applyMat(float matID, vec3 pos, vec3 viewDir, | |
vec3 norm, vec3 inCol, vec3 effects, float audio2) | |
{ | |
vec3 col = inCol; | |
// glass | |
if (matID < 1.0); | |
// dice frame | |
else if (matID < 2.0) | |
{ | |
// poi's matcap code from their toon shadur | |
vec3 worldViewUp = normalize(vec3(0, 1, 0) - viewDir * dot(viewDir, vec3(0, 1, 0))); | |
vec3 worldViewRight = normalize(cross(viewDir, worldViewUp)); | |
vec2 matcapUV = vec2(dot(worldViewRight, norm), dot(worldViewUp, norm)) * 0.5 + 0.5; | |
vec4 matcapCol = texture(_Matcap1, matcapUV); | |
col = (matcapCol.rgb * _FrameCol.rgb) * effects * _FrameCol.a; | |
} | |
// circles | |
else if (matID < 3.0) | |
{ | |
vec4 biOut; | |
// biplanar mapping | |
Biplanar_float(_CircleTex1, | |
pos * _CircleTex1Scale, norm, biOut); | |
biOut *= _CircleCol; | |
float scale = clamp(1.0 - (matID - floor(matID)) / 0.01, 0.0, 1.0); | |
float aa = 0.7 * fwidth(scale); | |
col = mix(col, biOut.rgb * effects, | |
smoothstep(0.75 - aa - audio2 * 0.25, 1.0 + aa - audio2 * 0.25, scale) * biOut.a); | |
} | |
return col; | |
} | |
void marchInner(inout marchInOut mI, LightInfo light, float max_steps, float audio1, float audio2, mat4 xMODEL_MATRIX) | |
{ | |
const float cref = 0.95; | |
vec3 col = mI.col.rgb; | |
vec3 iniPos = mI.ro; | |
vec3 iniDir = mI.rd; | |
vec3 n = mI.norm; | |
float iniMat = mI.matID; | |
vec3 refl = reflect(mI.rd, n); | |
vec3 refr = refract(mI.rd, n, cref); | |
// march into the back of the dice | |
mI.ro = mI.ro + refr; | |
mI.rd = -refr; | |
marchOuter(mI, 32.0); | |
vec3 nout = diceNorm(mI.pos); | |
float dout = mI.dist; | |
vec4 c = vec4(0.); | |
if (_Cloud_On) { | |
dout = 1.0 - dout; | |
#if defined(ENABLE_CLOUDS) | |
c = marchClouds(iniPos, iniDir, 0., dout, max_steps, light, audio1); | |
#endif | |
} | |
vec3 refrOut = refract(-mI.rd, nout, cref); | |
refrOut = mat3(xMODEL_MATRIX) * (refrOut); | |
vec3 iniWorldPos = (xMODEL_MATRIX * vec4(iniPos, 1.0)).xyz; | |
vec3 reflWorldPos = (xMODEL_MATRIX * vec4(mI.pos, 1.0)).xyz; | |
// do colors | |
// inside | |
float ao = diceCheapAO(iniPos, n); | |
vec3 colInner = refProbe(reflWorldPos, refrOut); | |
if (_Cloud_On) { | |
colInner *= 0.5; | |
} | |
colInner = applyMat(mI.matID, mI.pos, mI.rd, nout, colInner, vec3(ao * (1. - c.a)), audio2); | |
col = mix(colInner, c.rgb, c.a); | |
// outside | |
col = applyMat(iniMat, iniPos, iniDir, n, col, vec3(ao), audio2); | |
mI.col = vec4(col, 1.0); | |
mI.matID = iniMat; | |
mI.rd = iniDir; | |
mI.norm = n; | |
} | |
// reuse the fragment shader for both forward base and forward add passes | |
void fragment () { | |
#ifndef DEBUG_VERTEX_SHADER | |
marchInOut mI; | |
mI.ro = varying_ro; | |
mI.rd = varying_rd; | |
mI.pos = vec3(0., 0., 0.); | |
mI.norm = vec3(0., 1., 0.); | |
mI.col = vec4(0., 0., 0., 1.); | |
mI.depth = 0.0; | |
mI.matID = 0.0; | |
mI.dist = 0.0; | |
vec3 worldPos; | |
vec3 normal; | |
vec3 surfacePos; | |
vec4 clipPos; | |
// If there's no shadow pass just do the ray march | |
/*if (DepthTextureExists())*/ | |
#if !defined(USE_DEPTH_TEXTURE) | |
//if (!use_depth_texture){ | |
marchOuter(mI, 200.0); | |
if(mI.col.a < 0.01) { | |
discard; | |
} | |
surfacePos = mI.pos; | |
worldPos = (MODEL_MATRIX * vec4(surfacePos, 1.0)).xyz; | |
clipPos = PROJECTION_MATRIX * VIEW_MATRIX * vec4(worldPos, 1.0); | |
//} | |
// If there is a shadow pass reconstruct the world position | |
#else | |
{ | |
// https://github.com/netri/Neitri-Unity-Shaders/blob/master/World%20Normal%20Nice%20Slow.shader | |
// get the world position from the depth pass | |
//vec4 screenPos = PROJECTION_MATRIX * VIEW_MATRIX * MODEL_MATRIX * modelPos; | |
// Transform from adjusted screen pos back to world pos | |
//worldPos = calculateWorldSpace(INV_VIEW_MATRIX * INV_PROJECTION_MATRIX * screenPos, screenPos, (INV_VIEW_MATRIX)[3].xyz, DEPTH_TEXTURE, INV_PROJECTION_MATRIX) + (INV_VIEW_MATRIX)[3].xyz; | |
//float scrdepth = textureLod(DEPTH_TEXTURE, SCREEN_UV, 0.0).r; | |
//vec4 upos = INV_PROJECTION_MATRIX * vec4(SCREEN_UV * 2.0 - 1.0, scrdepth, 1.0); // * 2.0 - 1.0 | |
//worldPos = upos.xyz / upos.w; | |
float depth = texture(DEPTH_TEXTURE, SCREEN_UV).x; | |
vec4 ndc = vec4(SCREEN_UV * 2.0 - 1.0, depth , 1) ; | |
vec4 view = INV_PROJECTION_MATRIX * ndc; | |
view /= view.w; | |
worldPos = (INV_VIEW_MATRIX * view).xyz; | |
worldPos += normalize((INV_VIEW_MATRIX)[2]).xyz * 0.0001; | |
// check the SDF to discard other geometry based on view distance | |
clipPos = PROJECTION_MATRIX * VIEW_MATRIX * vec4(worldPos, 1.0); | |
surfacePos = (inverse(MODEL_MATRIX) * vec4(worldPos, 1.0)).xyz; | |
vec3 dist = mapDice(surfacePos); | |
mI.matID = dist.y; | |
float checkDist = 0.0005 / maxScale * (1.0 + distance(worldPos, transpose(INV_VIEW_MATRIX)[3].xyz)); | |
if (dist.x > checkDist) discard; | |
} | |
#endif | |
normal = diceNorm(surfacePos); | |
mI.ro = surfacePos; | |
mI.norm = normal; | |
mI.depth = clipPos.z / clipPos.w; | |
float shadowIN = 0.0; | |
/* | |
// stuff for directional shadow receiving | |
#if defined (SHADOWS_SCREEN) | |
// setup shadow struct for screen space shadows | |
shadowInput shadowIN; | |
#if defined(UNITY_NO_SCREENSPACE_SHADOWS) | |
// mobile directional shadow | |
shadowIN._ShadowCoord = mul(unity_WorldToShadow[0], vec4(worldPos, 1.0)); | |
#else | |
// screen space directional shadow | |
shadowIN._ShadowCoord = ComputeScreenPos(clipPos); | |
#endif // UNITY_NO_SCREENSPACE_SHADOWS | |
#else | |
// no shadow, or no directional shadow | |
shadowIN = 0.0; | |
#endif // SHADOWS_SCREEN | |
*/ | |
NORMAL = mat3(VIEW_MATRIX) * normal; | |
// basic lighting | |
/* | |
vec3 worldNormal = mat3(MODEL_MATRIX) * normal; | |
vec3 worldLightDir = normalize(UnityWorldSpaceLightDir(worldPos)); | |
vec3 worldDir = UnityObjectToWorldDir(mI.rd); | |
float ndotl = saturate(dot(worldNormal, worldLightDir)); | |
// get shadow, attenuation, and cookie | |
UNITY_LIGHT_ATTENUATION(atten, shadowIN, worldPos); | |
// per pixel lighting | |
vec3 lighting = _LightColor0 * atten * ndotl; | |
vec3 vertexLighting = vec3(0.); | |
*/ | |
/* | |
#if defined(VERTEXLIGHT_ON) | |
// "per vertex" non-important lights | |
vertexLighting = Shade4PointLights( | |
unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0, | |
unity_LightColor[0].rgb, unity_LightColor[1].rgb, unity_LightColor[2].rgb, unity_LightColor[3].rgb, | |
unity_4LightAtten0, worldPos, worldNormal); | |
lighting += vertexLighting; | |
#endif // VERTEXLIGHT_ON | |
*/ | |
// audiolink | |
float audio1 =(sin(TIME * 10.0) + 1.0) * 0.5; | |
float audio2 =(sin(TIME * 14.23) + 0.6) * 0.2; | |
//audio1 = (sin(_Time.y * 10) + 1.0) * 0.5; | |
//audio1 = AudioLinkData( ALPASS_AUDIOLINK + int2( 0, 0 ) ).r; | |
//audio2 = AudioLinkData( ALPASS_AUDIOLINK + int2( 0, 2 ) ).r * 2.0; | |
LightInfo light; | |
light.isDirectionalLight = true; | |
light.color = vec3(0.72,0.6,0.9); //_LightColor0 + vertexLighting; | |
// no point lights in foward base, so just make everything directional | |
// from the center | |
light.dir = vec3(0.86,0.5,0.0); //normalize(UnityWorldSpaceLightDir(i.center)); | |
marchInner(mI, light, 64., audio1, audio2, MODEL_MATRIX); | |
//light.color = lighting; | |
//light.dir = worldLightDir; | |
//apply lighting | |
//vec3 specularTint; | |
//float oneMinusReflectivity; | |
ROUGHNESS = 1.0 - _Smoothness; | |
METALLIC = (mI.matID == 1.0) ? 0.7 : 0.2; | |
//vec3 albedo = DiffuseAndSpecularFromMetallic( | |
// mI.col.rgb, metallic, specularTint, oneMinusReflectivity | |
//); | |
ALBEDO = mI.col.rgb; | |
/* | |
UnityIndirect indirectLight; | |
#ifdef UNITY_PASS_FORWARDADD | |
indirectLight.diffuse = indirectLight.specular = 0; | |
#else | |
indirectLight.diffuse = max(0, ShadeSH9(vec4(worldNormal, 1))); | |
vec3 reflectionDir = reflect(-worldDir, worldNormal); | |
Unity_GlossyEnvironmentData envData; | |
envData.roughness = 1 - smoothness; | |
envData.reflUVW = reflectionDir; | |
indirectLight.specular = Unity_GlossyEnvironment( | |
UNITY_PASS_TEXCUBE(unity_SpecCube0), unity_SpecCube0_HDR, envData | |
); | |
#endif | |
mI.col.rgb = UNITY_BRDF_PBS( | |
albedo, specularTint, | |
oneMinusReflectivity, smoothness, | |
worldNormal, -worldDir, | |
light, indirectLight | |
); | |
*/ | |
//#ifdef UNITY_PASS_FORWARDBASE | |
vec3 glow = diceCheapGlow(surfacePos, mI.rd, audio1); | |
glow.rgb = glow.rgb * (1.0 + audio1); | |
mI.col.rgb += glow * ((mI.matID == 1.0) ? 0.1 : 1.0); | |
//#endif | |
DEPTH = mI.depth; | |
// fog | |
//float fogCoord = clipPos.z; | |
//UNITY_APPLY_FOG(fogCoord, mI.col); | |
ALBEDO = mI.col.rgb; | |
#endif | |
} | |
/* | |
void frag_shadow_only () { | |
marchInOut mI; | |
mI.ro = varying_ro; | |
mI.rd = varying_rd; | |
mI.pos = vec3(0., 0., 0.); | |
mI.norm = vec3(0., 1., 0.); | |
mI.col = vec4(1., 1., 1., 1.); | |
mI.depth = 0.0; | |
mI.matID = 0.0; | |
mI.dist = 0.0; | |
marchOuter(mI, 200.0); | |
if (mI.col.a < 0.01) { | |
discard; | |
} | |
vec3 surfacePos = mI.pos; | |
// output modified depth | |
//vec4 clipPos = UnityClipSpaceShadowCasterPos(surfacePos, surfacePos); | |
//clipPos = UnityApplyLinearShadowBias(clipPos); | |
//outDepth = clipPos.z / clipPos.w; | |
} | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment