three.js修改内置材质着色器代码

通常我们是通过修改扩展three.js内置的材质来实现一些复杂的效果的,而不是使用shaderMaterial材质从零开始实现。比如说很满意MeshStandardMaterial(一种常规材质)的效果,但是我们希望在这个材质上添加一些顶点动画。如果我们打算从头开始写一个全新的MeshStandardMaterial,处理灯光、环境图、物理渲染、各种纹理等等就会耗费我们大量的时间。
有两种常见的方式可以修改着色器:

  1. 通过使用一个Three.js提供的“钩子”函数,在着色器编译之前改动着色器并插入我们的代码。
  2. 新建一个全新的材质,先复现原来的材质的功能效果,然后再添加一个我们自己需要的效果

很明显第二种方式一般情况下并不太适合,因为需要花费的时间比较多

[1]获取材质的着色器

要修改内置材质的着色器带啊吗,首先需要访问获取到相应的着色器,要获取着色器等信息我们可以调用材质的onBeforeCompile方法:

// Material
const material = new THREE.MeshStandardMaterial( {map: mapTexture,normalMap: normalTexture
})
material.onBeforeCompile = (shader) =>{console.log(shader)
}

在这里插入图片描述

在这里插入图片描述

[2]向顶点着色器中添加内容

先打印看看原始材质的顶点着色器的内容:

#define STANDARD
varying vec3 vViewPosition;
#ifdef USE_TRANSMISSIONvarying vec3 vWorldPosition;
#endif
#include <common>
#include <uv_pars_vertex>
#include <uv2_pars_vertex>
#include <displacementmap_pars_vertex>
#include <color_pars_vertex>
#include <fog_pars_vertex>
#include <normal_pars_vertex>
#include <morphtarget_pars_vertex>
#include <skinning_pars_vertex>
#include <shadowmap_pars_vertex>
#include <logdepthbuf_pars_vertex>
#include <clipping_planes_pars_vertex>
void main() {#include <uv_vertex>#include <uv2_vertex>#include <color_vertex>#include <beginnormal_vertex>#include <morphnormal_vertex>#include <skinbase_vertex>#include <skinnormal_vertex>#include <defaultnormal_vertex>#include <normal_vertex>#include <begin_vertex>#include <morphtarget_vertex>#include <skinning_vertex>#include <displacementmap_vertex>#include <project_vertex>#include <logdepthbuf_vertex>#include <clipping_planes_vertex>vViewPosition = - mvPosition.xyz;#include <worldpos_vertex>#include <shadowmap_vertex>#include <fog_vertex>
#ifdef USE_TRANSMISSIONvWorldPosition = worldPosition.xyz;
#endif
}

并没有太多的代码,这是因为three.js对大部分着色器可复用的代码进行了封装,以防止在不同材质之间重复相同的代码。每个都将 #include … 注入位于 Three.js依赖项的特定文件夹中的代码。
也就是说我们好些就可以直接使用JavaScript的replace方法直接替换掉其中的代码了
但是我们不知道哪个部分在做什么以及要替换哪个部分。为了理解代码,我们需要深入研究 three 依赖关系。
为此,我们可以看看/node_modules/three/src/renderers/shaders/ 文件夹,这里可以找到大多数three.js着色器代码
在这里插入图片描述

再上面的顶点着色器中包含内容都称为块(chunk),都可以在ShaderChunk/ 文件夹中找到它们,然后更加可以按照上面打印的材质的着色器代码大概读一些相应的chunk,或者按照你的想法,去读和修改就行。
比如我想对通过修改顶点着色器修改模型的y坐标,经过阅读原有的chunk后发现#include <begin_vertex>这个块里面的代码:

export default /* glsl */`
vec3 transformed = vec3( position );
`;

那我直接替换大法:

const material = new THREE.MeshStandardMaterial( {map: mapTexture,normalMap: normalTexture
})material.onBeforeCompile = (shader) =>{shader.vertexShader = shader.vertexShader.replace('#include <begin_vertex>',`#include <begin_vertex>transformed.y += 3.0;`)console.log(shader.vertexShader)
}

然后看这个打印的代码:
在这里插入图片描述

[3]旋转坐标

这里实验一下添加一个2D旋转矩阵(相关内容可参考The Book of Shaders),根据输入的角度返回一个循环矩阵,然后将这个矩阵应用到模型坐标中;
按照前面的思想,我们先创建一个旋转矩阵函数

mat2 get2dRotateMatrix(float _angle){return mat2(cos(_angle), - sin(_angle), sin(_angle), cos(_angle));
}

那么该将这个函数添加到那个chunk中呢?如果是我们之际编写的着色器,我们会把它放到main函数前面即可 ,那么看看有没有chunk,这里有个'#include <common>'chunk可以替换放置。

material.onBeforeCompile = (shader) =>{shader.vertexShader = shader.vertexShader.replace('#include <common>',`#include <common>mat2 get2dRotateMatrix(float _angle){return mat2(cos(_angle), - sin(_angle), sin(_angle), cos(_angle));}`)console.log(shader.vertexShader)
}

放置好后就可以在其他chunk中调用了

material.onBeforeCompile = (shader) =>{shader.vertexShader = shader.vertexShader.replace('#include <common>',`#include <common>mat2 get2dRotateMatrix(float _angle){return mat2(cos(_angle), - sin(_angle), sin(_angle), cos(_angle));}`)shader.vertexShader = shader.vertexShader.replace('#include <begin_vertex>',`#include <begin_vertex>float angle = position.y * 0.9;mat2 rotateMatrix = get2dRotateMatrix(angle);transformed.xz = rotateMatrix * transformed.xz;`)console.log(shader.vertexShader)}

[4]动画

也可以通过onBeforeCompile钩子函数传递要给uniform时间变量过去片段着色器,以实现动画效果

需要注意的是,这里不同shaderMaterial,不能将实时的时间通过如下方式传递material.uniforms.uTime.value = elapsedTime,而是向下面那样

const customUniforms = {uTime: { value: 0 }
}material.onBeforeCompile = (shader) =>{shader.uniforms.uTime = customUniforms.uTimeshader.vertexShader = shader.vertexShader.replace('#include <common>',`#include <common>uniform float uTime;mat2 get2dRotateMatrix(float _angle){return mat2(cos(_angle), - sin(_angle), sin(_angle), cos(_angle));}`)shader.vertexShader = shader.vertexShader.replace('#include <begin_vertex>',`#include <begin_vertex>float angle = (position.y + uTime) * 0.9;mat2 rotateMatrix = get2dRotateMatrix(angle);transformed.xz = rotateMatrix * transformed.xz;`)console.log(shader.vertexShader)}//。。。
/*** Animate*/
const clock = new THREE.Clock()const tick = () =>
{const elapsedTime = clock.getElapsedTime()// Update materialcustomUniforms.uTime.value = elapsedTime// Update controlscontrols.update()// Renderrenderer.render(scene, camera)// Call tick again on the next framewindow.requestAnimationFrame(tick)
}tick()

[5]阴影修复

当我们使用上面的方式对模型的顶点坐标进行变换后,如果你设置了阴影你会发现阴影的形状等并没有发生改变,这是因为
当我们使用阴影时,Three.js会从光源的视角对场景进行渲染。这些渲染结果会生成关于哪些部分在阴影中,哪些部分在光线下的图片。当这些渲染发生时,所有的材质都会被替换成另一套专门用于这种特定渲染的材质。问题在于,这种材质不会随我们变化后的材质变化而变化,因为它与我们修改过的MeshStandardMaterial没有任何关系
在这里插入图片描述

要解决这个问题,我们需要找到一种方式来同时改变这个特定的material
用于阴影的材质是 MeshDepthMaterial,要访问该材质并不容易,但是我们可以使用mesh的customDepthMaterial属性来覆盖它,以便告诉 Three.js 使用自定义材质
具体代码的做简单,只需要在加载后面后对每个mesh应用customDepthMaterial属性即可

//创建一个此自定义材质,这里使用MeshDepthMaterial,因为这正是Three.js在这些渲染过程中使用的
const depthMaterial = new THREE.MeshDepthMaterial({depthPacking: THREE.RGBADepthPacking
})
/*** Models*/
gltfLoader.load('/models/LeePerrySmith/LeePerrySmith.glb',(gltf) =>{// Modelconst mesh = gltf.scene.children[0]mesh.rotation.y = Math.PI * 0.5mesh.material = materialmesh.customDepthMaterial = depthMaterial // Update the depth materialscene.add(mesh)updateAllMaterials()}
)//同时也需要对改材质进行同样的操作
depthMaterial.onBeforeCompile = (shader) =>{shader.uniforms.uTime = customUniforms.uTimeshader.vertexShader = shader.vertexShader.replace('#include <common>',`#include <common>uniform float uTime;mat2 get2dRotateMatrix(float _angle){return mat2(cos(_angle), - sin(_angle), sin(_angle), cos(_angle));}`)shader.vertexShader = shader.vertexShader.replace('#include <begin_vertex>',`#include <begin_vertex>float angle = (position.y + uTime) * 0.9;mat2 rotateMatrix = get2dRotateMatrix(angle);transformed.xz = rotateMatrix * transformed.xz;`)
}

在这里插入图片描述

[6]法向修复

上面的进行阴影修复后其实还有个问题,那个阴影会随着顶点的旋转而旋转。这是一个与法线有关的问题。
因为法线是与每个顶点相关联的坐标,用于获取面朝向的方向。通过用于光照、反射和阴影等相关的内容
而当我们旋转顶点时,我们只是旋转位置,但我们没有旋转法线。我们需要修改处理法线的chunk
通过查找法线处理法线的chunk为beginnormal_vertex

export default /* glsl */`
vec3 objectNormal = vec3( normal );#ifdef USE_TANGENTvec3 objectTangent = vec3( tangent.xyz );#endif
`;

然后对顶点法线和顶点做同样的变换即可:

depthMaterial.onBeforeCompile = (shader) =>{shader.uniforms.uTime = customUniforms.uTimeshader.vertexShader = shader.vertexShader.replace('#include <common>',`#include <common>uniform float uTime;mat2 get2dRotateMatrix(float _angle){return mat2(cos(_angle), - sin(_angle), sin(_angle), cos(_angle));}`)shader.vertexShader = shader.vertexShader.replace('#include <beginnormal_vertex>',`#include <beginnormal_vertex>float angle = (position.y + uTime) * 0.9;mat2 rotateMatrix = get2dRotateMatrix(angle);objectNormal.xz = rotateMatrix * objectNormal.xz;`)shader.vertexShader = shader.vertexShader.replace('#include <begin_vertex>',`#include <begin_vertex>float angle = (position.y + uTime) * 0.9;mat2 rotateMatrix = get2dRotateMatrix(angle);transformed.xz = rotateMatrix * transformed.xz;`)
}

注意这块,有些版本是不支持上面的写法的,需要对一些语句去重,因为最后是将上的着色器代码合并到一块的,如果有重复语句会报错,但是在新的版本中则不会。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/29856.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

php webshell 免杀入门

webshell 查杀软件&#xff1a; d盾、安全狗、护卫神、Sangfor WebShellKill 在线查杀 百度WEBDIR https://scanner.baidu.com 河马 https://www.shellpub.com cloudwalker牧云 https://webshellchop.chaitin.cn 查杀技术 静态检测、动态检测、日志检查 静态检查&#xff1a…

Cesium中通过射线计算日照

Cesium中通过射线计算日照 前段时间接触到一个需求&#xff0c;需要实时的计算建筑的日照&#xff0c;通常优先通过shadow map来实现。通过shadow map可以直接获取某一时刻的光照信息&#xff0c;累积不同太阳光位置的shadow map即可得到物体表面的光照时长。 不过本人技术有限…

SPINN:基于设备和云的神经网络协同递进推理

SPINN&#xff1a;基于设备和云的神经网络协同递进推理 论文标题&#xff1a;SPINN: synergistic progressive inference of neural networks over device and cloud 原文链接&#xff1a;https://dl.acm.org/doi/10.1145/3372224.3419194 论文动机 现代CNN过多的计算需求&am…

将 Kwargs 传递给 Python 中的另一个函数

文章目录 Python 中的关键字参数在 Python 中使用**kwargs 调用函数使用 Python 将 kwargs 传递给另一个函数总结 Python 列出了可以传递给程序中的函数的两种类型的参数。 非关键字参数 (**args) 和关键字参数 (**kwargs)。 通常&#xff0c;python 函数必须使用正确数量的参…

华秋亮相2023世界汽车制造技术暨智能装备博览会,推动汽车产业快速发展

洞悉全球汽车产业格局&#xff0c;前瞻业界未来趋势。2023年7月27日-30日&#xff0c;时隔三年&#xff0c;重聚武汉国际博览中心&#xff0c;2023世界汽车制造技术暨智能装备博览会盛大开幕。深耕汽车行业多年的世界汽车制造技术暨智能装备博览会&#xff0c;掀起行业热点新高…

死磕Android性能优化,卡顿原因与优化方案

随着移动互联网的快速发展&#xff0c;Android应用的性能优化变得尤为重要。卡顿是用户体验中最常见的问题之一&#xff0c;它会导致应用的响应变慢、界面不流畅&#xff0c;甚至影响用户的使用体验。因此&#xff0c;我们需要深入了解卡顿问题的原因&#xff0c;并寻找相应的解…

Java中ArrayList常用方法的学习

Java中ArrayList常用方法的学习 需求分析代码实现小结Time 需求分析 ArrayList集合的常用方法学习 代码实现 java.util.ArrayList;/*** Author:LQ* Description:* Date:Created in 16:45 2023/8/9*/ public class ListTest {public static void main(String[] args) {ArrayLis…

WMS系列:层级树的surface 的创建

WMS 创建的surface 与 surfaceflinger 创建的Layer 是一一对应的&#xff0c;只不过可能是创建不同的 Layer 1. DefaultTaskDisplayArea 对应的surface 的创建 DefaultTaskDisplayArea 的调用栈如下&#xff0c;是在系统进程启动服务的时候&#xff0c;去创建对应的SurfaceCont…

Flutter:屏幕适配

flutter_screenutil flutter_screenutil是一个用于在Flutter应用程序中进行屏幕适配的工具包。它旨在帮助开发者在不同屏幕尺寸和密度的设备上创建响应式的UI布局。 flutter_screenutil提供了一些用于处理尺寸和间距的方法&#xff0c;使得开发者可以根据设备的屏幕尺寸和密度…

Segment Anything(SAM) 计算过程

给定输入图像 I ∈ R 3 H W I \in R^{3 \times H \times W} I∈R3HW。给定需要的prompts&#xff1a; M ∈ R 1 H W M \in R^{1 \times H \times W} M∈R1HW&#xff0c;代表图片的前背景信息。 P ∈ R N 2 P \in R^{N \times 2} P∈RN2&#xff0c;其中 N N N 是点的个数…

vscode 搭建STM32开发环境

1.需要软件 1.1 vscode 1.2 STM32CubeMX&#xff0c;这个不是必须的&#xff0c;我是为了方便生成STM32代码 2.vscode配置 2.1安装keil Assistant 2.2配置keil Assistant 3.STMCUBE生成个STM32代码 &#xff0c;如果有自己的代码可以忽略 4.代码添加到vscode&#xff0c;并…

模拟出栈的所有顺序(dfs+回溯)

题目&#xff1a; 已知某一个字母序列&#xff0c;把序列中的字母按出现顺序压入一个栈&#xff0c;在入栈的任意过程中&#xff0c;允许栈中的字母出栈&#xff0c;求所有可能的出栈顺序 示例&#xff1a; 输入abc 输出abc、acb、bac、bca、cba 代码如下 #define _CRT_SECURE…

JAVA集合框架 一:Collection(LIst,Set)和Iterator(迭代器)

目录 一、Java 集合框架体系 1.Collection接口&#xff1a;用于存储一个一个的数据&#xff0c;也称单列数据集合&#xff08;single&#xff09;。 2.Map接口&#xff1a;用于存储具有映射关系“key-value对”的集合&#xff08;couple&#xff09; 3.Iterator接口&#…

【ChatGPT 指令大全】怎么使用ChatGPT来辅助学习英语

在当今全球化的社会中&#xff0c;英语已成为一门世界性的语言&#xff0c;掌握良好的英语技能对个人和职业发展至关重要。而借助人工智能的力量&#xff0c;ChatGPT为学习者提供了一个有价值的工具&#xff0c;可以在学习过程中提供即时的帮助和反馈。在本文中&#xff0c;我们…

Android 视频播放器dkplayer

列表播放如图所示&#xff1a; 一、依赖 //添加RecyclerView的依赖包implementation androidx.recyclerview:recyclerview:1.2.1// 异步加载图片依赖implementation com.squareup.picasso:picasso:2.5.2// 上拉刷新、下来加载依赖implementation com.scwang.smartrefresh:Smart…

Kubernetes工作原理

一、案例概述 传统部署时代&#xff1a; 早期是在物理服务器上运行应用程序。无法为物理服务器中的应用程序定义资源边界&#xff0c;这会导致资源分配出现问题。例如&#xff1a;如果在物理服务器上运行多个应用程序&#xff0c;则可能会出现一个应用程序占用大部分资源的情况…

ffplay简介

本文为相关课程的学习记录&#xff0c;相关分析均来源于课程的讲解&#xff0c;主要学习音视频相关的操作&#xff0c;对字幕的处理不做分析 ffplay播放器的意义 ffplay.c是FFmpeg源码⾃带的播放器&#xff0c;调⽤FFmpeg和SDL API实现⼀个⾮常有⽤的播放器。 ffplay实现了播…

Spring Data学习笔记Day01-SpringData入门

Spring Data基本介绍 目录 Spring Data Redis 官方API参考手册&#xff01;★ Spring Data的价值★ Spring Data及其子项目★ 强大的Spring Data★ Repository接口★ 具体Repository接口★ Spring Data JPA开发★ Spring Boot如何选择DataSource★ 数据源相关配置★ 配置第三方…

爬虫014_文件操作_打开关闭_读写_序列化_反序列化---python工作笔记033

报错,没有指定路径,没有指定路径无法创建文件 这样可以在当前目录下创建一个可写的文件 可以看到找到刚才生成的文件,看看内容

Android进阶之SeekBar动态显示进度

SeekBar 在开发中并不陌生,默认的SeekBar是不显示进度的,当然用吐司或者文案在旁边实时显示也是可以的,那能不能移动的时候才显示&#xff0c;默认不显示呢,当然网上花哨的三方工具类太多了&#xff0c;但是我只是单纯的想在SeekBar的基础上去添加一个可以跟随移动显示的气泡而…