shader攻占笔记(七)抓屏玻璃大杂烩 GrabPass{}

编程入门 行业动态 更新时间:2024-10-19 20:32:01

shader攻占笔记(七)抓屏玻璃<a href=https://www.elefans.com/category/jswz/34/1767404.html style=大杂烩 GrabPass{}"/>

shader攻占笔记(七)抓屏玻璃大杂烩 GrabPass{}

这周主要学习了Grab相关的操作,有很多种对于抓取内容的处理。这里的“玻璃”主要是使用抓取的折射效果。

目录

  • 基础知识
  • 普通畸变玻璃
  • 像素畸变玻璃
  • 高斯卷积玻璃
  • 放大镜
  • 折射与水波

基础知识

  • 片段函数是以顶点函数输出的语义结构体为输入(实际数值是顶点经过装配成线面图元后再经扫描转换插值后的数值),以像素颜色为输出的流水线中的计算节点;
  • 顶点函数输入结构体的坐标空间是模型坐标系,片段函数输入结构体的坐标空间是投影/裁剪坐标系,两个坐标系之间差了一个MVP矩阵
  • 抓屏技术的原理是两趟渲染,第一趟将场景内容渲染到特定/指定贴图,第二趟在场景使用了该着色器的模型上使用这张贴图进行映射,纹理映射时通常采用畸变卷积特效处理方法;
  • GrabPass{}抓取第一趟的内容,形成贴图;后面要声明一个默认或指定纹理名称(有时还需要声明一个单位像素的纹理坐标尺寸TexelSize),以及在语义结构体里定义对应纹理坐标,在顶点函数里计算该纹理坐标,在片段着色器里使用这个纹理坐标(可能做变化)进行采样

具体着色器内部操作

  1. 修改RenderType、Queue两个Tag为Transparent;
  2. 在Pass前使用GrabPass{ “_GrabTex” }获取抓屏内容;其中_GrabTex处为自定义名称,也可以使用其他命名。_GrabTexture为默认名称,简写形式为GrabPass{}
  3. 需要在声明变量处声明 sampler2D _xxxxx,如 samper2D _GrabTex;
    使用**ComputeGrabScreenPos()**来获取抓取到的屏幕uv。
o.scrPos = ComputeGrabScreenPos(o.vertex);//写在顶点着色器中

需要区分顶点和屏幕位置贴图效果

  • 顶点位置 vertex 一般用于物体上不随着视觉角度产生差别的东西,比如玻璃上的贴图印花、起伏纹理(offset)等(vertex)
  • 屏幕位置 scrPos 一般用于物体抓取之后折射效果的描绘,与附加在玻璃上的offset叠加(scrPos + offset),具体见代码部分

需要区分tex2Dproj与tex2D
在对纹理进行采样之前,tex2Dproj将输入的UV xy坐标除以其w坐标。这是将坐标从正交投影转换为透视投影。

//以下两者返回值相同
float existingDepth01 = tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPosition)).r;
float existingDepth01 = tex2D(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPosition.xy / i.screenPosition.w)).r;

使用 tex2Dproj 的场合:裁剪空间的坐标经过缩放和偏移后就变成了(0,w),而当分量除以分量W以后,就变成了(0,1),这样在计算需要返回(0,1)值的时候,就可以直接使用tex2Dproj了。

需要区分畸变玻璃与卷积玻璃
所谓畸变特效,就是指对纹理进行错位采样,即对纹理坐标进行规则或不规则地错位,再进行纹理采样;
所谓卷积特效,就是对片段的邻域数值计算加权和,不同的卷积(加权)矩阵可以产生不同的效果;

普通畸变玻璃

所谓畸变特效,就是指对纹理进行错位采样,即对纹理坐标进行规则或不规则地错位,再进行纹理采样;


最基础版本的扰动玻璃,需要注意的地方都在基础知识处。只需要把Normal处换成喜欢的图。示例所用贴图如下:

其中需要注意,片元着色器中有一句以下内容,用于加上玻璃本身贴花。因为finalColor在此处已经处理过scrPos,且本例中scrPos.w为1,因此构造的float4的第四位不能为0,否则导致贴图显示不正确。

finalColor *= tex2Dproj(_MainTex, UNITY_PROJ_COORD(float4(i.uv,0,1)));

完整shader如下:

Shader "Lesson/sd_GrabGlass"
{Properties{_MainTex("Main Tex", 2D) = "white"{}_Color("Base Color", Color) = (0,0,0,0)_NormalMap("Normal Map", 2D) = "bump"{}_Distortion("Distortion", Range(0, 1.5)) = 0.5_Scale("Scale", float) = 1}SubShader{Tags { "RenderType"="Transparent" "Queue" = "Transparent" }LOD 100GrabPass{ "_GrabTex" }Pass{Blend SrcAlpha OneMinusSrcAlpha   CGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"sampler2D _MainTex;sampler2D _NormalMap;float _Distortion;sampler2D _GrabTex;fixed4 _Color;float _Scale;//float4 _GrabTex_TexelSize;struct appdata{float4 vertex : POSITION;float2 uv : TEXCOORD0;};struct v2f{float4 vertex : SV_POSITION;float2 uv : TEXCOORD0;float4 scrPos : TEXCOORD1;};v2f vert (appdata v){v2f o;o.vertex = UnityObjectToClipPos(v.vertex);o.uv = v.uv;o.scrPos = ComputeGrabScreenPos(o.vertex);return o;}fixed4 frag (v2f i) : SV_Target{float2 bump = UnpackNormal(tex2D(_NormalMap,i.uv)); //扰动引入float2 offset = bump.xy * _Distortion;//扰动程度fixed4 finalColor = tex2Dproj(_GrabTex, UNITY_PROJ_COORD(i.scrPos+ float4(offset,0,0)));//在抓屏贴图的结果上加上扰动效果finalColor *= tex2Dproj(_MainTex, UNITY_PROJ_COORD(float4(i.uv,0,1)));//加上屏幕贴图,注意第四位是1finalColor *= _Color;return finalColor;}ENDCG}}
}

像素畸变玻璃

与卡通章节的使用 floor “分层”相同,使用 floor 在片元着色器中对 scrPos 进行分层操作,其他步骤与上文代码同:

i.scrPos.xy = floor(i.scrPos * _Scale)/_Scale;

高斯卷积玻璃

所谓卷积特效,就是对片段的邻域数值计算加权和,不同的卷积(加权)矩阵可以产生不同的效果;

(仔细看有点像近视眼效)具体原理可以去搜索“高斯模糊”。我在excel里简单设计了一个5*5的卷积核:

卷积加上扰动效果更佳:


具体代码如下:

Shader "Lesson/sd_MaoGlass"
{Properties{_MainTex("Main Tex", 2D) = "white"{}_Bump("bump",2D) = "white"{}_Color("Base Color", Color) = (0,0,0,0)_Power("Power", Range(0, 10)) = 5.81_Distortion("Distortion", Range(0, 2)) = 0.43}SubShader{Tags { "RenderType"="Transparent" "Queue" = "Transparent" }//Tags{"RenderType" = "Opaque"}LOD 100GrabPass{ "_GrabTex" }Pass{Blend SrcAlpha OneMinusSrcAlpha   CGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"sampler2D _MainTex;sampler2D _Bump;sampler2D _GrabTex;fixed4 _Color;float _Scale;float _Power;float _Distortion;struct appdata{float4 vertex : POSITION;float2 uv : TEXCOORD0;};struct v2f{float4 vertex : SV_POSITION;float2 uv : TEXCOORD0;float4 scrPos : TEXCOORD1;};v2f vert (appdata v){v2f o;o.vertex = UnityObjectToClipPos(v.vertex);o.uv = v.uv;o.scrPos = ComputeGrabScreenPos(o.vertex);return o;}fixed4 frag (v2f i) : SV_Target{float myUnit = _Power * 0.005;float4 scrPos = i.scrPos;scrPos.x = scrPos.x + 0.2;scrPos.y = scrPos.y + 0.2;half4 sum = half4(0.0, 0.0, 0.0, 0.0);float core = 0.0135;float2 bump = UnpackNormal(tex2D(_Bump, i.uv));bump*= _Distortion;scrPos += float4(scrPos + float4(bump, 0, 0));sum += tex2Dproj(_GrabTex, UNITY_PROJ_COORD(float4(scrPos.x + 2 * myUnit, scrPos.y + 2 * myUnit, scrPos.zw))) * core;sum += tex2Dproj(_GrabTex, UNITY_PROJ_COORD(float4(scrPos.x - 2 * myUnit, scrPos.y + 2 * myUnit, scrPos.zw))) * core;sum += tex2Dproj(_GrabTex, UNITY_PROJ_COORD(float4(scrPos.x + 2 * myUnit, scrPos.y - 2 * myUnit, scrPos.zw))) * core;sum += tex2Dproj(_GrabTex, UNITY_PROJ_COORD(float4(scrPos.x - 2 * myUnit, scrPos.y - 2 * myUnit, scrPos.zw))) * core;sum += tex2Dproj(_GrabTex, UNITY_PROJ_COORD(float4(scrPos.x + 2 * myUnit, scrPos.y + 1 * myUnit, scrPos.zw))) * core * 2;sum += tex2Dproj(_GrabTex, UNITY_PROJ_COORD(float4(scrPos.x - 2 * myUnit, scrPos.y + 1 * myUnit, scrPos.zw))) * core * 2;sum += tex2Dproj(_GrabTex, UNITY_PROJ_COORD(float4(scrPos.x + 1 * myUnit, scrPos.y + 2 * myUnit, scrPos.zw))) * core * 2;sum += tex2Dproj(_GrabTex, UNITY_PROJ_COORD(float4(scrPos.x - 1 * myUnit, scrPos.y + 2 * myUnit, scrPos.zw))) * core * 2;sum += tex2Dproj(_GrabTex, UNITY_PROJ_COORD(float4(scrPos.x + 2 * myUnit, scrPos.y - 1 * myUnit, scrPos.zw))) * core * 2;sum += tex2Dproj(_GrabTex, UNITY_PROJ_COORD(float4(scrPos.x - 2 * myUnit, scrPos.y - 1 * myUnit, scrPos.zw))) * core * 2;sum += tex2Dproj(_GrabTex, UNITY_PROJ_COORD(float4(scrPos.x + 1 * myUnit, scrPos.y - 2 * myUnit, scrPos.zw))) * core * 2;sum += tex2Dproj(_GrabTex, UNITY_PROJ_COORD(float4(scrPos.x - 1 * myUnit, scrPos.y - 2 * myUnit, scrPos.zw))) * core * 2;sum += tex2Dproj(_GrabTex, UNITY_PROJ_COORD(float4(scrPos.x + 2 * myUnit, scrPos.y, scrPos.zw))) * core * 3;sum += tex2Dproj(_GrabTex, UNITY_PROJ_COORD(float4(scrPos.x - 2 * myUnit, scrPos.y, scrPos.zw))) * core * 3;sum += tex2Dproj(_GrabTex, UNITY_PROJ_COORD(float4(scrPos.x, scrPos.y - 2 * myUnit, scrPos.zw))) * core * 3;sum += tex2Dproj(_GrabTex, UNITY_PROJ_COORD(float4(scrPos.x, scrPos.y - 2 * myUnit, scrPos.zw))) * core * 3;sum += tex2Dproj(_GrabTex, UNITY_PROJ_COORD(float4(scrPos.x + 1 * myUnit, scrPos.y + 1 * myUnit, scrPos.zw))) * core * 4;sum += tex2Dproj(_GrabTex, UNITY_PROJ_COORD(float4(scrPos.x - 1 * myUnit, scrPos.y + 1 * myUnit, scrPos.zw))) * core * 4;sum += tex2Dproj(_GrabTex, UNITY_PROJ_COORD(float4(scrPos.x + 1 * myUnit, scrPos.y - 1 * myUnit, scrPos.zw))) * core * 4;sum += tex2Dproj(_GrabTex, UNITY_PROJ_COORD(float4(scrPos.x - 1 * myUnit, scrPos.y - 1 * myUnit, scrPos.zw))) * core * 4;sum += tex2Dproj(_GrabTex, UNITY_PROJ_COORD(float4(scrPos.x + 1 * myUnit, scrPos.y, scrPos.zw))) * core * 5;sum += tex2Dproj(_GrabTex, UNITY_PROJ_COORD(float4(scrPos.x - 1 * myUnit, scrPos.y, scrPos.zw))) * core * 5;sum += tex2Dproj(_GrabTex, UNITY_PROJ_COORD(float4(scrPos.x, scrPos.y - 1 * myUnit, scrPos.zw))) * core * 5;sum += tex2Dproj(_GrabTex, UNITY_PROJ_COORD(float4(scrPos.x, scrPos.y - 1 * myUnit, scrPos.zw))) * core * 5;sum += tex2Dproj(_GrabTex, UNITY_PROJ_COORD(scrPos)+ float4(bump, 0, 0)) * core * 6;float4 mainTex = tex2Dproj(_MainTex, UNITY_PROJ_COORD(scrPos));return saturate(sum) * _Color * mainTex;}ENDCG}}
}

放大镜


写一个放大镜主要思路:

  1. 进行操作的是中点(和第零章主角光环相同),需要在最后移回
  2. 使用step方法来区分是否在放大半径内
  3. 根据该像素点与放大中心的距离来按权重拉伸单位像素

核心代码如下:

fixed4 frag (v2f i) : SV_Target{float2 scrScale = float2(_ScreenParams.y /_ScreenParams.x, 1);float2 center = float2(.5, .5);float4 scrPos = i.scrPos;_Power *= 0.2;float2 dir = center - i.uv;//中点到该像素位置的向量float dis = length(dir);//中点到该位置的距离fixed inCircle = 1 - step(_Radius, dis);//选出圆心周围一定半径的圆形作为放大距离,返回值为1或0dis = 1 - dis;//距离越小的位置,放大的程度越大//在屏幕位置的基础上,加上 (将显示内容移回终点*整体放大程度*是否在圆圈内*抵消屏幕像素长宽比影响*不同位置缩放程度)fixed4 col = tex2Dproj(_GrabTex, UNITY_PROJ_COORD(float4(scrPos.xy + dir * _Power * inCircle * scrScale * dis, scrPos.zw)));col *= inCircle * _Color;//在镜面范围内染色return col;}

折射与水波

主要代码内容来自以下博客:

Unity Shader学习笔记 用UV动画实现沙滩上的泡沫

因为没有合适的泡沫贴图,无法调试…所以现在仅制作出了波光粼粼的三层表面,叠加了配合了表面波纹的动态的水下折射畸变效果。

在此记录一下在写(抄)代码时候的一些Q&A:
Q:uv只有xy两位,为什么这里使用了4维向量记录?

struct v2f
{float4 vertex : SV_POSITION;float4 uv1 : TEXCOORD0;//为什么是4维float4 uv2 : TEXCOORD1;float4 uv3 : TEXCOORD2;float2 uv4 : TEXCOORD3;//float2?float4 scrPos :TEXCOORD4;
};

A:Texture本身就有rgba四个通道,仅仅是代表着四个值。原文代码中,在片元函数中需要分别记录三层rbg以及a的uv,刚好是六对二维信息,可以用三个texture进行存储。
Q:TRANSFORM_TEX是什么?成对的TexName_ST是什么?

sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _Mask;
float4 _Mask_ST;
sampler2D _GrabTex;
float4 _GrabTex_ST;
sampler2D _NoiseTex;
float4 _NoiseTex_ST;

TRANSFORM_TEX详解

Q:这里计算u的一串公式代表什么?

float2 DelayOffsetUV(float2 uv, float offset, float offset_y)
{fixed pi = 3.14159f;float sinTime = sin(_Time[2] * _Speed * pi + offset * pi * 0.5f);float u = (sinTime + 1) * 0.5f * _WaveRange +(1-_WaveRange);//?uv.x += u;uv.y += offset_y;return uv;
}

使用_WaveRange = 0.4来测试了一下,发现返回的u是一个在[1-_WaveRange,1]之间进行正弦波动的值:
(像我一样无法用自己的脑子想象的朋友可以使用外设来提升理解能力)

Q:如何让折射的部分也随着波纹动起来?
A:刚开始是再片元着色器中先拿到了水波和折射内容两组颜色(水波颜色的水底没有扰动效果,折射颜色的水底有扰动效果),但是我希望水底的地面完全是从折射颜色中取得的(不要重影),这就意味着渲染出的水波颜色中透明的部分不能使用,因为能够直接看到没有经过扰动处理的水底(也就是无法通过单纯的相乘得到最后结果)。
并且水面的uv是直接来自v.uv->一些浮动处理->o.uv,那么_GrabTexture所拿到的uv信息同样应该受到DelayOffsetUV()的动态处理——因此scrPos在输出给片元着色器之前,也应该现在顶点着色器中进行动态处理。

//顶点着色器中需要:
//noise uvfloat2 uv_mask4 = DelayOffsetUV(inuv, _Offset03X - _AlphaDelay, _Offset03Y);o.uv4 = TRANSFORM_TEX(uv_mask4, _GrabTex);//片元着色器中需要:
//Noisefloat2 bump = UnpackNormal(tex2D(_NoiseTex,i.uv4));float2 offset = bump * _Distortion;fixed4 distorColor = tex2Dproj(_GrabTex, UNITY_PROJ_COORD(float4(i.scrPos + float4(offset, 0, 0))));

我希望最后的结果能够保留水波不透明的部分(也就是白色的“反光”部分),同时在水波颜色透明的地方使用扰动之后的水底效果。这是一个“如果”条件,着色器语言中可以使用 lerp 来描述“如果”条件:

fixed4 finalColor = lerp(distorColor, WaveColor, WaveColor.a);

翻译:如果水波颜色中Alpha越大,即不透明的地方,就显示水波颜色,否则就显示扰动处理后的水底颜色。

修改后的完整shader代码:

Shader "Lesson/sd_WaterFace"
{Properties{_MainTex ("Texture", 2D) = "white" {}_Color("Tint Color", Color) = (1,1,1,1)_Mask("Mask", 2D) = "white"{}_Speed("Speed", Range(-1, 1)) = 0_WaveRange("Wave Range", Range(-2, 2)) = 1_AlphaDelay("Alpha Delay", Range(0, 1)) = 0.5_NoiseTex("Noise Tes", 2D) = "white"{}_Distortion("Grab Distortion", Range(0, 2)) = 0.5_Offset01X("Offset 01 X", Range(-2, 2)) = 0_Offset01Y("Offset 01 Y", Range(-2, 2)) = 0_Offset02X("Offset 02 X", Range(-2, 2)) = 0_Offset02Y("Offset 02 Y", Range(-2, 2)) = 0_Offset03X("Offset 03 X", Range(-2, 2)) = 0_Offset03Y("Offset 03 Y", Range(-2, 2)) = 0}SubShader{Tags {"RenderType"="Transparent" "Queue" = "Transparent"}LOD 100GrabPass{"_GrabTex"}Pass{ZWrite offBlend SrcAlpha OneMinusSrcAlphaCGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"struct appdata{float4 vertex : POSITION;float2 uv : TEXCOORD0;};struct v2f{float4 vertex : SV_POSITION;float4 uv1 : TEXCOORD0;//为什么是4维float4 uv2 : TEXCOORD1;float4 uv3 : TEXCOORD2;float2 uv4 : TEXCOORD3;//float2?float4 scrPos :TEXCOORD4;};sampler2D _MainTex;float4 _MainTex_ST;sampler2D _Mask;float4 _Mask_ST;sampler2D _GrabTex;float4 _GrabTex_ST;sampler2D _NoiseTex;float4 _NoiseTex_ST;fixed4 _Color;float _Speed;float _WaveRange;float _AlphaDelay;float _Distortion;float2 _GrabTex_TexelSize;float _Offset01X;float _Offset01Y;float _Offset02X;float _Offset02Y;float _Offset03X;float _Offset03Y;float2 DelayOffsetUV(float2 uv, float offset, float offset_y){fixed pi = 3.14159f;float sinTime = sin(_Time[2] * _Speed * pi + offset * pi * 0.5f);float u = (sinTime + 1) * 0.5f * _WaveRange +(1-_WaveRange);uv.x += u;uv.y += offset_y;return uv;}v2f vert (appdata v){v2f o;o.vertex = UnityObjectToClipPos(v.vertex);o.uv4 = v.uv;o.scrPos = ComputeGrabScreenPos(o.vertex);float2 inuv = v.uv;//rgbfloat2 uv_text1 = DelayOffsetUV(inuv, _Offset01X, _Offset01Y);o.uv1.xy = TRANSFORM_TEX(uv_text1, _MainTex);float2 uv_text2 = DelayOffsetUV(inuv, _Offset02X, _Offset02Y);o.uv2.xy = TRANSFORM_TEX(uv_text2, _MainTex);float2 uv_text3 = DelayOffsetUV(inuv, _Offset03X, _Offset03Y);o.uv3.xy = TRANSFORM_TEX(uv_text3, _MainTex);//Alphafloat2 uv_mask1 = DelayOffsetUV(inuv, _Offset01X - _AlphaDelay, _Offset01Y);o.uv1.zw = TRANSFORM_TEX(uv_mask1, _Mask);float2 uv_mask2 = DelayOffsetUV(inuv, _Offset02X - _AlphaDelay, _Offset02Y);o.uv2.zw = TRANSFORM_TEX(uv_mask2, _Mask);float2 uv_mask3 = DelayOffsetUV(inuv, _Offset03X - _AlphaDelay, _Offset03Y);o.uv3.zw = TRANSFORM_TEX(uv_mask3, _Mask);//noise uvfloat2 uv_mask4 = DelayOffsetUV(inuv, _Offset03X - _AlphaDelay, _Offset03Y);o.uv4 = TRANSFORM_TEX(uv_mask4, _GrabTex);return o;}fixed GetFadeOutAlpha(float Offset){fixed pi = 3.14159f;float time = _Time.y * _Speed * pi + Offset * pi * 0.5 + 1.2 * pi;fixed a = (sin(time) + 1) * 0.5f;a *= a;return a;}fixed4 ColorBlend(fixed4 colorA, fixed4 colorB){fixed4 finalColor;finalColor.a = colorA.a + colorB.a  - colorA.a * colorB.a;colorA.a = max(0.001, colorA.a);  //避免下一步的除0错误finalColor.rgb = (colorA * colorA.a * (1 - colorB.a) + colorB * colorB.a) / colorA.a;return finalColor;}fixed4 frag (v2f i) : SV_Target{ //Noisefloat2 bump = UnpackNormal(tex2D(_NoiseTex,i.uv4));float2 offset = bump * _Distortion;fixed4 distorColor = tex2Dproj(_GrabTex, UNITY_PROJ_COORD(float4(i.scrPos + float4(offset, 0, 0))));//Surface rgbfixed4 color1 = tex2D(_MainTex, i.uv1.xy);fixed4 color2 = tex2D(_MainTex, i.uv2.xy);fixed4 color3 = tex2D(_MainTex, i.uv3.xy);//Wave Alphacolor1.a = tex2D(_Mask, i.uv1.zw) * GetFadeOutAlpha(_Offset01X);color2.a = tex2D(_Mask, i.uv2.zw) * GetFadeOutAlpha(_Offset02X);color3.a = tex2D(_Mask, i.uv3.zw) * GetFadeOutAlpha(_Offset03X);//Wave Colorfixed4 WaveColor = ColorBlend(color1, color2);WaveColor = ColorBlend(WaveColor, color3);WaveColor.a = WaveColor.a * 0.2;fixed4 finalColor = lerp(distorColor, WaveColor, WaveColor.a);return finalColor * _Color;}ENDCG}}
}

更多推荐

shader攻占笔记(七)抓屏玻璃大杂烩 GrabPass{}

本文发布于:2024-03-11 22:29:38,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1730063.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:大杂烩   玻璃   笔记   shader   GrabPass

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!