【Reading Notes】cp6-Fragment Shaders and Grab Pass

写在前面

到目前为止,我们都是基于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/float4
NORMAL 顶点的法向量,float3
TEXCOORD0 … TEXCOORDi 纹理坐标,float2/float3/float4
TANGENT 切向量,用于法线映射,float4
COLOR, COLOR0, DIFFUSE, SV_TARGET 顶点颜色信息, float4
COLOR1 顶点存储的第二颜色,通常是高光颜色。float4

+ 输出结构语义绑定

语义 描述
POSITION, SV_POSITION, HPOS 顶点坐标,在摄像机坐标系内(clip space,范围0-1)
COLOR, COLOR0, COL0, COL, SV_TARGET 主颜色
COLOR1, COL1 第二颜色
TEXCOORD0…TEXCOORDi 纹理坐标
WPOS The 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纹理坐标去构造形变。

image
image

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

扫描二维码关注公众号,回复: 908914 查看本文章
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函数,实现对形变的随时间的变化而改变。这里不过多叙述。
image

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

猜你喜欢

转载自blog.csdn.net/coderling/article/details/76796635