Three.js中顶点/片元着色器颜色为何受相机位置影响?
问题分析与解决方案:按点到原点距离分配颜色异常问题
问题背景
创建1000个点组成环形,使用ShaderMaterial关联顶点着色器和片元着色器,期望按每个点到原点的距离分配颜色,但实际颜色受相机位置影响;后续尝试通过varying变量传递距离值后,所有点直接变成蓝色。
原Shader代码
原顶点着色器
const vertexShader = ` uniform float uTime; uniform float uRadius; varying vec3 vColor; varying float vDistance; void main() { vDistance = distance(position, vec3(0.0)); // Do Not Touch gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4( position, 1.0 ); gl_PointSize = 5.0; } ` export default vertexShader
原片元着色器
const fragmentShader = ` uniform float uDistance[1000]; uniform float uResolutionWidth; uniform float uResolutionHeight; varying float vDistance; void main() { vec2 resolution = vec2(uResolutionWidth, uResolutionHeight); vec2 st = gl_FragCoord.xy/resolution; float pct = distance(st, vec2(1.0)); vec3 color = vec3(mix(vec3(1.0, 0.0, 0.0), vec3(0.0, 0.0, 1.0), pct)); gl_FragColor = vec4( color, 1.0 ); } ` export default fragmentShader
用户修改后的Shader代码
修改后的顶点着色器
varying vec3 vRealPosition; void main() { vDistance = distance(position, vec3(0.0)); vColor = mix(vec3(1.0, 0.0, 0.0), vec3(0.0, 0.0, 1.0), vDistance); vRealPosition = position; // Do Not Touch gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4( position, 1.0 ); gl_PointSize = 5.0; }
修改后的片元着色器
varying vec3 vRealPosition; void main() { vec2 resolution = vec2(uResolutionWidth, uResolutionHeight); vec2 st = gl_FragCoord.xy/resolution; float pct = distance(vRealPosition, vec3(0.0)); vec3 color = vec3(mix(vec3(1.0, 0.0, 0.0), vec3(0.0, 0.0, 1.0), pct)); gl_FragColor = vec4( vColor, 1.0 ); }
问题原因
- 原代码逻辑偏离需求:原片元着色器完全没用到传递的
vDistance,反而用gl_FragCoord计算屏幕坐标到右上角的距离,颜色自然受相机位置影响——相机移动会改变点的屏幕坐标,导致pct值变化。 - 修改后全蓝的核心问题:
mix函数的第三个参数要求是0~1之间的归一化值,但直接使用的vDistance是点到原点的实际距离(比如环形半径为5,该值就是5),远大于1,mix会直接取第二个颜色参数(蓝色),所以所有点都显示蓝色。
正确实现方案
核心思路:在顶点着色器中计算点到原点的距离,归一化到0~1范围后传递给片元着色器,或直接在顶点着色器中计算好颜色传递。
方案一:顶点传递归一化距离,片元计算颜色
顶点着色器
uniform float uRadius; // 环形的最大半径,作为归一化参考值 varying float vNormalizedDistance; void main() { // 计算点到原点的实际距离 float distanceToOrigin = distance(position, vec3(0.0)); // 归一化到0~1范围 vNormalizedDistance = distanceToOrigin / uRadius; gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0); gl_PointSize = 5.0; }
片元着色器
varying float vNormalizedDistance; void main() { // 用归一化距离混合颜色,0对应红色,1对应蓝色 vec3 color = mix(vec3(1.0, 0.0, 0.0), vec3(0.0, 0.0, 1.0), vNormalizedDistance); gl_FragColor = vec4(color, 1.0); }
方案二:顶点直接计算颜色,片元直接使用
如果不需要在片元做额外处理,可直接在顶点计算好颜色:
顶点着色器
uniform float uRadius; varying vec3 vColor; void main() { float distanceToOrigin = distance(position, vec3(0.0)); float normalizedDistance = distanceToOrigin / uRadius; vColor = mix(vec3(1.0, 0.0, 0.0), vec3(0.0, 0.0, 1.0), normalizedDistance); gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0); gl_PointSize = 5.0; }
片元着色器
varying vec3 vColor; void main() { gl_FragColor = vec4(vColor, 1.0); }
关键注意事项
- 确保
uRadius正确传递给着色器,值为环形的最大半径(所有点到原点的距离不会超过此值),保证归一化后的距离在0~1区间内。 - 避免使用屏幕坐标(
gl_FragCoord)计算与原点的距离,这是2D屏幕空间的坐标,和3D空间中点到原点的实际距离无关,会受相机位置影响。
内容的提问来源于stack exchange,提问作者ewakwwg12
相关产品推荐
相关产品推荐

