網(wǎng)站空間在哪買好微信管理系統(tǒng)登錄入口
? ? ? ? shader相信很多朋友們都聽說過,shader就是運行再GPU上的程序。雖然是這么說,但是我們發(fā)現(xiàn),很多IDE開發(fā)工具比如說visual studio 沒有辦法直接去運行shader代碼。這是因為,許多編譯器不會自動將shader文件編譯成可執(zhí)行的代碼然后發(fā)送給GPU,shader代碼的編譯需要開發(fā)人員手動動用GPU的shader編譯接口,將對應(yīng)的代碼編譯成可執(zhí)行的程序。在這里,筆者就為大家介紹,如何使用OpenGL提供的API編譯用戶自己的shader程序。
Shader
OpenGL渲染管線
? ? ? ? 這里先為大家介紹一下OpenGL渲染管線如下圖所示
事實上在一眾著色器當中,只有頂點著色器和片元著色器是必須的,其他都是可選的。具體想要了解更多,可以去看一些資料或者書籍,比如筆者手上的這本《OpenGL編程指南》
OpenGL shader
? ? ? ? OpenGL 支持的 shader 語法是 OpenGL shader language 簡稱是glsl。這個shader語法和C++基本上是大同小異,很快就可以輕松上手。
話說到這里,讓我們回顧一下上一篇文章OpenGL渲染結(jié)果移至ImGui窗口上,有細心的本有不難發(fā)現(xiàn),我并沒有去編譯用戶著色器,而且必要的頂點著色器和片元著色器都沒有進行編寫,但是我們?nèi)匀坏玫轿覀兿胍匿秩窘Y(jié)果。其原因是,OpenGL自帶默認的著色器,所以對于初學者來說,可以盡可能使用少的代碼去實現(xiàn)自己想要的結(jié)果,這就是為什么圖形接口的學習一般都是從OpenGL開始學起。
OpenGL著色器編譯
Shader 類
? ? ? ? 這里筆者寫了一個Shader類,整體的代碼如下
Shader.h
#pragma once#include<unordered_map>
typedef unsigned int GLenum;class Shader {
public:Shader(const std::string& filePath);~Shader();void Bind();void UBind();void UploadUniformFloat4(const std::string& name, float* value);private:std::string ReadFile(const std::string& filePath);std::unordered_map<GLenum, std::string> PreProcess(const std::string& source);void Compile(const std::unordered_map<GLenum, std::string>& shaderSources);
private:uint32_t m_ShaderID;std::string m_Name;
};
Shader.cpp
#include<glad/glad.h>
#include<string>
#include<array>
#include<fstream>
#include<iostream>#include"Shader.h"static GLenum ShaderTypeFromString(const std::string& type) {if (type == "vertex")return GL_VERTEX_SHADER;else if (type == "fragment" || type == "pixel")return GL_FRAGMENT_SHADER;std::cout << "Unknown shader type" << std::endl;return 0;
}Shader::Shader(const std::string& filePath) :m_ShaderID(0) {std::string source = ReadFile(filePath);auto shaderSource = PreProcess(source);Compile(shaderSource);auto lastSlash = filePath.find_last_of("/\\");lastSlash = lastSlash == std::string::npos ? 0 : lastSlash + 1;auto lastDot = filePath.rfind('.');auto count = lastDot == std::string::npos ? filePath.size() - lastSlash : lastDot - lastSlash;m_Name = filePath.substr(lastSlash, count);
}Shader::~Shader() {glDeleteProgram(m_ShaderID);
}void Shader::Bind(){glUseProgram(m_ShaderID);
}void Shader::UBind(){glUseProgram(0);
}void Shader::UploadUniformFloat4(const std::string& name, float* value) {int location = glGetUniformLocation(m_ShaderID, name.c_str());glUniform4f(location, value[0], value[1], value[2], value[3]);
}std::string Shader::ReadFile(const std::string& filePath) {std::string result;std::ifstream in(filePath, std::ios::in | std::ios::binary);if (in) {in.seekg(0, std::ios::end);result.resize(in.tellg());in.seekg(0, std::ios::beg);in.read(&result[0], result.size());in.close();}else {std::cout << "著色器文件沒有正常打開" << std::endl;__debugbreak();}return result;
}std::unordered_map<GLenum, std::string> Shader::PreProcess(const std::string& source) {std::unordered_map<GLenum, std::string> shaderSources;const char* typeToken = "#type";size_t typeTokenLength = strlen(typeToken);size_t pos = source.find(typeToken,0);while (pos != std::string::npos) {size_t eol = source.find_first_of("\r\n", pos);if (eol == std::string::npos) {std::cout << "著色器語法出錯" << std::endl;__debugbreak();}size_t begin = pos + typeTokenLength + 1;std::string type = source.substr(begin, eol - begin);if (!ShaderTypeFromString(type)) {std::cout << "這是一個不合法的著色器類型" << std::endl;__debugbreak();}size_t nextLinePos = source.find_first_of("\r\n", eol);pos = source.find(typeToken, nextLinePos);shaderSources[ShaderTypeFromString(type)] = source.substr(nextLinePos, pos - (nextLinePos == std::string::npos ? source.size() - 1 : nextLinePos));}return shaderSources;
}void Shader::Compile(const std::unordered_map<GLenum, std::string>& shaderSources) {unsigned int program = glCreateProgram();//一次性至多編譯兩種著色器if (shaderSources.size() < 2) {std::cout << "一次性至多編譯兩種著色器" << std::endl;__debugbreak();}std::array<GLenum, 2> glShaderIDs;int glShaderIDIndex = 0;for (auto& kv : shaderSources) {GLenum type = kv.first;const std::string& source = kv.second;unsigned int shader = glCreateShader(type);const char* sourceCStr = source.c_str();glShaderSource(shader, 1, &sourceCStr, 0);glCompileShader(shader);int isCompiled = 0;glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);if (isCompiled == GL_FALSE) {int maxLength = 0;glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);std::vector<char> infoLog(maxLength);glGetShaderInfoLog(shader, maxLength, &maxLength, &infoLog[0]);glDeleteShader(shader);std::cout << "著色器編譯出錯:" << infoLog.data() << std::endl;__debugbreak();break;}glAttachShader(program, shader);glShaderIDs[glShaderIDIndex++] = shader;}m_ShaderID = program;// Link our programglLinkProgram(program);// Note the different functions here: glGetProgram* instead of glGetShader*.int isLinked = 0;glGetProgramiv(program, GL_LINK_STATUS, (int*)&isLinked);if (isLinked == GL_FALSE) {int maxLength = 0;glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength);// The maxLength includes the NULL characterstd::vector<char> infoLog(maxLength);glGetProgramInfoLog(program, maxLength, &maxLength, &infoLog[0]);// We don't need the program anymore.glDeleteProgram(program);for (auto id : glShaderIDs)glDeleteShader(id);std::cout << "用戶著色器鏈接失敗:" << infoLog.data() << std::endl;__debugbreak();return;}for (auto id : glShaderIDs)glDetachShader(program, id);
}
著色器代碼
TextureShader.glsl
#type vertex
#version 450 core
//標記為0的內(nèi)存位置輸入一個有兩個分量的向量,這是頂點的位置
layout(location = 0) in vec2 v_Position;void main(){//頂點位置的數(shù)據(jù)進行賦值,需要轉(zhuǎn)換為齊次向量gl_Position = vec4(v_Position,0.0f,1.0f);
}#type fragment
#version 450 core
//標記為0的內(nèi)存位置輸出一個有四個分量的向量,這是像素的顏色
layout(location = 0) out vec4 o_Color;void main(){o_Color = vec4(0.8f,0.2f,0.3f,1.0f);
}
著色器介紹
? ? ? ? 上面雖然是一個著色器文件,其實這里面寫了兩個著色器,一個是頂點著色器,一個是片元著色器。#type vertex 下面的是頂點著色器,#type fragment 下面的是片元著色器。為什么這兩個要一起寫了?前面也介紹了,這兩個著色器是必需要有的,所以筆者推薦這個著色器最好就是一起寫。頂點著色器必須要有輸入數(shù)據(jù),片元著色器必須要有輸出數(shù)據(jù),不然屏幕上就看不到任何東西。
著色器編譯
筆者將其分成了3個步驟進行
1、讀取對應(yīng)的文件內(nèi)容
std::string Shader::ReadFile(const std::string& filePath) {std::string result;std::ifstream in(filePath, std::ios::in | std::ios::binary);if (in) {in.seekg(0, std::ios::end);result.resize(in.tellg());in.seekg(0, std::ios::beg);in.read(&result[0], result.size());in.close();}else {std::cout << "著色器文件沒有正常打開" << std::endl;__debugbreak();}return result;
}
將TextureShader.glsl當中的文本信息全部轉(zhuǎn)換成一個string類型當中進行存儲。
2、確定著色器的類型,以及每個著色器的代碼
std::unordered_map<GLenum, std::string> Shader::PreProcess(const std::string& source) {std::unordered_map<GLenum, std::string> shaderSources;const char* typeToken = "#type";size_t typeTokenLength = strlen(typeToken);size_t pos = source.find(typeToken,0);while (pos != std::string::npos) {size_t eol = source.find_first_of("\r\n", pos);if (eol == std::string::npos) {std::cout << "著色器語法出錯" << std::endl;__debugbreak();}size_t begin = pos + typeTokenLength + 1;std::string type = source.substr(begin, eol - begin);if (!ShaderTypeFromString(type)) {std::cout << "這是一個不合法的著色器類型" << std::endl;__debugbreak();}size_t nextLinePos = source.find_first_of("\r\n", eol);pos = source.find(typeToken, nextLinePos);shaderSources[ShaderTypeFromString(type)] = source.substr(nextLinePos, pos - (nextLinePos == std::string::npos ? source.size() - 1 : nextLinePos));}return shaderSources;
}
對于OpenGL來說我們不光要告訴它需要編譯的代碼,還要告訴它編譯的著色器代碼是什么類型的著色器代碼。在前面可以看到TextureShader.glsl當中有?#type vertex 這樣的語句,這個并不是glsl語法,我們在進行文本處理的時候需要省略掉才行,不然的話編譯會失敗,這個只是用來告訴程序下面著色器代碼是什么類型著色器的,所以這里選擇返回了一個字典,用來存儲著色器的類型和需要編譯的程序。能夠編譯的是下面兩段
#version 450 core
//標記為0的內(nèi)存位置輸入一個有兩個分量的向量,這是頂點的位置
layout(location = 0) in vec2 v_Position;void main(){//頂點位置的數(shù)據(jù)進行賦值,需要轉(zhuǎn)換為齊次向量gl_Position = vec4(v_Position,0.0f,1.0f);
}
#version 450 core
//標記為0的內(nèi)存位置輸出一個有四個分量的向量,這是像素的顏色
layout(location = 0) out vec4 o_Color;void main(){o_Color = vec4(0.8f,0.2f,0.3f,1.0f);
}
他們已經(jīng)被分開存儲了。?
3、編譯鏈接著色器
void Shader::Compile(const std::unordered_map<GLenum, std::string>& shaderSources) {//注冊使用下面兩個著色器的程序號unsigned int program = glCreateProgram();//一次性至多編譯兩種著色器if (shaderSources.size() < 2) {std::cout << "一次性至多編譯兩種著色器" << std::endl;__debugbreak();}std::array<GLenum, 2> glShaderIDs;int glShaderIDIndex = 0;for (auto& kv : shaderSources) {GLenum type = kv.first;const std::string& source = kv.second;//注冊對飲類型的著色器unsigned int shader = glCreateShader(type);const char* sourceCStr = source.c_str();glShaderSource(shader, 1, &sourceCStr, 0);//編譯著色器源碼glCompileShader(shader);int isCompiled = 0;glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);//檢查著色器是否編譯失敗if (isCompiled == GL_FALSE) {int maxLength = 0;glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);std::vector<char> infoLog(maxLength);glGetShaderInfoLog(shader, maxLength, &maxLength, &infoLog[0]);glDeleteShader(shader);std::cout << "著色器編譯出錯:" << infoLog.data() << std::endl;__debugbreak();break;}//將著色器加入到這個程序當中g(shù)lAttachShader(program, shader);glShaderIDs[glShaderIDIndex++] = shader;}m_ShaderID = program;// Link our programglLinkProgram(program);// Note the different functions here: glGetProgram* instead of glGetShader*.int isLinked = 0;glGetProgramiv(program, GL_LINK_STATUS, (int*)&isLinked);//檢查程序是否能夠鏈接成功if (isLinked == GL_FALSE) {int maxLength = 0;glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength);// The maxLength includes the NULL characterstd::vector<char> infoLog(maxLength);glGetProgramInfoLog(program, maxLength, &maxLength, &infoLog[0]);// We don't need the program anymore.glDeleteProgram(program);for (auto id : glShaderIDs)glDeleteShader(id);std::cout << "用戶著色器鏈接失敗:" << infoLog.data() << std::endl;__debugbreak();return;}for (auto id : glShaderIDs)glDetachShader(program, id);
}
上面大致流程就是,注冊程序的編號,創(chuàng)建對應(yīng)類型的著色器,根據(jù)下面的代碼
static GLenum ShaderTypeFromString(const std::string& type) {if (type == "vertex")return GL_VERTEX_SHADER;else if (type == "fragment" || type == "pixel")return GL_FRAGMENT_SHADER;std::cout << "Unknown shader type" << std::endl;return 0;
}
可以知道 #type vertex 對應(yīng)的著色器類型就是GL_VERTEX_SHADER,#type fragment 對應(yīng)的著色器類型就是GL_FRAGMENT_SHADER。創(chuàng)建了對應(yīng)的著色器類型過后就是對源碼進行編譯,放入到程序當中,檢查這個程序能否順利接入管線當中,隔離開然后等待被調(diào)用。
使用用戶自定義著色器
著色器使用,主函數(shù)代碼如下
#include<glad/glad.h>
#include<GLFW/glfw3.h>#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"#include<iostream>#include"FrameBuffer.h"
#include"Shader.h"int main() {glfwInit();GLFWwindow* window = glfwCreateWindow(640, 480, "Triangles", NULL, NULL);glfwMakeContextCurrent(window);glfwSwapInterval(1); // Enable vsync// Setup Dear ImGui contextIMGUI_CHECKVERSION();ImGui::CreateContext();ImGuiIO& io = ImGui::GetIO(); (void)io;io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controlsio.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controlsio.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Dockingio.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows//io.ConfigViewportsNoAutoMerge = true;//io.ConfigViewportsNoTaskBarIcon = true;// Setup Dear ImGui styleImGui::StyleColorsDark();//ImGui::StyleColorsLight();// When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones.ImGuiStyle& style = ImGui::GetStyle();if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable){style.WindowRounding = 0.0f;style.Colors[ImGuiCol_WindowBg].w = 1.0f;}// Setup Platform/Renderer backendsImGui_ImplGlfw_InitForOpenGL(window, true);ImGui_ImplOpenGL3_Init("#version 130");//需要初始化GLADif (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {std::cout << "Failed to initialize GLAD" << std::endl;return -1;}float positions[6] = {-0.5f, -0.5,0.0f, 0.5f,0.5f, -0.5f};GLuint buffer = 0;glGenBuffers(1, &buffer);glBindBuffer(GL_ARRAY_BUFFER, buffer);glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW);glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), NULL);glEnableVertexAttribArray(0);bool show_demo_window = true;ImVec2 viewPortSize(640,480);float colorEditor[4] = {1.0f, 1.0f, 1.0f, 1.0f};FrameBuffer *pFrameBuffer = new FrameBuffer(640, 480);Shader* pShader = new Shader("assets/shaders/TextureShader.glsl");pShader->UBind();while (!glfwWindowShouldClose(window)) {pFrameBuffer->Bind();pShader->Bind();glClear(GL_COLOR_BUFFER_BIT);glDrawArrays(GL_TRIANGLES, 0, 3);pFrameBuffer->UBind();// Start the Dear ImGui frameImGui_ImplOpenGL3_NewFrame();ImGui_ImplGlfw_NewFrame();ImGui::NewFrame();ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport());ImGui::Begin("ViewPort");viewPortSize = ImGui::GetContentRegionAvail();if (viewPortSize.x * viewPortSize.y > 0 && (viewPortSize.x != pFrameBuffer->GetWidth() || viewPortSize.y != pFrameBuffer->GetHeight())) {pFrameBuffer->Resize(viewPortSize.x, viewPortSize.y);glViewport(0, 0, viewPortSize.x, viewPortSize.y);}uint32_t textureID = pFrameBuffer->GetColorAttachment();ImGui::Image(reinterpret_cast<void*>(textureID), viewPortSize, { 0,1 }, { 1,0 });ImGui::End();ImGui::Begin("ColorEditor");ImGui::ColorEdit4("##colorEditor", colorEditor);ImGui::End();/*if(show_demo_window)ImGui::ShowDemoWindow(&show_demo_window);*/// RenderingImGui::Render();ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable){GLFWwindow* backup_current_context = glfwGetCurrentContext();ImGui::UpdatePlatformWindows();ImGui::RenderPlatformWindowsDefault();glfwMakeContextCurrent(backup_current_context);}glfwSwapBuffers(window);glfwPollEvents();}// CleanupImGui_ImplOpenGL3_Shutdown();ImGui_ImplGlfw_Shutdown();ImGui::DestroyContext();delete pFrameBuffer;delete pShader;glfwDestroyWindow(window);glfwTerminate();
}
得到的結(jié)果是
三角形被順利染成了紅色,有人可能會說這也有點費了這么大的勁,就把顏色改成了紅色,實在是有點無聊,那讓我們來做一些比較Cool的事。
我們修改一下著色器
#type vertex
#version 450 corelayout(location = 0) in vec2 v_Position;void main(){gl_Position = vec4(v_Position,0.0f,1.0f);
}#type fragment
#version 450 corelayout(location = 0) out vec4 o_Color;
//增加的片段
uniform vec4 u_Color;void main(){o_Color = u_Color;
}
主函數(shù)也修改一下
pShader->UBind();while (!glfwWindowShouldClose(window)) {pFrameBuffer->Bind();pShader->Bind();//新增片段pShader->UploadUniformFloat4("u_Color", colorEditor);glClear(GL_COLOR_BUFFER_BIT);
展示一下結(jié)果
我們現(xiàn)在可以通過ImGui上面的控件對三角形的顏色進行實時修改了,不用去改動程序,是不是很棒了。下面還是把整個主函數(shù)放出來,如果對里面的FrameBuffer類不了解的可以看筆者的OpenGL渲染結(jié)果移至ImGui窗口上這篇文章,同樣有源代碼,希望對大家能有幫助。
#include<glad/glad.h>
#include<GLFW/glfw3.h>#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"#include<iostream>#include"FrameBuffer.h"
#include"Shader.h"int main() {glfwInit();GLFWwindow* window = glfwCreateWindow(640, 480, "Triangles", NULL, NULL);glfwMakeContextCurrent(window);glfwSwapInterval(1); // Enable vsync// Setup Dear ImGui contextIMGUI_CHECKVERSION();ImGui::CreateContext();ImGuiIO& io = ImGui::GetIO(); (void)io;io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controlsio.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controlsio.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Dockingio.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows//io.ConfigViewportsNoAutoMerge = true;//io.ConfigViewportsNoTaskBarIcon = true;// Setup Dear ImGui styleImGui::StyleColorsDark();//ImGui::StyleColorsLight();// When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones.ImGuiStyle& style = ImGui::GetStyle();if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable){style.WindowRounding = 0.0f;style.Colors[ImGuiCol_WindowBg].w = 1.0f;}// Setup Platform/Renderer backendsImGui_ImplGlfw_InitForOpenGL(window, true);ImGui_ImplOpenGL3_Init("#version 130");//需要初始化GLADif (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {std::cout << "Failed to initialize GLAD" << std::endl;return -1;}float positions[6] = {-0.5f, -0.5,0.0f, 0.5f,0.5f, -0.5f};GLuint buffer = 0;glGenBuffers(1, &buffer);glBindBuffer(GL_ARRAY_BUFFER, buffer);glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW);glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), NULL);glEnableVertexAttribArray(0);bool show_demo_window = true;ImVec2 viewPortSize(640,480);float colorEditor[4] = {1.0f, 1.0f, 1.0f, 1.0f};FrameBuffer *pFrameBuffer = new FrameBuffer(640, 480);Shader* pShader = new Shader("assets/shaders/TextureShader.glsl");pShader->UBind();while (!glfwWindowShouldClose(window)) {pFrameBuffer->Bind();pShader->Bind();pShader->UploadUniformFloat4("u_Color", colorEditor);glClear(GL_COLOR_BUFFER_BIT);glDrawArrays(GL_TRIANGLES, 0, 3);pFrameBuffer->UBind();// Start the Dear ImGui frameImGui_ImplOpenGL3_NewFrame();ImGui_ImplGlfw_NewFrame();ImGui::NewFrame();ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport());ImGui::Begin("ViewPort");viewPortSize = ImGui::GetContentRegionAvail();if (viewPortSize.x * viewPortSize.y > 0 && (viewPortSize.x != pFrameBuffer->GetWidth() || viewPortSize.y != pFrameBuffer->GetHeight())) {pFrameBuffer->Resize(viewPortSize.x, viewPortSize.y);glViewport(0, 0, viewPortSize.x, viewPortSize.y);}uint32_t textureID = pFrameBuffer->GetColorAttachment();ImGui::Image(reinterpret_cast<void*>(textureID), viewPortSize, { 0,1 }, { 1,0 });ImGui::End();ImGui::Begin("ColorEditor");ImGui::ColorEdit4("##colorEditor", colorEditor);ImGui::End();/*if(show_demo_window)ImGui::ShowDemoWindow(&show_demo_window);*/// RenderingImGui::Render();ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable){GLFWwindow* backup_current_context = glfwGetCurrentContext();ImGui::UpdatePlatformWindows();ImGui::RenderPlatformWindowsDefault();glfwMakeContextCurrent(backup_current_context);}glfwSwapBuffers(window);glfwPollEvents();}// CleanupImGui_ImplOpenGL3_Shutdown();ImGui_ImplGlfw_Shutdown();ImGui::DestroyContext();delete pFrameBuffer;delete pShader;glfwDestroyWindow(window);glfwTerminate();
}