本文主要是介绍Inking(描边),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
Inking是为了能够能够从背景中更加清晰地勾画出网格附加在蒙皮网格上的模型特效,一般采用比较细的灰黑色的线条勾勒出网格的轮廓。
1.Rim-Light(边缘光)
背景:边缘光即对应当前视角方向,对物体上位于边缘的地方额外施加一个光的效果。
原理:通过N(法线方向)和V(视线方向)的夹角来判断判断一个点是否在物体的边缘。当V(视线方向)与N(法线方向)垂直时,这个法线对应的面就与视线方向平行,说明当前这个点对于当前视角来说就处在边缘;而视线方向与法线方向一致时,这个法线对应的面就垂直于视线方向,说明当前是直视这个面。所以,我们就可以根据dot(N,V)来获得视线方向与法线方向的余弦值,通过这个值来区分该像素是否处在边缘,进而判断是否需要增加以及增加边缘光的强弱。
公式:最终颜色 = (漫反射系数 x 纹理颜色 x RGB颜色)+自发光颜色
FinalColor=(Diffuse x Texture x RGBColor)+Emissive
Code:
Shader "Nina/Shader/Inking/Rim Light"
{//属性Properties{//主颜色_MainColor("Main Color", Color) = (0.5,0.5,0.5,1)//主纹理_MainTexture("Main Texture", 2D) = "white" {}//边缘发光颜色_RimColor("Rim Color", Color) = (0.5,0.5,0.5,1)//边缘发光强度_RimPower("Rim Power", Range(0.0, 36)) = 0.1//边缘发光强度系数_RimIntensity("Rim Intensity", Range(0.0, 100)) = 3}//子着色器SubShader{//渲染类型:不透明Tags{"RenderType" = "Opaque"}//通道Pass{//通道名称Name "ForwardBase"//光照模式:ForwardBaseTags{"LightMode" = "ForwardBase"}//开启CG着色器编程语言段CGPROGRAM//声明:顶点和片段着色函数名称#pragma vertex vert#pragma fragment frag//引入头文件:UnityCG AutoLight#include "UnityCG.cginc"#include "AutoLight.cginc"//指定Shader Model:3.0#pragma target 3.0//变量//系统光照颜色uniform float4 _LightColor0;//主颜色uniform float4 _MainColor;//主纹理uniform sampler2D _MainTexture;//主纹理_ST(sample texture)uniform float4 _MainTexture_ST;//边缘光颜色uniform float4 _RimColor;//边缘光强度uniform float _RimPower;//边缘光强度系数uniform float _RimIntensity;//顶点输入结构体struct VertexInput{//顶点位置float4 vertex : POSITION;//法线向量坐标float3 normal : NORMAL;//一级纹理坐标float4 texcoord : TEXCOORD0;};//顶点输出结构体struct VertexOutput{//像素位置float4 pos : SV_POSITION;//一级纹理坐标float2 uv : TEXCOORD0;//法线向量坐标float3 normal : NORMAL;//世界空间中的坐标位置float4 posWorld : TEXCOORD1;//创建光源坐标,用于内置的光照LIGHTING_COORDS(3,4)};//顶点着色函数VertexOutput vert(VertexInput v){//声明一个顶点输出结构对象VertexOutput o;//将输入纹理坐标赋值给输出纹理坐标,通过TRANSFORM_TEX宏转化纹理坐标,//主要处理了Offset和Tiling的改变,默认时等同于o.uv = v.texcoord.xy; o.uv = TRANSFORM_TEX(v.texcoord, _MainTexture);//获取顶点在世界空间中的法线向量坐标 o.normal = mul(float4(v.normal,0), unity_WorldToObject).xyz;//获得顶点在世界空间中的位置坐标 o.posWorld = mul(unity_ObjectToWorld, v.vertex);//获取像素位置o.pos = mul(UNITY_MATRIX_MVP, v.vertex);//返回此输出结构对象return o;}//片段着色函数fixed4 frag(VertexOutput i) : COLOR{//方向参数(V N L),归一化,即使在vert归一化也不行,从vert到frag阶段有差值处理,传入的法线方向并不是vertex shader直接传出//视角方向float3 ViewDirection = normalize(_WorldSpaceCameraPos.xyz - i.posWorld.xyz);//法线方向float3 Normalection = normalize(i.normal);//光照方向float3 LightDirection = normalize(_WorldSpaceLightPos0.xyz);//计算光照的衰减//衰减值float Attenuation = LIGHT_ATTENUATION(i);//衰减后颜色值float3 AttenColor = Attenuation * _LightColor0.xyz;//计算漫反射float NdotL = dot(Normalection, LightDirection);//环境光,unity自身的diffuse也是带了环境光fixed3 Ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * _MainColor.xyz;//兰伯特(或者半兰伯特)*材质diffuse颜色*衰减后光颜色值+环境光float3 Diffuse = max(0.0, NdotL) * AttenColor + Ambient;//准备自发光参数//计算边缘强度,视线方向与法线方向的夹角,half Rim = 1.0 - max(0, dot(i.normal, ViewDirection));//计算出边缘自发光强度float3 Emissive = _RimColor.rgb * pow(Rim,_RimPower) *_RimIntensity;//计在最终颜色中加入自发光颜色//最终颜色 = (漫反射系数 x 纹理颜色 x rgb颜色)+自发光颜色float3 finalColor = Diffuse * (tex2D(_MainTexture, i.uv).rgb * _MainColor.rgb) + Emissive;//返回最终颜色return fixed4(finalColor,1);}ENDCG}}//后备着色器为普通漫反射FallBack "Diffuse"}
2.Fresnel(菲尼尔)
背景: 菲涅耳公式用来描述光在不同折射率的介质之间的行为。菲涅尔公式是光学中的重要公式,用它能解释反射光的强度、折射光的强度、相位与入射光的强度的关系
反射公式: fresnel = fresnel基础值 + fresnel缩放量*pow( 1 - dot( N, V ), 5 )
原理: 类似于Rim Lighting, 使用视线方向(V)和点法线方向(N)的点积来判断边缘, 并将边缘高亮化.
优点: 效率高; 不需要单独的Pass就可以实现; 几乎所有的平滑的边缘都会得到高亮效果; 甚至对透明和半透明物体也有效.
缺点: 无法控制Inking线条的粗细, 这是因为Fresnel方法是针对于模型法线和摄像机视线的, 从而导致其仅与每个表面的法线方向有关, 而与表面的深度信息无关.
Code:
3.Mesh Doubling (复制网格)
背景: 类似于卡通Toon特效,重新绘制一个将所有表面都沿着法线方向延展过的模型,。两次渲染,第一次渲染背面,剔除正面,然后将正面剪裁掉。把模型顶点沿法线方向扩伸一定距离(用来表现描边的粗细);第二次渲染,渲染正面,剔除背面。
公式:
其中, L表示偏移向量; W表示轮廓线条粗细; D是物体和摄像机间的距离. V是标准化后的顶点坐标, 表示方向; N是顶点向量; f是插值参数.
优点: 效率高; 平台适应性好; 可以控制描边的线条粗细.。
缺点: 线条并不连续, 在平滑表面的表现虽然很好, 但是在锐利的表面上经常会出现断层; 只能绘制最外层轮廓, 而不对内部结构做任何处理。
Code:
Shader "Nina/Shader/Inking/Mesh Doubling"
{//属性Properties{//主纹理_MainTexture("Base (RGB)", 2D) = "white" { }//边缘发光颜色_RimColor("Rim Color", Color) = (0, 0, 0, 1)//边缘发光宽度_RimWidth("Rim width", Range(0.0, 1.0)) = .005}//子着色器SubShader{//1通道Pass{//剔除正面Cull front//开启CG着色器编程语言段CGPROGRAM//声明:顶点和片段着色函数名称#pragma vertex vert#pragma fragment frag//引入头文件:UnityCG#include "UnityCG.cginc"//指定Shader Model:3.0#pragma target 3.0//变量//边缘发光颜色uniform float4 _RimColor;//边缘发光宽度uniform float _RimWidth;//顶点输入结构体struct VertexInput{//顶点位置float4 vertex : POSITION;//法线向量坐标float3 normal : NORMAL;};//顶点输出结构体struct VertexOutput{//像素位置float4 pos : POSITION;};//顶点着色函数VertexOutput vert(VertexInput v){//声明一个顶点输出结构对象VertexOutput o;//设置顶点偏移v.vertex.xyz += v.normal * _RimWidth;//获取像素位置o.pos = mul(UNITY_MATRIX_MVP, v.vertex);//返回此输出结构对象return o;}//片段着色函数half4 frag(VertexOutput i) : COLOR{//返回边缘颜色return _RimColor;}ENDCG}//通道2Pass{SetTexture[_MainTexture]{Combine Primary * Texture}}/*或者通道2Pass {//开启CG着色器编程语言段CGPROGRAM//引入头文件:UnityCG #include "UnityCG.cginc" //声明:顶点和片段着色函数名称#pragma vertex vert_img #pragma fragment frag //变量uniform sampler2D _MainTex;//片段着色函数float4 frag(v2f_img i) : COLOR{//获取颜色float4 col = tex2D(_MainTex, i.uv);//返回最终颜色return col;}ENDCG}*/}
}
这篇关于Inking(描边)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!