Anonymous
Простой SSAO с Three.js
Сообщение
Anonymous » 18 фев 2026, 18:58
Я хочу создать SSAO с помощью Three.js и шейдеров, я использовал customLambertMaterial и пользовательские шейдеры
теперь я хочу реализовать функцию SSAO без композитора
инициализировать мой ssaoRender
Код: Выделить всё
RenderView.ssaoRenderer = new SSAORenderer(this._renderer as THREE.WebGLRenderer, this._renderEngine.mainScene, camera );
следующий в функции рендеринга
Код: Выделить всё
if (RenderView.ssaoRenderer?.enabled) {
RenderView.ssaoRenderer.render();
} else {
renderer.render(this, camera);
}
Мой шейдер
Код: Выделить всё
export const ssaoVertexShader = `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position.xy, 0.0, 1.0);
}
`;
export const ssaoFragmentShader = `
varying vec2 vUv;
uniform sampler2D depthMap;
uniform sampler2D rotateMap;
uniform float zNear;
uniform float zFar;
uniform float radius;
uniform float attBias;
uniform float attScale;
vec3 rndTable[8];
void initRndTable() {
rndTable[0] = vec3(-0.5, -0.5, -0.5);
rndTable[1] = vec3(0.5, -0.5, -0.5);
rndTable[2] = vec3(-0.5, 0.5, -0.5);
rndTable[3] = vec3(0.5, 0.5, -0.5);
rndTable[4] = vec3(-0.5, -0.5, 0.5);
rndTable[5] = vec3(0.5, -0.5, 0.5);
rndTable[6] = vec3(-0.5, 0.5, 0.5);
rndTable[7] = vec3(0.5, 0.5, 0.5);
}
float linearizeDepth(float z) {
float ndc = z * 2.0 - 1.0;
return 2.0 * zNear * zFar / (zFar + zNear - ndc * (zFar - zNear));
}
void main() {
initRndTable();
float zb = texture2D(depthMap, vUv).r;
float z = linearizeDepth(zb);
vec3 plane = 2.0 * texture2D(rotateMap, vUv).xyz - 1.0;
float att = 0.0;
for (int i = 0; i < 8; i++) {
vec3 sampleVec = reflect(rndTable[i], plane);
vec2 offsetUv = vUv + radius * sampleVec.xy / z;
if (offsetUv.x < 0.0 || offsetUv.x > 1.0 || offsetUv.y < 0.0 || offsetUv.y > 1.0) continue;
float zSample = texture2D(depthMap, offsetUv).r;
zSample = linearizeDepth(zSample);
if (zSample - z > 0.1) continue;
float dz = max(zSample - z, 0.0) * 30.0;
att += 1.0 / (1.0 + dz * dz);
}
att = clamp((att / 8.0 + attBias) * attScale, 0.0, 1.0);
gl_FragColor = vec4(vec3(att), 1.0);
}
`;
ssaoRender
Код: Выделить всё
ssaoRenderer.ts
import { THREE } from '@render/three';
import { DepthPass } from './depthPass';
import { ssaoFragmentShader, ssaoVertexShader } from '@render/utils/ssaoPass';
export class SSAORenderer {
private renderer: THREE.WebGLRenderer;
private scene: THREE.Scene;
private camera: THREE.Camera;
private depthPass: DepthPass;
private ssaoMaterial: THREE.ShaderMaterial;
private quadScene: THREE.Scene;
private quadCamera: THREE.OrthographicCamera;
enabled = true;
constructor(renderer: THREE.WebGLRenderer, scene: THREE.Scene, camera: THREE.Camera){
this.renderer = renderer;
this.scene = scene;
this.camera = camera;
const width = renderer.domElement.width;
const height = renderer.domElement.height;
this.depthPass = new DepthPass(width, height);
this.ssaoMaterial = new THREE.ShaderMaterial({
vertexShader: ssaoVertexShader,
fragmentShader: ssaoFragmentShader,
uniforms: {
depthMap: { value: null },
rotateMap: { value: null },
zNear: { value: (camera as THREE.PerspectiveCamera).near },
zFar: { value: (camera as THREE.PerspectiveCamera).far },
radius: { value: 0.2 },
attBias: { value: 0.3 },
attScale: { value: 1.0 }
}
});
const quad = new THREE.Mesh(new THREE.PlaneGeometry(2,2), this.ssaoMaterial);
this.quadScene = new THREE.Scene();
this.quadScene.add(quad);
this.quadCamera = new THREE.OrthographicCamera(-1,1,1,-1,0,1);
this.ssaoMaterial.uniforms.projectionMatrix = { value: camera.projectionMatrix };
this.ssaoMaterial.uniforms.invProjectionMatrix = { value: camera.projectionMatrixInverse };
}
public render(){
if(!this.enabled){
this.renderer.render(this.scene, this.camera);
return;
}
this.ssaoMaterial.uniforms.projectionMatrix.value = this.camera.projectionMatrix;
this.ssaoMaterial.uniforms.invProjectionMatrix.value = this.camera.projectionMatrixInverse;
const width = this.renderer.domElement.width;
const height = this.renderer.domElement.height;
this.depthPass.render(this.renderer, this.scene, this.camera);
this.ssaoMaterial.uniforms.tDepth.value = this.depthPass.getDepthTexture();
this.ssaoMaterial.uniforms.tColor.value = this.depthPass.getColorTexture();
this.ssaoMaterial.uniforms.tNormal.value = this.depthPass.getNormalTexture();
this.ssaoMaterial.uniforms.resolution.value.set(width,height);
this.ssaoMaterial.uniforms.projectionMatrix.value = this.camera.projectionMatrix;
this.ssaoMaterial.uniforms.invProjectionMatrix.value = this.camera.projectionMatrixInverse;
this.renderer.render(this.quadScene, this.quadCamera);
}
public resize(width:number, height:number){
this.depthPass.resize(width,height);
this.ssaoMaterial.uniforms.resolution.value.set(width,height);
}
public setIntensity(v:number){
this.ssaoMaterial.uniforms.intensity.value = v;
}
public setRadius(v:number){
this.ssaoMaterial.uniforms.radius.value = v;
}
}
DeepPass
Код: Выделить всё
// depthPass.ts
import { THREE } from '@render/three';
export class DepthPass {
public renderTarget: THREE.WebGLRenderTarget;
public normalTarget: THREE.WebGLRenderTarget;
private normalMaterial: THREE.ShaderMaterial;
constructor(width: number, height: number) {
this.renderTarget = new THREE.WebGLRenderTarget(width, height, {
minFilter: THREE.NearestFilter,
magFilter: THREE.NearestFilter,
format: THREE.RGBAFormat
});
this.renderTarget.depthTexture = new THREE.DepthTexture(width, height);
this.renderTarget.depthTexture.format = THREE.DepthFormat;
this.renderTarget.depthTexture.type = THREE.FloatType;
this.renderTarget.depthBuffer = true;
this.normalTarget = new THREE.WebGLRenderTarget(width, height, {
minFilter: THREE.NearestFilter,
magFilter: THREE.NearestFilter,
format: THREE.RGBAFormat
});
this.normalMaterial = new THREE.ShaderMaterial({
vertexShader: `
varying vec3 vNormalView;
void main() {
vNormalView = normalize((viewMatrix * vec4(normal,0.0)).xyz);
gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
}
`,
fragmentShader: `
varying vec3 vNormalView;
void main() {
gl_FragColor = vec4(vNormalView * 0.5 + 0.5, 1.0);
}
`
});
}
public render2(renderer: THREE.WebGLRenderer, scene: THREE.Scene, camera: THREE.Camera) {
// depth only
const depthMaterial = new THREE.MeshDepthMaterial();
scene.overrideMaterial = depthMaterial;
renderer.setRenderTarget(this.renderTarget);
renderer.clear();
renderer.render(scene, camera);
scene.overrideMaterial = null;
renderer.setRenderTarget(null);
}
public render(renderer: THREE.WebGLRenderer, scene: THREE.Scene, camera: THREE.Camera) {
const originalMaterials = new Map();
scene.traverse(obj => {
if (obj instanceof THREE.Mesh && obj.material) {
originalMaterials.set(obj, obj.material);
}
});
renderer.setRenderTarget(this.renderTarget);
renderer.clear(true, true, true);
renderer.render(scene, camera);
scene.traverse(obj => {
if (obj instanceof THREE.Mesh) obj.material = this.normalMaterial;
});
renderer.setRenderTarget(this.normalTarget);
renderer.clear(true, true, true);
renderer.render(scene, camera);
scene.traverse(obj => {
if (obj instanceof THREE.Mesh && originalMaterials.has(obj)) {
obj.material = originalMaterials.get(obj)!;
}
});
renderer.setRenderTarget(null);
}
public resize(width: number, height: number) {
this.renderTarget.setSize(width, height);
this.normalTarget.setSize(width, height);
}
public getDepthTexture(): THREE.DepthTexture {
return this.renderTarget.depthTexture;
}
public getColorTexture(): THREE.Texture {
return this.renderTarget.texture;
}
public getNormalTexture(): THREE.Texture {
return this.normalTarget.texture;
}
}
Проблема:
Шейдер применен неправильно; он слегка затемняет всю модель, и нет эффекта SSAO.
Я активирую ssao с помощью флажка, активация вока - это хорошо
Подробнее здесь:
https://stackoverflow.com/questions/798 ... h-three-js
1771430293
Anonymous
Я хочу создать SSAO с помощью Three.js и шейдеров, я использовал customLambertMaterial и пользовательские шейдеры теперь я хочу реализовать функцию SSAO без композитора инициализировать мой ssaoRender [code]RenderView.ssaoRenderer = new SSAORenderer(this._renderer as THREE.WebGLRenderer, this._renderEngine.mainScene, camera ); [/code] следующий в функции рендеринга [code]if (RenderView.ssaoRenderer?.enabled) { RenderView.ssaoRenderer.render(); } else { renderer.render(this, camera); } [/code] Мой шейдер [code]export const ssaoVertexShader = ` varying vec2 vUv; void main() { vUv = uv; gl_Position = vec4(position.xy, 0.0, 1.0); } `; export const ssaoFragmentShader = ` varying vec2 vUv; uniform sampler2D depthMap; uniform sampler2D rotateMap; uniform float zNear; uniform float zFar; uniform float radius; uniform float attBias; uniform float attScale; vec3 rndTable[8]; void initRndTable() { rndTable[0] = vec3(-0.5, -0.5, -0.5); rndTable[1] = vec3(0.5, -0.5, -0.5); rndTable[2] = vec3(-0.5, 0.5, -0.5); rndTable[3] = vec3(0.5, 0.5, -0.5); rndTable[4] = vec3(-0.5, -0.5, 0.5); rndTable[5] = vec3(0.5, -0.5, 0.5); rndTable[6] = vec3(-0.5, 0.5, 0.5); rndTable[7] = vec3(0.5, 0.5, 0.5); } float linearizeDepth(float z) { float ndc = z * 2.0 - 1.0; return 2.0 * zNear * zFar / (zFar + zNear - ndc * (zFar - zNear)); } void main() { initRndTable(); float zb = texture2D(depthMap, vUv).r; float z = linearizeDepth(zb); vec3 plane = 2.0 * texture2D(rotateMap, vUv).xyz - 1.0; float att = 0.0; for (int i = 0; i < 8; i++) { vec3 sampleVec = reflect(rndTable[i], plane); vec2 offsetUv = vUv + radius * sampleVec.xy / z; if (offsetUv.x < 0.0 || offsetUv.x > 1.0 || offsetUv.y < 0.0 || offsetUv.y > 1.0) continue; float zSample = texture2D(depthMap, offsetUv).r; zSample = linearizeDepth(zSample); if (zSample - z > 0.1) continue; float dz = max(zSample - z, 0.0) * 30.0; att += 1.0 / (1.0 + dz * dz); } att = clamp((att / 8.0 + attBias) * attScale, 0.0, 1.0); gl_FragColor = vec4(vec3(att), 1.0); } `; [/code] ssaoRender [code] ssaoRenderer.ts import { THREE } from '@render/three'; import { DepthPass } from './depthPass'; import { ssaoFragmentShader, ssaoVertexShader } from '@render/utils/ssaoPass'; export class SSAORenderer { private renderer: THREE.WebGLRenderer; private scene: THREE.Scene; private camera: THREE.Camera; private depthPass: DepthPass; private ssaoMaterial: THREE.ShaderMaterial; private quadScene: THREE.Scene; private quadCamera: THREE.OrthographicCamera; enabled = true; constructor(renderer: THREE.WebGLRenderer, scene: THREE.Scene, camera: THREE.Camera){ this.renderer = renderer; this.scene = scene; this.camera = camera; const width = renderer.domElement.width; const height = renderer.domElement.height; this.depthPass = new DepthPass(width, height); this.ssaoMaterial = new THREE.ShaderMaterial({ vertexShader: ssaoVertexShader, fragmentShader: ssaoFragmentShader, uniforms: { depthMap: { value: null }, rotateMap: { value: null }, zNear: { value: (camera as THREE.PerspectiveCamera).near }, zFar: { value: (camera as THREE.PerspectiveCamera).far }, radius: { value: 0.2 }, attBias: { value: 0.3 }, attScale: { value: 1.0 } } }); const quad = new THREE.Mesh(new THREE.PlaneGeometry(2,2), this.ssaoMaterial); this.quadScene = new THREE.Scene(); this.quadScene.add(quad); this.quadCamera = new THREE.OrthographicCamera(-1,1,1,-1,0,1); this.ssaoMaterial.uniforms.projectionMatrix = { value: camera.projectionMatrix }; this.ssaoMaterial.uniforms.invProjectionMatrix = { value: camera.projectionMatrixInverse }; } public render(){ if(!this.enabled){ this.renderer.render(this.scene, this.camera); return; } this.ssaoMaterial.uniforms.projectionMatrix.value = this.camera.projectionMatrix; this.ssaoMaterial.uniforms.invProjectionMatrix.value = this.camera.projectionMatrixInverse; const width = this.renderer.domElement.width; const height = this.renderer.domElement.height; this.depthPass.render(this.renderer, this.scene, this.camera); this.ssaoMaterial.uniforms.tDepth.value = this.depthPass.getDepthTexture(); this.ssaoMaterial.uniforms.tColor.value = this.depthPass.getColorTexture(); this.ssaoMaterial.uniforms.tNormal.value = this.depthPass.getNormalTexture(); this.ssaoMaterial.uniforms.resolution.value.set(width,height); this.ssaoMaterial.uniforms.projectionMatrix.value = this.camera.projectionMatrix; this.ssaoMaterial.uniforms.invProjectionMatrix.value = this.camera.projectionMatrixInverse; this.renderer.render(this.quadScene, this.quadCamera); } public resize(width:number, height:number){ this.depthPass.resize(width,height); this.ssaoMaterial.uniforms.resolution.value.set(width,height); } public setIntensity(v:number){ this.ssaoMaterial.uniforms.intensity.value = v; } public setRadius(v:number){ this.ssaoMaterial.uniforms.radius.value = v; } } [/code] DeepPass [code] // depthPass.ts import { THREE } from '@render/three'; export class DepthPass { public renderTarget: THREE.WebGLRenderTarget; public normalTarget: THREE.WebGLRenderTarget; private normalMaterial: THREE.ShaderMaterial; constructor(width: number, height: number) { this.renderTarget = new THREE.WebGLRenderTarget(width, height, { minFilter: THREE.NearestFilter, magFilter: THREE.NearestFilter, format: THREE.RGBAFormat }); this.renderTarget.depthTexture = new THREE.DepthTexture(width, height); this.renderTarget.depthTexture.format = THREE.DepthFormat; this.renderTarget.depthTexture.type = THREE.FloatType; this.renderTarget.depthBuffer = true; this.normalTarget = new THREE.WebGLRenderTarget(width, height, { minFilter: THREE.NearestFilter, magFilter: THREE.NearestFilter, format: THREE.RGBAFormat }); this.normalMaterial = new THREE.ShaderMaterial({ vertexShader: ` varying vec3 vNormalView; void main() { vNormalView = normalize((viewMatrix * vec4(normal,0.0)).xyz); gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0); } `, fragmentShader: ` varying vec3 vNormalView; void main() { gl_FragColor = vec4(vNormalView * 0.5 + 0.5, 1.0); } ` }); } public render2(renderer: THREE.WebGLRenderer, scene: THREE.Scene, camera: THREE.Camera) { // depth only const depthMaterial = new THREE.MeshDepthMaterial(); scene.overrideMaterial = depthMaterial; renderer.setRenderTarget(this.renderTarget); renderer.clear(); renderer.render(scene, camera); scene.overrideMaterial = null; renderer.setRenderTarget(null); } public render(renderer: THREE.WebGLRenderer, scene: THREE.Scene, camera: THREE.Camera) { const originalMaterials = new Map(); scene.traverse(obj => { if (obj instanceof THREE.Mesh && obj.material) { originalMaterials.set(obj, obj.material); } }); renderer.setRenderTarget(this.renderTarget); renderer.clear(true, true, true); renderer.render(scene, camera); scene.traverse(obj => { if (obj instanceof THREE.Mesh) obj.material = this.normalMaterial; }); renderer.setRenderTarget(this.normalTarget); renderer.clear(true, true, true); renderer.render(scene, camera); scene.traverse(obj => { if (obj instanceof THREE.Mesh && originalMaterials.has(obj)) { obj.material = originalMaterials.get(obj)!; } }); renderer.setRenderTarget(null); } public resize(width: number, height: number) { this.renderTarget.setSize(width, height); this.normalTarget.setSize(width, height); } public getDepthTexture(): THREE.DepthTexture { return this.renderTarget.depthTexture; } public getColorTexture(): THREE.Texture { return this.renderTarget.texture; } public getNormalTexture(): THREE.Texture { return this.normalTarget.texture; } } [/code] Проблема: Шейдер применен неправильно; он слегка затемняет всю модель, и нет эффекта SSAO. Я активирую ssao с помощью флажка, активация вока - это хорошо Подробнее здесь: [url]https://stackoverflow.com/questions/79891671/simple-ssao-with-three-js[/url]