• 【Unity3D】素描特效


    1 非真实渲染

    法线贴图和凹凸映射中讲述了普通光照的渲染原理,实现的效果比较贴近真实世界(照相写实主义,Photorealism),非真实渲染(Non-Photorealism Rendering,NPR)在照相写实主义的基础上添加了一些风格处理,如:卡通、水彩、素描等风格。

    ​ 本文完整资源见→Unity3D素描特效

    2 素描特效原理

    ​ 素描特效不直接渲染漫反射效果,而是通过线条的疏密程度表现漫反射效果,即:较亮处线条稀疏,较暗处线条密集。

    ​ 线条采样方法:先计算顶点对应的漫反射强度,假设为 diffuse,再根据 diffuse 所处的区间在多个素描纹理(如下)中采样。

    img

    ​ diffuse 计算如下,其中 normal 为顶点对应的单位法线向量,lightDir 为顶点指向光源的单位方向向量。

    float diffuse = max(0, dot(normal, lightDir));
    

    ​ 为方便划分区间,我们将 diffuse 乘以 7(6 个素描纹理 + 空白纹理),记为 factor,即 factor = diffuse * 7,我们将 factor 均匀划分 7 个区间,假设 factor 值对应的空白纹理和 line1 ~ line6 纹理的权值分别为 w0 ~ w6,则 w0 ~ w6 的计算如下:

    fixed w0 = 1; // 白色纹理权值
    fixed w1 = 0, w2 = 0, w3 = 0, w4 = 0, w5 = 0, w6 = 0; // line_1~line_6纹理权值
    if (factor > 6) { // 区间: (6, 7]
    	return; // 白色, 直接跳出
    } else if (factor > 5) { // 区间: (5, 6]
    	w1 = factor - 5;
    } else if (factor > 4) { // 区间: (4, 5]
    	w1 = factor - 4;
    	w2 = 1 - w1;
    } else if (factor > 3) { // 区间: (3, 4]
    	w2 = factor - 3;
    	w3 = 1 - w2;
    } else if (factor > 2) { // 区间: (2, 3]
    	w3 = factor - 2;
    	w4 = 1 - w3;
    } else if (factor > 1) { // 区间: (1, 2]
    	w4 = factor - 1;
    	w5 = 1 - w4;
    } else { // 区间: [0, 1]
    	w5 = factor;
    	w6 = 1 - w5;
    }
    w0 = 1 - w1 - w2 - w3 - w4 - w5 - w6;
    

    ​ 根据 w0 ~ w6 对白色纹理和 line1 ~ line6 纹理进行加权求和,得到顶点对应的素描颜色。

    3 素描特效实现

    ​ SketchEffect.cs

    using UnityEngine;
    
    [DisallowMultipleComponent] // 不允许在同一对象上挂载多个该组件
    public class SketchEffect : MonoBehaviour {
        private Material sketchMat; // 素描材质
    
        private void Awake() {
            sketchMat = Resources.Load("Sketch/Materials/SketchMat");
        }
    
        private void OnEnable() {
            Renderer[] renderers = GetComponentsInChildren();
            foreach (var renderer in renderers) { // 将rneder里的所有材质球都替换为sketchMat材质
                Material[] materials = new Material[renderer.sharedMaterials.Length];
                for (int i = 0; i < materials.Length; i++) {
                    materials[i] = sketchMat;
                }
                renderer.materials = materials;
            }
        }
    }
    

    ​ 说明:SketchEffect 脚本组件挂在需要渲染素描特效的对象上。

    ​ SketchEffect.shader

    Shader "MyShader/SketchEffect" {
    	Properties {
    		_Color ("Color", Color) = (1, 1, 1, 1) // 背景颜色
    		_Tilling ("Tilling", Float) = 1 // 纹理缩放, 值越大线条越密集
    		_Line1 ("Line 1", 2D) = "white" {} // 素描纹理1
    		_Line2 ("Line 2", 2D) = "white" {} // 素描纹理2
    		_Line3 ("Line 3", 2D) = "white" {} // 素描纹理3
    		_Line4 ("Line 4", 2D) = "white" {} // 素描纹理4
    		_Line5 ("Line 5", 2D) = "white" {} // 素描纹理5
    		_Line6 ("Line 6", 2D) = "white" {} // 素描纹理6
    	}
    	
    	SubShader {
    		Tags { "RenderType"="Opaque" "Queue"="Geometry"}
    
    		Pass {
    			Tags { "LightMode"="ForwardBase" }
    			
    			CGPROGRAM
    			
    			#pragma vertex vert
    			#pragma fragment frag 
    
    			#include "UnityCG.cginc"
    			
    			fixed4 _Color; // 背景颜色
    			float _Tilling; // 纹理缩放, 值越大线条越密集
    			sampler2D _Line1; // 素描纹理1
    			sampler2D _Line2; // 素描纹理2
    			sampler2D _Line3; // 素描纹理3
    			sampler2D _Line4; // 素描纹理4
    			sampler2D _Line5; // 素描纹理5
    			sampler2D _Line6; // 素描纹理6
    
    			struct v2f {
    				float4 pos : SV_POSITION;
    				half2 uv : TEXCOORD0;
    				fixed4 w1 : TEXCOORD1; // 前三张素描纹理的权值(w维存储白色)
    				fixed3 w2 : TEXCOORD2; // 后三张素描纹理的权值
    			};
    
    			void getLineWeights(float diffuse, out fixed4 w1, out fixed3 w2) { // 根据漫反射值获取6张素描纹理的权重
    				float factor = diffuse * 7.0;
    				w1 = fixed4(0, 0, 0, 1);
    				w2 = fixed3(0, 0, 0);
    				if (factor > 6) { // 区间: (6, 7]
    					return; // 白色, 直接跳出
    				} else if (factor > 5) { // 区间: (5, 6]
    					w1.x = factor - 5;
    				} else if (factor > 4) { // 区间: (4, 5]
    					w1.x = factor - 4;
    					w1.y = 1 - w1.x;
    				} else if (factor > 3) { // 区间: (3, 4]
    					w1.y = factor - 3;
    					w1.z = 1 - w1.y;
    				} else if (factor > 2) { // 区间: (2, 3]
    					w1.z = factor - 2;
    					w2.x = 1 - w1.z;
    				} else if (factor > 1) { // 区间: (1, 2]
    					w2.x = factor - 1;
    					w2.y = 1 - w2.x;
    				} else { // 区间: [0, 1]
    					w2.y = factor;
    					w2.z = 1 - w2.y;
    				}
    				w1.w = 1 - w1.x - w1.y - w1.z - w2.x - w2.y - w2.z; // w维存储白色
    			}
    			
    			v2f vert(appdata_base v) {
    				v2f o;
    				o.pos = UnityObjectToClipPos(v.vertex); // 计算裁剪坐标系中顶点坐标, 等价于: mul(unity_MatrixMVP, v.vertex)
    				o.uv = v.texcoord * _Tilling; // 对uv坐标进行缩放, 用于调整素描图像的稀疏和宽窄程度
    				fixed3 worldLightDir = normalize(WorldSpaceLightDir(v.vertex)); // 计算世界空间中顶点指向光源的向量
    				fixed3 worldNormal = UnityObjectToWorldNormal(v.normal); // 计算世界空间中顶点法线向量
    				fixed diffuse = max(0, dot(worldLightDir, worldNormal)); // 计算漫反射值
    				getLineWeights(diffuse, o.w1, o.w2); // 根据漫反射值获取6张素描纹理的权重
    				return o; 
    			}
    			
    			fixed4 frag(v2f i) : SV_Target {			
    				fixed4 lineTex1 = tex2D(_Line1, i.uv) * i.w1.x;
    				fixed4 lineTex2 = tex2D(_Line2, i.uv) * i.w1.y;
    				fixed4 lineTex3 = tex2D(_Line3, i.uv) * i.w1.z;
    				fixed4 lineTex4 = tex2D(_Line4, i.uv) * i.w2.x;
    				fixed4 lineTex5 = tex2D(_Line5, i.uv) * i.w2.y;
    				fixed4 lineTex6 = tex2D(_Line6, i.uv) * i.w2.z;
    				fixed4 whiteColor = fixed4(1, 1, 1, 1) * i.w1.w;
    				fixed4 sketchColor = lineTex1 + lineTex2 + lineTex3 + lineTex4 + lineTex5 + lineTex6 + whiteColor;
    				return fixed4(sketchColor.rgb * _Color.rgb, 1.0);
    			}
    			
    			ENDCG
    		}
    	}
    
    	FallBack "Diffuse"
    }
    

    ​ 说明:在 Assets/Resources/Sketch/Materials 目录下创建 Material,重命名为 SketchMat ,将 SketchEffect.shader 赋给 SketchMat 材质,并将 line1 ~ line6 纹理拖拽到 SketchMat 材质中对应位置,调整 Tilling 属性的值。

    4 运行效果

    1)原图

    img

    2)素描特效

    img

    ​ 声明:本文转自【Unity3D】素描特效

  • 相关阅读:
    基于Qlearning强化学习的倒立摆控制系统matlab仿真
    C&C++结构实训(国防科大)
    Linux之(9)shell基础概念(1)
    对负采样(negative sampling)的一些理解
    高端之争:国产手机踏上释放「战略势能」的长期征途
    鸿鹄工程项目管理系统 Spring Cloud+Spring Boot+前后端分离构建工程项目管理系统
    MEGC(FACIAL MICRO-EXPRESSION GRAND CHALLENGE)微表情识别比赛相关网站
    CVPR 2022 Paper Reading List
    ceres中的三种求导方式简单入门:自动求导、数值导数、解析求导
    user32dIl出错,丢失条目:lockworkstation是怎么回事
  • 原文地址:https://www.cnblogs.com/zhyan8/p/17624370.html