diff --git a/resources/textures/photo_2024-12-02_16-41-15.jpg b/resources/textures/photo_2024-12-02_16-41-15.jpg new file mode 100644 index 00000000..47a80e1b Binary files /dev/null and b/resources/textures/photo_2024-12-02_16-41-15.jpg differ diff --git a/tasks/local_shadertoy2/App.cpp b/tasks/local_shadertoy2/App.cpp new file mode 100644 index 00000000..9693ce85 --- /dev/null +++ b/tasks/local_shadertoy2/App.cpp @@ -0,0 +1,256 @@ +#include "App.hpp" + +#include +#include +#include +#include +#define STB_IMAGE_IMPLEMENTATION +#include +#include +#include +#include + + +App::App() + : resolution{1280, 720} + , skinTextureResolution{128, 128} + , useVsync{true} +{ + { + auto glfwInstExts = windowing.getRequiredVulkanInstanceExtensions(); + + std::vector instanceExtensions{glfwInstExts.begin(), glfwInstExts.end()}; + + std::vector deviceExtensions{VK_KHR_SWAPCHAIN_EXTENSION_NAME}; + + etna::initialize(etna::InitParams{ + .applicationName = "Local Shadertoy", + .applicationVersion = VK_MAKE_VERSION(0, 1, 0), + .instanceExtensions = instanceExtensions, + .deviceExtensions = deviceExtensions, + .physicalDeviceIndexOverride = {}, + .numFramesInFlight = 1, + }); + } + + osWindow = windowing.createWindow(OsWindow::CreateInfo{ + .resolution = resolution, + }); + + { + auto surface = osWindow->createVkSurface(etna::get_context().getInstance()); + + vkWindow = etna::get_context().createWindow(etna::Window::CreateInfo{ + .surface = std::move(surface), + }); + + auto [w, h] = vkWindow->recreateSwapchain(etna::Window::DesiredProperties{ + .resolution = {resolution.x, resolution.y}, + .vsync = useVsync, + }); + + resolution = {w, h}; + } + + commandManager = etna::get_context().createPerFrameCmdMgr(); + oneShotManager = etna::get_context().createOneShotCmdMgr(); + + { + etna::create_program("shadertoy2", {LOCAL_SHADERTOY2_SHADERS_ROOT "toy.vert.spv", + LOCAL_SHADERTOY2_SHADERS_ROOT "toy.frag.spv" }); + etna::create_program("skinTexture", {LOCAL_SHADERTOY2_SHADERS_ROOT "toy.vert.spv", + LOCAL_SHADERTOY2_SHADERS_ROOT "texture.frag.spv" }); + + + fragVertPipeline = etna::get_context().getPipelineManager().createGraphicsPipeline("shadertoy2", + etna::GraphicsPipeline::CreateInfo { + .fragmentShaderOutput = { + .colorAttachmentFormats = {vk::Format::eB8G8R8A8Srgb} + } + }); + + skinTexturePipeline = etna::get_context().getPipelineManager().createGraphicsPipeline("skinTexture", + etna::GraphicsPipeline::CreateInfo { + .fragmentShaderOutput = { + .colorAttachmentFormats = {vk::Format::eB8G8R8A8Srgb} + } + }); + + skinTextureImage = etna::get_context().createImage(etna::Image::CreateInfo{ + .extent = vk::Extent3D{skinTextureResolution.x, skinTextureResolution.y, 1}, + .name = "skinTexture", + .format = vk::Format::eB8G8R8A8Srgb, + .imageUsage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled, + }); + + defaultSampler = etna::Sampler(etna::Sampler::CreateInfo + { + .filter = vk::Filter::eLinear, + .addressMode = vk::SamplerAddressMode::eRepeat, + .name = "default_sampler" + }); + } +} + +App::~App() +{ + ETNA_CHECK_VK_RESULT(etna::get_context().getDevice().waitIdle()); +} + +void App::run() +{ + while (!osWindow->isBeingClosed()) + { + windowing.poll(); + + drawFrame(); + } + + ETNA_CHECK_VK_RESULT(etna::get_context().getDevice().waitIdle()); +} + +void App::drawFrame() +{ + auto currentCmdBuf = commandManager->acquireNext(); + + if (!initializedFileTexture) { + int x, y, n; + unsigned char *picData = stbi_load(TEXTURES_ROOT "photo_2024-12-02_16-41-15.jpg", &x, &y, &n, 4); + if (picData == NULL) { + std::cerr << "photo_2024-12-02_16-41-15.jpg not found" << std::endl; + std::terminate(); + } + + etna::Image::CreateInfo fileTextureInfo{ + .extent = vk::Extent3D{static_cast(x), static_cast(y), 1}, + .name = "fileTexture", + .format = vk::Format::eR8G8B8A8Srgb, + .imageUsage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled, + }; + fileTextureImage = etna::create_image_from_bytes(fileTextureInfo, currentCmdBuf, picData); + + stbi_image_free(picData); + initializedFileTexture = true; + } + + etna::begin_frame(); + + auto nextSwapchainImage = vkWindow->acquireNext(); + + if (nextSwapchainImage) + { + auto [backbuffer, backbufferView, backbufferAvailableSem] = *nextSwapchainImage; + + ETNA_CHECK_VK_RESULT(currentCmdBuf.begin(vk::CommandBufferBeginInfo{})); + { + etna::set_state( + currentCmdBuf, + skinTextureImage.get(), + vk::PipelineStageFlagBits2::eColorAttachmentOutput, + vk::AccessFlagBits2::eColorAttachmentWrite, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageAspectFlagBits::eColor); + + etna::flush_barriers(currentCmdBuf); + + { + etna::RenderTargetState state{currentCmdBuf, {{}, {skinTextureResolution.x, skinTextureResolution.y}}, + {{skinTextureImage.get(), skinTextureImage.getView({})}}, {}}; + auto skinInfo = etna::get_shader_program("skinTexture"); + + currentCmdBuf.bindPipeline(vk::PipelineBindPoint::eGraphics, skinTexturePipeline.getVkPipeline()); + + glm::uvec2 res = skinTextureResolution; + currentCmdBuf.pushConstants( + skinTexturePipeline.getVkPipelineLayout(), vk::ShaderStageFlagBits::eFragment, + 0, sizeof(res), &res); + + currentCmdBuf.draw(3, 1, 0, 0); + } + + + etna::set_state( + currentCmdBuf, + skinTextureImage.get(), + vk::PipelineStageFlagBits2::eFragmentShader, + vk::AccessFlagBits2::eColorAttachmentRead, + vk::ImageLayout::eShaderReadOnlyOptimal, + vk::ImageAspectFlagBits::eColor); + + etna::set_state( + currentCmdBuf, + fileTextureImage.get(), + vk::PipelineStageFlagBits2::eFragmentShader, + vk::AccessFlagBits2::eColorAttachmentRead, + vk::ImageLayout::eShaderReadOnlyOptimal, + vk::ImageAspectFlagBits::eColor); + + etna::set_state( + currentCmdBuf, + backbuffer, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, + vk::AccessFlagBits2::eColorAttachmentWrite, + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageAspectFlagBits::eColor); + + etna::flush_barriers(currentCmdBuf); + + { + etna::RenderTargetState state{currentCmdBuf, {{}, {resolution.x, resolution.y}}, + {{backbuffer, backbufferView}}, {}}; + + auto fragVertInfo = etna::get_shader_program("shadertoy2"); + auto set = etna::create_descriptor_set( + fragVertInfo.getDescriptorLayoutId(0), + currentCmdBuf, + { + etna::Binding{0, skinTextureImage.genBinding(defaultSampler.get(), vk::ImageLayout::eShaderReadOnlyOptimal)}, + etna::Binding{1, fileTextureImage.genBinding(defaultSampler.get(), vk::ImageLayout::eShaderReadOnlyOptimal)} + }); + + vk::DescriptorSet vkSet = set.getVkSet(); + + currentCmdBuf.bindPipeline(vk::PipelineBindPoint::eGraphics, fragVertPipeline.getVkPipeline()); + currentCmdBuf.bindDescriptorSets( + vk::PipelineBindPoint::eGraphics, fragVertPipeline.getVkPipelineLayout(), 0, 1, &vkSet, 0, nullptr); + + glm::uvec2 res = resolution; + + currentCmdBuf.pushConstants( + fragVertPipeline.getVkPipelineLayout(), vk::ShaderStageFlagBits::eFragment, + 0, sizeof(res), &res); + + currentCmdBuf.draw(3, 1, 0, 0); + } + + etna::set_state( + currentCmdBuf, + backbuffer, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, + {}, + vk::ImageLayout::ePresentSrcKHR, + vk::ImageAspectFlagBits::eColor); + etna::flush_barriers(currentCmdBuf); + } + ETNA_CHECK_VK_RESULT(currentCmdBuf.end()); + + auto renderingDone = + commandManager->submit(std::move(currentCmdBuf), std::move(backbufferAvailableSem)); + + const bool presented = vkWindow->present(std::move(renderingDone), backbufferView); + + if (!presented) + nextSwapchainImage = std::nullopt; + } + + etna::end_frame(); + + if (!nextSwapchainImage && osWindow->getResolution() != glm::uvec2{0, 0}) + { + auto [w, h] = vkWindow->recreateSwapchain(etna::Window::DesiredProperties{ + .resolution = {resolution.x, resolution.y}, + .vsync = useVsync, + }); + ETNA_VERIFY((resolution == glm::uvec2{w, h})); + } +} \ No newline at end of file diff --git a/tasks/local_shadertoy2/App.hpp b/tasks/local_shadertoy2/App.hpp new file mode 100644 index 00000000..63949f77 --- /dev/null +++ b/tasks/local_shadertoy2/App.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "wsi/OsWindowingManager.hpp" + + +class App +{ +public: + App(); + ~App(); + + void run(); + +private: + void drawFrame(); + +private: + OsWindowingManager windowing; + std::unique_ptr osWindow; + + glm::uvec2 resolution; + glm::uvec2 skinTextureResolution; + bool useVsync; + + etna::GraphicsPipeline skinTexturePipeline; + etna::GraphicsPipeline fragVertPipeline; + etna::Image skinTextureImage; + etna::Image fileTextureImage; + etna::Sampler defaultSampler; + + std::unique_ptr vkWindow; + std::unique_ptr commandManager; + std::unique_ptr oneShotManager; + + bool initializedFileTexture = false; +}; \ No newline at end of file diff --git a/tasks/local_shadertoy2/CMakeLists.txt b/tasks/local_shadertoy2/CMakeLists.txt index e69de29b..e8c5be36 100644 --- a/tasks/local_shadertoy2/CMakeLists.txt +++ b/tasks/local_shadertoy2/CMakeLists.txt @@ -0,0 +1,17 @@ +add_executable(local_shadertoy2 + main.cpp + App.cpp +) + +target_link_libraries(local_shadertoy2 + PRIVATE glfw etna glm::glm wsi gui) + +target_add_shaders(local_shadertoy2 + shaders/toy.vert + shaders/toy.frag + shaders/texture.frag +) + +target_link_libraries(local_shadertoy2 PUBLIC render_utils scene) + +add_compile_definitions(local_shadertoy2 PRIVATE TEXTURES_ROOT="${PROJECT_SOURCE_DIR}/resources/textures/") \ No newline at end of file diff --git a/tasks/local_shadertoy2/main.cpp b/tasks/local_shadertoy2/main.cpp new file mode 100644 index 00000000..b939bed3 --- /dev/null +++ b/tasks/local_shadertoy2/main.cpp @@ -0,0 +1,17 @@ +#include "App.hpp" + +#include + + +int main() +{ + { + App app; + app.run(); + } + + if (etna::is_initilized()) + etna::shutdown(); + + return 0; +} \ No newline at end of file diff --git a/tasks/local_shadertoy2/shaders/texture.frag b/tasks/local_shadertoy2/shaders/texture.frag new file mode 100644 index 00000000..7819e64b --- /dev/null +++ b/tasks/local_shadertoy2/shaders/texture.frag @@ -0,0 +1,35 @@ +#version 430 +#extension GL_GOOGLE_include_directive : require + + +layout(location = 0) out vec4 out_fragColor; + +layout(push_constant) uniform params_t +{ + uvec2 resolution; +} params; + +const float iTime = 1.0; + + +// https://www.shadertoy.com/view/MsXSzM + +#define TIMESCALE 0.25 +#define TILES 8 +#define COLOR 0.7, 1.6, 2.8 + +void main() +{ + vec2 uv = gl_FragCoord.xy / vec2(128, 128); + uv.x *= 128 / 128; + + vec4 noise = vec4(72897, 1273498, 7129347, 61251); + float p = 1.0 - mod(noise.r + noise.g + noise.b + iTime * float(TIMESCALE), 1.0); + p = min(max(p * 3.0 - 1.8, 0.1), 2.0); + + vec2 r = mod(uv * float(TILES), 1.0); + r = vec2(pow(r.x - 0.5, 2.0), pow(r.y - 0.5, 2.0)); + p *= 1.0 - pow(min(1.0, 12.0 * dot(r, r)), 2.0); + + out_fragColor = vec4(COLOR, 1.0) * p; +} diff --git a/tasks/local_shadertoy2/shaders/toy.frag b/tasks/local_shadertoy2/shaders/toy.frag new file mode 100644 index 00000000..0a41cbac --- /dev/null +++ b/tasks/local_shadertoy2/shaders/toy.frag @@ -0,0 +1,299 @@ +#version 430 +#extension GL_GOOGLE_include_directive : require + + +layout(binding = 0) uniform sampler2D iChannel0; +layout(binding = 1) uniform sampler2D iChannel1; + +layout(location = 0) out vec4 out_fragColor; + +layout(push_constant) uniform params_t +{ + uvec2 resolution; +} params; + + +const float iTime = 10.0f; + +const float PI = 3.14159265359; + +const vec2 iResolution = vec2(1280, 720); +const ivec3 iMouse = ivec3(0, 1, 0); + +const vec3 eye = vec3 ( 4, 0, 2 ); +const vec3 light = vec3 ( 15.0, -1.0, 0.0 ); +const int maxSteps = 70; +const float eps = 0.01; + +// Rotation matrix around the X axis. +mat3 rotateX(float theta) { + float c = cos(theta); + float s = sin(theta); + return mat3( + vec3(1, 0, 0), + vec3(0, c, -s), + vec3(0, s, c) + ); +} + +// Rotation matrix around the Y axis. +mat3 rotateY(float theta) { + float c = cos(theta); + float s = sin(theta); + return mat3( + vec3(c, 0, s), + vec3(0, 1, 0), + vec3(-s, 0, c) + ); +} + +float dSphere ( vec3 p, in vec3 c ) +{ + return length ( p - c ) - 1.0 + 0.03 * sin(20.0*p.x + iTime); +} + +float length8 ( in vec2 p ) +{ + return pow ( pow ( p.x, 8.0 ) + pow ( p.y, 8.0 ), 1.0/ 8.0 ); +} + +float length8 ( in vec3 p ) +{ + return pow ( pow ( p.x, 8.0 ) + pow ( p.y, 8.0 ) + pow ( p.z, 8.0 ), 1.0/ 8.0 ); +} + + +float dTorus ( vec3 p, vec2 t ) +{ + vec2 q = vec2 ( length8 ( p.xz ) - t.x, p.y ); + + return length8 ( q ) - t.y; +} + +float smin ( float a, float b, float k ) +{ + float res = exp ( -k*a ) + exp ( -k*b ); + return -log ( res ) / k; +} + +float sdf ( in vec3 p ) +{ + //return dSphere ( p, vec3 ( 0, 0, 0 ) ); + //return dBox ( p, vec3 ( 0.5, 0.2, 0.7 ) ); + return dTorus ( p, vec2 ( 0.73, 0.5 ) ); +} + +float sdPyramid( vec3 p, float h ) +{ + float m2 = h*h + 0.25; + + p.xz = abs(p.xz); + p.xz = (p.z>p.x) ? p.zx : p.xz; + p.xz -= 0.5; + + vec3 q = vec3( p.z, h*p.y - 0.5*p.x, h*p.x + 0.5*p.y); + + float s = max(-q.x,0.0); + float t = clamp( (q.y-0.5*p.z)/(m2+0.25), 0.0, 1.0 ); + + float a = m2*(q.x+s)*(q.x+s) + q.y*q.y; + float b = m2*(q.x+0.5*t)*(q.x+0.5*t) + (q.y-m2*t)*(q.y-m2*t); + + float d2 = min(q.y,-q.x*m2-q.y*0.5) > 0.0 ? 0.0 : min(a,b); + + return sqrt( (d2+q.z*q.z)/m2 ) * sign(max(q.z,-p.y)); +} + +float sdf ( in vec3 p, in mat3 m ) +{ + vec3 q = m * p; + + //return dSphere ( p, vec3 ( 2, 0, 0 ) ); + //return dBox ( q, vec3 ( 0.5, 0.2, 0.7 ) ); + return sdPyramid(q, 0.9); +} + +vec3 trace ( in vec3 from, in vec3 dir, out bool hit, in mat3 m ) +{ + vec3 p = from; + float totalDist = 0.0; + + hit = false; + + for ( int steps = 0; steps < maxSteps; steps++ ) + { + float dist = sdf ( p, m ); + + if ( dist < 0.01 ) + { + hit = true; + break; + } + + totalDist += dist; + + if ( totalDist > 20.0 ) + break; + + p += dist * dir; + } + + return p; +} + +vec3 generateNormal ( vec3 z, float d, in mat3 m ) +{ + float e = max (d * 0.5, eps ); + float dx1 = sdf(z + vec3(e, 0, 0), m); + float dx2 = sdf(z - vec3(e, 0, 0), m); + float dy1 = sdf(z + vec3(0, e, 0), m); + float dy2 = sdf(z - vec3(0, e, 0), m); + float dz1 = sdf(z + vec3(0, 0, e), m); + float dz2 = sdf(z - vec3(0, 0, e), m); + + return normalize ( vec3 ( dx1 - dx2, dy1 - dy2, dz1 - dz2 ) ); +} + +const float roughness = 0.2; +const vec3 r0 = vec3 ( 1.0, 0.92, 0.23 ); +const vec3 clr = vec3 ( 0.7, 0.7, 0.5 ); +const float gamma = 10.0; +const float pi = 3.1415926; +const float FDiel = 0.04; // Fresnel for dielectrics + +vec3 fresnel ( in vec3 f0, in float product ) +{ + product = clamp ( product, 0.0, 1.0 ); // saturate + + return mix ( f0, vec3 (1.0), pow(1.0 - product, 5.0) ); +} + +float D_blinn(in float roughness, in float NdH) +{ + float m = roughness * roughness; + float m2 = m * m; + float n = 2.0 / m2 - 2.0; + return (n + 2.0) / (2.0 * pi) * pow(NdH, n); +} + +float D_beckmann ( in float roughness, in float NdH ) +{ + float m = roughness * roughness; + float m2 = m * m; + float NdH2 = NdH * NdH; + + return exp( (NdH2 - 1.0) / (m2 * NdH2) ) / (pi * m2 * NdH2 * NdH2); +} + +float D_GGX ( in float roughness, in float NdH ) +{ + float m = roughness * roughness; + float m2 = m * m; + float NdH2 = NdH * NdH; + float d = (m2 - 1.0) * NdH2 + 1.0; + + return m2 / (pi * d * d); +} + +float G_schlick ( in float roughness, in float nv, in float nl ) +{ + float k = roughness * roughness * 0.5; + float V = nv * (1.0 - k) + k; + float L = nl * (1.0 - k) + k; + + return 0.25 / (V * L); +} + +float G_neumann ( in float nl, in float nv ) +{ + return nl * nv / max ( nl, nv ); +} + +float G_klemen ( in float nl, in float nv, in float vh ) +{ + return nl * nv / (vh * vh ); +} + +float G_default ( in float nl, in float nh, in float nv, in float vh ) +{ + return min ( 1.0, min ( 2.0*nh*nv/vh, 2.0*nh*nl/vh ) ); +} + +vec4 cookTorrance ( in vec3 p, in vec3 n, in vec3 l, in vec3 v, vec3 base) +{ + vec3 h = normalize ( l + v ); + float nh = dot (n, h); + float nv = dot (n, v); + float nl = dot (n, l); + float vh = dot (v, h); + float metallness = 1.0; +// vec3 base = pow ( clr, vec3 ( gamma ) ); + vec3 F0 = mix ( vec3(FDiel), clr, metallness ); + + // compute Beckman + float d = D_beckmann ( roughness, nh ); + + // compute Fresnel + vec3 f = fresnel ( F0, nv ); + + // default G + float g = G_default ( nl, nh, nv, vh ); + + // resulting color + vec3 ct = f*(0.25 * d * g / nv); + vec3 diff = max(nl, 0.0) * ( vec3 ( 1.0 ) - f ) / pi; + float ks = 0.5; + + return vec4 ( pow ( diff * base + ks * ct, vec3 ( 1.0 / gamma ) ), 1.0 ); +} + +vec4 boxmap( in sampler2D s, in vec3 p, in vec3 n, in float k ) +{ + // project+fetch + vec4 x = texture( s, p.yz ); + vec4 y = texture( s, p.zx ); + vec4 z = texture( s, p.xy ); + + // blend weights + vec3 w = pow( abs(n), vec3(k) ); + // blend and return + return (x*w.x + y*w.y + z*w.z) / (w.x + w.y + w.z); +} + +void main() +{ + + vec4 fragColor; + vec2 fragCoord = gl_FragCoord.xy; + + // Normalized pixel coordinates (from 0 to 1) + bool hit; + vec3 mouse = vec3(iMouse.xy/iResolution.xy - 0.5,iMouse.z-.5); + mat3 m = rotateX ( 6.0*mouse.y ) * rotateY ( 6.0*mouse.x); + vec2 scale = 9.0 * iResolution.xy / max ( iResolution.x, iResolution.y ) ; + vec2 uv = scale * ( fragCoord/iResolution.xy - vec2 ( 0.5 ) ); + vec3 dir = normalize ( vec3 ( uv, 0 ) - eye ); + vec4 color = vec4 ( 0, 0, 0, 1 ); + vec3 p = trace ( eye, dir, hit, m ); + + // vec3 base = pow ( clr, vec3 ( gamma ) ); + + + if ( hit ) + { + vec3 l = normalize ( light - p ); + vec3 v = normalize ( eye - p ); + vec3 n = generateNormal ( p, 0.001, m ); + float nl = max ( 0.0, dot ( n, l ) ); + vec3 h = normalize ( l + v ); + float hn = max ( 0.0, dot ( h, n ) ); + float sp = pow ( hn, 150.0 ); + + color = cookTorrance ( p, n, l, v, boxmap(iChannel0, m * p, generateNormal(p, 0.5, m), 0.5).xyz); + } else { + color = texture(iChannel1, uv); + } + + // Output to screen + out_fragColor = color; +} diff --git a/tasks/local_shadertoy2/shaders/toy.vert b/tasks/local_shadertoy2/shaders/toy.vert new file mode 100644 index 00000000..7d67f07c --- /dev/null +++ b/tasks/local_shadertoy2/shaders/toy.vert @@ -0,0 +1,14 @@ +#version 430 +#extension GL_GOOGLE_include_directive : require + + +void main(void) +{ + if (gl_VertexIndex == 0) { + gl_Position = vec4(-1.0f, -1.0f, 0.0f, 1.0f); + } else if (gl_VertexIndex == 1) { + gl_Position = vec4(3.0f, -1.0f, 0.0f, 1.0f); + } else { + gl_Position = vec4(-1.0f, 3.0f, 0.0f, 1.0f); + } +} \ No newline at end of file