unity shader:非真实感渲染

时间:2022-05-12 21:56:44

非真实感渲染的定义:就是使用一些渲染方法使得画面达到某些特殊的绘画风格相似的效果,简称NPR。

渲染轮廓线的方式:常见渲染方式如下:
1.基于观察角度和表面法线的轮廓线渲染:这种方法就是使用视角方向和法线方向的点乘结果来得到轮廓信息。但是很多模型渲染出来的描边效果却不尽人意,而且也不能控制轮廓线风格的渲染。
2.过程式几何轮廓线渲染:这种方法就是使用两个pass渲染,第一个pass用来绘制背面的片面并且使用一些技术让它的轮廓可见。第二个pass再正常渲染正面的面片。但是这种方法不适用平整的立方体模型,而且也不能控制轮廓线风格的渲染。
3.基于图像处理的轮廓线渲染:这种方法就是使用边缘检测的方式来识别轮廓信息。但是像桌子上纸张这种一些深度和法线变化小的轮廓却无法被检测出来,而且也不能控制轮廓线风格的渲染。
4.基于轮廓边检测的轮廓线渲染:就是通过判断一条边相邻的两个面片是否满足(n0.v >0) # (n1.v > 0)计算公式,也就是一条边相邻两个面片是否一个朝正面,一个朝背面
的方式来精确的获取一个轮廓边,然后按照自己想要的风格来直接渲染它们。但是由于逐帧提取额轮廓边,所以帧之间会出现跳跃性。
5.混合1~4方式的轮廓线渲染:也就是首先精确的获取轮廓边,把模型和轮廓边渲染到纹理当中,再使用图像处理的方式识别轮廓边,并在图像空间中实现不同风格式的渲染。

卡通风格的渲染:使用漫反射系数对一张纹理进行采样,以控制漫反射的色调。然后对轮廓线渲染来进行描边。并控制好高光反射系数,让高光系数和阈值进行比较,使高光区域边缘平滑渐变,从而实现卡通风格的渲染。实现代码如下:

// 这个shader用来绘制模型背面并使视角方向按照法线进行偏移,使轮廓线渲染可以看见
SubShader {
        Tags { "RenderType"="Opaque" "Queue"="Geometry"}

        Pass {
            NAME "OUTLINE"

            Cull Front

            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            float _Outline;
            fixed4 _OutlineColor;

            struct a2v {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            }; 

            struct v2f {
                float4 pos : SV_POSITION;
            };

            v2f vert (a2v v) {
                v2f o;

                float4 pos = mul(UNITY_MATRIX_MV, v.vertex); 
                float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);  
                normal.z = -0.5;
                pos = pos + float4(normalize(normal), 0) * _Outline;
                o.pos = mul(UNITY_MATRIX_P, pos);

                return o;
            }

            float4 frag(v2f i) : SV_Target { 
                return float4(_OutlineColor.rgb, 1);               
            }

            ENDCG
        }
// 这个shader主要对漫反射进行纹理采样并且控制高光区域边缘渐变
v2f vert (a2v v) {
    v2f o;

    o.pos = mul( UNITY_MATRIX_MVP, v.vertex);
    o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);
    o.worldNormal  = UnityObjectToWorldNormal(v.normal);
    o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

    TRANSFER_SHADOW(o);

    return o;
    }

    float4 frag(v2f i) : SV_Target { 
    fixed3 worldNormal = normalize(i.worldNormal);
    fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
    fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
    fixed3 worldHalfDir = normalize(worldLightDir + worldViewDir);

    fixed4 c = tex2D (_MainTex, i.uv);
    fixed3 albedo = c.rgb * _Color.rgb;

    fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

    UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);

    fixed diff =  dot(worldNormal, worldLightDir);
    diff = (diff * 0.5 + 0.5) * atten;

    fixed3 diffuse = _LightColor0.rgb * albedo * tex2D(_Ramp, float2(diff, diff)).rgb;

    fixed spec = dot(worldNormal, worldHalfDir);
    fixed w = fwidth(spec) * 2.0;
    fixed3 specular = _Specular.rgb * lerp(0, 1, smoothstep(-w, w, spec + _SpecularScale - 1)) * step(0.0001, _SpecularScale);

    return fixed4(ambient + diffuse + specular, 1.0);
    }

素描风格的渲染:使用6张素描纹理进行渲染。在顶点着色器中计算逐顶点光照,根据光照结果决定6张素描纹理的混合权重,并传递给片元着色器,然后在片元着色器中根据这些权重来混合6张纹理的采样结果,从而实现素描风格的渲染。实现代码如下:

v2f vert(a2v v) {
    v2f o;

    o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

    o.uv = v.texcoord.xy * _TileFactor;

    fixed3 worldLightDir = normalize(WorldSpaceLightDir(v.vertex));
    fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
    fixed diff = max(0, dot(worldLightDir, worldNormal));

    o.hatchWeights0 = fixed3(0, 0, 0);
    o.hatchWeights1 = fixed3(0, 0, 0);

    float hatchFactor = diff * 7.0;

    if (hatchFactor > 6.0) {
        // Pure white, do nothing
    } else if (hatchFactor > 5.0) {
        o.hatchWeights0.x = hatchFactor - 5.0;
    } else if (hatchFactor > 4.0) {
        o.hatchWeights0.x = hatchFactor - 4.0;
        o.hatchWeights0.y = 1.0 - o.hatchWeights0.x;
    } else if (hatchFactor > 3.0) {
        o.hatchWeights0.y = hatchFactor - 3.0;
        o.hatchWeights0.z = 1.0 - o.hatchWeights0.y;
    } else if (hatchFactor > 2.0) {
        o.hatchWeights0.z = hatchFactor - 2.0;
        o.hatchWeights1.x = 1.0 - o.hatchWeights0.z;
    } else if (hatchFactor > 1.0) {
        o.hatchWeights1.x = hatchFactor - 1.0;
        o.hatchWeights1.y = 1.0 - o.hatchWeights1.x;
    } else {
        o.hatchWeights1.y = hatchFactor;
        o.hatchWeights1.z = 1.0 - o.hatchWeights1.y;
    }

    o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

    TRANSFER_SHADOW(o);

    return o; 
    }

fixed4 frag(v2f i) : SV_Target {            
    fixed4 hatchTex0 = tex2D(_Hatch0, i.uv) * i.hatchWeights0.x;
    fixed4 hatchTex1 = tex2D(_Hatch1, i.uv) * i.hatchWeights0.y;
    fixed4 hatchTex2 = tex2D(_Hatch2, i.uv) * i.hatchWeights0.z;
    fixed4 hatchTex3 = tex2D(_Hatch3, i.uv) * i.hatchWeights1.x;
    fixed4 hatchTex4 = tex2D(_Hatch4, i.uv) * i.hatchWeights1.y;
    fixed4 hatchTex5 = tex2D(_Hatch5, i.uv) * i.hatchWeights1.z;
    fixed4 whiteColor = fixed4(1, 1, 1, 1) * (1 - i.hatchWeights0.x - i.hatchWeights0.y - i.hatchWeights0.z - 
                i.hatchWeights1.x - i.hatchWeights1.y - i.hatchWeights1.z);

    fixed4 hatchColor = hatchTex0 + hatchTex1 + hatchTex2 + hatchTex3 + hatchTex4 + hatchTex5 + whiteColor;

    UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);

    return fixed4(hatchColor.rgb * _Color.rgb * atten, 1.0);
    }

注意:
1.step(p1,p2)函数的意思就是p2小于p1时返回0否则返回1。而smoothstep(-w, w, p)则表示p在[-w,w]之间时返回的是0到1之间的插值,否则如果p小于-w则返回0,如果p大于w则返回1。
2.fwidth函数用来获取相邻像素之间的近似导数值。