Seascape
This is a Shadertoy example. Because of the way this docs are built, its housed in a minimal Indigo project for built purposes, but the shader code isn't run because it's incompatible with Indigo, so all you get is a black screen.
This example is a port of 'Seascape' by TDM, and you can view it here: https://www.shadertoy.com/view/Ms2SD1
The GLSL Output
This is the shadertoy compatible GLSL code that Ultraviolet manufactures:
const int NUM_STEPS=8;
const float PI=3.141592;
const float EPSILON=0.001;
#define EPSILON_NRM 0.1/iResolution.x
const int ITER_GEOMETRY=3;
const int ITER_FRAGMENT=5;
const float SEA_HEIGHT=0.6;
const float SEA_CHOPPY=4.0;
const float SEA_SPEED=0.8;
const float SEA_FREQ=0.16;
const vec3 SEA_BASE=vec3(0.0,0.09,0.18);
const vec3 SEA_WATER_COLOR=vec3(0.8,0.9,0.6)*0.6;
#define SEA_TIME 1.0+(iTime*SEA_SPEED)
const mat2 octave_m=mat2(1.6,1.2,-1.2,1.6);
mat3 fromEuler(in vec3 ang){
vec2 a1=vec2(sin(ang.x),cos(ang.x));
vec2 a2=vec2(sin(ang.y),cos(ang.y));
vec2 a3=vec2(sin(ang.z),cos(ang.z));
mat3 m;
m[0]=(vec3((a1.y*a3.y)+((a1.x*a2.x)*a3.x),((a1.y*a2.x)*a3.x)+(a3.y*a1.x),(-a2.y)*a3.x));
m[1]=(vec3((-a2.y)*a1.x,a1.y*a2.y,a2.x));
m[2]=(vec3(((a3.y*a1.x)*a2.x)+(a1.y*a3.x),(a1.x*a3.x)-((a1.y*a3.y)*a2.x),a2.y*a3.y));
return m;
}
float hash(in vec2 p){
float h=dot(p,vec2(127.1,311.7));
return fract(sin(h)*43758.547);
}
float noise(in vec2 p){
vec2 i=floor(p);
vec2 f=fract(p);
vec2 u=(f*f)*(3.0-(2.0*f));
return (-1.0)+(2.0*(mix(mix(hash(i+vec2(0.0,0.0)),hash(i+vec2(1.0,0.0)),u.x),mix(hash(i+vec2(0.0,1.0)),hash(i+vec2(1.0,1.0)),u.x),u.y)));
}
float diffuse(in vec3 n,in vec3 l,in float p){
return pow((dot(n,l)*0.4)+0.6,p);
}
float specular(in vec3 n,in vec3 l,in vec3 e,in float s){
float nrm=(s+8.0)/(PI*8.0);
return pow(max(dot(reflect(e,n),l),0.0),s)*nrm;
}
vec3 getSkyColor(in vec3 c){
vec3 e=vec3(c.x,((max(c.y,0.0)*0.8)+0.2)*0.18,c.z);
return (vec3(pow(1.0-e.y,2.0),1.0-e.y,0.6+((1.0-e.y)*0.4)))*1.1;
}
float sea_octave(in vec2 uv,in float choppy){
float n=noise(uv);
vec2 uv2=vec2(uv.x+n,uv.y+n);
vec2 wv=1.0-abs(sin(uv2));
vec2 swv=abs(cos(uv2));
wv=mix(wv,swv,wv);
return pow(1.0-(pow(wv.x*wv.y,0.65)),choppy);
}
float map(in vec3 p){
float freq=SEA_FREQ;
float amp=SEA_HEIGHT;
float choppy=SEA_CHOPPY;
vec2 uv=p.xz;
uv=vec2(uv.x*0.75,uv.y);
float d=0.0;
float h=0.0;
for(int val0=0;val0<ITER_GEOMETRY;val0=val0+1){
d=sea_octave((uv+SEA_TIME)*freq,choppy);
d=d+(sea_octave((uv-SEA_TIME)*freq,choppy));
h=h+(d*amp);
uv=uv*octave_m;
freq=freq*1.9;
amp=amp*0.22;
choppy=mix(choppy,1.0,0.2);
}
return p.y-h;
}
vec3 getSeaColor(in vec3 p,in vec3 n,in vec3 l,in vec3 eye,in vec3 dist){
float fresnel=clamp(1.0-(dot(n,-eye)),0.0,1.0);
fresnel=pow(fresnel,3.0)*0.5;
vec3 reflected=getSkyColor(reflect(eye,n));
vec3 refracted=SEA_BASE+((diffuse(n,l,80.0)*SEA_WATER_COLOR)*0.12);
vec3 color=mix(refracted,reflected,fresnel);
float atten=max(1.0-(dot(dist,dist)*0.001),0.0);
color=color+(((SEA_WATER_COLOR*(p.y-SEA_HEIGHT))*0.18)*atten);
color=color+vec3(specular(n,l,eye,60.0));
return color;
}
vec3 getNormal(in vec3 p,in float eps){
vec3 n;
n=vec3(n.x,map(p),n.z);
n=vec3((map(vec3(p.x+eps,p.y,p.z)))-n.y,n.y,n.z);
n=vec3(n.x,n.y,(map(vec3(p.x,p.y,p.z+eps)))-n.y);
n=vec3(n.x,eps,n.z);
return normalize(n);
}
vec3 pOut=vec3(0.0);
float heightMapTracing(in vec3 ori,in vec3 dir){
float tm=0.0;
float tx=1000.0;
float hx=map(ori+(dir*tx));
float res=0.0;
if(hx>0.0){
pOut=ori+(dir*tx);
res=tx;
}else{
float hm=map(ori+(dir*tm));
float tmid=0.0;
for(int val1=0;val1<NUM_STEPS;val1=val1+1){
tmid=mix(tm,tx,hm/(hm-hx));
pOut=ori+(dir*tmid);
float hmid=map(pOut);
if(hmid<0.0){
tx=tmid;
hx=hmid;
}else{
tm=tmid;
hm=hmid;
}
}
res=tmid;
}
return res;
}
vec3 getPixel(in vec2 coord,in float time){
vec2 uv=coord/iResolution.xy;
uv=(uv*2.0)-1.0;
uv=vec2(uv.x*(iResolution.x/iResolution.y),uv.y);
vec3 ang=vec3((sin(time*3.0))*0.1,(sin(time)*0.2)+0.3,time);
vec3 ori=vec3(0.0,3.5,time*5.0);
vec3 dir=normalize(vec3(uv.xy,-2.0));
dir=vec3(dir.xy,dir.z+(length(uv)*0.14));
dir=normalize(dir)*fromEuler(ang);
heightMapTracing(ori,dir);
vec3 dist=pOut-ori;
vec3 n=getNormal(pOut,dot(dist,dist)*EPSILON_NRM);
vec3 light=normalize(vec3(0.0,1.0,0.8));
return mix(getSkyColor(dir),getSeaColor(pOut,n,light,dir,dist),pow(smoothstep(0.0,-0.02,dir.y),0.2));
}
void mainImage(out vec4 fragColor,in vec2 fragCoord){
float time=(iTime*0.3)+(iMouse.x*0.01);
vec3 color=getPixel(fragCoord,time);
fragColor=vec4(pow(color,vec3(0.65)),1.0);
}
Example Links
Ultraviolet Shadertoy code
Reminder: The live demo will not work, below is the Shadertoy compatible code.
object ShaderToyExample:
inline def image =
Shader[ShaderToyEnv, Unit] { env =>
@const val NUM_STEPS: Int = 8
@const val PI: Float = 3.141592f
@const val EPSILON: Float = 1e-3f
@define val EPSILON_NRM: Float = 0.1f / env.iResolution.x
// sea
@const val ITER_GEOMETRY: Int = 3
@const val ITER_FRAGMENT: Int = 5
@const val SEA_HEIGHT: Float = 0.6f
@const val SEA_CHOPPY: Float = 4.0f
@const val SEA_SPEED: Float = 0.8f
@const val SEA_FREQ: Float = 0.16f
@const val SEA_BASE: vec3 = vec3(0.0f, 0.09f, 0.18f)
@const val SEA_WATER_COLOR: vec3 = vec3(0.8f, 0.9f, 0.6f) * 0.6f
@define val SEA_TIME: Float = 1.0f + env.iTime * SEA_SPEED
@const val octave_m: mat2 = mat2(1.6f, 1.2f, -1.2f, 1.6f)
// math
def fromEuler(ang: vec3): mat3 =
val a1: vec2 = vec2(sin(ang.x), cos(ang.x))
val a2: vec2 = vec2(sin(ang.y), cos(ang.y))
val a3: vec2 = vec2(sin(ang.z), cos(ang.z))
val m: mat3 = null
m(0) =
vec3(a1.y * a3.y + a1.x * a2.x * a3.x, a1.y * a2.x * a3.x + a3.y * a1.x, -a2.y * a3.x)
m(1) = vec3(-a2.y * a1.x, a1.y * a2.y, a2.x)
m(2) = vec3(a3.y * a1.x * a2.x + a1.y * a3.x, a1.x * a3.x - a1.y * a3.y * a2.x, a2.y * a3.y)
m
def hash(p: vec2): Float =
val h: Float = dot(p, vec2(127.1, 311.7))
fract(sin(h) * 43758.5453123f)
def noise(p: vec2): Float =
val i: vec2 = floor(p)
val f: vec2 = fract(p)
val u: vec2 = f * f * (3.0f - 2.0f * f)
-1.0f + 2.0f *
mix(
mix(hash(i + vec2(0.0f, 0.0f)), hash(i + vec2(1.0f, 0.0f)), u.x),
mix(hash(i + vec2(0.0f, 1.0f)), hash(i + vec2(1.0f, 1.0f)), u.x),
u.y
)
// lighting
def diffuse(n: vec3, l: vec3, p: Float): Float =
pow(dot(n, l) * 0.4f + 0.6f, p)
def specular(n: vec3, l: vec3, e: vec3, s: Float): Float =
val nrm: Float = (s + 8.0f) / (PI * 8.0f)
pow(max(dot(reflect(e, n), l), 0.0f), s) * nrm
// sky
def getSkyColor(c: vec3): vec3 =
val e = vec3(
c.x,
(max(c.y, 0.0f) * 0.8f + 0.2f) * 0.18f,
c.z
)
vec3(pow(1.0f - e.y, 2.0f), 1.0f - e.y, 0.6f + (1.0f - e.y) * 0.4f) * 1.1f
// sea
def sea_octave(uv: vec2, choppy: Float): Float =
val n = noise(uv)
val uv2 = vec2(uv.x + n, uv.y + n)
var wv: vec2 = 1.0f - abs(sin(uv2))
val swv: vec2 = abs(cos(uv2))
wv = mix(wv, swv, wv)
pow(1.0f - pow(wv.x * wv.y, 0.65f), choppy)
def map(p: vec3): Float = {
var freq: Float = SEA_FREQ
var amp: Float = SEA_HEIGHT
var choppy: Float = SEA_CHOPPY
var uv: vec2 = p.xz
uv = vec2(uv.x * 0.75f, uv.y)
var d: Float = 0.0f
var h = 0.0f
cfor(0, _ < ITER_GEOMETRY, _ + 1) { _ =>
d = sea_octave((uv + SEA_TIME) * freq, choppy)
d += sea_octave((uv - SEA_TIME) * freq, choppy)
h += d * amp
uv = uv * octave_m
freq *= 1.9f
amp *= 0.22f
choppy = mix(choppy, 1.0f, 0.2f)
}
p.y - h
}
def getSeaColor(p: vec3, n: vec3, l: vec3, eye: vec3, dist: vec3): vec3 =
var fresnel: Float = clamp(1.0f - dot(n, -eye), 0.0f, 1.0f)
fresnel = pow(fresnel, 3.0f) * 0.5f
val reflected: vec3 = getSkyColor(reflect(eye, n))
val refracted: vec3 = SEA_BASE + diffuse(n, l, 80.0f) * SEA_WATER_COLOR * 0.12f
var color: vec3 = mix(refracted, reflected, fresnel)
val atten: Float = max(1.0f - dot(dist, dist) * 0.001f, 0.0f)
color += SEA_WATER_COLOR * (p.y - SEA_HEIGHT) * 0.18f * atten
color += vec3(specular(n, l, eye, 60.0f))
color
// tracing
def getNormal(p: vec3, eps: Float): vec3 =
var n: vec3 = null
n = vec3(n.x, map(p), n.z)
n = vec3(map(vec3(p.x + eps, p.y, p.z)) - n.y, n.y, n.z)
n = vec3(n.x, n.y, map(vec3(p.x, p.y, p.z + eps)) - n.y)
n = vec3(n.x, eps, n.z)
normalize(n)
var pOut: vec3 = vec3(0.0)
def heightMapTracing(ori: vec3, dir: vec3): Float =
var tm: Float = 0.0f
var tx: Float = 1000.0f
var hx: Float = map(ori + dir * tx)
var res = 0.0f
if (hx > 0.0f) {
pOut = ori + dir * tx
res = tx
} else {
var hm: Float = map(ori + dir * tm)
var tmid: Float = 0.0f
cfor(0, _ < NUM_STEPS, _ + 1) { _ =>
tmid = mix(tm, tx, hm / (hm - hx))
pOut = ori + dir * tmid
val hmid: Float = map(pOut)
if (hmid < 0.0f) {
tx = tmid
hx = hmid
} else {
tm = tmid
hm = hmid
}
}
res = tmid
}
res
def getPixel(coord: vec2, time: Float): vec3 = {
var uv: vec2 = coord / env.iResolution.xy
uv = uv * 2.0f - 1.0f
uv = vec2(uv.x * (env.iResolution.x / env.iResolution.y), uv.y)
// ray
val ang: vec3 = vec3(sin(time * 3.0f) * 0.1f, sin(time) * 0.2f + 0.3f, time)
val ori: vec3 = vec3(0.0f, 3.5f, time * 5.0f)
var dir: vec3 = normalize(vec3(uv.xy, -2.0f))
dir = vec3(dir.xy, dir.z + (length(uv) * 0.14f))
dir = normalize(dir) * fromEuler(ang)
// tracing
heightMapTracing(ori, dir)
val dist: vec3 = pOut - ori
val n: vec3 = getNormal(pOut, dot(dist, dist) * EPSILON_NRM)
val light: vec3 = normalize(vec3(0.0f, 1.0f, 0.8f))
// color
mix(
getSkyColor(dir),
getSeaColor(pOut, n, light, dir, dist),
pow(smoothstep(0.0f, -0.02f, dir.y), 0.2f)
)
}
// main
def mainImage(fragColor: vec4, fragCoord: vec2): vec4 =
val time: Float = env.iTime * 0.3f + env.iMouse.x * 0.01f
val color: vec3 = getPixel(fragCoord, time)
vec4(pow(color, vec3(0.65f)), 1.0f)
}
Then to get the code as a String so that we might print it or output it to a file or whatever, we can do the following:
object Output:
val imageCode: String =
ShaderToyExample.image.toGLSL[ShaderToy].toOutput.code