庄懂老师TA学习笔记 - 玉石效果

我们先来看一下玉石效果是什么样子的

它的制作原理大体分为两个部分

1)先通过半兰伯特光照模型,将一张玉石贴图映射到模型上,这张贴图的特点是半兰伯特的亮部用暗色,暗部用亮色,这样就可以制作出受光面比较暗,其他面亮的效果,贴图如下图所示(PS:由于Up是程序员,所以这张图没有做好,以后做的更好了再来更新)

2)通过添加菲涅尔反射,实现玉石周围泛光效果

ShaderForge版

代码版

Shader "Unlit/JadeCode"
{
    Properties
    {
        _RampTex ("Ramp Tex", 2D) = "white" {}
        _RimColor("Rim Color", color) = (1,1,1,1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_instancing

            #include "UnityCG.cginc"
            #include "AutoLight.cginc"

            struct appdata
            {
                UNITY_VERTEX_INPUT_INSTANCE_ID
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };

            struct v2f
            {
                UNITY_VERTEX_INPUT_INSTANCE_ID
                float4 vertex : SV_POSITION;
                float4 worldPos : TEXCOORD0;
                float3 worldNormal : TEXCOORD1;
                LIGHTING_COORDS(2,3)
            };

            sampler2D _RampTex;
            float4 _RampTex_ST;
            UNITY_INSTANCING_BUFFER_START( Props )
            UNITY_DEFINE_INSTANCED_PROP( float4, _RimColor)
            UNITY_INSTANCING_BUFFER_END( Props )

            v2f vert (appdata v)
            {
                v2f o;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_TRANSFER_INSTANCE_ID(v, o);
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                TRANSFER_VERTEX_TO_FRAGMENT(o)
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                UNITY_SETUP_INSTANCE_ID(i);
                float3 nDir = normalize(i.worldNormal);
                float3 lDir = normalize(_WorldSpaceLightPos0.xyz);
                float3 vDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
                float nDotl = 0.5 + dot(nDir, lDir) * 0.5;
                float4 texColor = tex2D(_RampTex,TRANSFORM_TEX(float2(nDotl, 0.5), _RampTex));
                // fresnel = fresnel基础值 + fresnel缩放量*pow( 1 - dot( N, V ), 5 )
                float4 fresnel = pow(1 - max(0, dot(nDir, vDir)), 5.0) * _RimColor;
                // 混合两个颜色 Screen混合公式
                float4 color = saturate((1.0-(1.0-texColor)*(1.0-fresnel)));
                return color;
            }
            ENDCG
        }
    }
}

这段代码共有这样几个知识点

1)菲涅尔反射公式 

fresnel = fresnel基础值 + fresnel缩放量*pow( 1 - dot( N, V ), 常数x )

我们先看一下菲尼尔反射的定义 ,引用自CSDN的一段介绍

 当你站在湖边,低头看脚下的水,你会发现水是透明的,反射不是特别强烈,如果你抬头看远处的湖面,会发现水不是透明的,反射非常强烈,这就是“菲涅尔效应”。从数学上来讲,就是视线垂直于水平面呈90°,则反射较弱,而视线方向逐渐改变,非垂直于水平面,而是与水平面夹角从90°慢慢减小,反射就越明显。如果你正对着看一个球体,视线聚焦于球心的时候反射较弱,视线越靠近球体边缘反射越强,这个菲涅尔反射效应在现实世界和三维图形中有很广泛的使用。

看了这段介绍,我们大体知道这个菲尼尔反射就是一个物体边缘的反射,这个反射的强度主要是根据法线方向和视线方向决定的。简单的讲,就是视线垂直于表面时,反射较弱,而当视线非垂直表面时,夹角越小,反射越明显。如果你看向一个圆球,那圆球中心的反射较弱,靠近边缘较强。

根据这个原理,我们需要先获取到发现方向和视线方向,将他们点乘求出两个方向的重合程度,并把他们限制在(0,1)之间,对于圆球中心的坐标,他的法线方向和我们视线方向方向相同乘积为1,对于最边缘的坐标,他的法线方向和视线方向垂直乘积为0。但是我们需要重合程度越大反射效果越不明显,越小越明显,所以我们可以通过1减这个乘积得到一个我们需要的数值,最后通过进行pow操作进行强度的缩放,也就是公式里的pow( 1 - dot( N, V ), 常数x )。

2)GPU instancing

我们看一下GPU instacing的定义

使用 GPU 实例化可使用少量绘制调用一次绘制(或渲染)同一网格的多个副本。它对于绘制诸如建筑物、树木和草地之类的在场景中重复出现的对象非常有用。

这段话没有明确说明他的定义是什么,但是告诉我们如果材质启用了GPU instancing,那么就可以在减少绘制调用,也就是我们常说的draw call,将使用GPU instancing材质的物体全部打包一次发送给GPU,减少了CPU的压力,非常适合大量重复物体使用。在我们的Shader中,要使用GPU instancing,需要如下几步操作

1.声明我们需要使用instancing 

#pragma multi_compile_instancing

2.在顶点和片元的输出结构中,为他们定义instance ID 

struct appdata
            {
                UNITY_VERTEX_INPUT_INSTANCE_ID
                ...
            };

            struct v2f
            {
                UNITY_VERTEX_INPUT_INSTANCE_ID
                ...
            };

 3.将属性的声明改为instancing模式

            UNITY_INSTANCING_BUFFER_START( Props )
            UNITY_DEFINE_INSTANCED_PROP( float4, _RimColor)
            UNITY_INSTANCING_BUFFER_END( Props )

4.通过宏使Shader的函数可以获取到instance id,并使用特定的函数进行变量获取

 v2f vert (appdata v)
            {
                v2f o;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_TRANSFER_INSTANCE_ID(v, o);
                ...
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                UNITY_SETUP_INSTANCE_ID(i);
                ...
                float4 instancingRimColor = UNITY_ACCESS_INSTANCED_PROP( Props, _RimColor);
                ...
                return color;
            }

3)菲涅尔和基础颜色的融合公式

我们最后使用的公式是滤色模式公式,我们看一下网络上对滤色模式的解释

一、滤色screen混合模式速览

screen混合模式,国内称为“滤色”,其计算公式是:

de2c7005c7135f747115e5aa741a5870.png

公式中的C表示最终混合的RGB色值(范围是0-255),A和B表示用来混合的两个颜色的RGB色值(范围也是0-255)。

从公式的内容可以看出,滤色混合模式的颜色,是将两个颜色的互补色的像素值相乘,然后除以255的互补色值。

例如有一个红色,其RGB值是(255,0,0),还有一个蓝色,其RGB值是(0,0,255),则这两个颜色使用滤色混合模式之后的颜色色值是:

R = 255 – (255 – 255) * (255 – 0) / 255 = 255

G = 255 – (255 – 0) * (255 – 0) / 255 = 0

B = 255 – (255 – 0) * (255 – 255) / 255 = 255 

滤色模式是PS中的一个图片叠加模式,我们再来看一下其他模式,引用知乎博主的一张图片

猜你喜欢

转载自blog.csdn.net/HelloCSDN666/article/details/124644845
今日推荐