diff --git a/impeller/fixtures/BUILD.gn b/impeller/fixtures/BUILD.gn index 41a1b0d30f3cc..367f1b70f8bf4 100644 --- a/impeller/fixtures/BUILD.gn +++ b/impeller/fixtures/BUILD.gn @@ -32,6 +32,8 @@ impeller_shaders("shader_fixtures") { "instanced_draw.vert", "mipmaps.frag", "mipmaps.vert", + "planet.frag", + "planet.vert", "sample.comp", "sepia.frag", "sepia.vert", diff --git a/impeller/fixtures/planet.frag b/impeller/fixtures/planet.frag new file mode 100644 index 0000000000000..2e687e415dc93 --- /dev/null +++ b/impeller/fixtures/planet.frag @@ -0,0 +1,486 @@ +uniform FragInfo { + vec2 resolution; + float time; + float speed; + float planet_size; + float show_normals; + float show_noise; + float seed_value; +} +frag_info; + +in vec2 v_screen_position; +out vec4 frag_color; + +float inverseLerp(float v, float min_value, float max_value) { + return (v - min_value) / (max_value - min_value); +} + +float remap(float v, float in_min, float in_max, float out_min, float out_max) { + float t = inverseLerp(v, in_min, in_max); + return mix(out_min, out_max, t); +} + +float saturate(float x) { + return clamp(x, 0.0, 1.0); +} + +// Copyright (C) 2011 by Ashima Arts (Simplex noise) +// Copyright (C) 2011-2016 by Stefan Gustavson (Classic noise and others) +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: The above copyright +// notice and this permission notice shall be included in all copies or +// substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", +// WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR +// THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// https://github.com/ashima/webgl-noise/tree/master/src +vec3 mod289(vec3 x) { + return x - floor(x / 289.0) * 289.0; +} + +vec4 mod289(vec4 x) { + return x - floor(x / 289.0) * 289.0; +} + +vec4 permute(vec4 x) { + return mod289((x * 34.0 + 1.0) * x); +} + +vec4 taylorInvSqrt(vec4 r) { + return 1.79284291400159 - r * 0.85373472095314; +} + +vec4 snoise(vec3 v) { + const vec2 C = vec2(1.0 / 6.0, 1.0 / 3.0); + + // First corner + vec3 i = floor(v + dot(v, vec3(C.y))); + vec3 x0 = v - i + dot(i, vec3(C.x)); + + // Other corners + vec3 g = step(x0.yzx, x0.xyz); + vec3 l = 1.0 - g; + vec3 i1 = min(g.xyz, l.zxy); + vec3 i2 = max(g.xyz, l.zxy); + + vec3 x1 = x0 - i1 + C.x; + vec3 x2 = x0 - i2 + C.y; + vec3 x3 = x0 - 0.5; + + // Permutations + i = mod289(i); // Avoid truncation effects in permutation + vec4 p = permute(permute(permute(i.z + vec4(0.0, i1.z, i2.z, 1.0)) + i.y + + vec4(0.0, i1.y, i2.y, 1.0)) + + i.x + vec4(0.0, i1.x, i2.x, 1.0)); + + // Gradients: 7x7 points over a square, mapped onto an octahedron. + // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294) + vec4 j = p - 49.0 * floor(p / 49.0); // mod(p,7*7) + + vec4 x_ = floor(j / 7.0); + vec4 y_ = floor(j - 7.0 * x_); + + vec4 x = (x_ * 2.0 + 0.5) / 7.0 - 1.0; + vec4 y = (y_ * 2.0 + 0.5) / 7.0 - 1.0; + + vec4 h = 1.0 - abs(x) - abs(y); + + vec4 b0 = vec4(x.xy, y.xy); + vec4 b1 = vec4(x.zw, y.zw); + + vec4 s0 = floor(b0) * 2.0 + 1.0; + vec4 s1 = floor(b1) * 2.0 + 1.0; + vec4 sh = -step(h, vec4(0.0)); + + vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy; + vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww; + + vec3 g0 = vec3(a0.xy, h.x); + vec3 g1 = vec3(a0.zw, h.y); + vec3 g2 = vec3(a1.xy, h.z); + vec3 g3 = vec3(a1.zw, h.w); + + // Normalize gradients + vec4 norm = + taylorInvSqrt(vec4(dot(g0, g0), dot(g1, g1), dot(g2, g2), dot(g3, g3))); + g0 *= norm.x; + g1 *= norm.y; + g2 *= norm.z; + g3 *= norm.w; + + // Compute noise and gradient at P + vec4 m = + max(0.6 - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0); + vec4 m2 = m * m; + vec4 m3 = m2 * m; + vec4 m4 = m2 * m2; + vec3 grad = -6.0 * m3.x * x0 * dot(x0, g0) + m4.x * g0 + + -6.0 * m3.y * x1 * dot(x1, g1) + m4.y * g1 + + -6.0 * m3.z * x2 * dot(x2, g2) + m4.z * g2 + + -6.0 * m3.w * x3 * dot(x3, g3) + m4.w * g3; + vec4 px = vec4(dot(x0, g0), dot(x1, g1), dot(x2, g2), dot(x3, g3)); + return 42.0 * vec4(grad, dot(m4, px)); +} + +// The MIT License +// Copyright © 2013 Inigo Quilez +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: The above copyright +// notice and this permission notice shall be included in all copies or +// substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", +// WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR +// THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// https://www.youtube.com/c/InigoQuilez +// https://iquilezles.org/ +// +// https://www.shadertoy.com/view/Xsl3Dl +vec3 hash3(vec3 p) // replace this by something better +{ + p = vec3(dot(p, vec3(127.1, 311.7, 74.7)), dot(p, vec3(269.5, 183.3, 246.1)), + dot(p, vec3(113.5, 271.9, 124.6))); + + return -1.0 + 2.0 * fract(sin(p) * 43758.5453123); +} + +float noise(in vec3 p) { + vec3 i = floor(p); + vec3 f = fract(p); + + vec3 u = f * f * (3.0 - 2.0 * f); + + return mix( + mix(mix(dot(hash3(i + vec3(0.0, 0.0, 0.0)), f - vec3(0.0, 0.0, 0.0)), + dot(hash3(i + vec3(1.0, 0.0, 0.0)), f - vec3(1.0, 0.0, 0.0)), + u.x), + mix(dot(hash3(i + vec3(0.0, 1.0, 0.0)), f - vec3(0.0, 1.0, 0.0)), + dot(hash3(i + vec3(1.0, 1.0, 0.0)), f - vec3(1.0, 1.0, 0.0)), + u.x), + u.y), + mix(mix(dot(hash3(i + vec3(0.0, 0.0, 1.0)), f - vec3(0.0, 0.0, 1.0)), + dot(hash3(i + vec3(1.0, 0.0, 1.0)), f - vec3(1.0, 0.0, 1.0)), + u.x), + mix(dot(hash3(i + vec3(0.0, 1.0, 1.0)), f - vec3(0.0, 1.0, 1.0)), + dot(hash3(i + vec3(1.0, 1.0, 1.0)), f - vec3(1.0, 1.0, 1.0)), + u.x), + u.y), + u.z); +} + +float fbm6(vec3 p, float persistence, float lacunarity, float exponentiation) { + float amplitude = 0.5; + float frequency = 1.0; + float total = 0.0; + float normalization = 0.0; + + p = p + frag_info.seed_value; + for (int i = 0; i < 6; ++i) { + float noiseValue = snoise(p * frequency).w; + total += noiseValue * amplitude; + normalization += amplitude; + amplitude *= persistence; + frequency *= lacunarity; + } + + total /= normalization; + total = total * 0.5 + 0.5; + total = pow(total, exponentiation); + + return total; +} + +float fbm2(vec3 p, float persistence, float lacunarity, float exponentiation) { + float amplitude = 0.5; + float frequency = 1.0; + float total = 0.0; + float normalization = 0.0; + p = p + frag_info.seed_value; + + for (int i = 0; i < 2; ++i) { + float noiseValue = snoise(p * frequency).w; + total += noiseValue * amplitude; + normalization += amplitude; + amplitude *= persistence; + frequency *= lacunarity; + } + + total /= normalization; + total = total * 0.5 + 0.5; + total = pow(total, exponentiation); + + return total; +} + +vec3 GenerateStarGrid(vec2 pixel_coords, + float cell_width, + float star_radius, + float seed, + bool twinkle) { + // fract() gives you 0.0 to 1.0 for the cell_width + // - 0.5 will move the origin from the bottom left to the cetner + vec2 cell_coords = fract(pixel_coords / cell_width) - 0.5; + // "each cell is now scaled in terms of pixels" + cell_coords *= cell_width; + + vec2 cell_id = (floor(pixel_coords / cell_width) + seed) / 100.0; // "(x,y)" + vec3 cell_hash_value = + hash3(vec3(cell_id, 0.0)); // [-1, 1] - note; "z" is unused right now. + + // Hash gives you -1 to 1; saturate clamps to 0,1. This will effectivly + // kill some of the stars (wanted) vs remap giving you a star per-cell + float starBrighness = saturate(cell_hash_value.z); // -1,1 -> 0->1 + + // Get a star position and + vec2 star_position = vec2(0.0); + star_position += cell_hash_value.xy * (cell_width * 0.5 - star_radius * 4.0); + + // float distance_to_star = length(cell_coords); // stars in the middle + float distance_to_star = length(cell_coords + star_position); + + // better falloff + float glow = exp(-2.0 * distance_to_star / star_radius); + + if (twinkle) { + // verticle and horizontal flare + float noise_sample = noise(vec3(cell_id, frag_info.time * 1.5)); + float twinkle_size = + remap(noise_sample, -1.0, 1.0, 1.0, 0.1) * star_radius * 6.0; + + vec2 abs_distance = + abs(cell_coords - star_position); // manhattan distance to cell center. + + // horizontal + float twinkleValue = smoothstep(star_radius * 0.25, 0.0, abs_distance.y) * + smoothstep(twinkle_size, 0.0, abs_distance.x); + // vertical + twinkleValue += smoothstep(star_radius * 0.25, 0.0, abs_distance.x) * + smoothstep(twinkle_size, 0.0, abs_distance.y); + + glow += twinkleValue; + } + + return vec3(glow * starBrighness); +} + +vec3 GenerateStars(vec2 pixel_coords) { + vec3 stars = vec3(0.0); + float size = 4.0; + float cell_width = 500.0; + for (float i = 0.0; i <= 2.0; i++) { + stars += GenerateStarGrid(pixel_coords, cell_width, size, + i + frag_info.seed_value, true); + size *= 0.5; + cell_width *= 0.35; + } + + for (float i = 3.0; i <= 5.0; i++) { + stars += GenerateStarGrid(pixel_coords, cell_width, size, + i + frag_info.seed_value, false); + size *= 0.5; + cell_width *= 0.35; + } + return stars; +} + +// 2D circle +float sdfCircle(vec2 p, float r) { + return length(p) - r; +} + +float map(vec3 pos) { + return fbm6(pos, 0.5, 2.0, 4.0); +} + +vec3 calcNormal(vec3 pos, vec3 n) { + // if you sample the noise field along each axis, a small amount of distance + // away from the position you're interested, you'll get a gradient + vec2 e = vec2(0.0001, 0.0); + /* -500.0 was added without comment, but it gives bump */ + return normalize(n + -500.0 * vec3(map(pos + e.xyy) - map(pos - e.xyy), + map(pos + e.yxy) - map(pos - e.yxy), + map(pos + e.yyx) - map(pos - e.yyx))); +} + +mat3 rotateY(float radians) { + float s = sin(radians); + float c = cos(radians); + return mat3( // split + c, 0.0, s, // 1 + 0.0, 1.0, 0.0, // 2 + -s, 0.0, c); // 3 +} + +vec3 DrawPlanet(vec2 pixel_coords, + vec3 color, + float planet_size, + float atmosphere_thickness, + float rotation_speed) { + vec3 planet_color = vec3(1.0); + + // Get a nice big 2D circle and the distance to the edge. + float d = sdfCircle(pixel_coords, planet_size); + + if (d <= 0.0) { + // inside the planet. + float x = pixel_coords.x / planet_size; + float y = pixel_coords.y / planet_size; + + // surface area of a sphere is x^2 + y^2 + z^2 = 1, so... + // z = 1 - x^2 - y^2 + float z = sqrt(1.0 - x * x - y * y); + + // sping around; right round... + mat3 planet_rotation = rotateY(frag_info.time * rotation_speed); + + // veiw space normal; + vec3 view_normal = vec3(x, y, z); + vec3 worldspace_position = planet_rotation * view_normal; + vec3 worldspace_normal = planet_rotation * normalize(worldspace_position); + vec3 wsViewDir = planet_rotation * vec3(0.0, 0.0, 1.0); + + vec3 noise_coord = worldspace_position * 2.0; + float noise_sample = fbm6(noise_coord, 0.5, 2.0, 4.0); + float moistureMap = fbm2(noise_coord * 0.5 + vec3(20.0), 0.5, 2.0, 4.0); + + vec3 shallowWaterColor = vec3(0.01, 0.09, 0.55); // light blue + vec3 deepWater = vec3(0.09, 0.26, 0.57); + vec3 waterColor = + mix(shallowWaterColor, deepWater, + smoothstep(0.02 /*deep*/, 0.06 /* shallow */, noise_sample)); + + vec3 coast_land = vec3(0.5, 1.0, 0.3); + vec3 jungle_land = vec3(0.0, 0.7, 0.0); + vec3 land_color = + mix(coast_land, jungle_land, smoothstep(0.05, 0.1, noise_sample)); + + vec3 sandyColor = vec3(1.0, 1.0, 0.5); + land_color = + mix(sandyColor, land_color, smoothstep(0.05, 0.1, moistureMap)); + + // Put in some mountains and snow... + vec3 mountainColor = vec3(0.5); + land_color = + mix(land_color, mountainColor, smoothstep(0.1, 0.2, noise_sample)); + + vec3 snowColor = vec3(1.0); + land_color = + mix(land_color, snowColor, smoothstep(0.15, 0.3, noise_sample)); + + // Take care of the poles... + land_color = + mix(land_color, vec3(0.9), smoothstep(0.6, 0.9, abs(view_normal.y))); + + planet_color = + mix(waterColor, land_color, smoothstep(0.05, 0.06, noise_sample)); + + // Lighting + // Check out the previous sections on lighting. + + // specularity of water vs land is different. + float water_selector = smoothstep(0.05, 0.06, noise_sample); + vec2 spec_params = mix(vec2(0.5, 32.0), // land + vec2(0.01, 2.0), // sea + water_selector); + + vec3 worldspace_light_direction = + planet_rotation * normalize(vec3(0.5, 1.0, 0.5)); + + // update: make water flat - though from space we should see some waves + // (to-do) + vec3 worldspace_surface_normal = + mix(worldspace_normal, calcNormal(noise_coord, worldspace_normal), + water_selector); + + if (frag_info.show_normals > 0.0) { + planet_color = worldspace_surface_normal; + } + float wrap = 0.05; + // float dp = max(0.0, dot(worldspace_light_direction, + // worldspace_surface_normal)); // dot product nvida surface scattering + // trick + float dp = max( + 0.0, + (dot(worldspace_light_direction, worldspace_surface_normal) + wrap) / + (1.0 + wrap)); + + vec3 darkRed = vec3(0.25, 0.0, 0.0); + vec3 lightColor = mix(darkRed, vec3(0.75), smoothstep(0.05, 0.5, dp)); + + vec3 ambient = vec3(0.002); // lets not have complete darkness + vec3 diffuse = lightColor * dp; + + vec3 r = normalize( + reflect(-worldspace_light_direction, worldspace_surface_normal)); + float phongValue = max(0.0, dot(wsViewDir, r)); + phongValue = pow(phongValue, spec_params.y); + + vec3 specular = vec3(phongValue) * spec_params.x * diffuse; + + vec3 planetShading = planet_color * (diffuse + ambient) + specular; + planet_color = planetShading; + + // Fresnel for atmosphere + float fresnel = smoothstep(1.0, 0.1, view_normal.z); // z == from camera. + // "blue halo goes around the dark side of the planet + // fresnel = pow(fresnel, 8.0); + fresnel = pow(fresnel, 8.0) * dp; + planet_color = mix(planet_color, vec3(0.0, 0.5, 1.0), fresnel); + + if (frag_info.show_noise > 0.0) { + planet_color = vec3(noise_sample); + } + } + // (color -> planet_color) when "d" is between 0 and -1 (inside the circle) + color = mix(color, planet_color, smoothstep(0.0, -1.0, d)); + + // Atmospheric glow + // 40 pixels outside the planet + if (d < atmosphere_thickness && d >= -1.0) { + // color = vec3(1.0); // draw the halo + float planetSizeAtmos = planet_size + atmosphere_thickness; + mat3 planet_rotation = rotateY(frag_info.time * rotation_speed); + + float x = pixel_coords.x / planetSizeAtmos; + float y = pixel_coords.y / planetSizeAtmos; + float z = sqrt(1.0 - x * x - y * y); + vec3 normal = planet_rotation * vec3(x, y, z); + + float lighting = dot(normal, normalize(vec3(0.5, 1.0, 0.5))); + lighting = + smoothstep(-0.15, 1.0, lighting); // same as above; just no wrap. + + vec3 glow_color = + vec3(0.05, 0.3, 0.9) * exp(-0.01 * d * d) * lighting * 0.75; + color += glow_color; + } + + return color; +} + +void main() { + vec2 vUvs = (gl_FragCoord.xy - 0.5) / frag_info.resolution; + vUvs.y = 1.0 - vUvs.y; // flip flutter's upside down. + vec2 pixel_coords = (vUvs - 0.5) * frag_info.resolution; + + vec3 color = vec3(0.0); + color = GenerateStars(pixel_coords); + color = DrawPlanet(pixel_coords, color, frag_info.planet_size, + frag_info.planet_size * 0.1, frag_info.speed); + + frag_color = vec4(pow(color, vec3(1.0 / 2.2)), 1.0); +} diff --git a/impeller/fixtures/planet.vert b/impeller/fixtures/planet.vert new file mode 100644 index 0000000000000..3aa5bec5b8a92 --- /dev/null +++ b/impeller/fixtures/planet.vert @@ -0,0 +1,16 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +uniform FrameInfo { + mat4 mvp; +} +frame_info; + +in vec2 vertex_position; +out vec2 v_screen_position; + +void main() { + v_screen_position = vertex_position; + gl_Position = frame_info.mvp * vec4(vertex_position, 0.0, 1.0); +} diff --git a/impeller/renderer/renderer_unittests.cc b/impeller/renderer/renderer_unittests.cc index fa32f2c685395..b574328fafe4f 100644 --- a/impeller/renderer/renderer_unittests.cc +++ b/impeller/renderer/renderer_unittests.cc @@ -24,6 +24,8 @@ #include "impeller/fixtures/instanced_draw.vert.h" #include "impeller/fixtures/mipmaps.frag.h" #include "impeller/fixtures/mipmaps.vert.h" +#include "impeller/fixtures/planet.frag.h" +#include "impeller/fixtures/planet.vert.h" #include "impeller/fixtures/sepia.frag.h" #include "impeller/fixtures/sepia.vert.h" #include "impeller/fixtures/swizzle.frag.h" @@ -926,6 +928,72 @@ TEST_P(RendererTest, TheImpeller) { OpenPlaygroundHere(callback); } +TEST_P(RendererTest, Planet) { + using VS = PlanetVertexShader; + using FS = PlanetFragmentShader; + + auto context = GetContext(); + auto pipeline_descriptor = + PipelineBuilder::MakeDefaultPipelineDescriptor(*context); + ASSERT_TRUE(pipeline_descriptor.has_value()); + pipeline_descriptor->SetSampleCount(SampleCount::kCount4); + pipeline_descriptor->SetStencilAttachmentDescriptors(std::nullopt); + auto pipeline = + context->GetPipelineLibrary()->GetPipeline(pipeline_descriptor).Get(); + ASSERT_TRUE(pipeline && pipeline->IsValid()); + + auto host_buffer = HostBuffer::Create(context->GetResourceAllocator()); + + SinglePassCallback callback = [&](RenderPass& pass) { + static Scalar speed = 0.1; + static Scalar planet_size = 550.0; + static bool show_normals = false; + static bool show_noise = false; + static Scalar seed_value = 42.0; + + auto size = pass.GetRenderTargetSize(); + + ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); + ImGui::SliderFloat("Speed", &speed, 0.0, 10.0); + ImGui::SliderFloat("Planet Size", &planet_size, 0.1, 1000); + ImGui::Checkbox("Show Normals", &show_normals); + ImGui::Checkbox("Show Noise", &show_noise); + ImGui::InputFloat("Seed Value", &seed_value); + ImGui::End(); + + pass.SetPipeline(pipeline); + pass.SetCommandLabel("Planet scene"); + VertexBufferBuilder builder; + builder.AddVertices({{Point()}, + {Point(0, size.height)}, + {Point(size.width, 0)}, + {Point(size.width, 0)}, + {Point(0, size.height)}, + {Point(size.width, size.height)}}); + pass.SetVertexBuffer(builder.CreateVertexBuffer(*host_buffer)); + + VS::FrameInfo frame_info; + EXPECT_EQ(pass.GetOrthographicTransform(), Matrix::MakeOrthographic(size)); + frame_info.mvp = pass.GetOrthographicTransform(); + VS::BindFrameInfo(pass, host_buffer->EmplaceUniform(frame_info)); + + FS::FragInfo fs_uniform; + fs_uniform.resolution = Point(size); + fs_uniform.time = GetSecondsElapsed(); + fs_uniform.speed = speed; + fs_uniform.planet_size = planet_size; + fs_uniform.show_normals = show_normals ? 1.0 : 0.0; + fs_uniform.show_noise = show_noise ? 1.0 : 0.0; + fs_uniform.seed_value = seed_value; + FS::BindFragInfo(pass, host_buffer->EmplaceUniform(fs_uniform)); + + pass.Draw().ok(); + host_buffer->Reset(); + return true; + }; + OpenPlaygroundHere(callback); +} + TEST_P(RendererTest, ArrayUniforms) { using VS = ArrayVertexShader; using FS = ArrayFragmentShader;