news 2026/6/25 14:55:41

TA不一样(六)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
TA不一样(六)

前言:本节介绍边缘光(Rim Light)的原理与实现

基础Rim Light

1.核心原理

Rim Light 利用‌视线方向(V)与表面法线(N)的夹角‌来计算边缘亮度,当视线方向与表面法线接近垂直时(即物体的轮廓边缘位置),点积结果趋近于0,通过:

1 - saturate(dot(N, V))

计算得到的边缘光强度会达到最大值,从而在物体边缘生成高亮效果。
这种效果模拟了现实中光线从物体背面或侧面穿透、勾勒轮廓的视觉表现,常用于卡通风格、次表面散射材质的渲染。


2.代码实现
Shader "MyCustom/BasicRimLight" { Properties { // 边缘光颜色 _RimLightColor ("_RimLightColor", Color) = (1, 1, 1, 1) // _RimLightPower ("_RimLightPower", Range(0, 10)) = 1 _RimLightIntensity ("_RimLightIntensity", Range(0, 10)) = 1 } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag // make fog work #pragma multi_compile_fog #include "UnityCG.cginc" float4 _RimLightColor; float _RimLightPower; float _RimLightIntensity; struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; }; struct v2f { float4 vertex : SV_POSITION; float3 worldNormal : TEXCOORD0; float3 worldView : TEXCOORD1; }; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); // 法线方向 o.worldNormal = UnityObjectToWorldNormal(v.normal); float4 worldPos = mul(unity_ObjectToWorld, v.vertex); // 视线方向 o.worldView = normalize(_WorldSpaceCameraPos.xyz - worldPos.xyz); return o; } // 边缘光公式 float3 funcFresnel(float3 worldNormal, float3 worldView) { float nv = max(0, dot(worldNormal, worldView)); float3 fresnel = pow(1 - nv, _RimLightPower) * _RimLightIntensity * _RimLightColor.rgb; return fresnel; } fixed4 frag (v2f i) : SV_Target { float3 fresnel = funcFresnel(i.worldNormal, i.worldView); return float4(fresnel, 1); } ENDCG } } FallBack "Diffuse" }

Fake Rim Light

1.核心原理

Fake Rim Light(假边缘光)‌ 是一种更轻量、更具风格化的实现方案。它不依赖复杂的法线与视线夹角计算,而是通过‌贴图‌来模拟轮廓高亮。Fake Rim Light 的核心在于‌“不求物理正确,但求视觉有效”‌。


2.代码实现
Shader "MyCustom/FakeRimLight" { Properties { // 边缘光贴图 _FakeRimLightTex ("_FakeRimLightTex", 2D) = "white" {} _RimLightColor ("_RimLightColor", Color) = (1, 1, 1, 1) _RimLightPower ("_RimLightPower", Range(0, 10)) = 1 _RimLightIntensity ("_RimLightIntensity", Range(0, 10)) = 1 } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag // make fog work #pragma multi_compile_fog #include "UnityCG.cginc" float4 _RimLightColor; float _RimLightPower; float _RimLightIntensity; sampler2D _FakeRimLightTex; struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; float3 normal : NORMAL; }; struct v2f { float4 vertex : SV_POSITION; float2 uv : TEXCOORD0; float3 worldNormal : TEXCOORD1; float3 worldView : TEXCOORD2; }; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; o.worldNormal = UnityObjectToWorldNormal(v.normal); float4 worldPos = mul(unity_ObjectToWorld, v.vertex); o.worldView = normalize(_WorldSpaceCameraPos.xyz - worldPos.xyz); return o; } float3 funcFresnel(float3 worldNormal, float3 worldView) { float nv = max(0, dot(worldNormal, worldView)); float3 fresnel = pow(1 - nv, _RimLightPower) * _RimLightIntensity * _RimLightColor.rgb; return fresnel; } fixed4 frag (v2f i) : SV_Target { float3 fresnel = funcFresnel(i.worldNormal, i.worldView); // 没有使用标准的 UV,而是利用‌世界空间法线的 X 分量‌来采样纹理 // 无论模型如何旋转,纹理都会根据法线朝向“投影”到模型表面。例如,朝左的面会采样纹理左侧的颜色,朝右的面采样右侧。 float u = i.worldNormal.x * 0.5 + 0.5; float3 fakeRimLight = tex2D(_FakeRimLightTex, float2(u, i.uv.y)).rgb; float3 finalColor = fakeRimLight * fresnel; return float4(finalColor, 1); } ENDCG } } FallBack "Diffuse" }

基于FresnelSchlicks的可变换范围的Rim Light

代码实现:

Shader "MyCustom/SchlickFresnel" { Properties { _RimLightColor0 ("_RimLightColor0", Color) = (1, 0, 0, 1) _RimLightColor1 ("_RimLightColor1", Color) = (0, 0, 1, 1) _RimLightPower ("_RimLightPower", Range(0, 10)) = 1 _RimLightIntensity ("_RimLightIntensity", Range(0, 10)) = 1 _SchlickFresnelBias ("_SchlickFresnelBias", Range(0, 2)) = 0.6 _SchlickFresnelEta ("_SchlickFresnelEta", Range(0, 10)) = 0.4 _AngleMin ("_AngleMin", Range(0, 360)) = 0 _AngleMax ("_AngleMax", Range(0, 360)) = 45 _Inverse ("_Inverse", Range(-1, 1)) = 1 } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag // make fog work #pragma multi_compile_fog #include "UnityCG.cginc" #include "MyCustomHeader.cginc" #define PI 3.14159265359 float4 _RimLightColor0; float4 _RimLightColor1; float _RimLightPower; float _RimLightIntensity; float _SchlickFresnelBias; float _SchlickFresnelEta; float _AngleMin; float _AngleMax; float _Inverse; struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; float3 normal : NORMAL; }; struct v2f { float4 vertex : SV_POSITION; float2 uv : TEXCOORD0; float3 viewNormal : TEXCOORD1; float3 worldNormal : TEXCOORD2; float3 worldView : TEXCOORD3; }; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; o.viewNormal = normalize(mul((float3x3)UNITY_MATRIX_IT_MV, v.normal)); o.worldNormal = UnityObjectToWorldNormal(v.normal); float4 worldPos = mul(unity_ObjectToWorld, v.vertex); o.worldView = normalize(_WorldSpaceCameraPos.xyz - worldPos.xyz); return o; } // 边缘光公式 float3 funcFresnel(float3 worldNormal, float3 worldView) { float nv = max(0, dot(worldNormal, worldView)); float3 fresnel = pow(1 - nv, _RimLightPower) * _RimLightIntensity * _RimLightColor0.rgb; return fresnel; } // 将value在input范围中对应到output中,输出v float funcSetRange(float2 input, float2 output, float value) { float v; Unity_Remap_float(value, input, output, v); return v; } // 非线性调节菲涅尔曲线形状,改变高光衰减速度 float funcBias(float bias, float t) { return pow(t, -log2(bias)); } // 法线转极坐标,将 (x,y)转换为 (θ,r)用于角度判断 float2 polarCoordinates(float2 uv) { float2 output; Unity_PolarCoordinates_float(uv, 0, 1, 1, output); return output; } // 平滑的角度区间遮罩,在 _AngleMin 和 _AngleMax 之间生成 0~1 的平滑过渡值 float customLerp(float a, float b, float t) { return (step(a, t) - step(b, t)) * (smoothstep(a, a + (b - a) / 2, t) * (1 - smoothstep(a + (b - a) / 2, b, t))); } // 实现SchlickFresnel float3 funcSchlickFresnel(float bias, float eta, float intensity, float nv) { // 插值输出,填的是一些经验参数 float a = funcSetRange(float2(0, 1), float2(0, 0.5), bias); float f0 = funcSetRange(float2(0, 1), float2(0, 0.2), eta); //经验参数公式: float fresnelFactor = _FresnelRatio + (1 - _FresnelRatio) * pow(1 - dot(viewDir, i.worldNormal), 5); float fresnel = f0 + (1 - f0) * pow(1 - nv, 5); // 应用非线性调节参数 float SchlickFresnel = funcBias(a, fresnel) * intensity; return SchlickFresnel; } float3 schlickFresnel(float3 worldNormal, float3 worldView) { float nv = max(0, dot(worldNormal, worldView)); float3 fresnel = funcSchlickFresnel(_SchlickFresnelBias, _SchlickFresnelEta, _RimLightIntensity, nv); return fresnel; } float3 schlickFresnelSegment(float3 viewNormal, float3 worldNormal, float3 worldView, float inverse) { // 计算fresnel的显示区域 float2 uv = float2(viewNormal.x * inverse, viewNormal.y); float2 p = polarCoordinates(uv); // 角度插值输出 float angleMin = funcSetRange(float2(0, 360), float2(0, 1), _AngleMin); float angleMax = funcSetRange(float2(0, 360), float2(0, 1), _AngleMax); float t = clamp(p.y, 0, 1); float v = customLerp(angleMin, angleMax, t); // 计算fresnel float3 fresnel = schlickFresnel(worldNormal, worldView); // 计算区域fresnel float3 fresnelSegment = fresnel * v * pow(p.x, 2); return fresnelSegment; } // 根据附加两种边缘颜色 float3 schlickFresnelSegmentColor(float3 viewNormal, float3 worldNormal, float3 worldView) { float3 color0 = schlickFresnelSegment(viewNormal, worldNormal, worldView, _Inverse) * _RimLightColor0.rgb; float3 color1 = schlickFresnelSegment(viewNormal, worldNormal, worldView, -_Inverse) * _RimLightColor1.rgb; return color0 + color1; } fixed4 frag (v2f i) : SV_Target { float3 fresnel = schlickFresnelSegmentColor(i.viewNormal, i.worldNormal, i.worldView); return float4(fresnel, 1); } ENDCG } } FallBack "Diffuse" }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/25 14:55:30

团队级AI协同操作系统:五层架构实现Claude Code规模化落地

1. 这不是“AI工具使用指南”,而是一套团队级AI协同操作系统我带过三支不同规模的技术团队落地AI编码辅助,从5人初创小队到40人的跨职能研发组。前两年,我们和所有人一样,把Claude Code当成“高级版Copilot”——开发者自己装、自…

作者头像 李华
网站建设 2026/6/25 14:55:17

逆向工程底层逻辑:还原网站识别机制,用Python模拟合法请求

免责声明 本文仅用于安全研究、接口调试与自动化测试等合法场景。文中所有案例均基于公开测试平台与自建靶场,未针对任何真实生产站点。请严格遵守《网络安全法》及目标站点的robots.txt与服务条款,禁止将技术用于未授权访问、数据爬取或破坏性操作。技术本身无罪,但使用者需…

作者头像 李华
网站建设 2026/6/25 14:55:02

企业数据孤岛如何打通?智能体集成方案解析

一、引言许多企业在数字化转型中都会面临一个现实问题:花了大量资金上线ERP、MES、PDM等系统,但各部门的数据依然像一个个独立的“仓库”——图纸放在PDM里,订单在ERP里流转,质量数据存在Excel表格中,BOM变更后生产部门…

作者头像 李华
网站建设 2026/6/25 14:52:35

嵌入式USB主机开发实战:从寄存器配置到错误处理全解析

1. 项目概述与核心价值如果你正在开发一个需要USB功能的嵌入式设备,比如一个便携式的数据采集器、一个智能家居的中控,或者一个工业手持终端,那么你大概率绕不开USB OTG(On-The-Go)这个技术。它让我们的设备不再仅仅是…

作者头像 李华
网站建设 2026/6/25 14:41:38

K8s背得滚瓜烂熟,面试还是挂了

云原生方向三年,K8s、Istio、Helm、ArgoCD,该会的都会。 上个月面试一家公司,技术轮过得很顺,到最后一面,面试官问了我一个问题,我答不上来: “你做过的这些云原生工作,给业务带来了…

作者头像 李华
网站建设 2026/6/25 14:37:24

多模态 AI 架构原理解析:它是怎么同时“看懂”图文音视频的?

文章标签:#多模态大模型 #GPT-5.5 #扩散模型 #深度学习 #Transformer #人工智能 多模态 AI 架构原理解析:它是怎么同时“看懂”图文音视频的? 摘要:当我们在用 GPT-5.5 或 LLaVA 时,AI 似乎长了“眼睛”和“耳朵”。但…

作者头像 李华