「UnityShader笔记」08. 卡通渲染技术—渐变纹理
2022/6/6 23:23:08
本文主要是介绍「UnityShader笔记」08. 卡通渲染技术—渐变纹理,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
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" }
这篇关于「UnityShader笔记」08. 卡通渲染技术—渐变纹理的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23Springboot应用的多环境打包入门
- 2024-11-23Springboot应用的生产发布入门教程
- 2024-11-23Python编程入门指南
- 2024-11-23Java创业入门:从零开始的编程之旅
- 2024-11-23Java创业入门:新手必读的Java编程与创业指南
- 2024-11-23Java对接阿里云智能语音服务入门详解
- 2024-11-23Java对接阿里云智能语音服务入门教程
- 2024-11-23JAVA对接阿里云智能语音服务入门教程
- 2024-11-23Java副业入门:初学者的简单教程
- 2024-11-23JAVA副业入门:初学者的实战指南