Three.js自定义地形Phong着色器开发及相关技术问题咨询
自定义Three.js Phong着色器的完整指南
一、找到并查看默认Phong着色器的源码
Three.js的内置着色器都存放在src/renderers/shaders/ShaderLib目录中,Phong对应的是phong.glsl.js文件——这里面包含了官方Phong着色器的全部顶点、片段着色器代码,从光照计算到阴影接收的核心逻辑都能在这里找到,直接打开文件就能查看所有细节。
要是不想翻源码文件,在浏览器控制台里打印THREE.ShaderLib.phong也能快速查看完整的着色器代码、uniforms和属性定义:
console.log(THREE.ShaderLib.phong);
二、复制默认代码创建独立的ShaderMaterial
如果你想完全基于Phong从头构建自定义材质,不用onBeforeCompile的话,可以按以下步骤操作:
- 先获取官方Phong的着色器代码:
const phongShader = THREE.ShaderLib.phong; const vertexShader = phongShader.vertexShader; let fragmentShader = phongShader.fragmentShader;
- 合并默认uniforms和你的自定义参数——比如你需要的世界地图、多套漫反射/法线纹理:
const customUniforms = THREE.UniformsUtils.merge([ phongShader.uniforms, { worldMap: { value: 你的世界地图纹理 }, grassDiffuse: { value: 草地漫反射纹理 }, grassNormal: { value: 草地法线纹理 }, rockDiffuse: { value: 岩石漫反射纹理 }, rockNormal: { value: 岩石法线纹理 }, // 按需添加更多纹理即可 } ]);
- 修改片段着色器,加入根据世界地图切换纹理的逻辑。比如在计算漫反射之前,根据世界地图的颜色值选择对应纹理:
// 将这段代码插入到片段着色器的main函数开头 vec4 worldColor = texture2D(worldMap, vUv); vec4 diffuseColor; vec3 normalValue; // 举个简单示例:根据世界地图的红色通道值切换纹理 if (worldColor.r > 0.6) { diffuseColor = texture2D(rockDiffuse, vUv); normalValue = texture2D(rockNormal, vUv).rgb * 2.0 - 1.0; } else { diffuseColor = texture2D(grassDiffuse, vUv); normalValue = texture2D(grassNormal, vUv).rgb * 2.0 - 1.0; }
之后记得替换原片段着色器里默认的漫反射和法线采样部分,用上面计算出的diffuseColor和normalValue参与光照计算。
- 最后创建自定义ShaderMaterial,别忘了开启光照和阴影支持:
const customPhongMat = new THREE.ShaderMaterial({ uniforms: customUniforms, vertexShader: vertexShader, fragmentShader: fragmentShader, // 修改后的片段着色器代码 lights: true, // 必须开启,否则光照不生效 side: THREE.FrontSide, shadowSide: THREE.BackSide, // 让材质能接收阴影 // 其他参数如transparent、opacity按需设置 });
三、关于你使用的onBeforeCompile方法的补充
你之前尝试的onBeforeCompile其实是更轻量化的修改方式,不用全量复制代码,直接在原有Phong材质基础上修改即可。举个实用的示例:
const material = new THREE.MeshPhongMaterial({ map: 默认纹理, // 其他Phong默认参数 }); material.onBeforeCompile = function(shader) { // 添加自定义uniforms shader.uniforms.worldMap = { value: 你的世界地图纹理 }; shader.uniforms.rockDiffuse = { value: 岩石纹理 }; // 修改片段着色器,插入纹理切换逻辑 shader.fragmentShader = shader.fragmentShader.replace( 'void main() {', ` uniform sampler2D worldMap; uniform sampler2D rockDiffuse; void main() { vec4 worldColor = texture2D(worldMap, vUv); // 这里添加你的纹理切换逻辑,比如替换原有的diffuse采样 vec4 customDiffuse = worldColor.g > 0.5 ? texture2D(rockDiffuse, vUv) : diffuseColor; ` ); // 阴影无需额外担心,Phong默认已支持接收阴影,只要场景灯光和阴影设置正确即可 };
不管用哪种方法,核心是要保留官方Phong着色器里的光照计算、阴影相关代码逻辑,这样你的自定义材质才能和场景中的灯光、阴影正常交互。
内容的提问来源于stack exchange,提问作者StrandedKitty




