【Reading Notes】cp6-Fragment Shaders and Grab Pass

xiaoxiao2021-02-28  109

写在前面

到目前为止,我们都是基于Surface Shaders来实现功能。Surface Shader的设计提供了一种简单的shader编码实现方法,给艺术家提供了十分有意义的工具。如果我们想要更进一步的了解Shader,是时候踏进Vertex(顶点)和Fragment(片元)着色器的领域了。

本章内容: + 明白顶点着色和片元着色的基本 + 使用Grab pass + 实现Grab 着色 + 实现水效果,2D游戏

介绍

相对于Surface Shader, 顶点和片元着色器少了物理着色功能,但换来的是更强大的能量,没有了物理束缚,可以实现很多非真实的渲染效果。本章节将着重介绍Grab Pass,她使shader可以模拟物体的形变。

明白顶点着色器和片元着色器的基本

学习vertex和Fragment着色器的最好方法是,亲自动手去实现一个。这一小节将实现一个简单的vertex,fragment着色器,简单的读取主纹理和颜色。

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)' // Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)' Shader "CookbookShaders/self/grab" { Properties { _MainTex("Base (RGB) Trans(A)", 2D) = "white"{} _Color("Color", Color) = (1, 1, 1, 1) } SubShader{ Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" sampler2D _MainTex; float _Magnitude; float4 _Color; struct vertInput { float4 vertex: POSITION; float2 texcoord: TEXCOORD0; }; struct vertOutput { float4 vertex: POSITION; float2 texcoord: TEXCOORD0; }; vertOutput vert(vertInput v) { vertOutput o; o.vertex = UnityObjectToClipPos(v.vertex); o.texcoord = v.texcoord; return o; } half4 frag(vertOutput i):COLOR { half4 mainColor = tex2D(_MainTex, i.texcoord); return col * mainColor * _Color; } ENDCG } } FallBack "Diffuse" }

How is works 几个要点

#pragma预编译指令,指定顶点着色函数和片元着色函数 #pragma vertex vert #pragma fragment frag 定义函数的输入和输出结构,这里涉及到语义关联,后面将会说到 struct vertInput { float4 vertex: POSITION; float2 texcoord: TEXCOORD0; }; struct vertOutput { float4 vertex: POSITION; float2 texcoord: TEXCOORD0; }; 然后顶点函数(vert)和片元函数(frag)执行逻辑,计算最后的颜色。 vert:在顶点着色器中主要的工作是把3D对象的本地坐标转换到视锥体,这里新版的Unity使用了UnityObjectToClipPos()的内置函数,以前的是 UNITY_MATRIX_MVP内置宏。然后是传递纹理坐标frag:进行纹理采样,和_Color混合,得到最后的颜色。

语义绑定

输入结构语言绑定 语义描述POSITION, SV_POSITION顶点的坐标,在模型空间内,float3/float4NORMAL顶点的法向量,float3TEXCOORD0 … TEXCOORDi纹理坐标,float2/float3/float4TANGENT切向量,用于法线映射,float4COLOR, COLOR0, DIFFUSE, SV_TARGET顶点颜色信息, float4COLOR1顶点存储的第二颜色,通常是高光颜色。float4

+ 输出结构语义绑定

语义描述POSITION, SV_POSITION, HPOS顶点坐标,在摄像机坐标系内(clip space,范围0-1)COLOR, COLOR0, COL0, COL, SV_TARGET主颜色COLOR1, COL1第二颜色TEXCOORD0…TEXCOORDi纹理坐标WPOSThe position, in pixels, in the window (origin in the lower left corner)(不是很懂)

虽说是语义绑定,但是其实要在结构里面存储什么样的数据都是开发者可以控制的。你完全可以用POSITION语义保存NORMAL的数据。

使用Grab Pass

在第四章中为PBR材质添加了透明属性,了解到一个材质是如何实现透明的。虽然一个透明程度材质可以被绘制在场景中,但却不能改变场景中已经绘制的东西。这也就表示这些材质不能表现出玻璃或者水下的舞台形变。为了模拟这类形变,在这里介绍一种新的技术:Grab pass,这允许我们访问到当前绘制为止已经绘制在场景中的数据并使用他。

Grab pass

代码很简单,grab pass是Unity定义的一个特殊的pass,他将自动创建一个名字为TextureName的纹理,默认的名称为_GrabTexture,你需要在下一个pass中定义才能进行访问。通过对该纹理的采样就可以得到当前绘制之前的内容。计算采样的纹理坐标的思路是把顶点坐标转换成屏幕坐标,这里Unity为我们提供了内之爱函数:ComputeGrabScreenPos(o.vertex);,然后在片元着色器真正采样的时候使用宏UNITY_PROJ_COORD得到最后的结果,这个宏可以认为他就是返回了原本的值,查看他的定义可以知道只是根据PSP做了区分。

#if defined(SHADER_API_PSP2) #define UNITY_BUGGY_TEX2DPROJ4 #define UNITY_PROJ_COORD(a) (a).xyw #else #define UNITY_PROJ_COORD(a) a #endif GrabPass{"TextureName"}

实现玻璃效果

玻璃是一个比较复杂的材质,大部分玻璃材质并不完美,因为为了更完美需要使的透过玻璃看到的物体产生形变。思想是在顶点和片元着色器中利用Grab pass的texture稍微改变UV纹理坐标去构造形变。

可以看出透过球体能看到形变(不怎么透明,还是可以看出来的),二期从侧面看还有环境映射的效果。从片元着色器代码可以看出,我们沿法线方向改变了对Grab pass纹理的采样,而达到看上去的形变效果。

Shader "CookbookShaders/self/grab" { Properties { _MainTex("Base (RGB) Trans(A)", 2D) = "white"{} _BumpMap("Noise text", 2D) = "bump" {} _Magnitude("Magnitude", Range(0, 1)) = 0.05 _Color("Color", Color) = (1, 1, 1, 1) } SubShader{ Tags{ "Queue" = "Transparent" } GrabPass { "_GrabTexture" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" sampler2D _GrabTexture; sampler2D _MainTex; sampler2D _BumpMap; float _Magnitude; float4 _Color; struct vertInput { float4 vertex: POSITION; float2 texcoord: TEXCOORD0; }; struct vertOutput { float4 vertex: POSITION; float2 texcoord: TEXCOORD0; float4 uvgrab: TEXCOORD1; }; vertOutput vert(vertInput v) { vertOutput o; o.vertex = UnityObjectToClipPos(v.vertex); o.texcoord = v.texcoord; o.uvgrab = ComputeGrabScreenPos(o.vertex); return o; } half4 frag(vertOutput i):COLOR { half4 mainColor = tex2D(_MainTex, i.texcoord); half4 bump = tex2D(_BumpMap, i.texcoord); half2 distortion = UnpackNormal(bump).rg; i.uvgrab.xy += distortion * _Magnitude; fixed4 col = tex2Dproj(_GrabTexture, UNITY_PROJ_COORD(i.uvgrab)); return col * mainColor * _Color; } ENDCG } } FallBack "Diffuse" }

实现水,2D Games

原理和上面的差不过,只不过增加了噪声贴图和引入sin函数,实现对形变的随时间的变化而改变。这里不过多叙述。

Shader "CookbookShaders/self/water" { Properties{ _NoiseTex("Base (RGB) Trans(A)", 2D) = "white"{} _Period("Period", Range(0, 50)) = 1 _Magnitude("Magnitude", Range(0, 1)) = 0.05 _Color("Color", Color) = (1, 1, 1, 1) _Scale("Scale", Range(0, 10)) = 1 } SubShader{ Tags{ "Queue" = "Transparent" } GrabPass { "_GrabTexture" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" sampler2D _GrabTexture; sampler2D _NoiseTex; float _Period; float _Magnitude; float4 _Color; float _Scale; struct vertInput { float4 vertex: POSITION; float2 texcoord: TEXCOORD0; float4 uvgrab: TEXCOORD2; }; struct vertOutput { float4 vertex: POSITION; fixed4 color : COLOR; float2 texcoord: TEXCOORD0; float4 worldPos: TEXCOORD1; float4 uvgrab: TEXCOORD2; }; vertOutput vert(vertInput v) { vertOutput o; o.vertex = UnityObjectToClipPos(v.vertex); o.texcoord = v.texcoord; o.worldPos = mul(unity_ObjectToWorld, v.vertex); o.uvgrab = ComputeGrabScreenPos(o.vertex); return o; } half4 frag(vertOutput i) :COLOR { float sinT = sin(_Time.w / _Period); float2 distortion = float2(tex2D(_NoiseTex, i.worldPos.xy / _Scale + float2(sinT, 0)).r - 0.5, tex2D(_NoiseTex, i.worldPos.xy / _Scale + float2(0, sinT)).r - 0.5 ); i.uvgrab.xy += distortion * _Magnitude; fixed4 col = tex2Dproj(_GrabTexture, UNITY_PROJ_COORD(i.uvgrab)); col *= _Color; return col; } ENDCG } } FallBack "Diffuse" }

最后

从效率上来说,Grab Pass还是比较差的,每次都要渲染生成一张额外的texture

转载请注明原文地址: https://www.6miu.com/read-68975.html

最新回复(0)