百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

Shader知识普及之:素描效果的实现

csdh11 2024-12-12 11:18 16 浏览

本篇文章我们一起来学习下如何用shader为模型添加素描效果。

素描画(丢勒作品)


我们观察下素描画作,可以借鉴到素描风格模型的大致实现思路:事先我们要准备六张不同疏密程度、不同颜色深浅的素描贴图,根据模型表面情况,混合贴图比例到模型,实现模型素描化。


场景动物模型(素描效果处理)


我们先来看一下在Unity中对动物模型的shader代码处理过程:

Shader "Unlit/Sketch"

{

Properties

{

_Color("Color",Color) = (1,1,1,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"{}

_OutlineFactor("OutlineFactor",Range(0.0,0.1))=0.01

}

SubShader

{

Tags{ "Queue" = "Transparent" }

Pass

{

Cull Front

ZWrite Off

Offset 1,1

CGPROGRAM

#include "UnityCG.cginc"

#pragma vertex vert

#pragma fragment frag

float _OutlineFactor;

struct v2f

{

float4 pos : SV_POSITION;

};

v2f vert(appdata_full v)

{

v2f o;

o.pos = UnityObjectToClipPos(v.vertex);

float3 vnormal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);

float2 offset = TransformViewToProjection(vnormal.xy);

o.pos.xy += offset * _OutlineFactor;

return o;

}

fixed4 frag(v2f i) : SV_Target

{

return float4(0,0,0,1);

}

ENDCG

}

Pass

{

CGPROGRAM

#include "UnityCG.cginc"

#include "Lighting.cginc"

#include "AutoLight.cginc"

#pragma vertex vert

#pragma fragment frag

#pragma multi_compile_fwdbase

float4 _Color;

float _TileFactor;

sampler2D _Hatch0;

sampler2D _Hatch1;

sampler2D _Hatch2;

sampler2D _Hatch3;

sampler2D _Hatch4;

sampler2D _Hatch5;

struct v2f

{

float2 uv : TEXCOORD0;

float4 vertex : SV_POSITION;

float3 hatchWeights0:TEXCOORD1;

float3 hatchWeights1:TEXCOORD2;

SHADOW_COORDS(4)

float3 worldPos:TEXCOORD3;

};

v2f vert (appdata_full v)

{

v2f o;

o.vertex = UnityObjectToClipPos(v.vertex);

o.uv = v.texcoord* _TileFactor;

float3 worldLightDir = normalize(WorldSpaceLightDir(v.vertex));

float3 worldNormal = UnityObjectToWorldNormal(v.normal);

float diffuse = max(0, dot(worldLightDir, worldNormal));

o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz ;

o.hatchWeights0 = float3(0, 0, 0);

o.hatchWeights1 = float3(0, 0, 0);

float hatchFactor = diffuse * 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 (v2f 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;

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.worldPos);

return float4(hatchColor.rgb*_Color.rgb*atten, 1.0);

}

ENDCG

}

}

}


首先对模型进行描边操作,通过计算光源与物体表面漫反射值确定权重系数,从而确定贴图采集情况。


下面我们对逻辑进行拆分和解析:

描边逻辑:使用两个通道,其中一个通道沿法线方向挤出一点,输出形成描边的颜色

投影转换操作:将法线方向转换到最终投影阶段输出并进行偏移操作:

float3 vnormal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);

float2 offset = TransformViewToProjection(vnormal.xy);

o.pos.xy += offset * _OutlineFactor;

计算模型表面漫反射:

float diffuse = max(0, dot(worldLightDir, worldNormal));

o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz ;

对漫反射结果与不同贴图特征进行混合操作:漫反射越暗,线条越密集

float hatchFactor = diffuse * 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;

}

混合白色基础颜色信息:漫反射深色部分权重越大,白色越少

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.worldPos);

返回最终效果

return float4(hatchColor.rgb*_Color.rgb*atten, 1.0);

效果调整:

我们设置一个范围0-10的贴图平铺系数,修改系数处理贴图疏密程度

相关代码:o.uv = v.texcoord* _TileFactor;


调整平铺系数大小,实现线条稀疏密度的效果调整,如下图

设置高密度线条

设置低密度线条


准备好六张素描纹理贴图,在顶点着色阶段计算逐顶点的漫反射光照,通过光照结果决定六张纹理混合权重,然后将数据传输到片元着色器,片元着色器再根据权重结果混合六张纹理的采样结果。


本期的素描风格shader到此就结束了,我们下期再见!

相关推荐

探索Java项目中日志系统最佳实践:从入门到精通

探索Java项目中日志系统最佳实践:从入门到精通在现代软件开发中,日志系统如同一位默默无闻却至关重要的管家,它记录了程序运行中的各种事件,为我们排查问题、监控性能和优化系统提供了宝贵的依据。在Java...

用了这么多年的java日志框架,你真的弄懂了吗?

在项目开发过程中,有一个必不可少的环节就是记录日志,相信只要是个程序员都用过,可是咱们自问下,用了这么多年的日志框架,你确定自己真弄懂了日志框架的来龙去脉嘛?下面笔者就详细聊聊java中常用日志框架的...

物理老师教你学Java语言(中篇)(物理专业学编程)

第四章物质的基本结构——类与对象...

一文搞定!Spring Boot3 定时任务操作全攻略

各位互联网大厂的后端开发小伙伴们,在使用SpringBoot3开发项目时,你是否遇到过定时任务实现的难题呢?比如任务调度时间不准确,代码报错却找不到方向,是不是特别头疼?如今,随着互联网业务规模...

你还不懂java的日志系统吗 ?(java的日志类)

一、背景在java的开发中,使用最多也绕不过去的一个话题就是日志,在程序中除了业务代码外,使用最多的就是打印日志。经常听到的这样一句话就是“打个日志调试下”,没错在日常的开发、调试过程中打印日志是常干...

谈谈枚举的新用法--java(java枚举的作用与好处)

问题的由来前段时间改游戏buff功能,干了一件愚蠢的事情,那就是把枚举和运算集合在一起,然后运行一段时间后buff就出现各种问题,我当时懵逼了!事情是这样的,做过游戏的都知道,buff,需要分类型,且...

你还不懂java的日志系统吗(javaw 日志)

一、背景在java的开发中,使用最多也绕不过去的一个话题就是日志,在程序中除了业务代码外,使用最多的就是打印日志。经常听到的这样一句话就是“打个日志调试下”,没错在日常的开发、调试过程中打印日志是常干...

Java 8之后的那些新特性(三):Java System Logger

去年12月份log4j日志框架的一个漏洞,给Java整个行业造成了非常大的影响。这个事情也顺带把log4j这个日志框架推到了争议的最前线。在Java领域,log4j可能相对比较流行。而在log4j之外...

Java开发中的日志管理:让程序“开口说话”

Java开发中的日志管理:让程序“开口说话”日志是程序员的朋友,也是程序的“嘴巴”。它能让程序在运行过程中“开口说话”,告诉我们它的状态、行为以及遇到的问题。在Java开发中,良好的日志管理不仅能帮助...

吊打面试官(十二)--Java语言中ArrayList类一文全掌握

导读...

OS X 效率启动器 Alfred 详解与使用技巧

问:为什么要在Mac上使用效率启动器类应用?答:在非特殊专业用户的环境下,(每天)用户一般可以在系统中进行上百次操作,可以是点击,也可以是拖拽,但这些只是过程,而我们的真正目的是想获得结果,也就是...

Java中 高级的异常处理(java中异常处理的两种方式)

介绍异常处理是软件开发的一个关键方面,尤其是在Java中,这种语言以其稳健性和平台独立性而闻名。正确的异常处理不仅可以防止应用程序崩溃,还有助于调试并向用户提供有意义的反馈。...

【性能调优】全方位教你定位慢SQL,方法介绍下!

1.使用数据库自带工具...

全面了解mysql锁机制(InnoDB)与问题排查

MySQL/InnoDB的加锁,一直是一个常见的话题。例如,数据库如果有高并发请求,如何保证数据完整性?产生死锁问题如何排查并解决?下面是不同锁等级的区别表级锁:开销小,加锁快;不会出现死锁;锁定粒度...

看懂这篇文章,你就懂了数据库死锁产生的场景和解决方法

一、什么是死锁加锁(Locking)是数据库在并发访问时保证数据一致性和完整性的主要机制。任何事务都需要获得相应对象上的锁才能访问数据,读取数据的事务通常只需要获得读锁(共享锁),修改数据的事务需要获...