当前位置: 首页 > news >正文

素描风格渲染

素描风格渲染(Hatching Style Rendering),是一种非真实感渲染(NPR),主要目的是使3D模型看起来像 手绘素描的视觉效果。这种风格的渲染常用于游戏、动画和电影中,用来创造一种独特的艺术风格

1、基本原理

用漫反射系数决定采样权重,在多张具有不同密度和方向的素描纹理中进行采样,并将采样结果进行叠加得到最终效果
关键点:

  • 多张具有不同密度和方向的素描纹理

美术需要提供多张素描纹理,我们之后会根据不同位置的光照强度决定从哪种纹理中进行采样

  • 漫反射系数决定采样权重

通过兰伯特光照模型中的

max(0, 标准化后物体表面法线向量· 标准化后光源方向向量)* (素描纹理数 + 1)
将漫反射光照强度 0~1 扩充到 0~N ,如果是6张素描纹理,那么就是 0 ~ 7
根据不同顶点的不同光照,决定在哪一张纹理中进行采样的权重更大,该权重决定最后的颜色叠加
6~7:不在素描纹理中采样;
5~6:第1张素描纹理中采样;
4~5:第1、2素描张纹理中采样;
3~4:第2、3张纹理中采样;
2~3:第3、4张纹理中采样;
1~2:第4、5张纹理中采样;
0~1:第5、6张纹理中采样;

  • 采样结果进行叠加

根据之前的权重计算,越亮的地方、越趋近于白色,或使用的素描纹理中线条更少更稀疏
而越暗的地方使用的素描纹理中线条更密集

因此只需要使用之前的权重值和纹理采样结果相乘,最后将纹理颜色进行叠加即可

2、实现

Version 1:

Shader "ShaderProj/20/Sketch"
{Properties{_Color ("Color", Color) = (1,1,1,1)_TileFactor ("TileFactor", Float) = 1_Sketch0 ("Sketch0", 2D) = ""{}_Sketch1 ("Sketch1", 2D) = ""{}_Sketch2 ("Sketch2", 2D) = ""{}_Sketch3 ("Sketch3", 2D) = ""{}_Sketch4 ("Sketch4", 2D) = ""{}_Sketch5 ("Sketch5", 2D) = ""{}}SubShader{Tags { "RenderType"="Opaque" }Pass{CGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"struct v2f{float2 uv : TEXCOORD0;float4 vertex : SV_POSITION;// x,y,z 分别代表第 1,2,3张素描纹理的权重fixed3 sketchWeight0 : TEXCOORD1;// x,y,z 分别代表第 4,5,6张素描纹理的权重fixed3 sketchWeight1 : TEXCOORD2;};fixed4 _Color;float _TileFactor;sampler2D _Sketch0;sampler2D _Sketch1;sampler2D _Sketch2;sampler2D _Sketch3;sampler2D _Sketch4;sampler2D _Sketch5;v2f vert (appdata_base v){v2f o;o.vertex = UnityObjectToClipPos(v.vertex);// uv 坐标平铺缩放,值越大,细节越多o.uv = v.texcoord.xy * _TileFactor;fixed3 worldLightDir = normalize(WorldSpaceLightDir(v.vertex));fixed3 worldNormal = normalize(UnityObjectToWorldNormal(v.normal));fixed diffFac = max(0, dot(worldLightDir, worldNormal)) * 7.0;o.sketchWeight0 = fixed3(0, 0, 0);o.sketchWeight1 = fixed3(0, 0, 0);if (diffFac > 6.0){}// 最亮的部分,不从纹理中采样else if(diffFac > 5.0)  // 从第1张图中采样{o.sketchWeight0.x = diffFac - 5.0;}else if(diffFac > 4.0) // 从第2张图中采样{o.sketchWeight0.y = diffFac - 4.0;}else if (diffFac > 3.0) // 从第3张图中采样{ o.sketchWeight0.z = diffFac - 3.0;} else if (diffFac > 2.0) // 从第4张图中采样{ o.sketchWeight1.x = diffFac - 2.0;}else if (diffFac > 1.0) // 从第5张图中采样{o.sketchWeight1.y = diffFac - 1.0;}else  // 从第6张图中采样{o.sketchWeight1.z = diffFac;}return o;}fixed4 frag (v2f i) : SV_Target{fixed4 sketchColor0 = tex2D(_Sketch0, i.uv) * i.sketchWeight0.x;fixed4 sketchColor1 = tex2D(_Sketch1, i.uv) * i.sketchWeight0.y;fixed4 sketchColor2 = tex2D(_Sketch2, i.uv) * i.sketchWeight0.z;fixed4 sketchColor3 = tex2D(_Sketch3, i.uv) * i.sketchWeight1.x;fixed4 sketchColor4 = tex2D(_Sketch4, i.uv) * i.sketchWeight1.y;fixed4 sketchColor5 = tex2D(_Sketch5, i.uv) * i.sketchWeight1.z;// 最亮的部分(白色)fixed4 whiteColor = fixed4(1,1,1,1) * (1 - i.sketchWeight0.x - i.sketchWeight0.y - i.sketchWeight0.z - i.sketchWeight1.x - i.sketchWeight1.y - i.sketchWeight1.z);fixed4 sketchColor = whiteColor + sketchColor0 + sketchColor1 + sketchColor2 + sketchColor3 + sketchColor4 + sketchColor5;return fixed4(sketchColor.rgb, 1);}ENDCG}}
}

现在是有问题的,可以看到材质球最暗的部分显示的是白色,同时有黑色的部分会有白色的条纹,这分别是因为:

  • 当跑到 【o.sketchWeight1.z = diffFac】 的分支时,值是很小的,趋近于0 ,因此最终计算的 whiteColor 基本为白色
  • 当跑到【o.sketchWeight0.x = diffFac - 5.0】的分支时,如果 diffFac 趋近于 5,那么权重也几近于 0,因此在该分支的边缘部分会变成白色

为了解决这个问题,可以每个分支用两张纹理进行采样,采样权重用【1- weight】,这样就可以在计算 whiteColor 的时候减少白色的填充(除了最亮的地方,因为本来就是白色)

Shader "ShaderProj/20/Sketch"
{Properties{_Color ("Color", Color) = (1,1,1,1)_TileFactor ("TileFactor", Float) = 1_Sketch0 ("Sketch0", 2D) = ""{}_Sketch1 ("Sketch1", 2D) = ""{}_Sketch2 ("Sketch2", 2D) = ""{}_Sketch3 ("Sketch3", 2D) = ""{}_Sketch4 ("Sketch4", 2D) = ""{}_Sketch5 ("Sketch5", 2D) = ""{}_OutLineColor ("OutLineColor", Color) = (1,1,1,1)_OutLineWidth ("OutLineWidth", Range(0,0.1)) = 0.04}SubShader{Tags { "RenderType"="Opaque" }UsePass "ShaderProj/19/Kartoon/OUTLINE"Pass{CGPROGRAM#pragma vertex vert#pragma fragment frag#pragma multi_compile_fwdbase#include "UnityCG.cginc"#include "Lighting.cginc"#include "AutoLight.cginc"struct v2f{float2 uv : TEXCOORD0;float4 pos : SV_POSITION;// x,y,z 分别代表第 1,2,3张素描纹理的权重fixed3 sketchWeight0 : TEXCOORD1;// x,y,z 分别代表第 4,5,6张素描纹理的权重fixed3 sketchWeight1 : TEXCOORD2;float3 worldPos: TEXCOORD3;SHADOW_COORDS(4)};fixed4 _Color;float _TileFactor;sampler2D _Sketch0;sampler2D _Sketch1;sampler2D _Sketch2;sampler2D _Sketch3;sampler2D _Sketch4;sampler2D _Sketch5;v2f vert (appdata_base v){v2f o;o.pos = UnityObjectToClipPos(v.vertex);// uv 坐标平铺缩放,值越大,细节越多o.uv = v.texcoord.xy * _TileFactor;o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;TRANSFER_SHADOW(o);fixed3 worldLightDir = normalize(WorldSpaceLightDir(v.vertex));fixed3 worldNormal = normalize(UnityObjectToWorldNormal(v.normal));fixed diffFac = max(0, dot(worldLightDir, worldNormal)) * 7.0;o.sketchWeight0 = fixed3(0, 0, 0);o.sketchWeight1 = fixed3(0, 0, 0);if (diffFac > 6.0){}// 最亮的部分,不从纹理中采样else if(diffFac > 5.0)  // 从第1张图中采样{o.sketchWeight0.x = diffFac - 5.0;}else if(diffFac > 4.0) // 从第1, 2张图中采样{o.sketchWeight0.x = diffFac - 4.0;o.sketchWeight0.y = 1- o.sketchWeight0.x;}else if (diffFac > 3.0) // 从第2, 3张图中采样{ o.sketchWeight0.y = diffFac - 3.0;o.sketchWeight0.z = 1 - o.sketchWeight0.y;} else if (diffFac > 2.0) // 从第3, 4张图中采样{ o.sketchWeight0.z = diffFac - 2.0;o.sketchWeight1.x = 1 - o.sketchWeight0.z;}else if (diffFac > 1.0) // 从第4, 5张图中采样{o.sketchWeight1.x = diffFac - 1.0;o.sketchWeight1.y = 1 - o.sketchWeight1.x;}else  // 从第5, 6张图中采样{o.sketchWeight1.y = diffFac;o.sketchWeight1.z = 1 - o.sketchWeight1.y;}return o;}fixed4 frag (v2f i) : SV_Target{fixed4 sketchColor0 = tex2D(_Sketch0, i.uv) * i.sketchWeight0.x;fixed4 sketchColor1 = tex2D(_Sketch1, i.uv) * i.sketchWeight0.y;fixed4 sketchColor2 = tex2D(_Sketch2, i.uv) * i.sketchWeight0.z;fixed4 sketchColor3 = tex2D(_Sketch3, i.uv) * i.sketchWeight1.x;fixed4 sketchColor4 = tex2D(_Sketch4, i.uv) * i.sketchWeight1.y;fixed4 sketchColor5 = tex2D(_Sketch5, i.uv) * i.sketchWeight1.z;// 最亮的部分(白色)fixed4 whiteColor = fixed4(1,1,1,1) * (1 - i.sketchWeight0.x - i.sketchWeight0.y - i.sketchWeight0.z - i.sketchWeight1.x - i.sketchWeight1.y - i.sketchWeight1.z);fixed4 sketchColor = whiteColor + sketchColor0 + sketchColor1 + sketchColor2 + sketchColor3 + sketchColor4 + sketchColor5;UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);return fixed4(sketchColor.rgb * atten * _Color.rgb, 1);}ENDCG}}Fallback "Diffuse"
}

相关文章:

素描风格渲染

素描风格渲染(Hatching Style Rendering),是一种非真实感渲染(NPR),主要目的是使3D模型看起来像 手绘素描的视觉效果。这种风格的渲染常用于游戏、动画和电影中,用来创造一种独特的艺术风格 1、…...

STM32使用DSP库 Keil方式添加

文章目录 前言一、添加DSP库二、使能FPU及配置1. 使能FPU2. 增加编译的宏3.增加头文件的检索路径三. 验证1. 源码中添加2.代码测试前言 添加DSP有两种方案,本文采用的是是Keil 中添加。 一、添加DSP库 在创建好的工程中添加DSP库:步骤如下: 步骤1:选择运行环境管理; 步…...

【机器学习实战入门项目】MNIST数字分类机器学习项目

Python 深度学习项目:手写数字识别 为了使机器更加智能,开发者们正在深入研究机器学习和深度学习技术。人类通过不断练习和重复来学习执行某项任务,从而记住如何完成这些任务。然后,大脑中的神经元会自动触发,他们能够…...

利用 LNMP 实现 WordPress 站点搭建

部署MySQL数据库 在主机192.168.138.139主机部署数据库服务 包安装数据库 apt-get install mysql-server 创建wordpress数据库和用户并授权 mysql> create database wordpress;#MySQL8.0要求指定插件 mysql> create user wordpress192.168.138.% identified with mys…...

模块化架构与微服务架构,哪种更适合桌面软件开发?

前言 在现代软件开发中,架构设计扮演着至关重要的角色。两种常见的架构设计方法是模块化架构与微服务架构。它们各自有独特的优势和适用场景,尤其在C#桌面软件开发领域,模块化架构往往更加具有实践性。本文将对这两种架构进行对比&#xff0…...

2025.1.17——1200

2025.1.17——1200 Q1. 1200 Jellyfish has n n n green apples with values a 1 , a 2 , … , a n a_1, a_2, \dots, a_n a1​,a2​,…,an​ and Gellyfish has m m m green apples with values b 1 , b 2 , … , b m b_1,b_2,\ldots,b_m b1​,b2​,…,bm​. They will …...

vite工程化

Vite 通过直接利用浏览器的模块加载能力、将 CommonJS 模块转换为 ES 模块并缓存结果、基于原生 ES 模块的 HMR 以及对 TypeScript 的直接支持,提供了更快的开发体验和更高的开发效率。 1.直接利用浏览器模块加载功能 更快加载速度:不需要打包&#xf…...

Mysql常见问题处理集锦

Mysql常见问题处理集锦 root用户密码忘记,重置的操作(windows上的操作)MySQL报错:ERROR 1118 (42000): Row size too large. 或者 Row size too large (> 8126).场景:报错原因解决办法 详解行大小限制示例:内容来源于网…...

Android SystemUI——CarSystemBar添加到窗口(十)

上一篇文章我们看到了车载状态栏 CarSystemBar 视图的创建流程,这里我们继续分析将车载状态栏添加到 Windows 窗口中。 一、添加状态栏到窗口 前面我们已经分析了构建视图对象容器和构建视图对象内容,接下来我们继续分析 attachNavBarWindows() 方法将视…...

《重生到现代之从零开始的C++生活》—— 类和对象1

类 我嘞个豆,类可是太重要了,简直是重中之重 class为定义类的关键字,stack为类的名字,{}为类的主题 class stack {void add (int a,int b){return ab;}//类的方法,成员函数int _c;int _d;//类的属性,成…...

《FMambaIR:一种基于混合状态空间模型和频域的方法用于图像恢复》学习笔记

paper:(PDF) FMambaIR: A Hybrid State Space Model and Frequency Domain for Image Restoration 目录 摘要 一、引言 二、相关工作 1、图像恢复 2、频率学习 3、状态空间模型(SSM) 三、框架 1、基本知识 2、整体框架 3、F-Mamba…...

每日十题八股-2025年1月18日

1.服务器处理并发请求有哪几种方式? 2.讲一下io多路复用 3.select、poll、epoll 的区别是什么? 4.epoll 的 边缘触发和水平触发有什么区别? 5.redis,nginx,netty 是依赖什么做的这么高性能? 6.零拷贝是什么…...

海康威视摄像头RTSP使用nginx推流到服务器直播教程

思路: 之前2020年在本科的时候,由于项目的需求需要将海康威视的摄像头使用推流服务器到网页进行直播。这里将自己半个月琢磨出来的步骤给大家发一些。切勿转载!!!! 使用网络摄像头中的rtsp协议---------通…...

搭建一个基于Spring Boot的书籍学习平台

搭建一个基于Spring Boot的书籍学习平台可以涵盖多个功能模块,例如用户管理、书籍管理、学习进度跟踪、笔记管理、评论和评分等。以下是一个简化的步骤指南,帮助你快速搭建一个基础的书籍学习平台。 — 1. 项目初始化 使用 Spring Initializr 生成一个…...

Go 语言的slice是如何扩容的?

Go 语言中的 slice 是一种灵活、动态的视图,是对底层数组的抽象。当对 slice 进行追加元素等操作导致其长度超过容量时,就会发生扩容。 一、扩容的基本原理 当 slice 需要扩容时,Go 语言会根据当前的容量来确定新的容量。一般来说&#xff…...

Apache Hive--排序函数解析

在大数据处理与分析中,Apache Hive是一个至关重要的数据仓库工具。其丰富的函数库为数据处理提供了诸多便利,排序函数便是其中一类非常实用的工具。通过排序函数,我们能够在查询结果集中为每一行数据分配一个排名值,这对于数据分析…...

Java 接口安全指南

Java 接口安全指南 概述 在现代 Web 应用中,接口(API)是前后端交互的核心。然而,接口的安全性常常被忽视,导致数据泄露、未授权访问等安全问题。本文将详细介绍 Java 中如何保障接口安全,涵盖以下内容&am…...

合合信息名片全能王上架原生鸿蒙应用市场,成为首批数字名片类应用

长期以来,名片都是企业商务沟通的重要工具。随着企业数字化转型,相较于传统的纸质名片,数字名片对于企业成员拓展业务、获取商机、提升企业形象等方面发挥着重要作用。近期,合合信息旗下名片全能王正式上线原生鸿蒙应用市场&#…...

38.【3】CTFHUB web sql 报错注入

进入靶场 按照提示输入1 显示查询正确 既然是报错注入,先判断整形还是字符型注入 先输入1 and 11 再输入1 and 12 都显示查询正确,可知此为字符串型注入,不是数字型注入 然后就不会了 求助AI和其他wp 由以上2张搜索结果知updatexml是适用…...

RC2在线加密工具

RC2是由著名密码学家Ron Rivest设计的一种传统对称分组加密算法,它可作为DES算法的建议替代算法。RC2是一种分组加密算法,RC2的密钥长度可变,可以从8字节到128字节,安全性选择更加灵活。 开发调试上,有时候需要进行对…...

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...

相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解

【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下: 一、场景操作步骤 操作步…...

Python爬虫实战:研究feedparser库相关技术

1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...

解锁数据库简洁之道:FastAPI与SQLModel实战指南

在构建现代Web应用程序时,与数据库的交互无疑是核心环节。虽然传统的数据库操作方式(如直接编写SQL语句与psycopg2交互)赋予了我们精细的控制权,但在面对日益复杂的业务逻辑和快速迭代的需求时,这种方式的开发效率和可…...

前端开发面试题总结-JavaScript篇(一)

文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包(Closure)?闭包有什么应用场景和潜在问题?2.解释 JavaScript 的作用域链(Scope Chain) 二、原型与继承3.原型链是什么?如何实现继承&a…...

【JavaSE】绘图与事件入门学习笔记

-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...

图表类系列各种样式PPT模版分享

图标图表系列PPT模版,柱状图PPT模版,线状图PPT模版,折线图PPT模版,饼状图PPT模版,雷达图PPT模版,树状图PPT模版 图表类系列各种样式PPT模版分享:图表系列PPT模板https://pan.quark.cn/s/20d40aa…...

蓝桥杯3498 01串的熵

问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798&#xff0c; 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...

在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案

这个问题我看其他博主也写了&#xff0c;要么要会员、要么写的乱七八糟。这里我整理一下&#xff0c;把问题说清楚并且给出代码&#xff0c;拿去用就行&#xff0c;照着葫芦画瓢。 问题 在继承QWebEngineView后&#xff0c;重写mousePressEvent或event函数无法捕获鼠标按下事…...

保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek

文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama&#xff08;有网络的电脑&#xff09;2.2.3 安装Ollama&#xff08;无网络的电脑&#xff09;2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...