ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

「UnityShader笔记」08. 卡通渲染技术—渐变纹理

2022-06-06 20:32:21  阅读:193  来源: 互联网

标签:采样 卡通 Color 08 纹理 rgb UnityShader worldNormal fixed3


Part1.效果图

Part2.方法简介

渐变纹理是一种可以用来实现卡通渲染效果的技术,其原理十分简单,使用光照模型计算结果,在一个一维的渐变纹理上进行采样

以半兰伯特模型为例,其表达式为 0.5 * ( Normal · LightDirection) + 0.5,可以看到如果光照方向对于某顶点越接近于直射,其半兰伯特模型计算结果越大,越接近1,对应的在渐变纹理上采样得到的颜色结果也会越深,如果把采样纹理离散化,比如下图的模式

 

 

 

(取指越接近0越靠左,越接近1越靠右)

这样以来,连续的光照计算结果,在经过纹理采样后的颜色值将被离散化,从而模拟卡通风格的块状阴影和高光的美术风格

Part3.代码逐段分析

Properties
{
    _Color ("Color", Color) = (1,1,1,1)
    _MainTex("Main Tex",2D) = "white"{}  //主纹理
    _RampTex("Ramp Tex", 2D) = "white" {}  //渐变纹理
    _Specular("Specular", Color) = (1,1,1,1)
    _Gloss("Gloss", Range(8.0,256)) = 20
}

在属性块,需要额外定义一个渐变纹理,不要忘了在CG代码片中也要定义相同的变量

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

在顶点着色的输入结构体中,我们需要获取顶点位置,顶点法向量以及顶点的纹理uv坐标

struct v2f{
    float4 pos : SV_POSITION;
    float3 worldNormal : TEXCOORD0;
    float3 worldPos : TEXCOORD1;
    float2 uv : TEXCOORD2;
};

在顶点着色器到片元着色器的通信结构体中,我们需要获取裁剪空间下的顶点位置,世界空间下的顶点位置和法向量,以及经过变换后的纹理uv

v2f vert(a2v v){
    v2f o;
    //将顶点从模型空间变换到裁剪空间
    o.pos = UnityObjectToClipPos(v.vertex); 

    //使用内置函数和变换矩阵,将模型空间的法向量和顶点位置变换到世界空间,用于片元着色器的光照计算
    o.worldNormal  = UnityObjectToWorldNormal(v.normal);
    o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

    //对纹理uv坐标进行尺度变换和偏置
    o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
    return o;
}

顶点着色器的工作相对简单,我们只需要完成一些常规工作即可

fixed4 frag(v2f i) : SV_Target{
    fixed3 worldNormal = normalize(i.worldNormal);

    //通过Unity内置函数,获取世界空间下的光照向量,并单位化
    fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

    //对主纹理进行采样,获取像素点颜色
    fixed3 albedo = tex2D(_MainTex, i.uv)* _Color.rgb;

    //计算环境光
    fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

    //根据半兰伯特模型计算漫反射项
    fixed halfLambert = 0.5 * dot(worldNormal, worldLightDir) + 0.5;

    //根据半兰伯特模型计算结果,在渐变纹理上采样,得到被离散化的漫反射颜色
    fixed3 diffuseColor = tex2D(_RampTex, fixed2(halfLambert,halfLambert)).rgb * albedo * halfLambert;

    //后面进行正常的光照计算,输出最终颜色,注释从略,可参考之前笔记
    fixed3 diffuse = _LightColor0.rgb * diffuseColor;
    fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
    fixed3 halfDir = normalize(worldLightDir + viewDir);
    fixed3 specular = _LightColor0.rgb * _Specular.rgb + pow(max(0,dot(worldNormal,halfDir)),_Gloss);

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

在片元着色器中,我们首先需要处理主纹理,即对主纹理采样获得颜色,并用该颜色计算环境光

接下来我们需要获取像素点处半兰伯特模型的光照结果,并根据该结果对渐变纹理进行采样,注意渐变纹理虽然是一维的,但仍然是以2维存储的,其中维度y上的取值均相同,因此我们需要使用一个二维点进行采样,其中两个维度的取值相同即可,因为y轴上没有意义

采样完后,直接与之前计算的主纹理颜色相乘,再与半兰伯特模型结果相乘即可得到离散化的漫反射光照颜色

接下来的工作十分常规化,正常按照Blinn-Phong模型计算高光,输出最终光照结果即可

Part4.一些问题

Q1.为什么要使用半兰伯特模型而不是兰伯特模型?

因为兰伯特模型会出现死黑问题,对于卡通渲染这种颜色鲜艳的风格,死黑问题十分致命

Q2.为什么渐变纹理是一维的,却需要二维的UV坐标去采样?

因为渐变纹理虽然是一维的,但还是按照二维来存储的(可参考Part2给出的样例渐变纹理),但Y维度上取值是完全相同的,因此在Y维度上的采样实际上是没有意义的,代码中简单使用UV相同的采样点采样即可

Part5.完整代码

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

Shader "Chapter7/RampGirl"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex("Main Tex",2D) = "white"{}
        _RampTex("Ramp Tex", 2D) = "white" {} 
        _Specular("Specular", Color) = (1,1,1,1)
        _Gloss("Gloss", Range(8.0,256)) = 20
    }
    SubShader
    {
        Pass{
            Tags { "LightMode"="ForwardBase" }

            CGPROGRAM
            
            #pragma vertex vert
            #pragma fragment frag

            #include "Lighting.cginc"

            fixed4 _Color;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _RampTex;
            float4 _RampTex_ST;
            fixed4 _Specular;
            float _Gloss;

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

            struct v2f{
                float4 pos : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
                float3 worldPos : TEXCOORD1;
                float2 uv : TEXCOORD2;
            };

            v2f vert(a2v v){
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex); 

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

                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target{
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

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

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

                fixed halfLambert = 0.5 * dot(worldNormal, worldLightDir) + 0.5;
                fixed3 diffuseColor = tex2D(_RampTex, fixed2(halfLambert,halfLambert)).rgb * albedo * halfLambert;

                fixed3 diffuse = _LightColor0.rgb * diffuseColor;
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
                fixed3 halfDir = normalize(worldLightDir + viewDir);
                fixed3 specular = _LightColor0.rgb * _Specular.rgb + pow(max(0,dot(worldNormal,halfDir)),_Gloss);

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

            ENDCG
        }
    }
    FallBack "Specular"
}

 

标签:采样,卡通,Color,08,纹理,rgb,UnityShader,worldNormal,fixed3
来源: https://www.cnblogs.com/MuTsuKi/p/16349570.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有