1.論文解讀
論文標題為實時的畫影線,實時的畫影線的主要作用可以用於產生素描畫的效果。該論文屬於計算機圖形學中的圖像處理技術和紋理技術。
根據摘要,論文可以被分為四個部分。該論文通過分析已有技術中的問題,從而產生新的解決方法;通過提出色調藝術圖的概念並利用色調藝術圖中的陰影筆劃,從而得出非真實感渲染效果。第一部分主要說明背景。第二部分主要介紹如何自動預處理產生以及所產生的色調藝術圖的形態。第三部分介紹渲染的算法以及在運行過程中為了保持空間和時間的一致性所建立的重疊紋理參數。第四部分為結果、總結和未來工作。
論文所引用的文獻主要是與計算機圖形論壇相關的會議論文。該論文在谷歌學術中被引用的次數為491次。
論文的背景部分主要闡述畫影線的作用、用於三維渲染後所能產生的效果以及實現這一效果的技術挑戰,並提出所要解決的三個問題:「有限的實時計算」、「筆畫之間的連續性和相幹性」、「動態觀察角度下對筆畫的大小和密度的控製」。作者所提出的解決方法是:引入色調藝術圖以及可以自動生成色調藝術圖的算法、創造具有空間和時間一致性的多紋理算法、將貼圖和方向場整合在一起來為三維模型著色。
論文的第二部分通過介紹非真實感渲染的輸入形態和「優先筆觸紋理」、「藝術地圖」的概念,從而引進色調藝術圖(TAM)的概念和作用。作者所使用的色調藝術圖有六張,每張有四個不同精細等級的紋理映射圖。隨著暗度的不斷增加,所對應的色調藝術圖的筆畫數也在不斷的加權。而隨著與模型的距離不斷擴大,紋理映射圖所使用的等級也在不斷地增加。其自動生成的原理在於從亮部到暗部紋理,需要將亮部的色調藝術圖作爲暗部的色調藝術圖的子集來增加新的筆畫作出新的色調藝術圖。而在色調藝術圖的紋理映射圖中則是在粗糙圖上每增加筆畫,則需要在更精細圖像中繪製相同的筆刷,從而保證粗糙圖像中壁畫總能出現在更加精細的圖像中。
論文的第三部分所介紹的算法思路為對模型的每個頂點選擇合適的色調藝術圖,每個面最多混合六個紋理,每個頂點混合兩種紋理。同時針對每個面,在三個頂點處理混合紋理并在空間上使用頂點重心權重來組合。其代碼思路提供了兩種方法。第一種方法是可以通過一個PASS來對六張紋理進行混合。第二種方法是利用三個PASS,一個PASS混合兩張紋理,一個面渲染三次來進行混合。在論文中,作者提出第一種方法所能呈現的效果更加好因爲更加自然,但是其缺點在於對於水墨畫一類的效果不太理想,可以在該算法的基礎上引入閾值處理。
論文的第四部分則是表達了希望未來的工作可以調整筆畫的長度和寬度、更加普遍的色調藝術圖以及可以隨著視點的移動改變筆畫方向。
2.代碼實現
Shader "Unlit/hatching"
{
Properties
{
//描邊
_OutlineFactor("Outline", range(0.01, 1.0)) = 0.5
_OutlineCol("OutlineCol", Color) = (0,0,0,1)
//貼圖TAM控制
_Color("ModelCol",Color) = (0,0,0,1)
_TileFactor("TileFactor", Range(0, 10)) = 1
_Hatch0("Hatch0",2D)="white"{}
_Hatch1("Hatch1",2D) = "white"{}
_Hatch2("Hatch2",2D) = "white"{}
_Hatch3("Hatch3",2D) = "white"{}
_Hatch4("Hatch4",2D) = "white"{}
_Hatch5("Hatch5",2D) = "white"{}
}
SubShader
{
Tags{ "Queue" = "Transparent" }
//描邊
Pass
{
//剔除正面,只渲染背面
Cull Front
//關閉深度寫入
ZWrite Off
//控制深度偏移
Offset 1,1
CGPROGRAM
#include "UnityCG.cginc"
#pragma vertex vert
#pragma fragment frag
uniform float _OutlineFactor;
uniform fixed4 _OutlineCol;
//輸入結構
struct VertexInput
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
//輸出結構
struct VertexOutput
{
float4 pos : SV_POSITION;
};
//頂點著色器
VertexOutput vert(VertexInput v)
{
VertexOutput o = (VertexOutput)0;
float3 pos = UnityObjectToViewPos(v.vertex);//mul(UNITY_MATRIX_MV, v.vertex);
float3 normal = UnityObjectToWorldNormal(v.normal);
normal.z = -0.5;
normal.x = -0.5;
//normal.y = -0.5;
pos = pos + float4(normalize(normal), 0) * _OutlineFactor;
o.pos = UnityViewToClipPos(pos);
return o;
}
//像素著色器
fixed4 frag(VertexOutput i) : SV_Target
{
return float4(_OutlineCol.rgb,1);
}
ENDCG
}
//主紋理顔色輸出
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
//使用阴影需添加
#include "AutoLight.cginc"
//使主要平行光产生阴影
#pragma multi_compile_fwdbase
uniform float4 _Color;
uniform float _TileFactor;
uniform sampler2D _Hatch0;
uniform sampler2D _Hatch1;
uniform sampler2D _Hatch2;
uniform sampler2D _Hatch3;
uniform sampler2D _Hatch4;
uniform sampler2D _Hatch5;
//輸入結構
struct VertexInput
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
};
//輸出結構
struct VertexOutput
{
float4 pos : SV_POSITION;
float3 posWS : TEXCOORD0;
float2 uv : TEXCOORD1;
//貼圖TAM權重
float3 hatchWeights0 : TEXCOORD2;
float3 hatchWeights1 : TEXCOORD3;
//陰影
SHADOW_COORDS(4)
};
//頂點著色器
VertexOutput vert (VertexInput v)
{
VertexOutput o = (VertexOutput)0;
o.pos = UnityObjectToClipPos(v.vertex);
o.posWS = mul(unity_ObjectToWorld, v.vertex);
//手動修改uv貼圖平鋪係數
o.uv = v.uv * _TileFactor;
//頂點計算光照
float3 lDir = normalize(WorldSpaceLightDir(v.vertex));
float3 nDir = normalize(UnityObjectToWorldNormal(v.normal));
float3 lrDir = normalize(reflect(lDir, nDir));
float3 vDir = normalize(_WorldSpaceCameraPos.xyz - o.posWS.xyz);
//準備點積
float ndotl = dot(nDir, lDir);
float vdotlr = dot(vDir, lrDir);
//光照糢型
//半蘭伯特光照模型
float diffuse = ndotl * 0.5 + 0.5;
//blinnPhong光照糢型
float specular = -vdotlr; //normalize( pow(vdotlr, _specularPow));
//主光源混合
// float light = diffuse; //只有漫反射
//float light = specular; //只有高光反射
float light = (diffuse + specular); //漫反射+高光反射
//六張圖的權重
o.hatchWeights0 = float3(0, 0, 0);
o.hatchWeights1 = float3(0, 0, 0);
//根據光照計算权重,光綫越暗,綫條越密集
float hatchFactor = light * 7.0;
if (hatchFactor > 6.0) {
}
else if (hatchFactor > 5.0) {
o.hatchWeights0.x = hatchFactor - 5.0;
}
else if (hatchFactor > 4.0) {
o.hatchWeights0.x = hatchFactor - 4.0;
o.hatchWeights0.y = 1.0 - o.hatchWeights0.x;
}
else if (hatchFactor > 3.0) {
o.hatchWeights0.y = hatchFactor - 3.0;
o.hatchWeights0.z = 1.0 - o.hatchWeights0.y;
}
else if (hatchFactor > 2.0) {
o.hatchWeights0.z = hatchFactor - 2.0;
o.hatchWeights1.x = 1.0 - o.hatchWeights0.z;
}
else if (hatchFactor > 1.0) {
o.hatchWeights1.x = hatchFactor - 1.0;
o.hatchWeights1.y = 1.0 - o.hatchWeights1.x;
}
else {
o.hatchWeights1.y = hatchFactor;
o.hatchWeights1.z = 1.0 - o.hatchWeights1.y;
}
//陰影
TRANSFER_SHADOW(o);
return o;
}
//像素著色器
fixed4 frag (VertexOutput i) : SV_Target
{
float4 hatchTex0 = tex2D(_Hatch0, i.uv) * i.hatchWeights0.x;
float4 hatchTex1 = tex2D(_Hatch1, i.uv) * i.hatchWeights0.y;
float4 hatchTex2 = tex2D(_Hatch2, i.uv) * i.hatchWeights0.z;
float4 hatchTex3 = tex2D(_Hatch3, i.uv) * i.hatchWeights1.x;
float4 hatchTex4 = tex2D(_Hatch4, i.uv) * i.hatchWeights1.y;
float4 hatchTex5 = tex2D(_Hatch5, i.uv) * i.hatchWeights1.z;
//暗色權重越大,白色越少
//用最極端的思路去想就是,直接光照為0,即hatchWeights1.z=1,whiteCol=0,hatchCol=hatchTex5,輸出結果,直接不用混合了,權重直接偏向最高的貼圖
float4 whiteColor = float4(1, 1, 1, 1)*(1 - i.hatchWeights0.x - i.hatchWeights0.y - i.hatchWeights0.z - i.hatchWeights1.x - i.hatchWeights1.y - i.hatchWeights1.z);
float4 hatchColor = hatchTex0 + hatchTex1 + hatchTex2 + hatchTex3 + hatchTex4 + hatchTex5+ whiteColor;
//陰影
UNITY_LIGHT_ATTENUATION(atten, i, i.posWS);
return float4(hatchColor.rgb*_Color.rgb*atten, 1.0);
}
ENDCG
}