文本介绍卡通渲染的基本技术,实现会放在另外的文档
Cel Shading,ToonShading,色块、色调,各向异性,描边,高光
看起来像手绘的图片
少渐变(指光影的变换),有明显的分界
颜色少
色调少
看起来粗略(凸出主要特征)
卡通渲染的技术是为了实现这些特征
一般是 = 环境光 + 漫反射 + 高光,根据需要也可以加入对环境的反射
边缘线 + 边缘光
头发上的高光反射
把渐变分出好几个级别,渐变变成突变,在边界做一点过渡,一般是根据【法线和视线】or 【法线和光线】计算一个值,根据这个值进行分段,某个范围映射到一个固定的值。
如果要美术定制,可以使用光照查询纹理,是一个分块的颜色纹理,根据光照计算的结果(也就是上面的方法里计算出来的值的相关值),一般是一个[0,1]的值作为uv来查询映射纹理
称为RampMap
使用冷色调,暖色调,根据需要使用的色调配置不同的查询纹理,比如冷白皮肤,热带环境,极地环境
色块化方式:
if dot(normalWS, lightDirectionWS) > 0
明亮
else
暗
使用smoothstep做明暗变化的过渡
float lightIntensity = smoothstep(0, 0.01, NdotL);
现实世界效果
卡通渲染效果
真实世界光照表现的不同在于,卡通渲染的高光区不会有太多过渡,是大块大块的突变
float specularIntensity = pow(NdotH * lightIntensity, _Glossiness * _Glossiness);
float specularIntensitySmooth = smoothstep(0.005, 0.01, specularIntensity);
dot(normal, viewDirectionWS)越小,夹角越接近90度,说明是模型的边,但是获得的轮廓不均匀
优化,将法线变换到投影空间要乘以transform的转置逆再乘projection矩阵,这样法线不会受到非等比缩放的影响,即使用NDC空间的距离
这里面的精髓:多乘了一个w,这样进行透视除法的时候不影响我们设置的数值
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
float3 norm = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);
float2 extendDir = normalize(TransformViewToProjection(norm.xy));
//o.pos.xy += extendDir * (_OutlineWidth * 0.1);//拉动镜头粗细会变化,下面是优化的
o.pos.xy += extendDir * (o.pos.w * _OutlineWidth * 0.1);
参考
背面扩张法(Procedural Geometry Silhouetting),缺点是同一个顶点在不同面上有不同法线的地方会有缺口(模型交接的位置出现缺口)
缺点:背面可能和原来的模型发生深度冲突,导致遮挡模型
一种解决方法是给backfaces设置Z-offset,使轮廓线埋没到临近的面里。另一种解决方法是修改backfaces扩张的法线,使轮廓线扁平化
一般使用Sobel边缘检测算子
缺点:
一些z变化很小的轮廓,比如桌子上的纸张,就无法检测出来
不能区分Silhouette edge和Crease Edge
如果直接在贴图上绘制线条,缩放会模糊
使用本村线,角色表面描边的方法,并不是外描边,它是将模型的 UV 打直,只绘制于垂直于 U 轴或者 V 轴的直线,避免斜线线条的采样问题
缺点:
制作周期会比较长
把模型上没有整齐排布的uv给排整齐
不直的UV
直的UV
根据相机位置和法线,夹角越接近90度,边缘光越强
如果用视线方向,如果模型在右侧45度,并且正面朝着相机,边缘光会出问题,所以要用相机位置
viewDir = normalize(cameraPos - position)
float4 rimDot = 1 - dot(viewDir, normal);
使用smoothstep进行过渡
float rimIntensity = smoothstep(_RimAmount - 0.01, _RimAmount + 0.01, rimDot);
float rimIntensity = rimDot * NdotL;
rimIntensity = smoothstep(_RimAmount - 0.01, _RimAmount + 0.01, rimIntensity);
float rimIntensity = rimDot * pow(NdotL, _RimThreshold);
rimIntensity = smoothstep(_RimAmount - 0.01, _RimAmount + 0.01, rimIntensity);
卡通渲染的阴影也是一块一块呈现,没有渐变,只在交界处有过渡
float shadow = SHADOW_ATTENUATION(i);
float lightIntensity = smoothstep(0, 0.01, NdotL * shadow);
有些地方容易产生阴影,有些地方则很难产生阴影。用一张贴图或者顶点色控制阴影的倾向,对上面的映射函数进行偏移
在PBR光照模型的计算中加入卡通渲染,使用NDotL、NDotV的值进行操作
一般的高光都是使用法线方向进行制作的,但是此处使用了副切线(T)。原因是我们模拟的对象(发丝)是一个圆柱形,它的形状导致了它的法线的不唯一性,所以法线不能用来计算我们的高光。法线不确定,导致我们的切线也不确定,但是他们的平面是固定的,所以我们可以使用副切线(同时垂直于由法线与切线的向量)
算法说明http://web.engr.oregonstate.edu/~mjb/cs519/Projects/Papers/HairRendering.pdf
卡通渲染的模型一般需要人工调整法线
用于调整阴影和高光区域的形状
用于调整阴影的颜色,暗部颜色 = mainTex * sssMap
unity官方卡通渲染方案
Unity Toon Shader (Unity-Chan Toon Shader 3) | Unity Toon Shader | 0.6.1-preview
基础技术
Unity Toon Shader Tutorial - Roystan
outline
综合
天使环
多了噪声做扰动
【Cel-Shading】各向异性发丝高光 | Invictus maneo
头发各向异性渲染写的详细且深入
图形学基础|各项异性与头发渲染_桑来93的博客-CSDN博客_头发各向异性
各向异性(Anisotropic)指的是在不同方向上表现出的光照效果会产生差异