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

素描风格渲染

素描风格渲染(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"
}


http://www.kler.cn/a/510006.html

相关文章:

  • C# 修改项目类型 应用程序程序改类库
  • [计算机网络]一. 计算机网络概论第一部分
  • PG vs MySQL mvcc机制实现的异同
  • PHP智慧小区物业管理小程序
  • 51.WPF应用加图标指南 C#例子 WPF例子
  • vue自适应高度(缩放浏览器)
  • 基于Java+Sql Server实现的(GUI)学籍管理系统
  • springboot基于微信小程序的传统美食文化宣传平台小程序
  • docker 基础语法学习,K8s基础语法学习,零基础学习
  • python-leetcode-存在重复元素 II
  • Linux shell zip 命令实现不切换当前终端的工作目录打包另一个路径下的文件和文件夹
  • TCP 重传演进:TCP RACK Timer 能替代 RTO 吗
  • 【触想智能】工业电脑一体机在数控机床设备上应用的注意事项以及工业电脑日常维护知识分享
  • 《汽车与驾驶维修》是什么级别的期刊?是正规期刊吗?能评职称吗?
  • 使用 Java 和 FreeMarker 实现自动生成供货清单,动态生成 Word 文档,简化文档处理流程。
  • Vue.js组件开发全解析
  • Excel中函数SIGN()的用法
  • Reactive StreamsReactor Core
  • ES elasticsearch安装(8.17)
  • spring-cloud-starter-gateway 使用中 KafkaAppender的问题
  • C# OpenCV机器视觉:特征匹配 “灵魂伴侣”
  • Vue.js组件开发-实现输入框与筛选逻辑
  • Nginx反向代理架构介绍
  • RabbitMQ-消息可靠性以及延迟消息
  • Python虚拟环境使用的全方位指南
  • 抖音ip属地不准是什么原因?可以改吗