1 前言

立方体纹理(Cubemap)和天空盒子(Skybox)中介绍了生成立方体纹理和制作天空盒子的方法,本文将使用立方体纹理进行采样,实现反射、菲涅耳反射和折射效果。另外,本文还使用了 GrabPass 抓取屏幕图像,替代立方体纹理,作为折射的采样纹理。

​ 立方体纹理采样原理:从世界坐标系的坐标原点出发,发射一条射线,与边长为 1 的立方体相交(其中心在坐标原点,并且每个面与对应坐标轴垂直),交点位置的像素即为采样的像素。立方体纹理采样函数如下,cubemap 为立方体纹理,worldVec 为世界坐标系中采样方向向量,color 为采样的颜色。

// 立方体纹理采样
fixed4 color = texCUBE(cubemap, worldVec)

​ 本文完整资源见→Unity3D反射和折射

2 反射

​ 对于模型上的任意一点,其反射颜色计算方法为:首先计算相机指向该点的向量,再根据该点的法线信息计算反射向量,接着使用反射向量在 cubemap 中进行纹理采样,最后使用采样后的颜色和漫反射颜色进行混合。

​ Reflect.shader

Shader "MyShader/Reflection" { // 反射
Properties {
_Color("Color Tint", Color) = (1, 1, 1, 1) // 物体颜色
_ReflectColor("Reflection Color", Color) = (1, 1, 1, 1) // 反射光的颜色
_ReflectAmount("Reflect Amount", Range(0, 1)) = 1 // 反射比例(用于漫反射和反射之间插值)
_Cubemap("Reflection Cubemap", Cube) = "_Skybox" {} // 立方体纹理
} SubShader{
Tags { "RenderType" = "Opaque" "Queue" = "Geometry"} Pass {
Tags { "LightMode" = "ForwardBase" } CGPROGRAM
#pragma vertex vert
#pragma fragment frag #include "Lighting.cginc" fixed4 _Color; // 物体颜色
fixed4 _ReflectColor; // 反射光的颜色
fixed _ReflectAmount; // 反射比例(用于漫反射和反射之间插值)
samplerCUBE _Cubemap; // 立方体纹理 struct a2v {
float4 vertex : POSITION; // 模型空间顶点坐标
float3 normal : NORMAL; // 模型空间法相向量
}; struct v2f {
float4 pos : SV_POSITION; // 裁剪空间顶点坐标
float3 worldPos : TEXCOORD0; // 世界空间顶点坐标
fixed3 worldNormal : TEXCOORD1; // 顶点法线向量
fixed3 worldViewDir : TEXCOORD2; // 观察向量(顶点指向相机)
fixed3 worldRefl : TEXCOORD3; // 反射向量
}; v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex); // 模型空间顶点坐标变换到裁剪空间, 等价于: mul(UNITY_MATRIX_MVP, v.vertex)
o.worldNormal = UnityObjectToWorldNormal(v.normal); // 计算世界空间中顶点法线向量(已归一化)
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; // 计算世界空间中顶点坐标
o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos); // 计算世界空间中观察向量(顶点指向相机)
o.worldRefl = reflect(-o.worldViewDir, o.worldNormal); // 计算观察向量的反射向量
return o;
} fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal); // 法线向量
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); // 灯光向量(顶点指向光源)
fixed3 worldViewDir = normalize(i.worldViewDir); // 观察向量(顶点指向相机)
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; // 环境光颜色
fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir)); // 漫反射光颜色
fixed3 reflection = texCUBE(_Cubemap, i.worldRefl).rgb * _ReflectColor.rgb; // 反射光颜色
fixed3 color = ambient + lerp(diffuse, reflection, _ReflectAmount); // 漫反射光与反射光颜色进行插值
return fixed4(color, 1.0);
} ENDCG
}
} FallBack "Reflective/VertexLit"
}

​ _ReflectAmount 值为 1 反射效果如下:

​ _ReflectAmount 值由 0 至 1 渐变反射效果如下:

​ 说明:即使没有房间模型,中间的 5 个物体也会反射房间环境,这是因为它们使用的纹理源于 Cubemap 采样,而 Cubemap 一旦生成,就与环境无关。

3 菲涅耳反射

​ 菲涅耳反射描述了一种光学现象,即当光线射到物体表面时,一部分发生反射,一部分进入物体内部,发生折射或散射。被反射的光和入射角度存在一定的比率关系(入射角越小,反射的光越少;入射角越大,反射的光越多;当入射角大到某个值时,会发生全反射,即没有折射现象),这个比值可以通过菲涅耳等式计算得到。当前应用比较广泛的菲涅耳近似等式主要有:Schlick 菲涅耳近似等式、Empricial 菲涅耳近似等式。

1)Schlick 菲涅耳近似等式

​ 说明:F0 为反射系数,用于控制菲涅耳反射的强度,用户可以根据物体材质特性进行设定,v、n 分别为入射向量和法线向量。

2)Empricial 菲涅耳近似等式

​ 说明:bias、scale、power 都是待定参数,用户可以根据物体材质特性进行设定,v、n 分别为入射向量和法线向量。

​ FresnelReflect.shader

Shader "MyShader/FresnelReflect" { // 菲涅耳反射
Properties {
_Color("Color Tint", Color) = (1, 1, 1, 1) // 物体颜色
_FresnelScale("Fresnel Scale", Range(0, 1)) = 0.5 // 菲涅耳反射系数缩放值
_Cubemap("Reflection Cubemap", Cube) = "_Skybox" {} // 立方体纹理
} SubShader{
Tags { "RenderType" = "Opaque" "Queue" = "Geometry"} Pass {
Tags { "LightMode" = "ForwardBase" } CGPROGRAM #pragma vertex vert
#pragma fragment frag #include "Lighting.cginc" fixed4 _Color; // 物体颜色
fixed _FresnelScale; // 菲涅耳反射系数缩放值
samplerCUBE _Cubemap; // 立方体纹理 struct a2v {
float4 vertex : POSITION; // 模型空间顶点坐标
float3 normal : NORMAL; // 模型空间法相向量
}; struct v2f {
float4 pos : SV_POSITION; // 裁剪空间顶点坐标
float3 worldPos : TEXCOORD0; // 世界空间顶点坐标
fixed3 worldNormal : TEXCOORD1; // 顶点法线向量
fixed3 worldViewDir : TEXCOORD2; // 观察向量(顶点指向相机)
fixed3 worldRefl : TEXCOORD3; // 反射向量
}; v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex); // 模型空间顶点坐标变换到裁剪空间, 等价于: mul(UNITY_MATRIX_MVP, v.vertex)
o.worldNormal = UnityObjectToWorldNormal(v.normal); // 计算世界空间中顶点法线向量(已归一化)
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; // 计算世界空间中顶点坐标
o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos); // 计算世界空间中观察向量(顶点指向相机)
o.worldRefl = reflect(-o.worldViewDir, o.worldNormal); // 计算观察向量的反射向量
return o;
} fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal); // 法线向量
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); // 灯光向量(顶点指向光源)
fixed3 worldViewDir = normalize(i.worldViewDir); // 观察向量(顶点指向相机)
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; // 环境光颜色
fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir)); // 漫反射光颜色
fixed3 reflection = texCUBE(_Cubemap, i.worldRefl).rgb; // 反射光颜色
fixed fresnel = _FresnelScale + (1 - _FresnelScale) * pow(1 - dot(worldViewDir, worldNormal), 5); // 菲涅耳反射系数
fixed3 color = ambient + lerp(diffuse, reflection, saturate(fresnel)); // 漫反射光与反射光颜色进行插值
return fixed4(color, 1.0);
} ENDCG
}
} FallBack "Reflective/VertexLit"
}

​ 菲涅耳反射效果如下:

​ 说明:即使没有房间模型,中间的 5 个物体也会反射房间环境,这是因为它们使用的纹理源于 Cubemap 采样,而 Cubemap 一旦生成,就与环境无关。

4 折射

​ 对于模型上的任意一点,其折射颜色计算方法为(仅考虑 1 次折射,现实世界会发生 2 次折射):首先计算相机指向该点的向量,再根据该点的法线信息和折射率比值计算折射向量,接着使用折射向量在 cubemap 中进行纹理采样,最后使用采样后的颜色和漫反射颜色进行混合。

​ Refract.shader

Shader "MyShader/Refraction" { // 折射
Properties {
_Color("Color Tint", Color) = (1, 1, 1, 1) // 物体颜色
_RefractColor("Refraction Color", Color) = (1, 1, 1, 1) // 折射光的颜色
_RefractAmount("Refraction Amount", Range(0, 1)) = 1 // 折射比例(用于漫反射和折射之间插值)
_RefractRatio("Refraction Ratio", Range(0.1, 1)) = 0.5 // 折射比(入射介质折射率/折射介质折射率)
_Cubemap("Refraction Cubemap", Cube) = "_Skybox" {} // 立方体纹理
} SubShader{
Tags { "RenderType" = "Opaque" "Queue" = "Geometry"} Pass {
Tags { "LightMode" = "ForwardBase" } CGPROGRAM #pragma vertex vert
#pragma fragment frag #include "Lighting.cginc" fixed4 _Color; // 物体颜色
fixed4 _RefractColor; // 折射光的颜色
float _RefractAmount; // 折射比例(用于漫反射和折射之间插值)
fixed _RefractRatio; // 折射比(入射介质折射率/折射介质折射率)
samplerCUBE _Cubemap; // 立方体纹理 struct a2v {
float4 vertex : POSITION; // 模型空间顶点坐标
float3 normal : NORMAL; // 模型空间法相向量
}; struct v2f {
float4 pos : SV_POSITION; // 裁剪空间顶点坐标
float3 worldPos : TEXCOORD0; // 世界空间顶点坐标
fixed3 worldNormal : TEXCOORD1; // 顶点法线向量
fixed3 worldViewDir : TEXCOORD2; // 观察向量(顶点指向相机)
fixed3 worldRefr : TEXCOORD3; // 折射向量
}; v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex); // 模型空间顶点坐标变换到裁剪空间, 等价于: mul(UNITY_MATRIX_MVP, v.vertex)
o.worldNormal = UnityObjectToWorldNormal(v.normal); // 计算世界空间中顶点法线向量(已归一化)
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; // 计算世界空间中顶点坐标
o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos); // 计算世界空间中观察向量(顶点指向相机)
o.worldRefr = refract(-normalize(o.worldViewDir), o.worldNormal, _RefractRatio); // 计算观察向量的折射向量
return o;
} fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal); // 法线向量
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); // 灯光向量(顶点指向光源)
fixed3 worldViewDir = normalize(i.worldViewDir); // 观察向量(顶点指向相机)
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; // 环境光颜色
fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir)); // 漫反射光颜色
fixed3 refraction = texCUBE(_Cubemap, i.worldRefr).rgb * _RefractColor.rgb; // 折射光颜色
fixed3 color = ambient + lerp(diffuse, refraction, _RefractAmount); // 漫反射光与折射光颜色进行插值
return fixed4(color, 1.0);
} ENDCG
}
} FallBack "Reflective/VertexLit"
}

​ _RefractAmount 值为 1 折射效果如下:

​ _RefractAmount 值由 0 至 1 渐变折射效果如下:

​ 说明:现实世界中,光线从空气射入半透明物体,再射出到空气中,会发生 2 次折射,但是 Unity Shader 是逐像素渲染,每个像素都是独立渲染的(便于 GPU 并行计算,提高渲染效率),光线在物体出射点发生折射时,无法获取到入射点的位置及法线信息,因此无法模拟二次折射效果。

5 基于 GrabPass 的折射

​ 第 4 节中基于 Cubemap 采样实现折射特效,本节基于 GrabPass 屏幕采样实现折射特效。

​ GrabPass 用于获取屏幕纹理,有以下两种形式:

// 1. 后续的Pass中通过_GrabTexture访问屏幕图像, 该方式较耗性能, Unity为每个使用了GrabPass的物体进行一次抓取屏幕图像操作
GrabPass {}
// 2. 后续的Pass中通过TextureName访问屏幕图像, 该方式性能较好, Unity只会在每一帧为第一个使用TextureName纹理的物体执行一次抓取屏幕图像操作, 这个纹理也可以在其他Pass中被访问
GrabPass { "TextureName" }

​ GrabPass 通常用于渲染透明物体,尽管代码里并不包含混合指令,但我们仍需要把物体的渲染队列设置为透明队列(即 "Queue"="Transparent")。这样才可以保证当渲染该物体时,所有不透明物体都已经被绘制在屏幕上,从而获取正确的屏幕图像。

​ GrabRefract.shader

Shader "MyShader/GrabRefract" { // 基于GrabPass纹理采样的折射
Properties {
_Color("Color Tint", Color) = (1, 1, 1, 1) // 物体颜色
_RefractColor("Refraction Color", Color) = (1, 1, 1, 1) // 折射光的颜色
_RefractAmount("Refraction Amount", Range(0, 1)) = 1 // 折射比例(用于漫反射和折射之间插值)
_RefractRatio("Refraction Ratio", Range(0.1, 1)) = 0.5 // 折射比(入射介质折射率/折射介质折射率)
} SubShader{
// 渲染队列必须设置为 transparent, 确保所有不透明物体都在该对象之前已经渲染在屏幕上
Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }
// 抓取屏幕图像并存储在_RefractionTex中, 作为折射采样的纹理
GrabPass { "_RefractionTex" } Pass {
Tags { "LightMode" = "ForwardBase" } CGPROGRAM #pragma vertex vert
#pragma fragment frag #include "Lighting.cginc" fixed4 _Color; // 物体颜色
fixed4 _RefractColor; // 折射光的颜色
float _RefractAmount; // 折射比例(用于漫反射和折射之间插值)
fixed _RefractRatio; // 折射比(入射介质折射率/折射介质折射率)
sampler2D _RefractionTex; // GrabPass抓取的屏幕图像, 作为折射采样的纹理 struct a2v {
float4 vertex : POSITION; // 模型空间顶点坐标
float3 normal : NORMAL; // 模型空间法相向量
}; struct v2f {
float4 pos : SV_POSITION; // 裁剪空间顶点坐标
float4 scrPos : TEXCOORD0; // 屏幕空间顶点坐标(_RefractionTex采样的uv坐标)
float3 worldPos : TEXCOORD1; // 世界空间顶点坐标
fixed3 worldNormal : TEXCOORD2; // 顶点法线向量
fixed3 worldViewDir : TEXCOORD3; // 观察向量(顶点指向相机)
fixed3 worldRefr : TEXCOORD4; // 折射向量
}; v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex); // 模型空间顶点坐标变换到裁剪空间, 等价于: mul(UNITY_MATRIX_MVP, v.vertex)
o.scrPos = ComputeGrabScreenPos(o.pos); // 屏幕空间顶点坐标(_RefractionTex采样的uv坐标)
o.worldNormal = UnityObjectToWorldNormal(v.normal); // 计算世界空间中顶点法线向量(已归一化)
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; // 计算世界空间中顶点坐标
o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos); // 计算世界空间中观察向量(顶点指向相机)
o.worldRefr = refract(-normalize(o.worldViewDir), o.worldNormal, _RefractRatio); // 计算观察向量的折射向量
return o;
} fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal); // 法线向量
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); // 灯光向量(顶点指向光源)
fixed3 worldViewDir = normalize(i.worldViewDir); // 观察向量(顶点指向相机)
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; // 环境光颜色
fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir)); // 漫反射光颜色
float offset = 1 - dot(-worldViewDir, normalize(i.worldRefr)); // 折射偏移
float2 cameraViewDir = normalize(mul(unity_MatrixV, float4(worldViewDir, 0)).xy); // 观察坐标系下观察向量坐标
i.scrPos.xy = i.scrPos.xy + cameraViewDir * offset; // 顶点对应的偏移后的屏幕坐标(屏幕纹理采样坐标)
fixed3 refraction = tex2D(_RefractionTex, i.scrPos.xy / i.scrPos.w).rgb; // 折射光颜色
fixed3 color = ambient + lerp(diffuse, refraction, _RefractAmount); // 漫反射光与折射光颜色进行插值
return fixed4(color, 1.0);
} ENDCG
}
} FallBack "Reflective/VertexLit"
}

​ _RefractAmount 值为 1 折射效果如下:

​ _RefractAmount 值由 0 至 1 渐变折射效果如下:

​ 声明:本文转自【Unity3D】反射和折射

【Unity3D】反射和折射的更多相关文章

  1. Unity3d BTDF实时折射模拟有粗糙度的半透明物体

    折射的原理是运用BTDF的一个球形高斯近似 需要考虑折射光的来源,一般会想到用环境贴图(IBL)或者grab texture,但是折射光不全都来自一个平面,所以选择环境贴图来作为折射光.这个效果主要是 ...

  2. Unity shader(CG) 写一个 散色、折射、反射、菲涅尔、gamma、简单后期屏幕特效

    http://www.lai18.com/content/506918.html 1.自生要求是很重要的,当然不是什么强迫工作之类的,而是自己有限的能力上不断的扩展兴趣上的内容. 2.用生活的眼光去发 ...

  3. Unity3D 图形问题之怎样使用水?

     怎样使用水? 注意:本页所述内容仅仅适用于台式机编辑器模式. Unity 的标准资源和专业版标准资源包 (Standard Assets and Pro Standard Assets pack ...

  4. unity3d Human skin real time rendering 真实模拟人皮实时渲染(转)

    先放出结果图片...由于网上下的模型是拼的,所以眼皮,脸颊,嘴唇看起来像 存在裂痕,解决方式是加入曲面细分和置换贴图 进行一定隆起,但是博主试了一下fragment shader的曲面细分,虽然细分成 ...

  5. Unity3d 基于物理渲染Physically-Based Rendering之specular BRDF

    在实时渲染中Physically-Based Rendering(PBR)中文为基于物理的渲染它能为渲染的物体带来更真实的效果,而且能量守恒 稍微解释一下字母的意思,为对后文的理解有帮助,从右到左L为 ...

  6. unity3d Human skin real time rendering 真实模拟人皮实时渲染

    先放出结果图片...由于网上下的模型是拼的,所以眼皮,脸颊,嘴唇看起来像存在裂痕,解决方式是加入曲面细分和置换贴图 进行一定隆起,但是博主试了一下fragment shader的曲面细分,虽然细分成功 ...

  7. .NET MVC通过反射获取数据修改历史记录,并插入数据表中

    本文属于原创,转载时请标明出处! 折磨了我一个晚上的问题,奈何对物理的反射印象太深了,整天去想着物理的反射.折射怎么解.感谢少将哥哥给我的指点,经过一个晚上对反射的恶补,最终搞定了.纪念一下. 1.核 ...

  8. .NET MVC通过反射获取数据修

    .NET MVC通过反射获取数据修 折磨了我一个晚上的问题,奈何对物理的反射印象太深了,整天去想着物理的反射.折射怎么解.感谢少将哥哥给我的指点,经过一个晚上对反射的恶补,最终搞定了.纪念一下. 1. ...

  9. 【Unity Shader】(八) ------ 高级纹理之立方体纹理及光线反射、折射的实现

    笔者使用的是 Unity 2018.2.0f2 + VS2017,建议读者使用与 Unity 2018 相近的版本,避免一些因为版本不一致而出现的问题.    [Unity Shader](三) -- ...

  10. 简单的图形学(二)——材质与反射

    在上一篇[游戏框架系列]简单的图形学(一)文章中,我们讲述了光线追踪的一个最简单的操作--依每个像素延伸出一条追踪光线,光线打到球上(产生交点),就算出这条线的长度,作为最终的灰度,打不到球上,就显示 ...

随机推荐

  1. Crackme逆向分析365例-001

    [Crackme逆向分析365例-001]    表哥是神,误落凡尘 说明:本篇练习是表哥逆向分析365系列的第1例,所使用的CrackMe本体来自于网站:https://crackmes.one/, ...

  2. 2021-11-18:给定一个长度len,表示一共有几位。所有字符都是小写(a~z),可以生成长度为1,长度为2,长度为3...长度为len的所有字符串。如果把所有字符串根据字典序排序,每个字符串都有

    2021-11-18:给定一个长度len,表示一共有几位.所有字符都是小写(a~z),可以生成长度为1,长度为2,长度为3-长度为len的所有字符串.如果把所有字符串根据字典序排序,每个字符串都有所在 ...

  3. vue请求后端数据和跨域问题

    最近遇到的一个问题 后端写好的接口,前端怎么获取数据 这是我后端的接口:GET 接口 这是我前端运行的项目地址: 简单使用: 咱门前端使用 颇受好评的 axios 来发起请求 这是它的官网:https ...

  4. ENVI手动地理配准栅格图像的方法

      本文介绍在ENVI软件中,手动划定地面控制点从而实现栅格图像相互间地理配准的方法:其中,所用软件版本为ENVI Classic 5.3 (64-bit).   首先,在软件中同时打开两景需要进行地 ...

  5. 2023-05-25:给定一个正整数 x,我们将会写出一个形如 x (op1) x (op2) x (op3) x ... 的表达式 其中每个运算符 op1,op2,… 可以是加、减、乘、除之一 例如

    2023-05-25:给定一个正整数 x,我们将会写出一个形如 x (op1) x (op2) x (op3) x ... 的表达式 其中每个运算符 op1,op2,- 可以是加.减.乘.除之一 例如 ...

  6. Unity框架与.NET, Mono框架的关系

    什么是C# C#是一种面向对象的编程语言. 什么是.NET .NET是一个开发框架,它遵循并采用CIL(Common Intermediate Language)和CLR(Common Languag ...

  7. R 语言关于 SSL 证书异常处理笔记

    一.关于 TCGAbiolinks TCGAbiolinks 是一个用于 TCGA 数据综合分析的 R/BioConductor 软件包,能够通过 GDC Application Programmin ...

  8. CentOS Linux 7 配置 nginx 支持 CGI

    Nginx 本身不能执行外部程序,Nginx 处理 PHP 是通过 PHP 的 fastcgi 管理器(php-fpm)进行处理,然后 nginx 再将结果返回给用户:所以如果我们需要通过 cgi 程 ...

  9. 驱动开发:PE导出函数与RVA转换

    在笔者上篇文章<驱动开发:内核扫描SSDT挂钩状态>中简单介绍了如何扫描被挂钩的SSDT函数,并简单介绍了如何解析导出表,本章将继续延申PE导出表的解析,实现一系列灵活的解析如通过传入函数 ...

  10. prometheus安装和使用记录

    Getting started | Prometheus Configuration | Prometheus Download | Prometheus Download Grafana | Gra ...