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

实现动态卡通笑脸的着色器实现

大家好!我是 [数擎 AI],一位热爱探索新技术的前端开发者,在这里分享前端和 Web3D、AI 技术的干货与实战经验。如果你对技术有热情,欢迎关注我的文章,我们一起成长、进步!
开发领域:前端开发 | AI 应用 | Web3D | 元宇宙
技术栈:JavaScript、React、ThreeJs、WebGL、Go
经验经验:6 年+ 前端开发经验,专注于图形渲染和 AI 技术
开源项目:智简未来、晓智元宇宙、数字孪生引擎 、源码地址

演示地址:https://shader.shuqin.cc/lscczl
源码地址:https://github.com/dezhizhang/shadertoy

效果概述

本文通过使用 Three.js 实现的动态卡通笑脸着色器,解析其核心实现原理。该效果具有以下特性:

  • 😃 基础笑脸轮廓
  • 👀 眨动的眼睛(带动态偏移)
  • 👄 周期性开合的嘴巴
  • 🍎 动态变化的红晕脸颊
  • ⏱️ 基于时间的动画效果

核心实现原理

1. 坐标系处理

vec2 uv = vUv;
uv -= 0.5;                // 坐标系中心移到画布中心
uv.x *= iResolution.x/iResolution.y; // 保持宽高比

通过坐标变换实现:

  • 中心点(0,0)对应屏幕中心
  • 消除屏幕宽高比变形

2. 基础图形函数

float rect(vec2 uv, vec2 pos, float width, float height) {return (step(pos.x-width, uv.x) - step(pos.x+width, uv.x)) *(step(pos.y-height, uv.y) - step(pos.y+height, uv.y));
}

利用 step 函数实现:

  • 横向范围:pos.x±width
  • 纵向范围:pos.y±height

圆形绘制函数

float Circle(vec2 uv, vec2 pos, float rad, float blur) {float d = length(uv-pos);return smoothstep(rad, rad-blur, d);
}

特点:

  • 支持边缘模糊效果
  • 半径控制精度达 0.01

2. 动态元素实现

眼睛动画

float v = abs(clamp(sin(iTime), 0.0, 0.07));
Mask = mix(Mask, black, Circle(uv, vec2(0.05 + v, 0.07), 0.03, 0.01));
  • sin(iTime)产生周期性运动
  • clamp 限制移动范围在[0,0.07]
  • 通过位置偏移实现眨眼效果

嘴巴动画

float w = abs(clamp(sin(iTime*3.0), 0.0, 0.03));
Mask = mix(Mask, vec3(3.0), rect(uv, vec2(0.00, 0.13-w), 0.15, 0.03));
  • 3 倍速动画(iTime*3.0)

  • 垂直方向位置偏移实现嘴巴开合

Mask = mix(Mask, red, Circle(uv, vec2(0.05+v, -0.05), 0.04, 0.01));
  • 与眼睛动画联动的位置偏移

  • 圆形叠加实现渐变效果

4. 颜色混合策略

使用 mix 函数层级叠加:

Mask = mix(baseColor, overlayColor, alpha);

混合顺序:

  • 黄色基础轮廓

  • 眼睛黑点

  • 嘴巴线条

  • 脸颊红晕

5. 完整代码


import * as THREE from 'three';class ShaderEffect {constructor() {this.renderer = new THREE.WebGLRenderer({ antialias: true });this.renderer.setSize(window.innerWidth, window.innerHeight);document.body.appendChild(this.renderer.domElement);this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);this.scene = new THREE.Scene();this.uniforms = {iTime: { value: 0 },iResolution: { value: new THREE.Vector2() },};this.init();this.animate();window.addEventListener('resize', () => this.onResize());}init() {const geometry = new THREE.PlaneGeometry(2, 2);const material = new THREE.ShaderMaterial({uniforms: this.uniforms,vertexShader: `varying vec2 vUv;void main() {vUv = uv;gl_Position = projectionMatrix * modelMatrix * viewMatrix * vec4(position, 1.0);}`,fragmentShader: `uniform vec2 iResolution;uniform float iTime;varying vec2 vUv;float rect(vec2 uv, vec2 pos, float width, float height) {float square = (step(pos.x - width, uv.x) - step(pos.x + width, uv.x)) *(step(pos.y - height, uv.y) - step(pos.y + height, uv.y));return square;}float Circle(vec2 uv, vec2 pos, float rad, float blur) {float d = length(uv - pos);float t = smoothstep(rad, rad - blur, d);return t;}void main() {vec3 red = vec3(0.8, 0.0, 0.0);vec3 white = vec3(1.0);vec3 yellow = vec3(0.9, 0.9, 0.3);vec3 blue = vec3(0.5, 0.8, 0.9);vec3 black = vec3(0.0);vec3 green = vec3(0.0, 1.0, 0.0);vec2 uv = vUv;uv -= 0.5;uv.x *= iResolution.x / iResolution.y;vec3 Mask = mix(black, vec3(3.0, 3.0, 0.0), Circle(uv, vec2(0.0, 0.01), 0.2, 0.01));Mask = mix(Mask, vec3(3.0, 3.0, 0.0), Circle(uv, vec2(-0.13, 0.15), 0.07, 0.01));Mask = mix(Mask, vec3(3.0, 3.0, 0.0), Circle(uv, vec2(0.13, 0.15), 0.07, 0.01));float v = abs(clamp(sin(iTime), 0.0, 0.07));Mask = mix(Mask, black, Circle(uv, vec2(0.05 + v, 0.07), 0.03, 0.01));Mask = mix(Mask, black, Circle(uv, vec2(-0.10 + v, 0.07), 0.03, 0.01));float w = abs(clamp(sin(iTime * 3.0), 0.0, 0.03));Mask = mix(Mask, vec3(3.0, 3.0, 0.0), rect(uv, vec2(0.00, 0.13 - w), 0.15, 0.03));Mask = mix(Mask, black, rect(uv, vec2(-0.02 + v, -0.05), 0.03, 0.010));vec3 Mouth = mix(black, vec3(-10.0), rect(uv, vec2(-0.02 + v, -0.05), 0.08, 0.015));Mask = mix(Mouth, blue, Mask);Mask = mix(Mask, red, Circle(uv, vec2(0.05 + v, -0.05), 0.04, 0.01));Mask = mix(Mask, red, Circle(uv, vec2(-0.09 + v, -0.05), 0.04, 0.01));gl_FragColor = vec4(Mask, 1.0);}`,});this.mesh = new THREE.Mesh(geometry, material);this.scene.add(this.mesh);this.onResize();}animate() {requestAnimationFrame(() => this.animate());this.uniforms.iTime.value = performance.now() / 1000;this.renderer.render(this.scene, this.camera);}onResize() {const width = window.innerWidth;const height = window.innerHeight;this.renderer.setSize(width, height);this.uniforms.iResolution.value.set(width, height);}
}new ShaderEffect();

相关文章:

实现动态卡通笑脸的着色器实现

大家好!我是 [数擎 AI],一位热爱探索新技术的前端开发者,在这里分享前端和 Web3D、AI 技术的干货与实战经验。如果你对技术有热情,欢迎关注我的文章,我们一起成长、进步! 开发领域:前端开发 | A…...

低代码系统-产品架构案例介绍、蓝凌(十三)

蓝凌低代码系统,依旧是从下到上,从左至右的顺序。 技术平台h/iPaas 指低层使用了哪些技术,例如:微服务架构,MySql数据库。个人认为,如果是市场的主流,就没必要赘述了。 新一代门户 门户设计器&a…...

Autosar-以太网是怎么运行的?(Davinci配置部分)

写在前面: 入行一段时间了,基于个人理解整理一些东西,如有错误,欢迎各位大佬评论区指正!!! 目录 1.Autosar ETH通讯软件架构 2.Ethernet MCAL配置 2.1配置对应Pin属性 2.2配置TXD引脚 2.3配…...

洛谷网站: P3029 [USACO11NOV] Cow Lineup S 题解

题目传送门: P3029 [USACO11NOV] Cow Lineup S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 前言: 这道题的核心问题是在一条直线上分布着不同品种的牛,要找出一个连续区间,使得这个区间内包含所有不同品种的牛,…...

Centos 8 离线升级openssh 9.9

背景 根据云服务漏检报告,需要升级云服务器openssh服务(离线环境)。本文将采用rpm包形式,将openssh服务由OpenSSH_8.0p1 升级至OpenSSH_9.9p1。准备一台能够联网的服务器(简称server1)用于下载程序包&#…...

C++多线程编程——call_once和单例模式

目录 1. 前言 2. call_once和once_flag 3. 后记 3.1 单例类的析构问题 3.2 饿汉式单例模式的线程安全问题 1. 前言 之前在讲解单例模式时,有提到懒汉式单例模式使用了双重检测Double-Checked Locking Pattern (DCLP)来解决多线程的安全访问问题。但是该方法也…...

Java程序员 面试如何介绍项目经验?

项目经历是面试过程中重点问的,但是很多人在回答的时候往往会有问题: 重点是介绍项目,而忽略了个人的经历。 经历是你做了什么、你怎么做的、做完后的结果。例如:项目中的哪些部分是你做的?你是不是核心人员&#xf…...

STM32 ADC模数转换器

ADC简介 ADC(Analog-Digital Converter)模拟-数字转换器 ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁 12位逐次逼近型ADC,1us转换时间 输入电压范围:0~3.3V&#xff0…...

结合深度学习、自然语言处理(NLP)与多准则决策的三阶段技术框架,旨在实现从消费者情感分析到个性化决策

针对电商个性化推荐场景的集成机器学习和稳健优化三阶段方案。 第一阶段:在线评论数据处理,利用深度学习和自然语言处理技术进行特征挖掘,进而进行消费者情感分析,得到消费者偏好 在第一阶段,我们主要关注如何通过深度学习和自然语…...

机器学习8-卷积和卷积核

机器学习7-卷积和卷积核 卷积与图像去噪卷积的定义与性质定义性质卷积的原理卷积步骤卷积的示例与应用卷积的优缺点优点缺点 总结 高斯卷积核卷积核尺寸的设置依据任务类型考虑数据特性实验与调优 高斯函数标准差的设置依据平滑需求结合卷积核尺寸实际应用场景 总结 图像噪声与…...

SpringBoot使用 easy-captcha 实现验证码登录功能

文章目录 一、 环境准备1. 解决思路2. 接口文档3. redis下载 二、后端实现1. 引入依赖2. 添加配置3. 后端代码实现4. 前端代码实现 在前后端分离的项目中,登录功能是必不可少的。为了提高安全性,通常会加入验证码验证。 easy-captcha 是一个简单易用的验…...

DIY Shell:探秘进程构建与命令解析的核心原理

个人主页:chian-ocean 文章专栏-Linux 前言: Shell(外壳)是一个操作系统的用户界面,它提供了一种方式,使得用户能够与操作系统进行交互。Shell 是用户与操作系统之间的桥梁,允许用户通过命令行…...

数据库备份、主从、集群等配置

数据库备份、主从、集群等配置 1 MySQL1.1 docker安装MySQL1.2 主从复制1.2.1 主节点配置1.2.2 从节点配置1.2.3 创建用于主从同步的用户1.2.4 开启主从同步1.2.4 主从同步验证 1.3 主从切换1.3.1 主节点设置只读(在192.168.1.151上操作)1.3.2 检查主从数…...

【数据采集】基于Selenium采集豆瓣电影Top250的详细数据

基于Selenium采集豆瓣电影Top250的详细数据 Selenium官网:https://www.selenium.dev/blog/ 豆瓣电影Top250官网:https://movie.douban.com/top250 写在前面 实验目标:基于Selenium框架采集豆瓣电影Top250的详细数据。 电脑系统:Windows 使用软件:PyCharm、Navicat 技术需求…...

(回溯递归dfs 电话号码的字母组合 remake)leetcode 17

只找边界条件和非边界条件&#xff0c;剩下的交给数学归纳法就行&#xff0c;考虑子问题的重复性 [class Solution {vector<string>str { "","","abc","def","ghi","jkl","mno","pqrs"…...

Redis --- 使用zset处理排行榜和计数问题

在处理计数业务时&#xff0c;我们一般会使用一个数据结构&#xff0c;既是集合又可以保证唯一性&#xff0c;所以我们会选择Redis中的set集合&#xff1a; 业务逻辑&#xff1a; 用户点击点赞按钮&#xff0c;需要再set集合内判断是否已点赞&#xff0c;未点赞则需要将点赞数1…...

响应式编程_04Spring 5 中的响应式编程技术栈_WebFlux 和 Spring Data Reactive

文章目录 概述响应式Web框架Spring WebFlux响应式数据访问Spring Data Reactive 概述 https://spring.io/reactive 2017 年&#xff0c;Spring 发布了新版本 Spring 5&#xff0c; Spring 5 引入了很多核心功能&#xff0c;这其中重要的就是全面拥抱了响应式编程的设计思想和实…...

C++ Primer 算术运算符

欢迎阅读我的 【CPrimer】专栏 专栏简介&#xff1a;本专栏主要面向C初学者&#xff0c;解释C的一些基本概念和基础语言特性&#xff0c;涉及C标准库的用法&#xff0c;面向对象特性&#xff0c;泛型特性高级用法。通过使用标准库中定义的抽象设施&#xff0c;使你更加适应高级…...

中位数定理:小试牛刀> _ <2025牛客寒假1

给定数轴上的n个点&#xff0c;找出一个到它们的距离之和尽量小的点&#xff08;即使我们可以选择不是这些点里的点&#xff0c;我们还是选择中位数的那个点最优&#xff09; 结论:这些点的中位数就是目标点。可以自己枚举推导&#xff08;很好想&#xff09; (对于 点的数量为…...

一些常用的HTML结构

1. 页面基本结构 DOCTYPE 声明&#xff1a; 作用&#xff1a;告知浏览器使用哪种 HTML 版本进行解析。示例&#xff1a; <!DOCTYPE html><html> 标签&#xff1a; 作用&#xff1a;作为整个 HTML 文档的根元素&#xff0c;包含文档的头部和主体。示例&#xff1…...

js的 encodeURI() encodeURIComponent() decodeURI() decodeURIComponent() 笔记250205

js的 encodeURI() encodeURIComponent() decodeURI() decodeURIComponent() 在JavaScript中&#xff0c;处理URI编码和解码的四个关键函数为&#xff1a;encodeURI()、encodeURIComponent()、decodeURI()和decodeURIComponent()。它们分别用于不同的场景&#xff0c;具体区别和…...

安全实验作业

一 拓扑图 二 要求 1、R4为ISP&#xff0c;其上只能配置IP地址&#xff1b;R4与其他所有直连设备间均使用共有IP 2、R3-R5-R6-R7为MGRE环境&#xff0c;R3为中心站点&#xff1b; 3、整个OSPF环境IP基于172.16.0.0/16划分&#xff1b; 4、所有设备均可访问R4的环回&#x…...

《Python预训练视觉和大语言模型》:从DeepSeek到大模型实战的全栈指南

就是当代AI工程师的日常&#xff1a;* - 砸钱买算力&#xff0c;却卡在分布式训练的“隐形坑”里&#xff1b; - 跟着论文复现模型&#xff0c;结果连1/10的性能都达不到&#xff1b; - 好不容易上线应用&#xff0c;却因伦理问题被用户投诉…… 当所有人都在教你怎么调用…...

血压计OCR文字检测数据集VOC+YOLO格式2147张11类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;2147 标注数量(xml文件个数)&#xff1a;2147 标注数量(txt文件个数)&#xff1a;2147 …...

Java 面试合集(2024版)

种自己的花&#xff0c;爱自己的宇宙 目录 第一章-Java基础篇 1、你是怎样理解OOP面向对象??? 难度系数&#xff1a;? 2、重载与重写区别??? 难度系数&#xff1a;? 3、接口与抽象类的区别??? 难度系数&#xff1a;? 4、深拷贝与浅拷贝的理解??? 难度系数&…...

Typora免费使用

一.下载地址 https://typoraio.cn/ 二.修改配置文件 1.找到安装路径下的LicenseIndex.180dd4c7.4da8909c.chunk.js文件 文件路径为:安装路径\resources\page-dist\static\js\LicenseIndex.180dd4c7.4da8909c.chunk.js 将js中的 e.hasActivated"true"e.hasActiva…...

第一性原理:游戏开发成本的思考

利润 营收-成本 营收定价x销量x分成比例 销量 曝光量x 点击率x &#xff08;购买率- 退款率&#xff09; 分成比例 100%- 平台抽成- 税- 引擎费- 发行抽成 成本开发成本运营成本 开发成本 人工外包办公地点租金水电设备折旧 人工成本设计成本开发成本迭代修改成本后续内容…...

裁员潮血洗硅谷,普通人惨遭裁员的血泪教训——要随时做好失业的准备

我大学室友21年暑假在meta的某AI组实习过&#xff0c;压力巨大&#xff01;组里大群天天消息99&#xff0c;年底实习结束直接就进到Google去了&#xff0c;听说eng组的intern十有八九都拿到了return offer&#xff0c;但都利用空余时间跳到了别的大厂。 离谱的时候&#xff0c…...

MacBook Pro(M1芯片)Qt环境配置

MacBook Pro&#xff08;M1芯片&#xff09;Qt环境配置 1、准备 试图写一个跨平台的桌面应用&#xff0c;此时想到了使用Qt&#xff0c;于是开始了搭建开发环境&#xff5e; 在M1芯片的电脑上安装&#xff0c;使用brew工具比较方便 Apple Silicon&#xff08;ARM/M1&#xf…...

智能编码在前端研发的创新应用

一、前端开发实例 今天主要想分享一些关于大模型如何协助我们进行前端编码的实践。首先&#xff0c;让我们以一个前端开发的实例开始。通常&#xff0c;当需要实现一个新的前端功能时&#xff0c;我们会收到相关的背景和需求描述。我的期望是&#xff0c;大模型能够直接使用这…...