UnityShader-LowPoly

7 篇文章 3 订阅
4 篇文章 0 订阅

LowPoly

一种低多边形风格画作或效果。
参考文章:
【Unity Shader】新书封面 — Low Polygon风格的渲染
图形学进阶——曲面细分与几何着色器
效果如下:

在这里插入图片描述
在这里插入图片描述

原理

在这里插入图片描述
就我个人的理解来看,原始的法线如上面的左图,在片元着色器阶段,会将三角形三个法线做插值得到当前着色点的法线,从而产生平滑的效果,也就是右图。
在这里插入图片描述
LowPoly风格正好是不需要平滑处理的,反而就是要左图这种棱角分明的感觉,所以核心思想就是当前所在三角形只使用一个法线,让该三角形区域的着色点使用的法线都是同一个。

Shader

Shader "NPR/LowPolyStyle"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
    }
    SubShader
    {
        Pass{
            Tags {"LightMode"="ForwardBase"}

            CGPROGRAM
            #pragma multi_compile_fwdbase

            #pragma target 4.0
            #pragma vertex vert
            #pragma geometry geom
            #pragma fragment frag

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

            fixed4 _Color;

            struct v2g{
                float4 pos:SV_POSITION;
                float2 uv:TEXCOORD0;
                float3 worldPos:TEXCOORD1;
            };

            struct g2f{
                float4 pos:SV_POSITION;
                float2 uv:TEXCOORD0;
                float3 worldPos:TEXCOORD1;
                float3 faceNormal:TEXCOORD2;
            };

            v2g vert(appdata_base v){
                v2g o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = v.texcoord;
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                return o;
            }

            [maxvertexcount(3)] //用于定义最大输出点
            void geom(triangle v2g IN[3], inout TriangleStream<g2f> triStream){
                // 定义图元输入:point、 line、 lineadj、 triangle、 triangleadj 分别为点线面
                // 定义图元输出:PointStream、 LineStream、 TriangleStream 同上

                float3 A = IN[1].worldPos.xyz - IN[0].worldPos.xyz;
                float3 B = IN[2].worldPos.xyz - IN[0].worldPos.xyz;
                float3 fn = normalize(cross(A, B));

                g2f o;
                o.pos = IN[0].pos;
                o.uv = IN[0].uv;
                o.worldPos = IN[0].worldPos;
                o.faceNormal = fn;
                triStream.Append(o);
                
                o.pos = IN[1].pos;
                o.uv = IN[1].uv;
                o.worldPos = IN[1].worldPos;
                o.faceNormal = fn;
                triStream.Append(o);
                
                o.pos = IN[2].pos;
                o.uv = IN[2].uv;
                o.worldPos = IN[2].worldPos;
                o.faceNormal = fn;
                triStream.Append(o);
            }

            fixed4 frag(g2f i):SV_Target{
                fixed3 lightDir = UnityWorldSpaceLightDir(i.worldPos);
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
				fixed3 normalDir = normalize(i.faceNormal);

				fixed diff = saturate(dot(normalDir, lightDir));

				fixed3 diffuse = _LightColor0.rgb * _Color.rgb * diff;

 				return fixed4(diffuse + ambient, 1);
            }

            ENDCG
        }

        Pass {
    		Tags {"LightMode"="ForwardAdd"}

    		Blend One One
		
			CGPROGRAM

			#pragma multi_compile_fwdadd

			#pragma target 4.0
			#pragma vertex vert
			#pragma geometry geom
			#pragma fragment frag

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

			fixed4 _Color;
		
			struct v2g {
    			float4 pos : SV_POSITION;
    			float2 uv : TEXCOORD0;
    			float3 worldPos : TEXCOORD1;
    			float4 _ShadowCoord : TEXCOORD2;
			};
			
			struct g2f {
    			float4 pos : SV_POSITION;
    			float2 uv : TEXCOORD0;
    			float3 worldPos : TEXCOORD1;
    			float3 faceNormal : TEXCOORD2;
    			float4 _ShadowCoord : TEXCOORD3;
			};

			v2g vert(appdata_base v) {
    			v2g o;
    			o.pos = UnityObjectToClipPos(v.vertex);
    			o.uv = v.texcoord;
    			o.worldPos = mul(unity_ObjectToWorld, v.vertex);

    			o._ShadowCoord = mul(unity_WorldToShadow[0], mul(unity_ObjectToWorld, v.vertex));

    			return o;
			}
			
			[maxvertexcount(3)]
			void geom(triangle v2g IN[3], inout TriangleStream<g2f> triStream) {
				float3 A = IN[1].worldPos.xyz - IN[0].worldPos.xyz;
				float3 B = IN[2].worldPos.xyz - IN[0].worldPos.xyz;
				float3 fn = normalize(cross(A, B));

				float3 worldPos = (IN[0].worldPos + IN[1].worldPos + IN[2].worldPos)/3.0;
			
				g2f o;
				o.pos = IN[0].pos;
				o.uv = IN[0].uv;
				o.worldPos = worldPos;
				o.faceNormal = fn;
				o._ShadowCoord = IN[0]._ShadowCoord;
				triStream.Append(o);

				o.pos = IN[1].pos;
				o.uv = IN[1].uv;
				o.worldPos = worldPos;
				o.faceNormal = fn;
				o._ShadowCoord = IN[1]._ShadowCoord;
				triStream.Append(o);

				o.pos = IN[2].pos;
				o.uv = IN[2].uv;
				o.worldPos = worldPos;
				o.faceNormal = fn;
				o._ShadowCoord = IN[2]._ShadowCoord;
				triStream.Append(o);
			}
			
			fixed4 frag(g2f i) : SV_Target {
				fixed3 lightDir = UnityWorldSpaceLightDir(i.worldPos);
				fixed3 normalDir = normalize(i.faceNormal);

				fixed diff = saturate(dot(normalDir, lightDir));

				UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);

				fixed3 diffuse = _LightColor0.rgb * _Color.rgb * diff * atten * atten;

 				return fixed4(diffuse, 1);
			}
			
			ENDCG
    	}
    }
    FallBack "Diffuse"
}

重点分析一下几何着色器的代码:

[maxvertexcount(3)] //用于定义最大输出点
void geom(triangle v2g IN[3], inout TriangleStream<g2f> triStream){
    // 定义图元输入:point、 line、 lineadj、 triangle、 triangleadj 分别为点线面
    // 定义图元输出:PointStream、 LineStream、 TriangleStream 同上

    float3 A = IN[1].worldPos.xyz - IN[0].worldPos.xyz;
    float3 B = IN[2].worldPos.xyz - IN[0].worldPos.xyz;
    float3 fn = normalize(cross(A, B));

    g2f o;
    o.pos = IN[0].pos;
    o.uv = IN[0].uv;
    o.worldPos = IN[0].worldPos;
    o.faceNormal = fn;
    triStream.Append(o);
    
    o.pos = IN[1].pos;
    o.uv = IN[1].uv;
    o.worldPos = IN[1].worldPos;
    o.faceNormal = fn;
    triStream.Append(o);
    
    o.pos = IN[2].pos;
    o.uv = IN[2].uv;
    o.worldPos = IN[2].worldPos;
    o.faceNormal = fn;
    triStream.Append(o);
}

其中 A, B 是用来获得三角面片的法线向量,如下图:

在这里插入图片描述
输入了三个顶点,输出三个顶点的法线值都为当前计算得到的这个,那么在传入到片元着色器之前所做的插值其实就并未改变法线向量,因为插值的三个法线向量都一样,怎么插值都是一样的。

得到法线后,后面的代码就和普通代码一样了,总体来说原理还是比较简单的,不过想要获得更有意思的效果,应该还有很多trick,学习之路无止境啊~~~

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值