You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Three.js中通过GLSL实现带高镜面高光的Phong着色

问题描述

我正尝试在Three.js中使用GLSL着色器渲染两个球体,实现自定义Phong着色。希望左侧绿色球体拥有带有锐利镜面反射的高光泽高光效果,但当前左侧球体过于偏向漫反射,缺乏足够的高光强度。要求仅修改顶点着色器与片元着色器,不改动其他代码,且保持片元着色器的参数值不变。

参考图效果:参考图
当前效果:当前效果

已尝试操作:

  • 提高matSh与matSpec参数值
  • 调整srcSpec与srcAmbi参数值

预期效果:绿色球体呈现高镜面高光,具备光泽感与反射质感。


当前代码
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>phong shading</title>
        <style>
            body { margin: 0; }
            canvas { display: block; }
        </style>
    </head>
    <body>
        <script id="vertexShader" type="x-shader/x-vertex">

            uniform mat4 modelViewMatrix; // optional when using ShaderMaterial
            uniform mat4 projectionMatrix; // optional when using ShaderMaterial
            uniform mat4 viewMatrix; // optional when using ShaderMaterial
            uniform mat4 modelMatrix; // optional when using ShaderMaterial
            uniform mat3 normalMatrix;
            uniform mat4 inverseTransposeM;

            uniform vec3 eyePos;
            attribute vec3 position;
            attribute vec3 normal;
            varying vec3 v_normal, v_view;

            void main() {
                v_normal = normal;
                gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4( position, 1.0 );
            }

        </script>

        <script id="fragmentShader" type="x-shader/x-fragment">

            precision mediump float;
            precision mediump int;

            vec3 matDiff, matSpec, matAmbi, matEmit;
            float matSh;
            vec3 srcDiff, srcSpec, srcAmbi;

            uniform vec3 lightDir;
            varying vec3 v_normal, v_view;

            void main() {
                
                // Material properties
                matDiff = vec3(0.0, 1.0, 0.0); // Diffuse color (green)
                matSpec = vec3(2.0, 2.0, 2.0); // Increase specular intensity
                matAmbi = vec3(0.0, 0.15, 0.0); // Slightly increased ambient intensity
                matSh = 500.0;                  // High shininess for a tight highlight
                
                // Light properties
                srcDiff = vec3(1.0, 1.0, 1.0); // Diffuse light color
                srcSpec = vec3(2.0, 2.0, 2.0); // Specular light color
                srcAmbi = vec3(1.0, 1.0, 1.0); // Ambient light color
        
                // Normalize the normal and light direction
                vec3 N = normalize(v_normal);
                vec3 L = normalize(lightDir);
                vec3 V = normalize(v_view); // View direction (towards the camera)
                vec3 R = reflect(-L, N);    // Reflection direction
        
                // Ambient component
                vec3 ambient = matAmbi * srcAmbi;
        
                // Diffuse component
                float diffFactor = max(dot(N, L), 0.0);
                vec3 diffuse = matDiff * srcDiff * diffFactor;
        
                // Specular component
                float specFactor = pow(max(dot(R, V), 0.0), matSh);
                vec3 specular = matSpec * srcSpec * specFactor;
        
                // Combine components
                vec3 color = ambient + diffuse + specular;
        
                // Set fragment color
                gl_FragColor = vec4(color, 1.0);
            }

        </script>
        <script type="importmap">
            {
                "imports": {
                    "three": "https://unpkg.com/three@v0.157.0/build/three.module.js",
                    "three/addons/": "https://unpkg.com/three@v0.157.0/examples/jsm/"
                }
            }
        </script>
        <script type="module">
            import * as THREE from 'three';
            import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

            const renderer = new THREE.WebGLRenderer();
            renderer.setSize( window.innerWidth, window.innerHeight );
            document.body.appendChild( renderer.domElement );

            const camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 500 );
            camera.position.set( 0, 3, -10 );
            camera.lookAt( 0, 0, 0 );
        
            const scene = new THREE.Scene();
            scene.background = new THREE.Color('black');

            const controls = new OrbitControls( camera, renderer.domElement );

            const ambilight = new THREE.AmbientLight(0xFFFFFF, 0.1);
            scene.add(ambilight);
            const dlight = new THREE.DirectionalLight(0xFFFFFF, 1);
            dlight.position.set(5, 2, -5);
            dlight.target.position.set(0, 0, 0);
            scene.add(dlight);
            scene.add(dlight.target);

            const geom = new THREE.SphereGeometry( 1, 30, 20 );
            const mat = new THREE.MeshPhongMaterial({
                color: 0xFF0000,
                flatShading: false,
            });

            const ball = new THREE.Mesh(geom,mat);
            scene.add(ball);
            ball.position.set(1.5,0,0);

            let m = new THREE.Matrix4();

            let uniforms = {
                inverseTransposeM:{type:"mat4", value:m},
                eyePos:{type:"vec3", value:camera.position},
                lightDir:{type:"vec3", value:dlight.position}
            }
            const mat2 = new THREE.RawShaderMaterial({
                uniforms: uniforms,
                vertexShader: document.getElementById( 'vertexShader' ).textContent,
                fragmentShader: document.getElementById( 'fragmentShader' ).textContent
            });

            const ball2 = new THREE.Mesh(geom,mat2)
            scene.add(ball2);
            ball2.position.set(-1.5,0,0);
            ball2.material.uniforms.inverseTransposeM.value = ball2.matrixWorld.invert().transpose();

            function animate(time) {
                controls.update();
                requestAnimationFrame( animate );
                renderer.render( scene, camera );
            }
            animate();
        </script>
    </body>
</html>

解决方案

问题核心在于顶点着色器没有正确传递用于高光计算的关键变量:

  1. 原始法线未经过模型变换校正,导致光照计算时法线方向错误
  2. v_view变量未初始化,片元着色器中使用的是无效值,直接导致高光失效

仅修改顶点着色器即可解决问题,片元着色器保持参数不变:

uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
uniform mat4 modelMatrix;
uniform mat3 normalMatrix;
uniform mat4 inverseTransposeM;

uniform vec3 eyePos;
attribute vec3 position;
attribute vec3 normal;
varying vec3 v_normal, v_view;

void main() {
    // 将法线转换到世界空间并归一化,确保模型变换后法线方向正确
    v_normal = normalize(mat3(inverseTransposeM) * normal);
    // 计算世界空间下的顶点位置
    vec3 worldPos = (modelMatrix * vec4(position, 1.0)).xyz;
    // 计算视线方向(相机位置到顶点的向量)
    v_view = eyePos - worldPos;
    gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4( position, 1.0 );
}

修改说明:

  • 用inverseTransposeM矩阵将顶点法线转换到世界空间,避免模型缩放导致法线变形
  • 计算世界空间顶点位置,进而得到正确的视线方向向量,传递给片元着色器用于高光计算
  • 片元着色器中原本的高光计算逻辑是正确的,只要传入有效的v_normal和v_view就能得到预期的锐利高光效果

内容的提问来源于stack exchange,提问作者peachi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 07:09:57