API update

main
cosmonaut 2022-03-02 15:41:52 -08:00
parent beb9a4dff0
commit 922e4cf0f1
7 changed files with 656 additions and 234 deletions

Binary file not shown.

@ -1 +1 @@
Subproject commit d2fca3654bd90817885f0006058cccd6437da22c Subproject commit 111df04c0f7be740108cc3536eda3629572714d8

Binary file not shown.

View File

@ -1,11 +1,12 @@
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using MoonWorks.Math;
namespace MoonWorksTest namespace MoonWorksTest
{ {
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
struct Vertex struct PositionTexture
{ {
public float x, y, z; public Vector3 Position;
public float u, v; public Vector2 Texture;
} }
} }

View File

@ -1,5 +1,5 @@
using MoonWorks;
using MoonWorks.Graphics; using MoonWorks.Graphics;
using MoonWorks.Window;
namespace MoonWorksTest namespace MoonWorksTest
{ {
@ -15,7 +15,13 @@ namespace MoonWorksTest
ScreenMode = ScreenMode.Windowed ScreenMode = ScreenMode.Windowed
}; };
TestGame game = new TestGame(windowCreateInfo, PresentMode.FIFO, 60, true); TestGame game = new TestGame(
windowCreateInfo,
PresentMode.FIFO,
60,
true
);
game.Run(); game.Run();
} }
} }

View File

@ -2,9 +2,8 @@ using MoonWorks;
using MoonWorks.Audio; using MoonWorks.Audio;
using MoonWorks.Graphics; using MoonWorks.Graphics;
using MoonWorks.Input; using MoonWorks.Input;
using MoonWorks.Window;
using System.IO; using System.IO;
using System.Threading; using System.Threading.Tasks;
namespace MoonWorksTest namespace MoonWorksTest
{ {
@ -20,28 +19,20 @@ namespace MoonWorksTest
Buffer vertexBuffer; Buffer vertexBuffer;
Rect renderArea;
Rect flip;
Color clearColor; Color clearColor;
DepthStencilValue depthStencilClear;
Texture mainColorTargetTexture;
TextureSlice mainColorTargetTextureSlice;
RenderTarget mainColorTarget;
RenderPass mainRenderPass;
Framebuffer mainFramebuffer;
GraphicsPipeline mainGraphicsPipeline; GraphicsPipeline mainGraphicsPipeline;
byte[] screenshotPixels; byte[] screenshotPixels;
Buffer screenshotBuffer; Buffer screenshotBuffer;
uint screenShotBufferSize; uint screenshotBufferSize;
Thread screenshotThread;
StaticSound music; StaticSound music;
StaticSoundInstance musicInstance; StaticSoundInstance musicInstance;
StreamingSoundOgg musicStream; StreamingSoundOgg musicStream;
bool screenshotInProgress = false;
public TestGame(WindowCreateInfo windowCreateInfo, PresentMode presentMode, int targetTimestep = 60, bool debugMode = false) : base(windowCreateInfo, presentMode, targetTimestep, debugMode) public TestGame(WindowCreateInfo windowCreateInfo, PresentMode presentMode, int targetTimestep = 60, bool debugMode = false) : base(windowCreateInfo, presentMode, targetTimestep, debugMode)
{ {
var windowWidth = windowCreateInfo.WindowWidth; var windowWidth = windowCreateInfo.WindowWidth;
@ -64,206 +55,60 @@ namespace MoonWorksTest
/* Load Vertex Data */ /* Load Vertex Data */
var vertices = new Vertex[3]; var vertices = new PositionTexture[3];
vertices[0].x = -1; vertices[0].Position.X = -1;
vertices[0].y = -1; vertices[0].Position.Y = -1;
vertices[0].z = 0; vertices[0].Position.Z = 0;
vertices[0].u = 0; vertices[0].Texture.X = 0;
vertices[0].v = 1; vertices[0].Texture.Y = 1;
vertices[1].x = 3; vertices[1].Position.X = 3;
vertices[1].y = -1; vertices[1].Position.Y = -1;
vertices[1].z = 0; vertices[1].Position.Z = 0;
vertices[1].u = 1; vertices[1].Texture.X = 1;
vertices[1].v = 1; vertices[1].Texture.Y = 1;
vertices[2].x = -1; vertices[2].Position.X = -1;
vertices[2].y = 3; vertices[2].Position.Y = 3;
vertices[2].z = 0; vertices[2].Position.Z = 0;
vertices[2].u = 0; vertices[2].Texture.X = 0;
vertices[2].v = 0; vertices[2].Texture.Y = 0;
vertexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Vertex, 4 * 5 * 3); vertexBuffer = Buffer.Create<PositionTexture>(GraphicsDevice, BufferUsageFlags.Vertex, 3);
uploadCommandBuffer.SetBufferData(vertexBuffer, vertices); uploadCommandBuffer.SetBufferData(vertexBuffer, vertices);
GraphicsDevice.Submit(uploadCommandBuffer); GraphicsDevice.Submit(uploadCommandBuffer);
/* Render Pass */
renderArea.X = 0;
renderArea.Y = 0;
renderArea.W = (int) windowWidth;
renderArea.H = (int) windowHeight;
flip.X = 0;
flip.Y = (int) windowHeight;
flip.W = (int) windowWidth;
flip.H = -(int) windowHeight;
clearColor.R = 237;
clearColor.G = 41;
clearColor.B = 57;
clearColor.A = byte.MaxValue;
ColorTargetDescription colorTargetDescription = new ColorTargetDescription
{
Format = TextureFormat.R8G8B8A8,
MultisampleCount = SampleCount.One,
LoadOp = LoadOp.Clear,
StoreOp = StoreOp.Store
};
mainRenderPass = new RenderPass(GraphicsDevice, colorTargetDescription);
mainColorTargetTexture = Texture.CreateTexture2D(
GraphicsDevice,
windowWidth,
windowHeight,
TextureFormat.R8G8B8A8,
TextureUsageFlags.ColorTarget
);
mainColorTargetTextureSlice = new TextureSlice(mainColorTargetTexture);
mainColorTarget = new RenderTarget(GraphicsDevice, mainColorTargetTextureSlice);
mainFramebuffer = new Framebuffer(
GraphicsDevice,
windowWidth,
windowHeight,
mainRenderPass,
null,
mainColorTarget
);
/* Pipeline */ /* Pipeline */
ColorTargetBlendState[] colorTargetBlendStates = new ColorTargetBlendState[1]
{
ColorTargetBlendState.None
};
ColorBlendState colorBlendState = new ColorBlendState
{
LogicOpEnable = false,
LogicOp = LogicOp.NoOp,
BlendConstants = new BlendConstants(),
ColorTargetBlendStates = colorTargetBlendStates
};
DepthStencilState depthStencilState = DepthStencilState.Disable;
ShaderStageState vertexShaderState = new ShaderStageState
{
ShaderModule = passthroughVertexShaderModule,
EntryPointName = "main",
UniformBufferSize = 0
};
ShaderStageState fragmentShaderState = new ShaderStageState
{
ShaderModule = raymarchFragmentShaderModule,
EntryPointName = "main",
UniformBufferSize = 16
};
MultisampleState multisampleState = MultisampleState.None;
GraphicsPipelineLayoutInfo pipelineLayoutInfo = new GraphicsPipelineLayoutInfo
{
VertexSamplerBindingCount = 0,
FragmentSamplerBindingCount = 2
};
RasterizerState rasterizerState = RasterizerState.CW_CullBack;
var vertexBindings = new VertexBinding[1]
{
new VertexBinding
{
Binding = 0,
InputRate = VertexInputRate.Vertex,
Stride = 4 * 5
}
};
var vertexAttributes = new VertexAttribute[2]
{
new VertexAttribute
{
Binding = 0,
Location = 0,
Format = VertexElementFormat.Vector3,
Offset = 0
},
new VertexAttribute
{
Binding = 0,
Location = 1,
Format = VertexElementFormat.Vector2,
Offset = 4 * 3
}
};
VertexInputState vertexInputState = new VertexInputState
{
VertexBindings = vertexBindings,
VertexAttributes = vertexAttributes
};
var viewports = new Viewport[1]
{
new Viewport
{
X = 0,
Y = 0,
W = windowWidth,
H = windowHeight,
MinDepth = 0,
MaxDepth = 1
}
};
var scissors = new Rect[1]
{
new Rect
{
X = 0,
Y = 0,
W = (int) windowWidth,
H = (int) windowHeight
}
};
ViewportState viewportState = new ViewportState
{
Viewports = viewports,
Scissors = scissors
};
var graphicsPipelineCreateInfo = new GraphicsPipelineCreateInfo
{
ColorBlendState = colorBlendState,
DepthStencilState = depthStencilState,
VertexShaderState = vertexShaderState,
FragmentShaderState = fragmentShaderState,
MultisampleState = multisampleState,
PipelineLayoutInfo = pipelineLayoutInfo,
RasterizerState = rasterizerState,
PrimitiveType = PrimitiveType.TriangleList,
VertexInputState = vertexInputState,
ViewportState = viewportState,
RenderPass = mainRenderPass
};
mainGraphicsPipeline = new GraphicsPipeline( mainGraphicsPipeline = new GraphicsPipeline(
GraphicsDevice, GraphicsDevice,
graphicsPipelineCreateInfo new GraphicsPipelineCreateInfo
{
AttachmentInfo = new GraphicsPipelineAttachmentInfo(
new ColorAttachmentDescription(
GraphicsDevice.GetSwapchainFormat(Window),
ColorAttachmentBlendState.None
)
),
DepthStencilState = DepthStencilState.Disable,
VertexShaderInfo = GraphicsShaderInfo.Create(passthroughVertexShaderModule, "main", 0),
VertexInputState = new VertexInputState(
VertexBinding.Create<PositionTexture>(),
VertexAttribute.Create<PositionTexture>("Position", 0),
VertexAttribute.Create<PositionTexture>("Texture", 1)
),
PrimitiveType = PrimitiveType.TriangleList,
FragmentShaderInfo = GraphicsShaderInfo.Create<RaymarchUniforms>(raymarchFragmentShaderModule, "main", 2),
RasterizerState = RasterizerState.CW_CullBack,
ViewportState = new ViewportState((int)Window.Width, (int)Window.Height),
MultisampleState = MultisampleState.None
}
); );
screenShotBufferSize = windowWidth * windowHeight * 4; screenshotBufferSize = windowWidth * windowHeight * 4;
screenshotPixels = new byte[screenShotBufferSize]; screenshotPixels = new byte[screenshotBufferSize];
screenshotBuffer = new Buffer(GraphicsDevice, 0, screenShotBufferSize); screenshotBuffer = new Buffer(GraphicsDevice, 0, screenshotBufferSize);
screenshotThread = new Thread(new ThreadStart(SaveScreenshot));
music = StaticSound.LoadOgg(AudioDevice, Path.Combine("Content", "title_screen.ogg")); music = StaticSound.LoadOgg(AudioDevice, Path.Combine("Content", "title_screen.ogg"));
musicInstance = music.CreateInstance(); musicInstance = music.CreateInstance();
@ -280,21 +125,20 @@ namespace MoonWorksTest
protected override void Draw(System.TimeSpan dt, double alpha) protected override void Draw(System.TimeSpan dt, double alpha)
{ {
var screenshotPressed = Inputs.Keyboard.IsPressed(Keycode.S);
var commandBuffer = GraphicsDevice.AcquireCommandBuffer(); var commandBuffer = GraphicsDevice.AcquireCommandBuffer();
var swapchainTexture = commandBuffer.AcquireSwapchainTexture(Window);
var takeScreenshot = Inputs.Keyboard.IsPressed(Keycode.S) && !screenshotInProgress && (swapchainTexture != null);
if (swapchainTexture != null)
{
commandBuffer.BeginRenderPass( commandBuffer.BeginRenderPass(
mainRenderPass, new ColorAttachmentInfo(swapchainTexture, clearColor)
mainFramebuffer,
renderArea,
depthStencilClear,
clearColor.ToVector4()
); );
commandBuffer.BindGraphicsPipeline(mainGraphicsPipeline); commandBuffer.BindGraphicsPipeline(mainGraphicsPipeline);
commandBuffer.BindVertexBuffers(0, new BufferBinding(vertexBuffer, 0)); commandBuffer.BindVertexBuffers(vertexBuffer);
commandBuffer.BindFragmentSamplers( commandBuffer.BindFragmentSamplers(
new TextureSamplerBinding(woodTexture, sampler), new TextureSamplerBinding(woodTexture, sampler),
new TextureSamplerBinding(noiseTexture, sampler) new TextureSamplerBinding(noiseTexture, sampler)
@ -304,25 +148,46 @@ namespace MoonWorksTest
commandBuffer.DrawPrimitives(0, 1, 0, fragmentParamOffset); commandBuffer.DrawPrimitives(0, 1, 0, fragmentParamOffset);
commandBuffer.EndRenderPass(); commandBuffer.EndRenderPass();
if (screenshotPressed) if (takeScreenshot)
{ {
commandBuffer.CopyTextureToBuffer(mainColorTargetTextureSlice, screenshotBuffer); commandBuffer.CopyTextureToBuffer(new TextureSlice(swapchainTexture), screenshotBuffer);
}
} }
commandBuffer.QueuePresent(mainColorTargetTextureSlice, flip, Filter.Nearest);
GraphicsDevice.Submit(commandBuffer); GraphicsDevice.Submit(commandBuffer);
if (screenshotPressed) if (takeScreenshot)
{ {
screenshotThread.Start(); Task.Run(() => SaveScreenshot());
} }
} }
private void SaveScreenshot() private void SaveScreenshot()
{ {
screenshotInProgress = true;
var name = "MoonWorksTest-" + System.DateTime.Now.ToString("MM-dd-yyyy-hh-mm-ss") + ".png";
System.Console.WriteLine("Saving screenshot " + name + " ...");
GraphicsDevice.Wait(); GraphicsDevice.Wait();
screenshotBuffer.GetData(screenshotPixels, screenShotBufferSize); screenshotBuffer.GetData(screenshotPixels, screenshotBufferSize);
Texture.SavePNG("screenshot.png", 1280, 720, screenshotPixels);
Texture.SavePNG(
name,
1280,
720,
GraphicsDevice.GetSwapchainFormat(Window),
screenshotPixels
);
System.Console.WriteLine("Screenshot saved!");
screenshotInProgress = false;
}
protected override void OnDestroy()
{
} }
} }
} }

550
src/hexagon_grid.frag Normal file
View File

@ -0,0 +1,550 @@
// Created by inigo quilez - iq/2020
// License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
// Everybody has to implement an hexagonal grid. This it mine.
// It does raycasting on it, efficiently (just a few muls per step) and robustly
// (works in integers). Each cell is visited only once and in the right order.
// Based on https://www.shadertoy.com/view/WtSBWK Check castRay() in line 92.
// That, plus the fact the ambient occlusion is analytical means this shader should
// run smoothly even on a crappy phone. It does on mine!
#version 450
layout(set = 1, binding = 0) uniform sampler2D iChannel0;
layout(set = 1, binding = 1) uniform sampler2D iChannel1;
layout(set = 3, binding = 0) uniform UniformBlock
{
float time;
vec2 resolution;
} Uniforms;
//layout(location = 0) in vec2 fragCoord;
layout(location = 0) out vec4 fragColor;
// make this bigger if you have a storng PC
#define AA 2
// -----------------------------------------
// mod3 - not as trivial as you first though
// -----------------------------------------
int mod3( int n )
{
return (n<0) ? 2-((2-n)%3) : n%3;
// Some methods of computing mod3: // PC-WebGL Native-OpenGL Android WebGL
// // -------- ------------- -------
// 1. return (n<0) ? 2-((2-n)%3) : n%3; // Ok Ok Ok
// 2. return int((uint(n)+0x80000001U)%3u); // Ok Ok Broken
// 3. n %= 3; return (n<0)?n+3:n; // Ok Broken Ok
// 4. n %= 3; n+=((n>>31)&3); return n; // Ok Broken Ok
// 5. return ((n%3)+3)%3; // Ok Broken Ok
// 6. return int[](1,2,0,1,2)[n%3+2]; // Ok Broken Ok
}
// --------------------------------------
// hash by Hugo Elias)
// --------------------------------------
int hash( int n ) { n=(n<<13)^n; return n*(n*n*15731+789221)+1376312589; }
// --------------------------------------
// basic hexagon grid functions
// --------------------------------------
ivec2 hexagonID( vec2 p )
{
const float k3 = 1.732050807;
vec2 q = vec2( p.x, p.y*k3*0.5 + p.x*0.5 );
ivec2 pi = ivec2(floor(q));
vec2 pf = fract(q);
int v = mod3(pi.x+pi.y);
int ca = (v<1)?0:1;
int cb = (v<2)?0:1;
ivec2 ma = (pf.x>pf.y)?ivec2(0,1):ivec2(1,0);
ivec2 id = pi + ca - cb*ma;
return ivec2( id.x, id.y - (id.x+id.y)/3 );
}
vec2 hexagonCenFromID( in ivec2 id )
{
const float k3 = 1.732050807;
return vec2(float(id.x),float(id.y)*k3);
}
// ---------------------------------------------------------------------
// the height function. yes, i know reading from a video texture is cool
// ---------------------------------------------------------------------
const float kMaxH = 6.0;
float map( vec2 p, in float time )
{
p *= 0.5;
float f = 0.5+0.5*sin(0.53*p.x+0.5*time+1.0*sin(p.y*0.24))*
sin(0.13*p.y+time);
f*= 0.75+0.25*sin(1.7*p.x+1.32*time)*sin(1.3*p.y+time*2.1);
return kMaxH*(0.005+0.995*f);
}
// --------------------------------------------------
// raycast. this function is the point of this shader
// --------------------------------------------------
vec4 castRay( in vec3 ro, in vec3 rd, in float time,
out ivec2 outPrismID, out int outFaceID )
{
ivec2 hid = hexagonID(ro.xz);
vec4 res = vec4( -1.0, 0.0, 0.0, 0.0 );
const float k3 = 0.866025;
const vec2 n1 = vec2( 1.0,0.0);
const vec2 n2 = vec2( 0.5,k3);
const vec2 n3 = vec2(-0.5,k3);
float d1 = 1.0/dot(rd.xz,n1);
float d2 = 1.0/dot(rd.xz,n2);
float d3 = 1.0/dot(rd.xz,n3);
float d4 = 1.0/rd.y;
float s1 = (d1<0.0)?-1.0:1.0;
float s2 = (d2<0.0)?-1.0:1.0;
float s3 = (d3<0.0)?-1.0:1.0;
float s4 = (d4<0.0)?-1.0:1.0;
ivec2 i1 = ivec2( 2,0); if(d1<0.0) i1=-i1;
ivec2 i2 = ivec2( 1,1); if(d2<0.0) i2=-i2;
ivec2 i3 = ivec2(-1,1); if(d3<0.0) i3=-i3;
// traverse hexagon grid (in 2D)
bool found = false;
vec2 t1, t2, t3, t4;
for( int i=0; i<100; i++ )
{
// fetch height for this hexagon
vec2 ce = hexagonCenFromID( hid );
float he = 0.5*map(ce, time);
// compute ray-hexaprism intersection
vec3 oc = ro - vec3(ce.x,he,ce.y);
t1 = (vec2(-s1,s1)-dot(oc.xz,n1))*d1;
t2 = (vec2(-s2,s2)-dot(oc.xz,n2))*d2;
t3 = (vec2(-s3,s3)-dot(oc.xz,n3))*d3;
t4 = (vec2(-s4,s4)*he-oc.y)*d4;
float tN = max(max(t1.x,t2.x),max(t3.x,t4.x));
float tF = min(min(t1.y,t2.y),min(t3.y,t4.y));
if( tN<tF && tF>0.0 )
{
found = true;
break;
}
// move to next hexagon
if( t1.y<t2.y && t1.y<t3.y ) hid += i1;
else if( t2.y<t3.y ) hid += i2;
else hid += i3;
}
if( found )
{
{res=vec4(t1.x,s1*vec3(n1.x,0,n1.y)); outFaceID=(d1<0.0)?-1: 1;}
if( t2.x>res.x ) {res=vec4(t2.x,s2*vec3(n2.x,0,n2.y)); outFaceID=(d2<0.0)?-2: 2;}
if( t3.x>res.x ) {res=vec4(t3.x,s3*vec3(n3.x,0,n3.y)); outFaceID=(d3<0.0)?-3: 3;}
if( t4.x>res.x ) {res=vec4(t4.x,s4*vec3( 0.0,1,0)); outFaceID=(d4<0.0)? 4:-4;}
outPrismID = hid;
}
return res;
}
// -------------------------------------------------------------------------
// same as above, but simpler sinec we don't need the normal and primtive id
// --------------------------------------------------------------------------
float castShadowRay( in vec3 ro, in vec3 rd, in float time )
{
float res = 1.0;
ivec2 hid = hexagonID(ro.xz);
const float k3 = 0.866025;
const vec2 n1 = vec2( 1.0,0.0);
const vec2 n2 = vec2( 0.5,k3);
const vec2 n3 = vec2(-0.5,k3);
float d1 = 1.0/dot(rd.xz,n1);
float d2 = 1.0/dot(rd.xz,n2);
float d3 = 1.0/dot(rd.xz,n3);
float d4 = 1.0/rd.y;
float s1 = (d1<0.0)?-1.0:1.0;
float s2 = (d2<0.0)?-1.0:1.0;
float s3 = (d3<0.0)?-1.0:1.0;
float s4 = (d4<0.0)?-1.0:1.0;
ivec2 i1 = ivec2( 2,0); if(d1<0.0) i1=-i1;
ivec2 i2 = ivec2( 1,1); if(d2<0.0) i2=-i2;
ivec2 i3 = ivec2(-1,1); if(d3<0.0) i3=-i3;
vec2 c1 = (vec2(-s1,s1)-dot(ro.xz,n1))*d1;
vec2 c2 = (vec2(-s2,s2)-dot(ro.xz,n2))*d2;
vec2 c3 = (vec2(-s3,s3)-dot(ro.xz,n3))*d3;
// traverse regular grid (2D)
for( int i=0; i<8; i++ )
{
vec2 ce = hexagonCenFromID( hid );
float he = 0.5*map(ce, time);
vec2 t1 = c1 + dot(ce,n1)*d1;
vec2 t2 = c2 + dot(ce,n2)*d2;
vec2 t3 = c3 + dot(ce,n3)*d3;
vec2 t4 = (vec2(1.0-s4,1.0+s4)*he-ro.y)*d4;
float tN = max(max(t1.x,t2.x),max(t3.x,t4.x));
float tF = min(min(t1.y,t2.y),min(t3.y,t4.y));
if( tN < tF && tF > 0.0)
{
res = 0.0;
break;
}
if( t1.y<t2.y && t1.y<t3.y ) hid += i1;
else if( t2.y<t3.y ) hid += i2;
else hid += i3;
}
return res;
}
// -------------------------------------------------------------------------
// analytic occlusion of a quad and an hexagon
// -------------------------------------------------------------------------
float macos(float x ) { return acos(clamp(x,-1.0,1.0));}
float occlusionQuad( in vec3 pos, in vec3 nor,
in vec3 v0, in vec3 v1,
in vec3 v2, in vec3 v3 )
{
v0 = normalize(v0-pos);
v1 = normalize(v1-pos);
v2 = normalize(v2-pos);
v3 = normalize(v3-pos);
float k01 = dot( nor, normalize( cross(v0,v1)) ) * macos( dot(v0,v1) );
float k12 = dot( nor, normalize( cross(v1,v2)) ) * macos( dot(v1,v2) );
float k23 = dot( nor, normalize( cross(v2,v3)) ) * macos( dot(v2,v3) );
float k30 = dot( nor, normalize( cross(v3,v0)) ) * macos( dot(v3,v0) );
return abs(k01+k12+k23+k30)/6.283185;
}
float occlusionHexagon( in vec3 pos, in vec3 nor,
in vec3 v0, in vec3 v1,
in vec3 v2, in vec3 v3,
in vec3 v4, in vec3 v5)
{
v0 = normalize(v0-pos);
v1 = normalize(v1-pos);
v2 = normalize(v2-pos);
v3 = normalize(v3-pos);
v4 = normalize(v4-pos);
v5 = normalize(v5-pos);
float k01 = dot( nor, normalize( cross(v0,v1)) ) * macos( dot(v0,v1) );
float k12 = dot( nor, normalize( cross(v1,v2)) ) * macos( dot(v1,v2) );
float k23 = dot( nor, normalize( cross(v2,v3)) ) * macos( dot(v2,v3) );
float k34 = dot( nor, normalize( cross(v3,v4)) ) * macos( dot(v3,v4) );
float k45 = dot( nor, normalize( cross(v4,v5)) ) * macos( dot(v4,v5) );
float k50 = dot( nor, normalize( cross(v5,v0)) ) * macos( dot(v5,v0) );
return abs(k01+k12+k23+k34+k45+k50)/6.283185;
}
// -------------------------------------------------------------------------
// get the walls and top face vertex positions
// -------------------------------------------------------------------------
bool getPrismWall( ivec2 prismID, int sid, in float time,
out vec3 v0, out vec3 v1, out vec3 v2, out vec3 v3 )
{
const ivec2 i1 = ivec2( 2,0);
const ivec2 i2 = ivec2( 1,1);
const ivec2 i3 = ivec2(-1,1);
vec2 ce = hexagonCenFromID( prismID );
vec3 ce3 = vec3(ce.x,0.0,ce.y);
float he = map( ce, time);
const float kRa = 2.0/sqrt(3.0);
const float kC1 = kRa*0.5;
const float kC2 = kRa*1.0;
if( sid==0 )
{
float he1p = map(hexagonCenFromID( prismID+i1 ), time);
if( he1p<he ) return false;
v0 = vec3(1.0,he, kC1);
v1 = vec3(1.0,he1p, kC1);
v2 = vec3(1.0,he1p,-kC1);
v3 = vec3(1.0,he, -kC1);
}
else if( sid==1 )
{
float he3m = map(hexagonCenFromID( prismID-i3 ), time);
if( he3m<he ) return false;
v0 = vec3( 1.0,he, -kC1);
v1 = vec3( 1.0,he3m,-kC1);
v2 = vec3( 0.0,he3m,-kC2);
v3 = vec3( 0.0,he, -kC2);
}
else if( sid==2 )
{
float he2m = map(hexagonCenFromID( prismID-i2 ), time);
if( he2m<he ) return false;
v0 = vec3( 0.0,he, -kC2);
v1 = vec3( 0.0,he2m,-kC2);
v2 = vec3(-1.0,he2m,-kC1);
v3 = vec3(-1.0,he, -kC1);
}
else if( sid==3 )
{
float he1m = map(hexagonCenFromID( prismID-i1 ), time);
if( he1m<he ) return false;
v0 = vec3(-1.0,he, -kC1);
v1 = vec3(-1.0,he1m,-kC1);
v2 = vec3(-1.0,he1m, kC1);
v3 = vec3(-1.0,he, kC1);
}
else if( sid==4 )
{
float he3p = map(hexagonCenFromID( prismID+i3 ), time);
if( he3p<he ) return false;
v0 = vec3(-1.0,he, kC1);
v1 = vec3(-1.0,he3p, kC1);
v2 = vec3( 0.0,he3p, kC2);
v3 = vec3( 0.0,he, kC2);
}
else //if( sid==5 )
{
float he2p = map(hexagonCenFromID( prismID+i2 ), time);
if( he2p<he ) return false;
v0 = vec3( 0.0,he, kC2);
v1 = vec3( 0.0,he2p, kC2);
v2 = vec3( 1.0,he2p, kC1);
v3 = vec3( 1.0,he, kC1);
}
v0 += ce3;
v1 += ce3;
v2 += ce3;
v3 += ce3;
return true;
}
void getPrismTop( ivec2 prismID, in float time,
out vec3 v0, out vec3 v1, out vec3 v2,
out vec3 v3, out vec3 v4, out vec3 v5 )
{
vec2 ce = hexagonCenFromID( prismID );
vec3 ce3 = vec3(ce.x,0.0,ce.y);
float he = map( ce, time);
const float kRa = 2.0/sqrt(3.0);
const float kC1 = kRa*0.5;
const float kC2 = kRa*1.0;
v0 = ce3+vec3( 0.0,he, -kC2);
v1 = ce3+vec3( -1.0,he, -kC1);
v2 = ce3+vec3( -1.0,he, kC1);
v3 = ce3+vec3( 0.0,he, kC2);
v4 = ce3+vec3( 1.0,he, kC1);
v5 = ce3+vec3( 1.0,he, -kC1);
}
// -------------------------------------------------------------------------
// compute analytical ambient occlusion, by using the solid angle of the
// faces surrounding the current point. if one face is missing (it's below
// the current prism's height) we ignore the portal and assume light comes
// through it. Ideally portals should be recursivelly traversed and clipped
// -------------------------------------------------------------------------
float calcOcclusion( in vec3 pos, in vec3 nor, in float time,
in ivec2 prismID, in int faceID )
{
const ivec2 i1 = ivec2( 2,0);
const ivec2 i2 = ivec2( 1,1);
const ivec2 i3 = ivec2(-1,1);
vec3 v0, v1, v2, v3, v4, v5;
if( faceID==-1 ) prismID += i1;
else if( faceID== 1 ) prismID -= i1;
else if( faceID==-2 ) prismID += i2;
else if( faceID== 2 ) prismID -= i2;
else if( faceID==-3 ) prismID += i3;
else if( faceID== 3 ) prismID -= i3;
float occ = 0.0;
if( faceID!=1 && getPrismWall( prismID, 0, time, v0, v1, v2, v3 ) )
occ += occlusionQuad(pos,nor,v0,v1,v2,v3);
if( faceID!=-3 && getPrismWall( prismID, 1, time, v0, v1, v2, v3 ) )
occ += occlusionQuad(pos,nor,v0,v1,v2,v3);
if( faceID!=-2 && getPrismWall( prismID, 2, time, v0, v1, v2, v3 ) )
occ += occlusionQuad(pos,nor,v0,v1,v2,v3);
if( faceID!=-1 && getPrismWall( prismID, 3, time, v0, v1, v2, v3 ) )
occ += occlusionQuad(pos,nor,v0,v1,v2,v3);
if( faceID!=3 && getPrismWall( prismID, 4, time, v0, v1, v2, v3 ) )
occ += occlusionQuad(pos,nor,v0,v1,v2,v3);
if( faceID!=2 && getPrismWall( prismID, 5, time, v0, v1, v2, v3 ) )
occ += occlusionQuad(pos,nor,v0,v1,v2,v3);
if( faceID!=4 )
{
getPrismTop( prismID, time, v0, v1, v2, v3, v4, v5 );
occ += occlusionHexagon(pos,nor,v0,v1,v2,v3,v4,v5);
occ = 1.0-min(0.5,0.2+0.8*(1.0-occ)*pos.y/kMaxH);
}
return 1.0-occ;
}
// -------------------------------------------------------------------------
// render = raycast + shade + light
// -------------------------------------------------------------------------
vec3 render( in vec3 ro, in vec3 rd, in float time )
{
// raycast
vec3 col = vec3(1.0);
ivec2 prismID; int faceID;
vec4 tnor = castRay( ro, rd, time, prismID, faceID );
float t = tnor.x;
// if intersection found
if( t>0.0 )
{
// data at intersection point
vec3 pos = ro + rd*t;
vec3 nor = -tnor.yzw;
vec2 ce = hexagonCenFromID(prismID);
float he = map(ce,time);
int id = prismID.x*131 + prismID.y*57;
// uvs
vec2 uv = (faceID==4) ? (pos.xz-ce)*0.15 :
vec2(atan(pos.x-ce.x,pos.z-ce.y)/3.14156,
(pos.y-he)/4.0 );
uv += ce;
// material color
vec3 mate = vec3(1.0);
id = hash(id); mate *= 0.1+0.9*float((id>>13)&3)/3.0;
id = hash(id); mate = ( ((id>>8)&15)==0 ) ? vec3(0.7,0.0,0.0) : mate;
vec3 tex = vec3(0.15,0.09,0.07)+0.75*pow(texture(iChannel0,uv.yx).xyz,vec3(1.0,0.95,0.9));
mate *= tex;
// lighting
float occ = calcOcclusion( pos, nor, time, prismID, faceID );
// diffuse
col = mate*pow(vec3(occ),vec3(0.95,1.05,1.1));
// specular
float ks = tex.x*2.0;
vec3 ref = reflect(rd,nor);
col *= 0.85;
float fre = clamp(1.0+dot(nor,rd),0.0,1.0);
col += vec3(1.1)*ks*
smoothstep(0.0,0.15,ref.y)*
(0.04 + 0.96*pow(fre,5.0))*
castShadowRay( pos+nor*0.001, ref, time );
// fog
col = mix(col,vec3(1.0), 1.0-exp2(-0.00005*t*t) );
}
return col;
}
//-----------------------------------------------
// main = animate + render + color grade
//-----------------------------------------------
void main()
{
vec2 fragCoord = gl_FragCoord.xy;
fragCoord.y = Uniforms.resolution.y - gl_FragCoord.y;
// init random seed
ivec2 q = ivec2(fragCoord);
// sample pixel and time
vec3 tot = vec3(0.0);
for( int m=0; m<AA; m++ )
for( int n=0; n<AA; n++ )
{
vec2 of = vec2(m,n)/float(AA) - 0.5;
vec2 p = (2.0*(fragCoord+of)-Uniforms.resolution.xy)/min(Uniforms.resolution.x,Uniforms.resolution.y);
#if AA>1
float d = 0.5+0.5*sin(fragCoord.x*147.0)*sin(fragCoord.y*131.0);
float time = Uniforms.time - 0.5*(1.0/24.0)*(float(m*AA+n)+d)/float(AA*AA);
#else
float time = Uniforms.time;
#endif
// camera
float cr = -0.1;
float an = 3.0*time;
vec3 ro = vec3(0.1,13.0,1.0-an);
vec3 ta = vec3(0.0,12.0,0.0-an);
// build camera matrix
vec3 ww = normalize( ta - ro);
vec3 uu = normalize(cross( ww,vec3(sin(cr),cos(cr),0.0) ));
vec3 vv = normalize(cross(uu,ww));
// distort
p *= 0.9+0.1*(p.x*p.x*0.4 + p.y*p.y);
// buid ray
vec3 rd = normalize( p.x*uu + p.y*vv + 2.0*ww );
// dof
#if AA>1
vec3 fp = ro + rd*17.0;
vec2 ra = texelFetch(iChannel1,(q+ivec2(13*m,31*n))&1023,0).xy;
ro.xy += 0.3*sqrt(ra.x)*vec2(cos(6.2831*ra.y),sin(6.2831*ra.y));
rd = normalize( fp - ro );
#endif
// render
vec3 col = render( ro, rd, time );
// accumulate for AA
tot += col;
}
tot /= float(AA*AA);
// hdr->ldr tonemap
tot = tot*1.6/(1.0+tot);
tot = tot*tot*(3.0-2.0*tot);
// gamma
tot = pow( clamp(tot,0.0,1.0), vec3(0.45) );
// color grade
vec2 p = fragCoord/Uniforms.resolution.xy;
tot.xyz += (p.xyy-0.5)*0.1;
// vignetting
tot *= 0.5 + 0.5*pow( 16.0*p.x*p.y*(1.0-p.x)*(1.0-p.y), 0.1 );
// output
fragColor = vec4( tot, 1.0 );
}