import { Utils } from "../utils/Utils";
import { Buffer } from "../utils/Buffer";
import { BlendMode } from "../data/BlendMode";
import { Light } from "../display/Light";
import { BatchRenderer } from "./BatchRenderer";
import { TextureInfo } from "../data/texture/TextureInfo";
/**
* @typedef {Object} LightRendererConfig
* @extends {RendererConfig}
* @property {number} lightNum
* @property {TextureInfo} sourceTexture
* @property {TextureInfo} normalMap
* @property {TextureInfo} heightMap
* @property {TextureInfo} roughnessMap
*/
/**
* <pre>
* Light renderer
* - Renders lights and shadows based on height, normal and roughness map
* - Height map could store the following values:
* - Red channel: start of a vertical object
* - Green channel: end of a vertical object
* - Blue channel: shiness of the surface
* - If the roughness map exists, the shiness and roughness values are
* derived from its red and green channels.
* - Every input texture are optional
* </pre>
* @extends {BatchRenderer}
*/
export class LightRenderer extends BatchRenderer {
/**
* Creates an instance of LightRenderer.
* @constructor
* @param {LightRendererConfig} options
*/
constructor(options = {}) {
options.config = Utils.initRendererConfig(options.config);
// prettier-ignore
options.config.locations = options.config.locations.concat([
"uNMTex",
"uSTTex",
"uRGTex",
"aExt",
"uTS",
"uUSTT",
"uUNMT",
"uURGT"
]);
const maxBatchItems = (options.maxBatchItems = options.lightNum || 1);
super(options);
this.clearBeforeRender = true;
this.clearColor.set(0, 0, 0, 1);
this.sourceTexture = options.sourceTexture;
this.normalMap = options.normalMap;
this.heightMap = options.heightMap;
this.roughnessMap = options.roughnessMap;
this._extensionBuffer = new Buffer("aExt", maxBatchItems, 4, 4);
this._lights = [];
}
/**
* Register a Light instance
* @param {Light} light
*/
registerLight(light) {
if (this._lights.indexOf(light) > -1) return;
let index = this._lights.indexOf(null);
index > -1
? (this._lights[index] = light)
: (index = this._lights.push(light) - 1);
light.registerData(index, this.$matrixBuffer.data, this._extensionBuffer.data);
}
/**
* Remove a Light instance
* @param {Light} light
*/
unregisterLight(light) {
const index = this._lights.indexOf(light);
if (index > -1) {
this._lights[index] = null;
light.unregisterData();
}
}
/**
* @ignore
*/
$render() {
this.context.setBlendMode(BlendMode.ADD);
let sizeable = this;
if (this.sourceTexture) {
this.$useTexture(this.sourceTexture, this.$locations.uSTTex);
sizeable = this.sourceTexture;
this.$gl.uniform1f(this.$locations.uUSTT, 1);
} else this.$gl.uniform1f(this.$locations.uUSTT, 0);
if (this.normalMap) {
this.$useTexture(this.normalMap, this.$locations.uNMTex);
sizeable = this.normalMap;
this.$gl.uniform1f(this.$locations.uUNMT, 1);
} else this.$gl.uniform1f(this.$locations.uUNMT, 0);
if (this.roughnessMap) {
this.$useTexture(this.roughnessMap, this.$locations.uRGTex);
sizeable = this.roughnessMap;
this.$gl.uniform1f(this.$locations.uURGT, 1);
} else this.$gl.uniform1f(this.$locations.uURGT, 0);
if (this.heightMap) {
this.$useTexture(this.heightMap, this.$locations.uTex);
sizeable = this.heightMap;
}
this.$gl.uniform2f(this.$locations.uTS, sizeable.width, sizeable.height);
this.$uploadBuffers();
this.$drawInstanced(this._lights.length);
}
/**
* @ignore
*/
$uploadBuffers() {
this._extensionBuffer.upload(this.$gl, this.$enableBuffers);
super.$uploadBuffers();
}
/**
* @ignore
*/
$createBuffers() {
super.$createBuffers();
this._extensionBuffer.create(this.$gl, this.$locations);
}
// prettier-ignore
/**
* @param {LightRendererConfig} options
* @returns {string}
* @ignore
*/
$createVertexShader(options) {
return Utils.GLSL.VERSION +
"precision highp float;\n" +
Utils.GLSL.DEFINE.PI +
"#define P vec4(1,-1,2,-2)\n" +
"in vec2 " +
"aPos;" +
"in mat4 " +
"aExt," +
"aMt;" +
"uniform float " +
"uFlpY;" +
"out float " +
"vHS," +
"vD," +
"vSpt;" +
"out vec2 " +
"vTUv," +
"vSln;" +
"out vec4 " +
"vUv," +
"vCl," +
"vDt;" +
"out mat4 " +
"vExt;" +
"void main(void){" +
"vec3 pos=vec3(aPos*2.-1.,1);" +
"vExt=aExt;" +
"vCl=aMt[2];" +
"vDt=aMt[3];" +
"vUv.xy=pos.xy;" +
"vHS=vExt[0].z;" +
"mat3 mt=mat3(aMt[0].xy,0,aMt[0].zw,0,aMt[1].xy,1);" +
"vD=aMt[1].z;" +
"if(vExt[0].x<1.){" +
"gl_Position=vec4(mt*pos,1);" +
"vTUv=(gl_Position.xy+P.xy)/P.zw;" +
"vUv.zw=(aMt[1].xy+P.xy)/P.zw;" +
"vSpt=PI-aMt[1].w;" +
"vSln=vec2(sin(vDt.w),cos(vDt.w));" +
"}else{" +
"mt[2].xy=vec2(-1,1);" +
"gl_Position=vec4(pos,1);" +
"vTUv=vec2(aPos.x,1.-aPos.y);" +
"vUv.zw=vTUv+((mt*vec3(1)).xy+P.xy)/P.zw;" +
"}" +
"gl_Position.y*=uFlpY;" +
"}";
}
// prettier-ignore
/**
* @param {LightRendererConfig} options
* @returns {string}
* @ignore
*/
$createFragmentShader(options) {
return Utils.GLSL.VERSION +
"precision highp float;\n" +
Utils.GLSL.DEFINE.HEIGHT +
Utils.GLSL.DEFINE.PI +
"#define PIH 1.570796326795\n" +
"in float " +
"vHS," +
"vD," +
"vSpt;" +
"in vec2 " +
"vTUv," +
"vSln;" +
"in vec4 " +
"vUv," +
"vCl," +
"vDt;" +
"in mat4 " +
"vExt;" +
"uniform sampler2D " +
"uNMTex," +
"uSTTex," +
"uRGTex," +
"uTex;" +
"uniform float " +
"uUSTT," +
"uURGT," +
"uUNMT;" +
"uniform vec2 " +
"uTS;" +
"out vec4 " +
"oCl;" +
Utils.GLSL.RANDOM +
"void main(void){" +
"oCl=vec4(0);" +
"if(vDt.x>0.){" +
"vec4 " +
"tc=texture(uTex,vTUv);" +
"float " +
"ph=tc.g*HEIGHT," +
"shn=tc.b," +
"rgh=1.;" +
"vec2 " +
"tUv=vTUv*uTS," +
"tCnt=vUv.zw*uTS;" +
"vec3 " +
"sf=vec3(tUv,ph)," +
"lp=vec3(tCnt,vHS)," +
"sftla=lp-sf;" +
"float " +
"dst=1.-length(sftla)/vD," +
"vol=vDt.z*vCl.a," +
"spc=0.;" +
"if(vol<=0.)discard;" +
"if(vExt[0].x<1.){" +
"vol*=dst;" +
"if(vol<=0.)discard;" +
"float " +
"slh=(vHS-ph)/HEIGHT;" +
"vec2 " +
"sl=vec2(" +
"slh*vSln.y-vUv.x*vSln.x," +
"slh*vSln.x+vUv.x*vSln.y" +
");" +
"if((" +
"atan(" +
"sl.x," +
"length(vec2(sl.y,vUv.y))" +
")+PIH" +
")-vSpt<0.)discard;" +
"}" +
"int " +
"flg=int(vExt[0].y);" +
"float " +
"fltDst=distance(tCnt,tUv)," +
"shdw=1.;" +
"if((flg&2)>0){" +
"vec3 " +
"nm=uUNMT<1." +
"?vec3(0,0,1.)" +
":normalize((texture(uNMTex,vTUv).rgb*2.-1.)*vec3(1,-1,1))," +
"sftl=normalize(sftla)," +
"sftv=normalize(vec3(" +
"(flg&16)>0" +
"?uTS*.5" +
":tUv," +
"HEIGHT" +
")-sf)," +
"hlf=normalize(sftl+sftv);" +
"float lght=dot(nm,sftl);" +
"vol*=lght;" +
"if(vol<=0.)discard;" +
"if(uURGT>0.){" +
"vec2 rgt=texture(uRGTex,vTUv).rg;" +
"rgh=rgt.r;" +
"shn=rgt.g;" +
"}" +
"spc=pow(dot(nm,hlf),HEIGHT*rgh)*shn*vExt[1].y;" +
"}" +
"if((flg&1)>0){" +
"ivec2 " +
"p;" +
"vec2 " +
"opd=(tUv-tCnt)/fltDst," +
"opdm=opd/uTS;" +
"float " +
"shl=vD/vDt.y," +
"st=ceil(max(1.,max(fltDst/vExt[1].x,vExt[0].w)))," +
"hst=(ph-vHS)/fltDst," +
"l=fltDst-st," +
"m=max(st,l-shl)," +
"i," +
"pc," +
"opdL=length(opd);" +
"for(i=l;i>m;i-=st){" +
"p=ivec2((vUv.zw+i*opdm)*uTS);" +
"tc=texelFetch(uTex,p,0)*HEIGHT;" +
"if((flg&4)>0&&tc.g>=vHS)discard;" +
"pc=vHS+i*hst;" +
"if(tc.r<=pc&&tc.g>=pc){" +
"shdw*=(fltDst-i*opdL)/shl;" +
"if(shdw<=0.)discard;" +
"}" +
"}" +
"}" +
"vec3 " +
"stCl=uUSTT<1.?vec3(1):texture(uSTTex,vTUv).rgb," +
"rCl=(flg&8)>0?vCl.rgb:vec3(1);" +
"oCl=vec4((stCl*vCl.rgb*shdw+rCl*spc)*vol,1);" +
"}" +
"}";
}
}