当前位置:首页 > 技术 > 正文内容

shader非真实感渲染技术

访客 技术 2026年9月14日 12

卡通风格渲染技术

轮廓线渲染方法

技术方案

  1. 基于观察角度与表面法线的渲染方法:通过计算视角方向与表面法线的点积结果来确定轮廓线。该方法实现简单,可在单次渲染中完成,但对复杂模型的渲染效果有限。

  2. 双面渲染技术:采用两次渲染,分别处理模型的前后表面,以实现轮廓线的可见性。此方法适合表面光滑的模型,但对于平面模型效果欠佳。

  3. 图像边缘检测技术:利用sobel算子等图像处理算法进行边缘检测。缺点是无法有效检测法线变化微小的边缘,如纸张等平面物体。

  4. 精确轮廓线渲染技术:通过检测相邻面片法线方向差异来实现精准的轮廓线渲染。其判断条件为:

$$ \left( \vec{n}_0 \cdot \vec{v} > 0 \right) \neq \left( \vec{n}_1 \cdot \vec{v} > 0 \right) $$

其中,$\vec{n}_0$和$\vec{n}_1$为相邻面片的法线向量,$\vec{v}$为视角到该边任意顶点的方向向量。

顶点扩展技术

在视角空间中将顶点沿法线方向向外扩展一段距离,从而实现背面轮廓线的显示。为了避免内凹模型出现背面遮挡正面的问题,我们对顶点法线的z分量进行预处理,使其保持一致后再进行扩展:

vertexNormal.z = -0.5;
vertexNormal = normalize(vertexNormal);
vertexPosition = vertexPosition + vertexNormal * outlineWidth;

高光反射技术

在卡通渲染中,高光区域通常表现为明显的纯色区块。我们采用半角向量与法线方向的点积结果与阈值进行比较,来控制高光反射系数。具体计算公式为:

$$ c_{specular} = (c \cdot m_{specular}) \cdot \max(0, \vec{n} \cdot \vec{h})^{m_{gloss}} $$

其中,$\vec{n}$为法线方向,$\vec{h}$为半角向量。

实现方案:

	Properties {
		_Color ("颜色遮罩", Color) = (1, 1, 1, 1)
		_MainTex ("主纹理", 2D) = "white" {}
		_Ramp ("渐变纹理", 2D) = "white" {}
		_Outline ("轮廓宽度", Range(0, 1)) = 0.1
		_OutlineColor ("轮廓颜色", Color) = (0, 0, 0, 1)
		_Specular ("高光颜色", Color) = (1, 1, 1, 1)
		_SpecularScale ("高光范围", Range(0, 0.1)) = 0.01
	}
    SubShader {
		Tags { "渲染类型"="不透明" "队列"="几何"}

		Pass {
			NAME "OUTLINE"

			//仅渲染背面
			Cull Front

			CGPROGRAM

			#pragma vertex vert
			#pragma fragment frag

			#include "UnityCG.cginc"

			float outlineWidth;
			fixed4 outlineColor;

			struct VertexInput {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};

			struct VertexOutput {
			    float4 pos : SV_POSITION;
			};

			VertexOutput vert (VertexInput v) {
				VertexOutput o;
				//计算视角空间顶点位置
				float4 pos = mul(UNITY_MATRIX_MV, v.vertex);
				//处理法线方向
				float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);
				normal.z = -0.5;
				normal = normalize(normal);
				//扩展顶点位置
				pos = pos + float4(normal, 0) * outlineWidth;
				//转换到裁剪空间
				o.pos = mul(UNITY_MATRIX_P, pos);

				return o;
			}

			fixed4 frag(VertexOutput i) : SV_Target {
				//设置轮廓颜色
				return fixed4(outlineColor.rgb, 1);
			}

			ENDCG
		}

		Pass {
			Tags { "光照模式"="前向基底" }
			//背面已经由前一个Pass处理
			Cull Back

			CGPROGRAM

			#pragma vertex vert
			#pragma fragment frag

			#pragma multi_compile_fwdbase

			#include "UnityCG.cginc"
			#include "Lighting.cginc"
			#include "AutoLight.cginc"
			#include "UnityShaderVariables.cginc"

			fixed4 _Color;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			//渐变纹理
			sampler2D _Ramp;
			fixed4 specularColor;
			fixed specularRange;

			struct VertexInput {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float4 texcoord : TEXCOORD0;
				float4 tangent : TANGENT;
			};

			struct VertexOutput {
				float4 pos : POSITION;
				float2 uv : TEXCOORD0;
				float3 worldNormal : TEXCOORD1;
				float3 worldPos : TEXCOORD2;
				SHADOW_COORDS(3)
			};

			VertexOutput vert (VertexInput v) {
				VertexOutput o;

				o.pos = UnityObjectToClipPos( v.vertex);
				o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);
				o.worldNormal  = UnityObjectToWorldNormal(v.normal);
				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

				TRANSFER_SHADOW(o);

				return o;
			}

			float4 frag(VertexOutput i) : SV_Target {
				fixed3 surfaceNormal = normalize(i.worldNormal);
				fixed3 lightDirection = normalize(UnityWorldSpaceLightDir(i.worldPos));
				fixed3 viewDirection = normalize(UnityWorldSpaceViewDir(i.worldPos));
				fixed3 halfDirection = normalize(lightDirection + viewDirection);

				fixed4 baseColor = tex2D (_MainTex, i.uv);
				fixed3 albedo = baseColor.rgb * _Color.rgb;

				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

				UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);

				fixed diff =  dot(surfaceNormal, lightDirection);
				diff = (diff * 0.5 + 0.5) * atten;

				fixed3 diffuse = _LightColor0.rgb * albedo * tex2D(_Ramp, float2(diff, diff)).rgb;

				fixed spec = dot(surfaceNormal, halfDirection);
				//计算邻近像素的梯度变化
				fixed w = fwidth(spec) * 2.0;
				fixed3 specular = specularColor.rgb * lerp(0, 1, smoothstep(-w, w, spec + specularRange - 1)) * step(0.0001, specularRange);

				return fixed4(ambient + diffuse + specular, 1.0);
			}

			ENDCG
		}
	}
	FallBack "Diffuse"

相关文章

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

linux screen 用法详情 (nohup 的替代方案)

一、screen 是什么?能干嘛?screen 是一个终端复用器,可以:在一个 SSH 会话中开多个“虚拟终端”SSH 断线后,程序仍然在后台运行随时重新连接到原来的会话特别适合:nohup 的替代方案跑脚本 / 爬虫 / 训练模型运维、远程开发二、安装 screen# CentOS / Rocky / Almayum install -y screen# Debian / Ubuntuapt i...

PHPStan 有什么用?怎么用?

PHPStan 是一个 PHP 的静态分析工具,在不运行代码的情况下就能帮你发现潜在问题,比如:传错类型(把 string 传给接受 int 的函数)访问不存在的属性 / 方法null 没处理好永远不会执行到的代码数组 key/值类型不一致返回值不符合声明注释和真实类型不匹配它非常适合:想提升代码质量、减少线上 bug、统一团队风格的人(尤其是中大型项目)。一、PHPStan 有什么用(通俗点说)...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。