diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..54dddc0 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,49 @@ +cmake_minimum_required(VERSION 3.30) + +project(FractalInspector + VERSION 1.0 + LANGUAGES CXX) + +if (PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR) + message(FATAL_ERROR "In-source builds are not allowed!") +endif() + +set(CMAKE_CXX_STANDARD 20) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +include(cmake/CPM.cmake) + +CPMAddPackage(NAME jlog + URL https://git.redacted.cc/josh/jlog/archive/Prerelease-18.zip) + +CPMAddPackage(NAME mcolor + URL https://git.redacted.cc/maxine/mcolor/archive/Prerelease-7.2.zip) + +CPMAddPackage(NAME Event + URL https://git.redacted.cc/josh/Event/archive/Release-12.zip) + +CPMAddPackage(NAME J3ML + URL https://git.redacted.cc/josh/j3ml/archive/3.4.5.zip) + +CPMAddPackage(NAME ReWindow + URL https://git.redacted.cc/Redacted/ReWindow/archive/Prerelease-32.zip) + +CPMAddPackage(NAME JGL + URL https://git.redacted.cc/josh/JGL/archive/Prerelease-55.zip) + +CPMAddPackage(NAME JUI + URL https://git.redacted.cc/josh/ReJUI/archive/Prerelease-5.17.zip) + +add_executable(FractalInspector main.cpp) + +target_include_directories(FractalInspector PUBLIC ${Event_SOURCE_DIR}/include) +target_include_directories(FractalInspector PUBLIC ${J3ML_SOURCE_DIR}/include) +target_include_directories(FractalInspector PUBLIC ${jlog_SOURCE_DIR}/include) +target_include_directories(FractalInspector PUBLIC ${JGL_SOURCE_DIR}/include) +target_include_directories(FractalInspector PUBLIC ${ReWindow_SOURCE_DIR}/include) +target_include_directories(FractalInspector PUBLIC ${JUI_SOURCE_DIR}/include) +target_include_directories(FractalInspector PUBLIC ${mcolor_SOURCE_DIR}/include) + +target_link_libraries(FractalInspector PUBLIC Event J3ML jlog ReWindow JGL JUI mcolor) + diff --git a/cmake/CPM.cmake b/cmake/CPM.cmake new file mode 100644 index 0000000..68c169b --- /dev/null +++ b/cmake/CPM.cmake @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: MIT +# +# SPDX-FileCopyrightText: Copyright (c) 2019-2023 Lars Melchior and contributors + +set(CPM_DOWNLOAD_VERSION 0.38.7) +set(CPM_HASH_SUM "83e5eb71b2bbb8b1f2ad38f1950287a057624e385c238f6087f94cdfc44af9c5") + +if(CPM_SOURCE_CACHE) + set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") +elseif(DEFINED ENV{CPM_SOURCE_CACHE}) + set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") +else() + set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake") +endif() + +# Expand relative path. This is important if the provided path contains a tilde (~) +get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE) + +file(DOWNLOAD + https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake + ${CPM_DOWNLOAD_LOCATION} EXPECTED_HASH SHA256=${CPM_HASH_SUM} +) + +include(${CPM_DOWNLOAD_LOCATION}) diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..5ace098 --- /dev/null +++ b/main.cpp @@ -0,0 +1,520 @@ +#include +#include +#include +#include +#include +#include +#include "JUI/Widgets/CommandLine.hpp" +#include "JGL/types/Shader.h" +#include +#include + +#include "JUI/Widgets/UtilityBar.hpp" + +std::vector string_expand(const std::string& input, char delimiter = ' '); + + +#pragma region OpenGL Introspection + +inline std::string GetGLVendor() { return {(const char*)glGetString(GL_VENDOR)}; } +inline std::string GetGLRenderer() { return {(const char*)glGetString(GL_RENDERER)}; } +inline std::string GetGLVersion() { return {(const char*)glGetString(GL_VERSION)}; } +inline std::string GetGLSLVersion() { return {(const char*)glGetString(GL_SHADING_LANGUAGE_VERSION)}; } +inline std::string GetGLExtensions() { return {(const char*)glGetString(GL_EXTENSIONS)}; } +inline std::vector GetGLExtensionList() { return string_expand({(const char*)glGetString(GL_EXTENSIONS)}); } + +#pragma endregion + +std::vector string_expand(const std::string& input, char delimiter) +{ + std::vector result; + std::stringstream ss (input); + std::string item; + + while (getline(ss, item, delimiter)) { + result.push_back(item); + } + return result; + +} + +using namespace JUI::UDimLiterals; + +/// A JUI Widget that displays a HSV color input dialog. +class ColorPicker : public JUI::Rect { +public: + Event OnColorValueChanged; + void SetColorValue(const Color4& color); + Color4 GetColorValue() const; + + JUI::TextRect* hex_label; + + + float hue = 0; + float sat = 1.f; + float bri = 1.f; // AKA val + + ColorPicker() : Rect() { + + Size({100_percent, 100_percent}); + auto* row_layout = new JUI::VerticalListLayout(this); + row_layout->Padding(5_px); + + hue_slider = new JUI::Slider(row_layout); + hue_slider->Size({100_percent, 28_px}); + hue_slider->Minimum(0.f); hue_slider->Maximum(1.f); + hue_slider->Interval(1e-3f); + hue_slider->ValueChanged += [this] (float val) mutable { + hue_label->SetContent(std::format("Hue: {}", Math::FloorInt(val*360))); + + hue = val * 360; + + hue_slider->ScrubberColor(Color4::FromHSV(hue, 1.f, 1.f)); + + OnColorValueChanged.Invoke(Color4::FromHSV(hue, sat, bri)); + }; + + hue_label = new JUI::Text(hue_slider); + hue_label->AlignCenterHorizontally(); + hue_label->AlignTop(); + hue_label->SetTextColor(Colors::Black); + hue_label->SetContent("Hue: 0"); + + sat_slider = new JUI::Slider(row_layout); + sat_slider->Size({100_percent, 28_px}); + sat_slider->Minimum(0); sat_slider->Maximum(1); + sat_slider->Interval(1e-3f); + sat_slider->ValueChanged += [this] (float val) mutable { + sat_label->SetContent(std::format("Saturation: {}%", val*100.f)); + + sat = val; + OnColorValueChanged.Invoke(Color4::FromHSV(hue, sat, bri)); + }; + + sat_label = new JUI::Text(sat_slider); + sat_label->AlignCenterHorizontally(); + sat_label->AlignTop(); + sat_label->SetTextColor(Colors::Black); + sat_label->SetContent("Saturation: 100%"); + + bri_slider = new JUI::Slider(row_layout); + bri_slider->Size({100_percent, 28_px}); + bri_slider->Minimum(0); bri_slider->Maximum(1); + bri_slider->Interval(1e-3f); + bri_slider->ValueChanged += [this] (float val) mutable { + bri_label->SetContent(std::format("Brightness: {}%", val*100.f)); + + bri = val; + + OnColorValueChanged.Invoke(Color4::FromHSV(hue, sat, bri)); + }; + + bri_label = new JUI::Text(bri_slider); + bri_label->AlignCenterHorizontally(); + bri_label->AlignTop(); + bri_label->SetTextColor(Colors::Black); + bri_label->SetContent("Brightness: 100%"); + + + + auto* hue_box = new JUI::Rect(); + } + + explicit ColorPicker(Widget* parent) : ColorPicker() { + Parent(parent); + } + +protected: + JUI::Slider* hue_slider; + JUI::Slider* sat_slider; + JUI::Slider* bri_slider; + JUI::Text* hue_label; + JUI::Text* sat_label; + JUI::Text* bri_label; +private: +}; + +class ReShaderProgram : public ReWindow::OpenGLWindow { +public: + JUI::Scene *scene; + JUI::UtilityBar* toolbar; + JUI::CommandLine *console; + JUI::Window* info_dialog; + JGL::Shader shader; + JGL::RenderTarget *canvas; + float u_time; + float u_scale = 2.f; + Vector2 u_translation {0.4f, 0.f}; + Color4 u_rgb_1 = Colors::Greens::Chartreuse; + Color4 u_rgb_2 = Colors::Reds::LightCoral; + std::string frag_name; + std::string vert_name; + + ReShaderProgram(); + + static std::filesystem::path VertexShaderFilepathFromPrefixName(const std::string &name) + { + return "../shaders/" + name + ".vert.glsl"; + } + + static std::filesystem::path FragmentShaderFilepathFromPrefixName(const std::string &name) + { + return "../shaders/" + name + ".frag.glsl"; + } + + void LoadShader(const std::string& name) + { + frag_name = name; + vert_name = name; + shader = Shader(VertexShaderFilepathFromPrefixName(name), FragmentShaderFilepathFromPrefixName(name)); + } + + void LoadShader(const std::string& vertex_name, const std::string& fragment_name) + { + this->vert_name = vertex_name; + this->frag_name = fragment_name; + shader = Shader(VertexShaderFilepathFromPrefixName(vert_name), FragmentShaderFilepathFromPrefixName(frag_name)); + } + + void LoadShaderWithDefaultVertex(const std::string& name) { + this->frag_name = name; + shader = Shader(VertexShaderFilepathFromPrefixName(vert_name), FragmentShaderFilepathFromPrefixName(name)); + } + + void ReloadShader() { + LoadShader(vert_name, frag_name); + } + + using CmdArgs = std::vector; + void PositionCmd(const CmdArgs& args) { + if (args.empty()) { // Output position + scale + console->Log(std::format("Pos: {}, {}, Zoom: {}", u_translation.x, u_translation.y, u_scale)); + return; + } + + if (args.size() >= 2) { // Set Position + u_translation.x = std::stof(args[0]); + u_translation.y = std::stof(args[1]); + if (args.size() >= 3) { // Also set scale + u_scale = std::stof(args[2]); + } + } + console->Log(std::format("{}", args.size())); + } + + void CreateMenu() { + using namespace JUI::UDimLiterals; + + toolbar = new JUI::UtilityBar(scene); + toolbar->AddSubmenu("File"); toolbar->AddSubmenu("Edit"); + auto* btn_toggle_console = toolbar->AddButton("Console"); + + btn_toggle_console->OnClickEvent += [this] (auto a, auto b) mutable { + console->Toggle(); + }; + + auto* btn_toggle_info_dialog = toolbar->AddButton("Info"); + + // TODO: Implement JUI structure that makes blocks of text easy to impelement. + info_dialog = new JUI::Window(scene); + info_dialog->SetTitle("FractalInspector - Read Me"); + + btn_toggle_info_dialog->OnClickEvent += [this] (auto a, auto b) mutable { + info_dialog->Toggle(); + }; + + auto* layout = new JUI::VerticalListLayout(info_dialog->ViewportInstance()); + layout->Padding(0_px); + + auto line_item = [&] (const std::string& text, int size = 20, const Color4& color = Colors::White) { + auto* content = new JUI::TextRect(layout); + content->Size({100_percent, 25_px}); + content->SetContent(text); + content->SetTextSize(size); + content->SetTextColor(color); + content->BGColor(Colors::Transparent); + content->AlignCenterHorizontally(); + }; + + + // TODO: Is this text useful and relevant, or does it come off as egotistic autofellatio? + line_item("FractalInspector V 1.0"); + line_item("Developed & Maintained by Josh O'Leary"); + line_item("(c) 2025 Redacted Software - redacted.cc"); + line_item("This project is part of an independent research project to develop high-performance,", 18); + line_item("useful software that stands in contrast to all modern software trends.", 18); + line_item("This program is written in C++, from scratch."); + + // TODO: Utilize for things later. + auto* wind = new JUI::Window(scene); + wind->SetTitle("Color-Picker A"); + wind->MinSize({200, 150}); + wind->Size({200_px, 150_px}); + wind->Position({50_px, 50_px}); + //wind->Close(); + + ColorPicker* pp = new ColorPicker(wind->ViewportInstance()); + pp->OnColorValueChanged += [this] (const Color4& color) mutable { + u_rgb_1 = color; + }; + + + auto* wind2 = new JUI::Window(scene); + wind2->SetTitle("Color-Picker B"); + wind2->MinSize({200, 150}); + wind2->Size({200_px, 150_px}); + wind2->Position({50_px, 250_px}); + + ColorPicker* pp2 = new ColorPicker(wind2->ViewportInstance()); + pp2->OnColorValueChanged += [this] (const Color4& color) mutable { + u_rgb_2 = color; + }; + + console = new JUI::CommandLine(scene); + console->MinSize(Vector2(200, 200)); + console->MaxSize(Vector2(GetWidth()-10, GetHeight()-10)); + console->Size({100_percent, 200_px}); + console->Position({0_percent, JUI::UDim(-200, 1)}); + console->OnInput += [this] (const std::string& message) { + auto tokens = string_expand(message); + + if (tokens.size() == 0) { + console->Log("No command input!", Colors::Red); + return; + } + + std::string cmd = tokens[0]; + + // Remove 0th element from tokens before passing to command delegates. + tokens.erase(tokens.begin()); + + + + + if (cmd == "r" || cmd == "reload") { + ReloadShader(); + } else if (cmd == "lf") { + if (tokens.size() > 1) { + std::string fragment = tokens[1]; + // TODO: Check for file in question. + LoadShaderWithDefaultVertex(fragment); + return; + } + console->Log("Error: Command syntax is lf ", Colors::Red); + } else if (cmd == "ls") { + if (tokens.size() > 1) { + std::string shader = tokens[1]; + // TODO: Check for file in question. + LoadShader(shader); + return; + } + console->Log("Error: Command syntax is ls ", Colors::Red); + } else if (cmd == "pos") { + return PositionCmd(tokens); + } else { + console->Log(std::format("No such command: {}", cmd), Colors::Red); + } + }; + } + + bool Open() override + { + if (!OpenGLWindow::Open()) + return false; + + auto size = GetSize(); + auto vec_size = Vector2i(size.x, size.y); + if (!JGL::Init(vec_size, 0.f, 1.f)) + return false; + JGL::Update(vec_size); + + + + glClearColor(0.f, 0.f, 0.f, 0.f); + + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + glDepthMask(GL_TRUE); + // TODO: Delete when we update to the next release of JGL + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // NOTE: This MUST be called for text rendering to work properly!!! + + scene = new JUI::Scene(); + CreateMenu(); + + canvas = new RenderTarget(vec_size); + LoadShader("test", "mandelbrot"); + + console->Log(std::format("OpenGL Renderer: {}", GetGLRenderer())); + console->Log(std::format("OpenGL Vendor: {}", GetGLVendor())); + console->Log(std::format("OpenGL Version: {}", GetGLVersion())); + console->Log(std::format("GLSL Version: {}", GetGLSLVersion())); + /*auto ext_list = GetGLExtensionList(); + + // TODO: output 3 at a time. + console->Log("GL-Extensions: "); + std::string formatted_output = "";//"GL-Extensions: {}"; + + + int iter = 0; + for (auto& str : ext_list) { + formatted_output += str + ", "; + iter++; + if (iter > 5) { + iter = 0; + console->Log(formatted_output); + formatted_output = ""; + } + }*/ + + return true; + } + void PropagateWindowSize() { + auto size = GetSize(); + Vector2i vSize = Vector2i(size.x, size.y); + JGL::Update(vSize); + scene->SetViewportSize(Vector2(vSize)); + console->MaxSize(Vector2(GetWidth()-10, GetHeight()-10)); + + // TODO: Causes the shader canvas to not appear...? + //canvas->Resize(vSize); + } + void Update(float elapsed) { + scene->Update(elapsed); + + if (IsKeyDown(Keys::F6)) { + u_scale = 2.f; + u_translation = {.4, 0}; + } + + // TODO: Vary the rate appropriately. + float rate = elapsed * 0.125f; + + if (IsKeyDown(Keys::LeftShift)) + rate *= 0.125f; + + if (IsKeyDown(Keys::LeftControl)) + rate *= 0.125f; + + if (IsKeyDown(Keys::Minus)) + u_scale += rate; + if (IsKeyDown(Keys::Equals)) + u_scale -= rate; + + if (IsKeyDown(Keys::DownArrow)) + u_translation.y += rate; + if (IsKeyDown(Keys::UpArrow)) + u_translation.y -= rate; + + if (IsKeyDown(Keys::LeftArrow)) + u_translation.x += rate; + if (IsKeyDown(Keys::RightArrow)) + u_translation.x -= rate; + + + u_time += elapsed; + + Vector2 u_res = Vector2(GetSize().x, GetSize().y); + + // TODO: Why do we need to multiply X by 1.5 here? + Vector2 u_mouse = Vector2(GetMouseCoordinates().x*1.5f, GetSize().y-GetMouseCoordinates().y); + + shader.SetVector2("u_resolution", u_res); + shader.SetFloat("u_time", u_time); + shader.SetVector2("u_mouse", u_mouse); + shader.SetVector3("u_rgb_1", Vector3(u_rgb_1.RN(), u_rgb_1.GN(), u_rgb_1.BN())); + shader.SetVector3("u_rgb_2", Vector3(u_rgb_2.RN(), u_rgb_2.GN(), u_rgb_2.BN())); + shader.SetFloat("u_scale", u_scale); + shader.SetVector2("u_translation", u_translation); + } + void Draw() { + + Shader::UseDefault(); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + // TODO: Have to be particular about the order-of-operations in regards to shaders. + // Protip: Use RenderTarget to draw a quad with the shader in question. *After rendering the rest of the scene.* + + J2D::Begin(); + J2D::DrawRenderTarget(canvas, {0, 0}); + J2D::End(); + + scene->Draw(); + + JGL::J2D::Begin(canvas, true); + shader.Use(); + JGL::J2D::FillRect(Colors::Black, {0, 0}, Vector2(GetSize().x, GetSize().y)); + JGL::J2D::End(); + + } + void OnRefresh(float elapsed) override { + PropagateWindowSize(); + Update(elapsed); + Draw(); + this->SwapBuffers(); + } + + void OnMouseButtonDown(const ReWindow::MouseButtonDownEvent &e) override { + if (e.Button == MouseButtons::Left) + scene->ObserveMouseInput(JUI::MouseButton::Left, true); + if (e.Button == MouseButtons::Middle) + scene->ObserveMouseInput(JUI::MouseButton::Middle, true); + if (e.Button == MouseButtons::Right) + scene->ObserveMouseInput(JUI::MouseButton::Right, true); + } + void OnMouseButtonUp(const ReWindow::MouseButtonUpEvent &e) override { + if (e.Button == MouseButtons::Left) + scene->ObserveMouseInput(JUI::MouseButton::Left, false); + if (e.Button == MouseButtons::Middle) + scene->ObserveMouseInput(JUI::MouseButton::Middle, false); + if (e.Button == MouseButtons::Right) + scene->ObserveMouseInput(JUI::MouseButton::Right, false); + } + void OnMouseMove(const ReWindow::MouseMoveEvent &e) override { + Vector2 nmp = Vector2(e.Position.x, e.Position.y); + scene->ObserveMouseMovement(nmp); + } + void OnKeyDown(const ReWindow::KeyDownEvent &e) override { + if (scene->ObserveKeyInput(e.key, true)) + return; + + if (e.key == Keys::R) + ReloadShader(); + } + void OnKeyUp(const ReWindow::KeyUpEvent &e) override { + scene->ObserveKeyInput(e.key, false); + } +protected: +private: +}; + +ReShaderProgram::ReShaderProgram(): ReWindow::OpenGLWindow("ReShader", 1800, 1000, 2, 1) { + Shader::OnCompilationErrorMessage += [this](std::string type, std::string infoLog) { + auto log_lines = string_expand(infoLog, '\n'); + console->Log(type, Colors::Red); + std::cerr << type << std::endl; + for (auto line: log_lines) { + console->Log(line, Colors::Red); + std::cerr << line << std::endl; + } + }; + + u_time = 0; +} + +int main(int argc, char** argv) { + ReWindow::Logger::Debug.EnableConsole(false); + + auto* program = new ReShaderProgram(); + + program->Open(); + program->SetFullscreen(false); + program->SetVsyncEnabled(false); + program->SetResizable(true); + + while (program->IsAlive()) + program->ManagedRefresh(); + + return 0; +} diff --git a/shaders/mandelbrot.frag.glsl b/shaders/mandelbrot.frag.glsl new file mode 100644 index 0000000..25894a9 --- /dev/null +++ b/shaders/mandelbrot.frag.glsl @@ -0,0 +1,106 @@ +#version 120 + +#ifdef GL_ES +precision highp float; +#endif + +#define PI 3.14159265359 + +uniform vec2 u_resolution; +uniform vec2 u_mouse; +uniform float u_time; + +uniform vec2 u_translation; +uniform float u_scale; + +uniform vec3 u_rgb_1; +uniform vec3 u_rgb_2; + +#define N 256. // Number of iterations? +#define B 4. // What does B mean? + + +vec3 pal(in float t, in vec3 a, in vec3 b, in vec3 c, in vec3 d) { + return a + b*cos(6.28318*(c*t+d)); +} + +// The mandelbrot set is a set of complex numbers c for which the function: +// f(z) = z*z + c +// stays bounded between a certain range of values when iterated from z = 0. + +#define MAX_ITERATIONS 100. + +vec2 square_imaginary(vec2 number) { + return vec2( + pow(number.x, 2) - pow(number.y, 2), + 2*number.x*number.y + ); +} + +float iterate_mandelbrot(vec2 coord) { + vec2 z = vec2(0,0); + for (int i = 0; i < MAX_ITERATIONS; i++) { + z = square_imaginary(z) + coord; + if (length(z) > 2) return i / MAX_ITERATIONS; + } + return MAX_ITERATIONS; +} + +float iterate(vec2 p) { + vec2 z = vec2(0), c = p; + float i; + for (i=0.; i < N; i++ ) { + z = mat2(z, -z.y, z.x) * z + c; + if (dot(z, z) > B*B) break; + } + + //return i; + // Compute the iteration smoothly, instead of in integers. + + if (p.y < 0.f) return i; + return i - log(log(dot(z, z)) / log(B)) / log(2.);; +} + +void main() { + vec2 R = u_resolution.xy; + + //float translate_h = .4; + //float translate_v = 0.0; + //float scale = 2.; + + //vec2 translation = vec2(translate_h, translate_v); + + vec2 uv = (u_scale * gl_FragCoord.xy - R - 1.) / R.y - u_translation; + //vec2 z = vec2(0), c = uv; + //float i; + + //for (i = 0.; i < N; i++) { + // z = mat2(z, -z.y, z.x) * z + c; + // if (dot(z, z) > B*B) break; + //} + + float n = iterate(uv) / N; + + vec3 col = pal(fract(n + 0.5), vec3(.5), u_rgb_1, u_rgb_2, vec3(.0, .1, .2)); + + //if (n == 1.) {n = 0; } + gl_FragColor = vec4(n == 1. ? vec3(0) : col, 1.); + + //vec2 st = gl_FragCoord.xy/u_resolution.xy; + //vec3 color = vec3(0.0); + + //vec3 pct = vec3(st.x); + + // pct.r = smoothstep(0.0,1.0, st.x); + // pct.g = sin(st.x*PI); + // pct.b = pow(st.x,0.5); + + //color = mix(colorA, colorB, pct); + + // Plot transition lines for each channel + // color = mix(color,vec3(1.0,0.0,0.0),plot(st,pct.r)); + // color = mix(color,vec3(0.0,1.0,0.0),plot(st,pct.g)); + // color = mix(color,vec3(0.0,0.0,1.0),plot(st,pct.b)); + + // gl_FragColor = vec4(color,1.0); +} diff --git a/shaders/test.vert.glsl b/shaders/test.vert.glsl new file mode 100644 index 0000000..55902c5 --- /dev/null +++ b/shaders/test.vert.glsl @@ -0,0 +1,6 @@ +#version 120 + +void main() { + //gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; + gl_Position = ftransform(); +} \ No newline at end of file